@dk/hipp 0.1.16 → 0.1.19

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 +96 -64
  2. package/hipp.js +109 -63
  3. package/package.json +2 -1
package/README.md CHANGED
@@ -2,40 +2,30 @@
2
2
 
3
3
  By Dmytri Kleiner <dev@dmytri.to>
4
4
 
5
- **HIPP** is a minimalist, stateless publishing tool designed to eliminate the
6
- friction of version-bump commits. It treats your **Git Tags** as the single
7
- source of truth, enforcing a "Ground State" where your `package.json` version
8
- remains permanently at `0.0.0`.
9
-
10
- HIPP provides **cryptographic signing** and **out-of-band verification** to
11
- guarantee that the package in the npm registry exactly matches your git tag.
5
+ **HIPP** is a minimalist, stateless publishing tool that eliminates version-bump
6
+ commits and merge conflicts by treating Git Tags as the single source of truth.
7
+ Your `package.json` version stays permanently at `0.0.0`.
12
8
 
13
9
  ---
14
10
 
15
- ## Why HIPP?
16
-
17
- ### Integrity
18
-
19
- Traditional NPM versioning requires you to store your "Version of Truth" inside
20
- your source code files (`package.json`). This creates a **State Conflict** that
21
- leads to several systemic problems:
11
+ ## The Problem
22
12
 
23
- `npm version` and `git tag` are two distinct, non-atomic actions. If you tag a
24
- commit but forget to update the JSON (or vice-versa), your registry package and
25
- your Git history diverge. This scenario makes it impossible to guarantee that
26
- the code in the registry matches the code at that tag.
13
+ Traditional NPM versioning requires storing the "Version of Truth" in `package.json`.
14
+ This creates a **State Conflict**:
27
15
 
28
- **HIPP ensures they are fundamentally linked by extracting the version directly
29
- from the Git Tag, and cryptographically signing the package contents.**
16
+ - `npm version` and `git tag` are two distinct, non-atomic actions
17
+ - If you tag a commit but forget to update the JSON (or vice-versa), your
18
+ registry package and Git history diverge
19
+ - Every release requires a "chore: bump version" commit
20
+ - When multiple branches are developed simultaneously, these trivial changes
21
+ cause constant merge conflicts
30
22
 
31
- ### No "Chore" Noise
23
+ ## The Solution
32
24
 
33
- Every release usually requires a "chore: bump version" commit. When multiple
34
- branches are developed simultaneously, these version changes cause constant,
35
- trivial merge conflicts.
25
+ **HIPP makes `package.json` version immutable (0.0.0)** - the **HIPP Doctrine**.
36
26
 
37
- **HIPP makes your `package.json` version immutable (0.0.0), so it never
38
- conflicts and your git history stays clean.**
27
+ Version is extracted directly from the Git Tag during publish. Your Git history
28
+ stays clean, and your registry package is guaranteed to match your Git tag.
39
29
 
40
30
  ---
41
31
 
@@ -43,7 +33,7 @@ conflicts and your git history stays clean.**
43
33
 
44
34
  ### Setup
45
35
 
46
- 1. Set your project's `package.json` version to `0.0.0`. This is the **HIPP Doctrine**.
36
+ 1. Set your project's `package.json` version to `0.0.0`:
47
37
 
