@dk/hipp 0.1.15 → 0.1.17

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 +112 -71
  2. package/hipp.js +98 -62
  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
11
+ ## The Problem
18
12
 
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:
13
+ Traditional NPM versioning requires storing the "Version of Truth" in `package.json`.
14
+ This creates a **State Conflict**:
22
15
 
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.
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
27
22
 
28
- **HIPP ensures they are fundamentally linked by extracting the version directly
29
- from the Git Tag, and cryptographically signing the package contents.**
23
+ ## The Solution
30
24
 
31
- ### No "Chore" Noise
25
+ **HIPP makes `package.json` version immutable (0.0.0)** - the **HIPP Doctrine**.
32
26
 
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.
36
-
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,26 +90,97 @@ 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
101
101
 
102
- 1. **Fetch from npm**: Downloads the package tarball and extracts the manifest
103
- 2. **Fetch from git**: Clones the repository at the tagged commit and extracts the public key
104
- 3. **Hash Verification**: Computes the hash of the git content and compares with the manifest
105
- 4. **Signature Verification**: Verifies the manifest signature using the public key
102
+ **Step 1: Get manifest from npm**
103
+
104
+ 1. Fetch the package tarball from npm registry
105
+ 2. Extract the README and parse the JSON manifest appended to it
106
+
107
+ The manifest contains:
108
+ ```json
109
+ {
110
+ "origin": "git@github.com:dk/your-package.git",
111
+ "tag": "v1.0.0",
112
+ "hash": "<sha256-of-tarball>",
113
+ "signature": "<base64-ed25519-signature>",
114
+ "name": "Jane Developer",
115
+ "email": "jane@example.com"
116
+ }
117
+ ```
118
+
119
+ **Step 2: Clone git and verify**
120
+
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
126
+
127
+ **Step 3: Verify signature**
128
+
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`
132
+
133
+ **Step 4: Rebuild verification**
134
+
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
138
+
139
+ ### Three Verification Checks
140
+
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 |
146
+
147
+ ### What Verification Guarantees
148
+
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.
160
+
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
106
176
 
107
- If both checks pass, you can be certain that:
108
- - The package contents in npm exactly match the git tag
109
- - The manifest was signed by the holder of the private key
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.
110
180
 
111
181
  ### Integrity Rules
112
182
 
113
- HIPP enforces strict integrity rules:
183
+ HIPP enforces strict integrity rules when publishing:
114
184
 
115
185
  - `package.json` version must be `0.0.0`
116
186
  - `package-lock.json` must exist and be tracked by git
@@ -126,14 +196,6 @@ HIPP enforces strict integrity rules:
126
196
 
127
197
  ---
128
198
 
129
- ## Security
130
-
131
- HIPP uses **Ed25519** public-key signatures. The private key never leaves your
132
- machine. The public key is distributed via git. Anyone can verify a signed
133
- package, but only private key holders can publish.
134
-
135
- ---
136
-
137
199
  ## License
138
200
 
139
201
  **0BSD** (BSD Zero Clause License) By Dmytri Kleiner <dev@dmytri.to>
@@ -149,34 +211,13 @@ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
149
211
  OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
150
212
  PERFORMANCE OF THIS SOFTWARE.
151
213
 
152
-
153
- ```json
154
- {
155
- "origin": "git@github.com:dmytri/hipp.git",
156
- "tag": "v0.1.10"
157
- }
158
- ```
159
-
160
- ```npx @dk/hipp @dk/hipp@0.1.10
161
- ```
162
-
163
- <!-- HIPP-META -->
164
- ```json
165
- {
166
- "origin": "git@github.com:dmytri/hipp.git",
167
- "tag": "v0.1.11"
168
- }
169
- ```
170
-
171
- ```npx @dk/hipp @dk/hipp@0.1.11
172
- ```
173
- <!-- /HIPP-META -->
174
-
175
214
  ```json
176
215
  {
177
216
  "origin": "git@github.com:dmytri/hipp.git",
178
- "tag": "v0.1.15",
179
- "hash": "cee8b863e598809fe8a194c1f102c857a0698e8158bef9e39493b31da6411226",
180
- "signature": "Hc5fpg5Kg67ztJBdbtM3DvdrccL1e0KEyYwenwVIHLqcCNadXT9oatpgkO9dFaaGkCMUfttMGtWv8iJtYy+tBg=="
217
+ "tag": "v0.1.17",
218
+ "hash": "7a9817b5d57a5c5c43cac3d608d4a0e907dcbdf8031252a62b29195391b38b26",
219
+ "signature": "/o75TYcgRUS0pVQEmJeCamZ5z6wTDQtawuaKYd4lbZCBWuqEeuaPECRefTgzC7tDr6bCyFd6p4biPGWIroBlCA==",
220
+ "name": "Dmytri Kleiner",
221
+ "email": "dev@dmytri.to"
181
222
  }
