@dk/hipp 0.1.29 → 0.1.31
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 +19 -6
- package/hipp.js +107 -66
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -93,6 +93,17 @@ with their original keys.
|
|
|
93
93
|
**Multiple publishers**: Each developer can use their own private key. Delete
|
|
94
94
|
`hipp.pub`, run HIPP, and a new keypair will be generated for that revision.
|
|
95
95
|
|
|
96
|
+
### Why This Works
|
|
97
|
+
|
|
98
|
+
The public key in `hipp.pub` is committed to git at the specific revision of
|
|
99
|
+
each release. Verification always uses the key from that historical revision,
|
|
100
|
+
not a current one. This means:
|
|
101
|
+
|
|
102
|
+
- You don't need to retain your private key — once published, past releases are
|
|
103
|
+
verifiable from the git history alone
|
|
104
|
+
- A compromised private key can only sign future releases, not forge past ones
|
|
105
|
+
- Multiple publishers work naturally because each release is self-contained
|
|
106
|
+
|
|
96
107
|
### Options
|
|
97
108
|
|
|
98
109
|
* `-y, --yes`: Skip the confirmation prompt (ideal for CI/CD pipelines).
|
|
@@ -135,6 +146,7 @@ The manifest contains:
|
|
|
135
146
|
"email": "jane@example.com",
|
|
136
147
|
"npm": "10.2.4",
|
|
137
148
|
"node": "v20.11.0",
|
|
149
|
+
"git": "2.34.1",
|
|
138
150
|
"hipp": "0.1.22"
|
|
139
151
|
}
|
|
140
152
|
```
|
|
@@ -241,20 +253,21 @@ PERFORMANCE OF THIS SOFTWARE.
|
|
|
241
253
|
Verify this package with [@dk/hipp](https://www.npmjs.com/package/@dk/hipp):
|
|
242
254
|
|
|
243
255
|
```bash
|
|
244
|
-
npx @dk/hipp verify @dk/hipp@0.1.
|
|
256
|
+
npx @dk/hipp verify @dk/hipp@0.1.31
|
|
245
257
|
```
|
|
246
258
|
|
|
247
259
|
```json
|
|
248
260
|
{
|
|
249
261
|
"origin": "git@github.com:dmytri/hipp.git",
|
|
250
|
-
"tag": "v0.1.
|
|
251
|
-
"revision": "
|
|
252
|
-
"hash": "
|
|
253
|
-
"signature": "
|
|
262
|
+
"tag": "v0.1.31",
|
|
263
|
+
"revision": "270b281d7bf0e908b7c267fb87f9f68e3dc2be86",
|
|
264
|
+
"hash": "1085bdf7d5774cae5d64236a18e95c95c8d2d198ae22e9f8cfa2c10afd0fe57b",
|
|
265
|
+
"signature": "mPj1cIun7czw0TQmsWkq/HEq7O+ZdTUQrMhTRZ8Cm4Z23dYu/G/m/itzyC2x/kBRVW+8h+m399acusrSArCzBA==",
|
|
254
266
|
"name": "Dmytri Kleiner",
|
|
255
267
|
"email": "dev@dmytri.to",
|
|
256
268
|
"npm": "11.12.1",
|
|
257
269
|
"node": "v25.8.2",
|
|
258
|
-
"
|
|
270
|
+
"git": "git version 2.47.3",
|
|
271
|
+
"hipp": "0.1.31"
|
|
259
272
|
}
|
|
260
273
|
```
|
package/hipp.js
CHANGED
|
@@ -483,12 +483,20 @@ async function runVerify(packageSpec) {
|
|
|
483
483
|
fail(`❌ Manifest not found or invalid in README`);
|
|
484
484
|
}
|
|
485
485
|
|
|
486
|
-
const { origin: originUrl, tag, revision, signature, name, email, npm: npmVer, node: nodeVer, hipp: hippVer } = manifest;
|
|
486
|
+
const { origin: originUrl, tag, revision, signature, name, email, npm: npmVer, node: nodeVer, hipp: hippVer, git: gitVer } = manifest;
|
|
487
487
|
|
|
488
488
|
log.info(`🌿 Cloning git origin at tag ${tag}...`);
|
|
489
489
|
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), `hipp-verify-git-`));
|
|
490
490
|
const stageDir = fs.mkdtempSync(path.join(os.tmpdir(), `hipp-verify-stage-`));
|
|
491
491
|
|
|
492
|
+
const results = {
|
|
493
|
+
revision: false,
|
|
494
|
+
pub: false,
|
|
495
|
+
signature: false,
|
|
496
|
+
manifestHash: false,
|
|
497
|
+
rebuild: false,
|
|
498
|
+
};
|
|
499
|
+
|
|
492
500
|
try {
|
|
493
501
|
let cloneResult;
|
|
494
502
|
try {
|
|
@@ -505,81 +513,112 @@ async function runVerify(packageSpec) {
|
|
|
505
513
|
|
|
506
514
|
const clonedRevision = git(['rev-parse', 'HEAD'], { cwd: tmpDir });
|
|
507
515
|
if (clonedRevision !== revision) {
|
|
508
|
-
|
|
516
|
+
log.error(`❌ Revision mismatch: manifest ${revision.slice(0, 12)} != cloned ${clonedRevision.slice(0, 12)}`);
|
|
517
|
+
} else {
|
|
518
|
+
log.success(`🏷️ Revision verified: ${revision.slice(0, 12)}...`);
|
|
519
|
+
results.revision = true;
|
|
509
520
|
}
|
|
510
|
-
log.success(`🏷️ Revision verified: ${revision.slice(0, 12)}...`);
|
|
511
521
|
|
|
512
522
|
const publicKeyPath = path.join(tmpDir, 'hipp.pub');
|
|
513
523
|
if (!fs.existsSync(publicKeyPath)) {
|
|
514
|
-
|
|
515
|
-
}
|
|
524
|
+
log.error(`❌ hipp.pub not found in git at tag ${tag}`);
|
|
525
|
+
} else {
|
|
526
|
+
const publicKey = fs.readFileSync(publicKeyPath, 'utf8');
|
|
516
527
|
|
|
517
|
-
|
|
528
|
+
log.info(`🔑 Public key: hipp.pub from git@rev ${revision.slice(0, 12)} at tag ${tag}`);
|
|
518
529
|
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
530
|
+
log.info(`🏗️ Staging git files...`);
|
|
531
|
+
const trackedFiles = getTrackedFilesFromDir(tmpDir);
|
|
532
|
+
copyTrackedFilesFromDir(stageDir, tmpDir, trackedFiles);
|
|
522
533
|
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
534
|
+
log.info(`📦 Packing clean git files...`);
|
|
535
|
+
const { tarballHash: cleanHash } = packAndHash(stageDir);
|
|
536
|
+
log.success(`📦 Clean hash: ${cleanHash.slice(0, 12)}...`);
|
|
526
537
|
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
538
|
+
log.info(`🔍 Check 2: Verifying manifest hash...`);
|
|
539
|
+
if (cleanHash !== manifest.hash) {
|
|
540
|
+
log.error(`❌ Manifest hash mismatch: clean ${cleanHash.slice(0, 12)} != manifest ${manifest.hash.slice(0, 12)}`);
|
|
541
|
+
} else {
|
|
542
|
+
log.success(`🔒 Manifest hash verified`);
|
|
543
|
+
results.manifestHash = true;
|
|
544
|
+
}
|
|
532
545
|
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
log.
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
546
|
+
log.info(`🔍 Check 1: Verifying signature...`);
|
|
547
|
+
const signData = buildSignData(manifest.hash, originUrl, tag, revision, name, email);
|
|
548
|
+
const signatureValid = verifySignature(signData, signature, publicKey);
|
|
549
|
+
if (!signatureValid) {
|
|
550
|
+
log.error(`❌ Signature verification failed`);
|
|
551
|
+
} else {
|
|
552
|
+
log.success(`🔏 Signature verified`);
|
|
553
|
+
results.signature = true;
|
|
554
|
+
results.pub = true;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
log.info(`🔍 Check 3: Rebuilding from source...`);
|
|
558
|
+
const stagedReadmePath = path.join(stageDir, 'README.md');
|
|
559
|
+
let stagedReadme = fs.readFileSync(stagedReadmePath, 'utf8');
|
|
560
|
+
const tagVersion = semver.clean(tag);
|
|
561
|
+
if (!tagVersion) {
|
|
562
|
+
log.error(`❌ Tag ${tag} is not valid semver`);
|
|
563
|
+
}
|
|
564
|
+
stagedReadme = stagedReadme.trimEnd() + '\n\n## Verify\n\n' +
|
|
565
|
+
'Verify this package with [@dk/hipp](https://www.npmjs.com/package/@dk/hipp):\n\n' +
|
|
566
|
+
'```bash\n' +
|
|
567
|
+
`npx @dk/hipp verify ${pkgName}@${tagVersion}\n` +
|
|
568
|
+
'```\n\n' +
|
|
569
|
+
'```json\n' + JSON.stringify(manifest, null, 2) + '\n```\n';
|
|
570
|
+
fs.writeFileSync(stagedReadmePath, stagedReadme);
|
|
571
|
+
|
|
572
|
+
const stagedPkgPath = path.join(stageDir, 'package.json');
|
|
573
|
+
const stagedPkg = JSON.parse(fs.readFileSync(stagedPkgPath, 'utf8'));
|
|
574
|
+
stagedPkg.version = tagVersion;
|
|
575
|
+
fs.writeFileSync(stagedPkgPath, JSON.stringify(stagedPkg, null, 2) + '\n');
|
|
576
|
+
|
|
577
|
+
const { tarballHash: rebuildHash } = packAndHash(stageDir);
|
|
578
|
+
log.success(`📦 Rebuild hash: ${rebuildHash.slice(0, 12)}...`);
|
|
579
|
+
|
|
580
|
+
if (rebuildHash !== npmHash) {
|
|
581
|
+
log.error(`❌ Rebuild mismatch: rebuild ${rebuildHash.slice(0, 12)} != npm ${npmHash.slice(0, 12)}`);
|
|
582
|
+
} else {
|
|
583
|
+
log.success(`🔄 Rebuild verified`);
|
|
584
|
+
results.rebuild = true;
|
|
585
|
+
}
|
|
569
586
|
}
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
587
|
+
|
|
588
|
+
const allPassed = results.revision && results.pub && results.signature && results.manifestHash && results.rebuild;
|
|
589
|
+
|
|
590
|
+
if (allPassed) {
|
|
591
|
+
log.success(`\n✅ Verified: all checks passed`);
|
|
592
|
+
log.info(`📍 Publisher: ${name} <${email}>`);
|
|
593
|
+
log.info(`📍 Origin: ${originUrl}`);
|
|
594
|
+
log.info(`📍 Tag: ${tag}`);
|
|
595
|
+
if (npmVer || nodeVer || hippVer || gitVer) {
|
|
596
|
+
const parts = [];
|
|
597
|
+
const displayHipp = hippVer === '0.0.0' ? tagVersion : hippVer;
|
|
598
|
+
if (hippVer) parts.push(`hipp: ${displayHipp}`);
|
|
599
|
+
if (npmVer) parts.push(`npm: ${npmVer}`);
|
|
600
|
+
if (nodeVer) parts.push(`node: ${nodeVer}`);
|
|
601
|
+
if (gitVer) parts.push(`git: ${gitVer}`);
|
|
602
|
+
log.info(`ℹ️ ${parts.join(' | ')}`);
|
|
603
|
+
}
|
|
604
|
+
log.info(`\nThis proves npm matches git. It does NOT prove:`);
|
|
605
|
+
log.info(` - The code is safe or bug-free`);
|
|
606
|
+
log.info(` - The publisher is trustworthy`);
|
|
607
|
+
log.info(` - The name/email is accurate`);
|
|
608
|
+
} else {
|
|
609
|
+
log.error(`\n❌ Verification failed.`);
|
|
610
|
+
if (results.revision && results.pub && results.signature) {
|
|
611
|
+
log.info(`\nRevision and signature verified. This failure may be due to tool`);
|
|
612
|
+
log.info(`version differences between publishing and verification environments.`);
|
|
613
|
+
if (npmVer || nodeVer || hippVer || gitVer) {
|
|
614
|
+
log.info(`\nPublished with: ${[
|
|
615
|
+
hippVer && `hipp: ${hippVer}`,
|
|
616
|
+
npmVer && `npm: ${npmVer}`,
|
|
617
|
+
nodeVer && `node: ${nodeVer}`,
|
|
618
|
+
gitVer && `git: ${gitVer}`,
|
|
619
|
+
].filter(Boolean).join(' | ')}`);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
583
622
|
}
|
|
584
623
|
} finally {
|
|
585
624
|
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
@@ -682,6 +721,7 @@ async function run() {
|
|
|
682
721
|
const revision = refInfo.head;
|
|
683
722
|
const npmVersion = runCmd('npm', ['--version']).stdout.trim();
|
|
684
723
|
const nodeVersion = process.version;
|
|
724
|
+
const gitVersion = runCmd('git', ['--version']).stdout.trim();
|
|
685
725
|
const hippPkgPath = path.join(path.dirname(process.argv[1]), 'package.json');
|
|
686
726
|
const hippPkg = JSON.parse(fs.readFileSync(hippPkgPath, 'utf8'));
|
|
687
727
|
const hippVersion = hippPkg.version === '0.0.0' ? version : hippPkg.version;
|
|
@@ -699,6 +739,7 @@ async function run() {
|
|
|
699
739
|
email: email,
|
|
700
740
|
npm: npmVersion,
|
|
701
741
|
node: nodeVersion,
|
|
742
|
+
git: gitVersion,
|
|
702
743
|
hipp: hippVersion,
|
|
703
744
|
};
|
|
704
745
|
|