@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.
Files changed (3) hide show
  1. package/README.md +19 -6
  2. package/hipp.js +107 -66
  3. 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.29
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.29",
251
- "revision": "33bc07a3b6621daa7220a3febde75ef5d3416518",
252
- "hash": "af235d18cbc26f9bff5dac57a101b9722687dc7d8360c590aa81c96e23178654",
253
- "signature": "Q5UmtBUV1DbII/oNmFcnWXzyLXndcVCGxpy4kKsnSHs+YZYyt69qg71fhHR+uy9ggzBCi3sc3nwtbEHEX+TXBg==",
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
- "hipp": "0.1.29"
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
- fail(`❌ Revision mismatch: manifest claims ${revision.slice(0, 12)} but tag points to ${clonedRevision.slice(0, 12)}`);
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
- fail(`❌ hipp.pub not found in git at tag ${tag}`);
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
- const publicKey = fs.readFileSync(publicKeyPath, 'utf8');
528
+ log.info(`🔑 Public key: hipp.pub from git@rev ${revision.slice(0, 12)} at tag ${tag}`);
518
529
 
519
- log.info(`🏗️ Staging git files...`);
520
- const trackedFiles = getTrackedFilesFromDir(tmpDir);
521
- copyTrackedFilesFromDir(stageDir, tmpDir, trackedFiles);
530
+ log.info(`🏗️ Staging git files...`);
531
+ const trackedFiles = getTrackedFilesFromDir(tmpDir);
532
+ copyTrackedFilesFromDir(stageDir, tmpDir, trackedFiles);
522
533
 
523
- log.info(`📦 Packing clean git files...`);
524
- const { tarballHash: cleanHash } = packAndHash(stageDir);
525
- log.success(`📦 Clean hash: ${cleanHash.slice(0, 12)}...`);
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
- log.info(`🔍 Check 2: Verifying manifest hash...`);
528
- if (cleanHash !== manifest.hash) {
529
- fail(`❌ Manifest hash mismatch: clean git tarball does not match manifest`);
530
- }
531
- log.success(`🔒 Manifest hash verified`);
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
- log.info(`🔍 Check 1: Verifying signature...`);
534
- const signData = buildSignData(manifest.hash, originUrl, tag, revision, name, email);
535
- const signatureValid = verifySignature(signData, signature, publicKey);
536
- if (!signatureValid) {
537
- fail(`❌ Signature verification failed`);
538
- }
539
- log.success(`🔏 Signature verified`);
540
-
541
- log.info(`🔍 Check 3: Rebuilding from source...`);
542
- const stagedReadmePath = path.join(stageDir, 'README.md');
543
- let stagedReadme = fs.readFileSync(stagedReadmePath, 'utf8');
544
- const tagVersion = semver.clean(tag);
545
- if (!tagVersion) {
546
- fail(`❌ Tag ${tag} is not valid semver`);
547
- }
548
- stagedReadme = stagedReadme.trimEnd() + '\n\n## Verify\n\n' +
549
- 'Verify this package with [@dk/hipp](https://www.npmjs.com/package/@dk/hipp):\n\n' +
550
- '```bash\n' +
551
- `npx @dk/hipp verify ${pkgName}@${tagVersion}\n` +
552
- '```\n\n' +
553
- '```json\n' + JSON.stringify(manifest, null, 2) + '\n```\n';
554
- fs.writeFileSync(stagedReadmePath, stagedReadme);
555
-
556
- const stagedPkgPath = path.join(stageDir, 'package.json');
557
- const stagedPkg = JSON.parse(fs.readFileSync(stagedPkgPath, 'utf8'));
558
- stagedPkg.version = tagVersion;
559
- fs.writeFileSync(stagedPkgPath, JSON.stringify(stagedPkg, null, 2) + '\n');
560
-
561
- const { tarballHash: rebuildHash } = packAndHash(stageDir);
562
- log.success(`📦 Rebuild hash: ${rebuildHash.slice(0, 12)}...`);
563
-
564
- if (rebuildHash !== npmHash) {
565
- log.error(`❌ Rebuild mismatch!`);
566
- log.error(` NPM tarball: ${npmHash}`);
567
- log.error(` Git rebuild: ${rebuildHash}`);
568
- fail(`❌ Package integrity compromised`);
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
- log.success(`🔄 Rebuild verified`);
571
-
572
- log.success(`✅ Verified: all checks passed`);
573
- log.info(`📍 Publisher: ${name} <${email}>`);
574
- log.info(`📍 Origin: ${originUrl}`);
575
- log.info(`📍 Tag: ${tag}`);
576
- if (npmVer || nodeVer || hippVer) {
577
- const parts = [];
578
- const displayHipp = hippVer === '0.0.0' ? tagVersion : hippVer;
579
- if (hippVer) parts.push(`hipp: ${displayHipp}`);
580
- if (npmVer) parts.push(`npm: ${npmVer}`);
581
- if (nodeVer) parts.push(`node: ${nodeVer}`);
582
- log.info(`ℹ️ ${parts.join(' | ')}`);
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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dk/hipp",
3
- "version": "0.1.29",
3
+ "version": "0.1.31",
4
4
  "description": "High Integrity Package Publisher",
5
5
  "main": "hipp.js",
6
6
  "bin": {