48
38
  ```json
49
39
  { "name": "your-package", "version": "0.0.0" }
@@ -72,11 +62,20 @@ HIPP will:
72
62
 
73
63
  On first run, HIPP generates an Ed25519 keypair:
74
64
 
75
- - **`hipp.priv`** - Your private signing key. **Never committed to git.** Added to `.gitignore` automatically.
65
+ - **`hipp.priv`** - Your private signing key. **Never committed to git.**
66
+ Added to `.gitignore` automatically.
76
67
  - **`hipp.pub`** - Your public verification key. **Committed to git** automatically.
77
68
 
78
69
  The private key holder can sign packages. The public key verifies signatures.
79
70
 
71
+ **Key rotation**: Delete `hipp.pub` and run HIPP again. A new keypair will be
72
+ generated and committed automatically. Verification uses the public key from
73
+ the specific git revision at the tag, so previous packages remain verifiable
74
+ with their original keys.
75
+
76
+ **Multiple publishers**: Each developer can use their own private key. Delete
77
+ `hipp.pub`, run HIPP, and a new keypair will be generated for that revision.
78
+
80
79
  ### Options
81
80
 
82
81
  * `-y, --yes`: Skip the confirmation prompt (ideal for CI/CD pipelines).
@@ -91,10 +90,11 @@ npx @dk/hipp -- --access public --tag beta
91
90
 
92
91
  ## Verification
93
92
 
94
- HIPP provides out-of-band verification to guarantee package integrity:
93
+ HIPP provides out-of-band verification to prove package integrity:
95
94
 
96
95
  ```bash
97
96
  npx @dk/hipp verify @dk/your-package[@version]
97
+ npx @dk/hipp verify # verifies the installed hipp version
98
98
  ```
99
99
 
100
100
  ### How Verification Works
@@ -102,8 +102,7 @@ npx @dk/hipp verify @dk/your-package[@version]
102
102
  **Step 1: Get manifest from npm**
103
103
 
104
104
  1. Fetch the package tarball from npm registry
105
- 2. Extract the README from the tarball
106
- 3. Parse the JSON manifest appended to the README
105
+ 2. Extract the README and parse the JSON manifest appended to it
107
106
 
108
107
  The manifest contains:
109
108
  ```json
@@ -111,42 +110,73 @@ The manifest contains:
111
110
  "origin": "git@github.com:dk/your-package.git",
112
111
  "tag": "v1.0.0",
113
112
  "hash": "<sha256-of-tarball>",
114
- "signature": "<base64-ed25519-signature>"
113
+ "signature": "<base64-ed25519-signature>",
114
+ "name": "Jane Developer",
115
+ "email": "jane@example.com"
115
116
  }
