@dk/hipp 0.1.13 → 0.1.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -11
- package/hipp.js +49 -83
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -175,16 +175,8 @@ PERFORMANCE OF THIS SOFTWARE.
|
|
|
175
175
|
```json
|
|
176
176
|
{
|
|
177
177
|
"origin": "git@github.com:dmytri/hipp.git",
|
|
178
|
-
"tag": "v0.1.
|
|
178
|
+
"tag": "v0.1.15",
|
|
179
|
+
"hash": "cee8b863e598809fe8a194c1f102c857a0698e8158bef9e39493b31da6411226",
|
|
180
|
+
"signature": "Hc5fpg5Kg67ztJBdbtM3DvdrccL1e0KEyYwenwVIHLqcCNadXT9oatpgkO9dFaaGkCMUfttMGtWv8iJtYy+tBg=="
|
|
179
181
|
}
|
|
180
182
|
```
|
|
181
|
-
|
|
182
|
-
```npx @dk/hipp @dk/hipp@0.1.13```
|
|
183
|
-
<!-- HIPP-MANIFEST -->
|
|
184
|
-
```json
|
|
185
|
-
{
|
|
186
|
-
"hash": "0157b775d49e9715e954bc9838f5017632c0fd2e2418d814b0e2bbb92abe3fc3",
|
|
187
|
-
"signature": "jyq8xJjFAt5HtqQWPPHw52A2ph7zVxEYD1QClEY3jRFcVFdUlaJj+sGatgP/FChjaI57HZM8flYUvl2r1DA5Dw=="
|
|
188
|
-
}
|
|
189
|
-
```
|
|
190
|
-
<!-- /HIPP-MANIFEST -->
|
package/hipp.js
CHANGED
|
@@ -123,46 +123,7 @@ function buildSignData(hash, origin, tag) {
|
|
|
123
123
|
return `${hash}\n${origin}\n${tag}\n`;
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
-
|
|
127
|
-
const MANIFEST_END = '<!-- /HIPP-MANIFEST -->';
|
|
128
|
-
|
|
129
|
-
function appendManifestToReadme(readmeContent, manifest) {
|
|
130
|
-
return `${readmeContent}${MANIFEST_START}\n\`\`\`json\n${manifest}\n\`\`\`\n${MANIFEST_END}\n`;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
function extractManifestFromReadme(readmeContent) {
|
|
134
|
-
const startIdx = readmeContent.indexOf(MANIFEST_START);
|
|
135
|
-
if (startIdx === -1) return null;
|
|
136
|
-
const endIdx = readmeContent.indexOf(MANIFEST_END, startIdx);
|
|
137
|
-
if (endIdx === -1) return null;
|
|
138
|
-
const content = readmeContent.slice(startIdx + MANIFEST_START.length, endIdx).trim();
|
|
139
|
-
const match = content.match(/^```json\n(.+)\n```$/s);
|
|
140
|
-
if (!match) return null;
|
|
141
|
-
return match[1];
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
function stripManifestFromReadme(readmeContent) {
|
|
145
|
-
const startIdx = readmeContent.indexOf(MANIFEST_START);
|
|
146
|
-
const endIdx = readmeContent.indexOf(MANIFEST_END, startIdx);
|
|
147
|
-
if (startIdx === -1 || endIdx === -1) return readmeContent;
|
|
148
|
-
|
|
149
|
-
const endLineIdx = readmeContent.indexOf('\n', endIdx);
|
|
150
|
-
const endOfManifest = endLineIdx !== -1 ? endLineIdx + 1 : readmeContent.length;
|
|
151
|
-
|
|
152
|
-
const beforeWithNewline = readmeContent.slice(0, startIdx);
|
|
153
|
-
const lastNewlineBefore = beforeWithNewline.lastIndexOf('\n');
|
|
154
|
-
const before = lastNewlineBefore !== -1 ? beforeWithNewline.slice(0, lastNewlineBefore) : beforeWithNewline;
|
|
155
|
-
|
|
156
|
-
const after = readmeContent.slice(endOfManifest);
|
|
157
|
-
return before + '\n' + after;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
function computeReadmeHash(readmeContent) {
|
|
161
|
-
const stripped = stripManifestFromReadme(readmeContent);
|
|
162
|
-
return sha256(stripped);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
function extractJsonMetaFromReadme(readmeContent) {
|
|
126
|
+
function findLastJsonBlock(readmeContent) {
|
|
166
127
|
const lines = readmeContent.split('\n');
|
|
167
128
|
let jsonStart = -1;
|
|
168
129
|
let braceCount = 0;
|
|
@@ -186,7 +147,7 @@ function extractJsonMetaFromReadme(readmeContent) {
|
|
|
186
147
|
const jsonStr = lines.slice(jsonStart, i + 1).join('\n');
|
|
187
148
|
try {
|
|
188
149
|
const parsed = JSON.parse(jsonStr);
|
|
189
|
-
if (parsed.origin && parsed.tag) {
|
|
150
|
+
if (parsed.origin && parsed.tag && parsed.hash && parsed.signature) {
|
|
190
151
|
lastValid = parsed;
|
|
191
152
|
}
|
|
192
153
|
} catch {
|
|
@@ -199,6 +160,26 @@ function extractJsonMetaFromReadme(readmeContent) {
|
|
|
199
160
|
return lastValid;
|
|
200
161
|
}
|
|
201
162
|
|
|
163
|
+
function packAndHash(stageDir) {
|
|
164
|
+
const result = spawnSync('npm', ['pack'], {
|
|
165
|
+
cwd: stageDir,
|
|
166
|
+
encoding: 'utf8',
|
|
167
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
if (result.status !== 0) {
|
|
171
|
+
throw new Error(`npm pack failed: ${result.stderr}`);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const tarballName = result.stdout.trim().split('\n').pop();
|
|
175
|
+
const tarballPath = path.join(stageDir, tarballName);
|
|
176
|
+
|
|
177
|
+
const tarballContent = fs.readFileSync(tarballPath);
|
|
178
|
+
fs.unlinkSync(tarballPath);
|
|
179
|
+
|
|
180
|
+
return { tarballName, tarballHash: sha256(tarballContent) };
|
|
181
|
+
}
|
|
182
|
+
|
|
202
183
|
function safeStageName(name) {
|
|
203
184
|
return name.replace(/[^a-zA-Z0-9._-]/g, '-');
|
|
204
185
|
}
|
|
@@ -490,23 +471,12 @@ async function runVerify(packageSpec) {
|
|
|
490
471
|
}
|
|
491
472
|
|
|
492
473
|
const stagedReadme = fs.readFileSync(stagedReadmePath, 'utf8');
|
|
493
|
-
const
|
|
494
|
-
if (!
|
|
495
|
-
fail(`❌
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
const manifestStr = extractManifestFromReadme(stagedReadme);
|
|
499
|
-
if (!manifestStr) {
|
|
500
|
-
fail(`❌ Manifest not found in README`);
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
const manifest = parseManifest(manifestStr);
|
|
504
|
-
if (!manifest || !manifest.hash || !manifest.signature) {
|
|
505
|
-
fail(`❌ Invalid manifest format`);
|
|
474
|
+
const manifest = findLastJsonBlock(stagedReadme);
|
|
475
|
+
if (!manifest || !manifest.origin || !manifest.tag || !manifest.hash || !manifest.signature) {
|
|
476
|
+
fail(`❌ Manifest not found or invalid in README`);
|
|
506
477
|
}
|
|
507
478
|
|
|
508
|
-
const originUrl =
|
|
509
|
-
const tag = jsonMeta.tag;
|
|
479
|
+
const { origin: originUrl, tag, hash: npmHash, signature } = manifest;
|
|
510
480
|
|
|
511
481
|
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), `hipp-verify-git-`));
|
|
512
482
|
const stageDir = fs.mkdtempSync(path.join(os.tmpdir(), `hipp-verify-stage-`));
|
|
@@ -526,26 +496,17 @@ async function runVerify(packageSpec) {
|
|
|
526
496
|
const trackedFiles = getTrackedFilesFromDir(tmpDir);
|
|
527
497
|
copyTrackedFilesFromDir(stageDir, tmpDir, trackedFiles);
|
|
528
498
|
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
fail(`❌ README.md not found in git at tag ${tag}`);
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
let stagedReadme = fs.readFileSync(stagedReadmePath, 'utf8');
|
|
535
|
-
const verifyBlock = '```npx @dk/hipp ' + pkgName + '@' + tag + '```';
|
|
536
|
-
const jsonMetaStr = '```json\n{\n "origin": "' + originUrl + '",\n "tag": "' + tag + '"\n}\n```';
|
|
537
|
-
stagedReadme = stagedReadme.trimEnd() + '\n\n' + jsonMetaStr + '\n\n' + verifyBlock + '\n';
|
|
538
|
-
|
|
539
|
-
const stagedHash = computeReadmeHash(stagedReadme);
|
|
499
|
+
log.info(`📦 Packing to verify content hash...`);
|
|
500
|
+
const { tarballHash } = packAndHash(stageDir);
|
|
540
501
|
|
|
541
|
-
if (
|
|
502
|
+
if (tarballHash !== npmHash) {
|
|
542
503
|
fail(`❌ Hash mismatch: git content does not match npm manifest`);
|
|
543
504
|
}
|
|
544
505
|
|
|
545
|
-
log.success(`🔒 Content hash verified: ${
|
|
506
|
+
log.success(`🔒 Content hash verified: ${npmHash.slice(0, 12)}...`);
|
|
546
507
|
|
|
547
|
-
const signData = buildSignData(
|
|
548
|
-
const signatureValid = verifySignature(signData,
|
|
508
|
+
const signData = buildSignData(npmHash, originUrl, tag);
|
|
509
|
+
const signatureValid = verifySignature(signData, signature, publicKey);
|
|
549
510
|
|
|
550
511
|
if (!signatureValid) {
|
|
551
512
|
fail(`❌ Signature verification failed`);
|
|
@@ -642,10 +603,9 @@ async function run() {
|
|
|
642
603
|
|
|
643
604
|
const { privateKey } = loadOrGenerateKeys();
|
|
644
605
|
|
|
645
|
-
|
|
646
|
-
const
|
|
647
|
-
|
|
648
|
-
fs.writeFileSync(stagedPkgPath, JSON.stringify(stagedPkg, null, 2) + '\n');
|
|
606
|
+
log.info(`📦 Packing to compute content hash...`);
|
|
607
|
+
const { tarballHash } = packAndHash(stageDir);
|
|
608
|
+
log.success(`🔒 Content hash: ${tarballHash.slice(0, 12)}...`);
|
|
649
609
|
|
|
650
610
|
const stagedReadmePath = path.join(stageDir, 'README.md');
|
|
651
611
|
let stagedReadme = '';
|
|
@@ -653,18 +613,24 @@ async function run() {
|
|
|
653
613
|
stagedReadme = fs.readFileSync(stagedReadmePath, 'utf8');
|
|
654
614
|
}
|
|
655
615
|
|
|
656
|
-
const
|
|
657
|
-
const jsonMetaStr = '```json\n{\n "origin": "' + provenance.remoteUrl + '",\n "tag": "' + rawTag + '"\n}\n```';
|
|
658
|
-
stagedReadme = stagedReadme.trimEnd() + '\n\n' + jsonMetaStr + '\n\n' + verifyBlock + '\n';
|
|
659
|
-
|
|
660
|
-
const readmeHash = computeReadmeHash(stagedReadme);
|
|
661
|
-
const dataToSign = buildSignData(readmeHash, provenance.remoteUrl, rawTag);
|
|
616
|
+
const dataToSign = buildSignData(tarballHash, provenance.remoteUrl, rawTag);
|
|
662
617
|
const signature = signContent(dataToSign, privateKey);
|
|
663
|
-
const manifest = createManifest(readmeHash, signature);
|
|
664
|
-
stagedReadme = appendManifestToReadme(stagedReadme, manifest);
|
|
665
618
|
|
|
619
|
+
const manifestJson = {
|
|
620
|
+
origin: provenance.remoteUrl,
|
|
621
|
+
tag: rawTag,
|
|
622
|
+
hash: tarballHash,
|
|
623
|
+
signature: signature,
|
|
624
|
+
};
|
|
625
|
+
|
|
626
|
+
stagedReadme = stagedReadme.trimEnd() + '\n\n```json\n' + JSON.stringify(manifestJson, null, 2) + '\n```\n';
|
|
666
627
|
fs.writeFileSync(stagedReadmePath, stagedReadme);
|
|
667
628
|
|
|
629
|
+
const stagedPkgPath = path.join(stageDir, 'package.json');
|
|
630
|
+
const stagedPkg = JSON.parse(fs.readFileSync(stagedPkgPath, 'utf8'));
|
|
631
|
+
stagedPkg.version = version;
|
|
632
|
+
fs.writeFileSync(stagedPkgPath, JSON.stringify(stagedPkg, null, 2) + '\n');
|
|
633
|
+
|
|
668
634
|
log.success('🔏 Manifest signed.');
|
|
669
635
|
|
|
670
636
|
log.info('🔥 Ignition...');
|