182
223
  ```
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,52 @@ 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
+ stagedReadme = stagedReadme.trimEnd() + '\n\n```json\n' + JSON.stringify(manifest, null, 2) + '\n```\n';
512
+ fs.writeFileSync(stagedReadmePath, stagedReadme);
513
+
514
+ const stagedPkgPath = path.join(stageDir, 'package.json');
515
+ const stagedPkg = JSON.parse(fs.readFileSync(stagedPkgPath, 'utf8'));
516
+ const tagVersion = semver.clean(tag);
517
+ if (!tagVersion) {
518
+ fail(`❌ Tag ${tag} is not valid semver`);
519
+ }
520
+ stagedPkg.version = tagVersion;
521
+ fs.writeFileSync(stagedPkgPath, JSON.stringify(stagedPkg, null, 2) + '\n');
522
+
523
+ const { tarballHash: rebuildHash } = packAndHash(stageDir);
524
+ log.success(`📦 Rebuild hash: ${rebuildHash.slice(0, 12)}...`);
525
+
526
+ if (rebuildHash !== npmHash) {
527
+ log.error(`❌ Rebuild mismatch!`);
528
+ log.error(` NPM tarball: ${npmHash}`);
529
+ log.error(` Git rebuild: ${rebuildHash}`);
530
+ fail(`❌ Package integrity compromised`);
531
+ }
532
+ log.success(`🔄 Rebuild verified`);
533
+
534
+ log.success(`✅ Verified: all checks passed`);
535
+ log.info(`📍 Publisher: ${name} <${email}>`);
517
536
  log.info(`📍 Origin: ${originUrl}`);
518
537
  log.info(`📍 Tag: ${tag}`);
519
538
  } finally {
@@ -613,7 +632,8 @@ async function run() {
613
632
  stagedReadme = fs.readFileSync(stagedReadmePath, 'utf8');
614
633
  }
615
634
 
616
- const dataToSign = buildSignData(tarballHash, provenance.remoteUrl, rawTag);
635
+ const { name, email } = getGitUserInfo();
636
+ const dataToSign = buildSignData(tarballHash, provenance.remoteUrl, rawTag, name, email);
617
637
  const signature = signContent(dataToSign, privateKey);
618
638
 
619
639
  const manifestJson = {
@@ -621,6 +641,8 @@ async function run() {
621
641
  tag: rawTag,
622
642
  hash: tarballHash,
623
643
  signature: signature,
644
+ name: name,
645
+ email: email,
624
646
  };
625
647
 
626
648
  stagedReadme = stagedReadme.trimEnd() + '\n\n```json\n' + JSON.stringify(manifestJson, null, 2) + '\n```\n';
@@ -666,19 +688,33 @@ const isVerify = process.argv.includes('verify');
666
688
  const verifyIndex = process.argv.indexOf('verify');
667
689
  const packageSpec = verifyIndex !== -1 ? process.argv[verifyIndex + 1] : null;
668
690
 
669
- if (isVerify && packageSpec) {
670
- runVerify(packageSpec);
691
+ if (isVerify) {
692
+ const specToVerify = packageSpec;
693
+ if (specToVerify) {
694
+ runVerify(specToVerify);
695
+ } else {
696
+ const hippPkgPath = path.join(path.dirname(process.argv[1]), 'package.json');
697
+ const hippPkg = JSON.parse(fs.readFileSync(hippPkgPath, 'utf8'));
698
+ runVerify(`${hippPkg.name}@${hippPkg.version}`);
699
+ }
671
700
  } else if (process.argv.includes('--help') || process.argv.includes('-h')) {
672
701
  console.log(`\x1b[36mHIPP - High Integrity Package Publisher\x1b[0m
673
702
 
674
703
  Usage:
675
704
  npx hipp [options] [-- npm-options]
676
- npx hipp verify <package>[@version]
705
+ npx hipp verify [@package[@version]]
706
+
707
+ Without arguments, verifies the installed hipp version.
677
708
 
678
709
  Options:
679
710
  -y, --yes Skip confirmation prompt
680
711
  -h, --help Show this help
681
712
 
713
+ Verify: Downloads npm tarball, clones git at tag, runs all three verification checks:
714
+ 1. Signature verification (manifest signed by private key)
715
+ 2. Manifest hash (clean git tarball matches manifest hash)
716
+ 3. Rebuild verification (npm tarball equals git rebuild with manifest+version)
717
+
682
718
  Integrity rules:
683
719
  - package.json version must be 0.0.0
684
720
  - 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.15",
3
+ "version": "0.1.17",
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": {