116
117
  ```
117
118
 
118
- **Step 2: Clone git and stage**
119
+ **Step 2: Clone git and verify**
119
120
 
120
- 4. Clone the repository at the tagged commit (using origin/tag from manifest)
121
- 5. Copy all tracked files to a staging directory
121
+ 3. Clone the repository at the tagged commit (using origin/tag from manifest)
122
+ 4. Stage all tracked files
123
+ 5. Run `npm pack` to create a tarball
124
+ 6. Compute SHA256 hash of the clean tarball
125
+ 7. Compare with the `hash` field from the npm manifest
122
126
 
123
- **Step 3: Verify content integrity**
127
+ **Step 3: Verify signature**
124
128
 
125
- 6. Run `npm pack` in the staging directory
126
- 7. Compute the SHA256 hash of the resulting tarball
127
- 8. Compare this hash with the `hash` field from the npm manifest
129
+ 8. Read `hipp.pub` from the cloned repository
130
+ 9. Verify the signature was created by signing:
131
+ `hash + "\n" + origin + "\n" + tag + "\n" + name + "\n" + email`
128
132
 
129
- **If the hashes match**: The npm package exactly matches the git repository at the tagged commit.
133
+ **Step 4: Rebuild verification**
130
134
 
131
- **Step 4: Verify signature authenticity**
135
+ 10. Append the manifest to the staged README
136
+ 11. Update the staged `package.json` version to match the tag
137
+ 12. Run `npm pack` again and verify the hash matches the npm tarball
132
138
 
133
- 9. Read `hipp.pub` from the cloned repository at the tagged commit
134
- 10. Verify the signature using the public key
139
+ ### Three Verification Checks
135
140
 
136
- The signature was created by signing: `hash + "\n" + origin + "\n" + tag`
137
-
138
- **If the signature is valid**: The package was published by the holder of the private key matching `hipp.pub`.
141
+ | Check | What it proves |
142
+ |-------|----------------|
143
+ | **1. Signature** | The manifest was signed by the holder of the private key matching `hipp.pub` |
144
+ | **2. Manifest hash** | The claimed hash is accurate for this exact git revision |
145
+ | **3. Rebuild** | The npm tarball exactly matches what you'd produce from clean git source |
139
146
 
140
147
  ### What Verification Guarantees
141
148
 
142
- | Check | Guarantees |
143
- |-------|-----------|
144
- | **Hash match** | npm package content exactly matches git at the tagged commit |
145
- | **Signature valid** | Published by holder of the private key matching `hipp.pub` |
149
+ - **Integrity**: The code in npm exactly matches git at the tagged commit
150
+ - **Authenticity**: The package was published by the holder of the private key
151
+ - **Reproducibility**: The npm tarball is byte-for-byte identical to a git rebuild
152
+
153
+ ### What Verification Does NOT Guarantee
154
+
155
+ - **Code is safe or bug-free**: Malicious or buggy code can be signed
156
+ - **Publisher is trustworthy**: The key holder could sign bad code intentionally
157
+
158
+ Verification proves that npm matches git - it says nothing about whether that
159
+ code is correct or safe.
146
160
 
147
- This provides two independent guarantees:
148
- - **Integrity**: The code in npm is exactly what was in git at the tag
149
- - **Authenticity**: The publisher controls the private key for `hipp.pub`
161
+ ---
162
+
163
+ ## Dual-Channel Trust
164
+
165
+ npm and git serve as independent verification channels:
166
+
167
+ - **npm** records *when* and *what* was published by *whom*
168
+ - **git** records the source code and the public key
169
+
170
+ An attacker would need to compromise both registries to forge a valid package.
171
+ This doesn't prove code is safe, but it proves the code in npm matches git.
172
+
173
+ ---
174
+
175
+ ## Security
176
+
177
+ HIPP uses **Ed25519** public-key signatures. The private key never leaves your
178
+ machine. The public key is distributed via git. Anyone can verify a signed
179
+ package, but only private key holders can publish.
150
180
 
151
181
  ### Integrity Rules
152
182
 
@@ -166,14 +196,6 @@ HIPP enforces strict integrity rules when publishing:
166
196
 
167
197
  ---
168
198
 
169
- ## Security
170
-
171
- HIPP uses **Ed25519** public-key signatures. The private key never leaves your
172
- machine. The public key is distributed via git. Anyone can verify a signed
173
- package, but only private key holders can publish.
174
-
175
- ---
176
-
177
199
  ## License
178
200
 
179
201
  **0BSD** (BSD Zero Clause License) By Dmytri Kleiner <dev@dmytri.to>
@@ -189,11 +211,21 @@ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
189
211
  OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
190
212
  PERFORMANCE OF THIS SOFTWARE.
191
213
 
214
+ ## Verify
215
+
216
+ Verify this package with [@dk/hipp](https://www.npmjs.com/package/@dk/hipp):
217
+
218
+ ```bash
219
+ npx @dk/hipp verify @dk/hipp@0.1.19
220
+ ```
221
+
192
222
  ```json
193
223
  {
194
224
  "origin": "git@github.com:dmytri/hipp.git",
195
- "tag": "v0.1.16",
196
- "hash": "935d7b90b43afd348840645f7fd629054bf50716c317fc41065b2cccf7513680",
197
- "signature": "QIM4TZ3kuYv7/z3RZSkVPK74XI1PFyD/XLARR2evDCEwNOt1VlXIPXwaMZFn1kH0gDQxx3PuF73Kokld+dMjBw=="
225
+ "tag": "v0.1.19",
226
+ "hash": "adcdd07b563c5667d405b36ee1e391710e1baf4ee9434b1fd2f5a11c2acdecf0",
227
+ "signature": "MdkA4AbDX4ofShqXKJxwWX+x+RWQJU16WpSkFvHU8LihgdIBJbsyNvzIfrjm+3cll2FdR5u3+/dwv+a0rHbUDw==",
228
+ "name": "Dmytri Kleiner",
229
+ "email": "dev@dmytri.to"
198
230
  }
199
231
  ```
package/hipp.js CHANGED
@@ -27,6 +27,12 @@ function git(args, options = {}) {
27
27
  }).trim();
28
28
  }
29
29
 
30
+ function getGitUserInfo() {
31
+ const name = git(['config', 'user.name']);
32
+ const email = git(['config', 'user.email']);
33
+ return { name, email };
34
+ }
35
+
30
36
  function runCmd(cmd, args, options = {}) {
31
37
  const result = spawnSync(cmd, args, {
32
38
  encoding: 'utf8',
@@ -64,10 +70,18 @@ function loadOrGenerateKeys() {
64
70
  const pubPath = getPublicKeyPath();
65
71
 
66
72
  if (fs.existsSync(privPath) && fs.existsSync(pubPath)) {
67
- return {
68
- privateKey: fs.readFileSync(privPath, 'utf8'),
69
- publicKey: fs.readFileSync(pubPath, 'utf8'),
70
- };
73
+ const privateKey = fs.readFileSync(privPath, 'utf8');
74
+ const publicKey = fs.readFileSync(pubPath, 'utf8');
75
+
76
+ const testData = 'hipp-key-validation';
77
+ const testSignature = signContent(testData, privateKey);
78
+ const valid = verifySignature(testData, testSignature, publicKey);
79
+
80
+ if (valid) {
81
+ return { privateKey, publicKey };
82
+ }
83
+
84
+ log.warn('⚠️ Key mismatch detected. Generating new keypair...');
71
85
  }
72
86
 
73
87
  log.info('🔑 Generating Ed25519 keypair...');
@@ -107,20 +121,8 @@ function verifySignature(data, signature, publicKey) {
107
121
  }, Buffer.from(signature, 'base64'));
108
122
  }
109
123
 
110
- function createManifest(hash, signature) {
111
- return JSON.stringify({ hash, signature }, null, 2);
112
- }
113
-
114
- function parseManifest(manifestStr) {
115
- try {
116
- return JSON.parse(manifestStr);
117
- } catch {
118
- return null;
119
- }
120
- }
121
-
122
- function buildSignData(hash, origin, tag) {
123
- return `${hash}\n${origin}\n${tag}\n`;
124
+ function buildSignData(hash, origin, tag, name, email) {
125
+ return `${hash}\n${origin}\n${tag}\n${name}\n${email}\n`;
124
126
  }
125
127
 
126
128
  function findLastJsonBlock(readmeContent) {
@@ -408,33 +410,18 @@ function copyTrackedFilesFromDir(stageDir, repoDir, files) {
408
410
  }
409
411
 
410
412
  async function runVerify(packageSpec) {
411
- let pkgName, pkgVersion;
412
- if (packageSpec.startsWith('@')) {
413
- const atIndex = packageSpec.indexOf('@', 1);
414
- if (atIndex === -1) {
415
- pkgName = packageSpec;
416
- pkgVersion = undefined;
417
- } else {
418
- pkgName = packageSpec.slice(0, atIndex);
419
- pkgVersion = packageSpec.slice(atIndex + 1);
420
- }
421
- } else {
422
- const atIndex = packageSpec.indexOf('@');
423
- if (atIndex === -1) {
424
- pkgName = packageSpec;
425
- pkgVersion = undefined;
426
- } else {
427
- pkgName = packageSpec.slice(0, atIndex);
428
- pkgVersion = packageSpec.slice(atIndex + 1);
429
- }
430
- }
413
+ const npa = require('npm-package-arg');
414
+ const parsed = npa(packageSpec);
415
+ const pkgName = parsed.name;
416
+ const pkgVersion = parsed.fetchSpec;
431
417
  log.info(`🔍 HIPP Verify: ${pkgName}${pkgVersion ? '@' + pkgVersion : ''}`);
432
418
 
433
- const registryUrl = `https://registry.npmjs.org/${encodeURIComponent(pkgName)}/${pkgVersion || 'latest'}`;
419
+ const registryUrl = `https://registry.npmjs.org/${parsed.escapedName}/${pkgVersion || 'latest'}`;
434
420
 
435
- log.info(`📦 Fetching from npm...`);
421
+ log.info(`📦 Fetching manifest from npm...`);
436
422
  const registryJson = runCmd('curl', ['-s', '-L', registryUrl]);
437
423
  let tarballUrl;
424
+ let manifest;
438
425
  try {
439
426
  const json = JSON.parse(registryJson.stdout.trim());
440
427
  tarballUrl = json.dist.tarball;
@@ -452,6 +439,10 @@ async function runVerify(packageSpec) {
452
439
  fail(`❌ Failed to download tarball`);
453
440
  }
454
441
 
442
+ const npmTarballContent = fs.readFileSync(tarballPath);
443
+ const npmHash = sha256(npmTarballContent);
444
+ log.success(`📦 NPM tarball hash: ${npmHash.slice(0, 12)}...`);
445
+
455
446
  if (fs.existsSync(extractDir)) {
456
447
  fs.rmSync(extractDir, { recursive: true });
457
448
  }
@@ -464,25 +455,25 @@ async function runVerify(packageSpec) {
464
455
  }
465
456
 
466
457
  const packageDir = path.join(extractDir, 'package');
467
- const stagedReadmePath = path.join(packageDir, 'README.md');
458
+ const npmReadmePath = path.join(packageDir, 'README.md');
468
459
 
469
- if (!fs.existsSync(stagedReadmePath)) {
470
- fail(`❌ README.md not found in package at ${stagedReadmePath}`);
460
+ if (!fs.existsSync(npmReadmePath)) {
461
+ fail(`❌ README.md not found in npm package`);
471
462
  }
472
463
 
473
- const stagedReadme = fs.readFileSync(stagedReadmePath, 'utf8');
474
- const manifest = findLastJsonBlock(stagedReadme);
475
- if (!manifest || !manifest.origin || !manifest.tag || !manifest.hash || !manifest.signature) {
464
+ const npmReadme = fs.readFileSync(npmReadmePath, 'utf8');
465
+ manifest = findLastJsonBlock(npmReadme);
466
+ if (!manifest || !manifest.origin || !manifest.tag || !manifest.hash || !manifest.signature || !manifest.name || !manifest.email) {
476
467
  fail(`❌ Manifest not found or invalid in README`);
477
468
  }
478
469
 
479
- const { origin: originUrl, tag, hash: npmHash, signature } = manifest;
470
+ const { origin: originUrl, tag, signature, name, email } = manifest;
480
471
 
472
+ log.info(`🌿 Cloning git origin at tag ${tag}...`);
481
473
  const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), `hipp-verify-git-`));
482
474
  const stageDir = fs.mkdtempSync(path.join(os.tmpdir(), `hipp-verify-stage-`));
483
475
 
484
476
  try {
485
- log.info(`🌿 Fetching from git origin at tag ${tag}...`);
486
477
  git(['clone', '--branch', tag, '--depth', '1', originUrl, tmpDir], { stdio: 'pipe' });
487
478
 
488
479
  const publicKeyPath = path.join(tmpDir, 'hipp.pub');
@@ -496,24 +487,57 @@ async function runVerify(packageSpec) {
496
487
  const trackedFiles = getTrackedFilesFromDir(tmpDir);
497
488
  copyTrackedFilesFromDir(stageDir, tmpDir, trackedFiles);
498
489
 
499
- log.info(`📦 Packing to verify content hash...`);
500
- const { tarballHash } = packAndHash(stageDir);
490
+ log.info(`📦 Packing clean git files...`);
491
+ const { tarballHash: cleanHash } = packAndHash(stageDir);
492
+ log.success(`📦 Clean hash: ${cleanHash.slice(0, 12)}...`);
501
493
 
502
- if (tarballHash !== npmHash) {
503
- fail(`❌ Hash mismatch: git content does not match npm manifest`);
494
+ log.info(`🔍 Check 2: Verifying manifest hash...`);
495
+ if (cleanHash !== manifest.hash) {
496
+ fail(`❌ Manifest hash mismatch: clean git tarball does not match manifest`);
504
497
  }
498
+ log.success(`🔒 Manifest hash verified`);
505
499
 
506
- log.success(`🔒 Content hash verified: ${npmHash.slice(0, 12)}...`);
507
-
508
- const signData = buildSignData(npmHash, originUrl, tag);
500
+ log.info(`🔍 Check 1: Verifying signature...`);
501
+ const signData = buildSignData(manifest.hash, originUrl, tag, name, email);
509
502
  const signatureValid = verifySignature(signData, signature, publicKey);
510
-
511
503
  if (!signatureValid) {
512
504
  fail(`❌ Signature verification failed`);
513
505
  }
514
-
515
506
  log.success(`🔏 Signature verified`);
516
- log.success(`✅ Package ${pkgName} verified successfully!`);
507
+
508
+ log.info(`🔍 Check 3: Rebuilding from source...`);
509
+ const stagedReadmePath = path.join(stageDir, 'README.md');
510
+ let stagedReadme = fs.readFileSync(stagedReadmePath, 'utf8');
511
+ const tagVersion = semver.clean(tag);
512
+ if (!tagVersion) {
513
+ fail(`❌ Tag ${tag} is not valid semver`);
514
+ }
515
+ stagedReadme = stagedReadme.trimEnd() + '\n\n## Verify\n\n' +
516
+ 'Verify this package with [@dk/hipp](https://www.npmjs.com/package/@dk/hipp):\n\n' +
517
+ '```bash\n' +
518
+ `npx @dk/hipp verify ${pkgName}@${tagVersion}\n` +
519
+ '```\n\n' +
520
+ '```json\n' + JSON.stringify(manifest, null, 2) + '\n```\n';
521
+ fs.writeFileSync(stagedReadmePath, stagedReadme);
522
+
523
+ const stagedPkgPath = path.join(stageDir, 'package.json');
524
+ const stagedPkg = JSON.parse(fs.readFileSync(stagedPkgPath, 'utf8'));
525
+ stagedPkg.version = tagVersion;
526
+ fs.writeFileSync(stagedPkgPath, JSON.stringify(stagedPkg, null, 2) + '\n');
527
+
528
+ const { tarballHash: rebuildHash } = packAndHash(stageDir);
529
+ log.success(`📦 Rebuild hash: ${rebuildHash.slice(0, 12)}...`);
530
+
531
+ if (rebuildHash !== npmHash) {
532
+ log.error(`❌ Rebuild mismatch!`);
533
+ log.error(` NPM tarball: ${npmHash}`);
534
+ log.error(` Git rebuild: ${rebuildHash}`);
535
+ fail(`❌ Package integrity compromised`);
536
+ }
537
+ log.success(`🔄 Rebuild verified`);
538
+
539
+ log.success(`✅ Verified: all checks passed`);
540
+ log.info(`📍 Publisher: ${name} <${email}>`);
517
541
  log.info(`📍 Origin: ${originUrl}`);
518
542
  log.info(`📍 Tag: ${tag}`);
519
543
  } finally {
@@ -613,7 +637,8 @@ async function run() {
613
637
  stagedReadme = fs.readFileSync(stagedReadmePath, 'utf8');
614
638
  }
615
639
 
616
- const dataToSign = buildSignData(tarballHash, provenance.remoteUrl, rawTag);
640
+ const { name, email } = getGitUserInfo();
641
+ const dataToSign = buildSignData(tarballHash, provenance.remoteUrl, rawTag, name, email);
617
642
  const signature = signContent(dataToSign, privateKey);
618
643
 
619
644
  const manifestJson = {
@@ -621,9 +646,16 @@ async function run() {
621
646
  tag: rawTag,
622
647
  hash: tarballHash,
623
648
  signature: signature,
649
+ name: name,
650
+ email: email,
624
651
  };
625
652
 
626
- stagedReadme = stagedReadme.trimEnd() + '\n\n```json\n' + JSON.stringify(manifestJson, null, 2) + '\n```\n';
653
+ stagedReadme = stagedReadme.trimEnd() + '\n\n## Verify\n\n' +
654
+ 'Verify this package with [@dk/hipp](https://www.npmjs.com/package/@dk/hipp):\n\n' +
655
+ '```bash\n' +
656
+ `npx @dk/hipp verify ${pkg.name}@${version}\n` +
657
+ '```\n\n' +
658
+ '```json\n' + JSON.stringify(manifestJson, null, 2) + '\n```\n';
627
659
  fs.writeFileSync(stagedReadmePath, stagedReadme);
628
660
 
629
661
  const stagedPkgPath = path.join(stageDir, 'package.json');
@@ -666,19 +698,33 @@ const isVerify = process.argv.includes('verify');
666
698
  const verifyIndex = process.argv.indexOf('verify');
667
699
  const packageSpec = verifyIndex !== -1 ? process.argv[verifyIndex + 1] : null;
668
700
 
669
- if (isVerify && packageSpec) {
670
- runVerify(packageSpec);
701
+ if (isVerify) {
702
+ const specToVerify = packageSpec;
703
+ if (specToVerify) {
704
+ runVerify(specToVerify);
705
+ } else {
706
+ const hippPkgPath = path.join(path.dirname(process.argv[1]), 'package.json');
707
+ const hippPkg = JSON.parse(fs.readFileSync(hippPkgPath, 'utf8'));
708
+ runVerify(`${hippPkg.name}@${hippPkg.version}`);
709
+ }
671
710
  } else if (process.argv.includes('--help') || process.argv.includes('-h')) {
672
711
  console.log(`\x1b[36mHIPP - High Integrity Package Publisher\x1b[0m
673
712
 
674
713
  Usage:
675
714
  npx hipp [options] [-- npm-options]
676
- npx hipp verify <package>[@version]
715
+ npx hipp verify [@package[@version]]
716
+
717
+ Without arguments, verifies the installed hipp version.
677
718
 
678
719
  Options:
679
720
  -y, --yes Skip confirmation prompt
680
721
  -h, --help Show this help
681
722
 
723
+ Verify: Downloads npm tarball, clones git at tag, runs all three verification checks:
724
+ 1. Signature verification (manifest signed by private key)
725
+ 2. Manifest hash (clean git tarball matches manifest hash)
726
+ 3. Rebuild verification (npm tarball equals git rebuild with manifest+version)
727
+
682
728
  Integrity rules:
683
729
  - package.json version must be 0.0.0
684
730
  - package-lock.json must exist and be tracked
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dk/hipp",
3
- "version": "0.1.16",
3
+ "version": "0.1.19",
4
4
  "description": "High Integrity Package Publisher",
5
5
  "main": "hipp.js",
6
6
  "bin": {
@@ -11,6 +11,7 @@
11
11
  },
12
12
  "author": "Dmytri Kleiner <dev@dmytri.to>",
13
13
  "dependencies": {
14
+ "npm-package-arg": "^13.0.2",
14
15
  "semver": ">=7.6.0"
15
16
  },
16
17
  "engines": {