@dk/hipp 0.1.19 â 0.1.21
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 +24 -17
- package/hipp.js +31 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -109,32 +109,36 @@ The manifest contains:
|
|
|
109
109
|
{
|
|
110
110
|
"origin": "git@github.com:dk/your-package.git",
|
|
111
111
|
"tag": "v1.0.0",
|
|
112
|
+
"revision": "<git-commit-hash>",
|
|
112
113
|
"hash": "<sha256-of-tarball>",
|
|
113
114
|
"signature": "<base64-ed25519-signature>",
|
|
114
115
|
"name": "Jane Developer",
|
|
115
|
-
"email": "jane@example.com"
|
|
116
|
+
"email": "jane@example.com",
|
|
117
|
+
"npm": "10.2.4",
|
|
118
|
+
"node": "v20.11.0"
|
|
116
119
|
}
|
|
117
120
|
```
|
|
118
121
|
|
|
119
122
|
**Step 2: Clone git and verify**
|
|
120
123
|
|
|
121
124
|
3. Clone the repository at the tagged commit (using origin/tag from manifest)
|
|
122
|
-
4.
|
|
123
|
-
5.
|
|
124
|
-
6.
|
|
125
|
-
7.
|
|
125
|
+
4. Verify the cloned commit hash matches the `revision` field in manifest
|
|
126
|
+
5. Stage all tracked files
|
|
127
|
+
6. Run `npm pack` to create a tarball
|
|
128
|
+
7. Compute SHA256 hash of the clean tarball
|
|
129
|
+
8. Compare with the `hash` field from the npm manifest
|
|
126
130
|
|
|
127
131
|
**Step 3: Verify signature**
|
|
128
132
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
133
|
+
9. Read `hipp.pub` from the cloned repository
|
|
134
|
+
10. Verify the signature was created by signing:
|
|
135
|
+
`hash + "\n" + origin + "\n" + tag + "\n" + revision + "\n" + name + "\n" + email`
|
|
132
136
|
|
|
133
137
|
**Step 4: Rebuild verification**
|
|
134
138
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
139
|
+
11. Append the manifest to the staged README
|
|
140
|
+
12. Update the staged `package.json` version to match the tag
|
|
141
|
+
13. Run `npm pack` again and verify the hash matches the npm tarball
|
|
138
142
|
|
|
139
143
|
### Three Verification Checks
|
|
140
144
|
|
|
@@ -216,16 +220,19 @@ PERFORMANCE OF THIS SOFTWARE.
|
|
|
216
220
|
Verify this package with [@dk/hipp](https://www.npmjs.com/package/@dk/hipp):
|
|
217
221
|
|
|
218
222
|
```bash
|
|
219
|
-
npx @dk/hipp verify @dk/hipp@0.1.
|
|
223
|
+
npx @dk/hipp verify @dk/hipp@0.1.21
|
|
220
224
|
```
|
|
221
225
|
|
|
222
226
|
```json
|
|
223
227
|
{
|
|
224
|
-
"origin": "
|
|
225
|
-
"tag": "v0.1.
|
|
226
|
-
"
|
|
227
|
-
"
|
|
228
|
+
"origin": "https://github.com/dmytri/hipp.git",
|
|
229
|
+
"tag": "v0.1.21",
|
|
230
|
+
"revision": "0a0be44db52e62d425959bda9f640049005c8809",
|
|
231
|
+
"hash": "50de72f67534fe6d9054c5b0cfab317aa1620d258cb291bd1dc65d60a43d77d8",
|
|
232
|
+
"signature": "m0+8tk8kM835KJcTeHPZPG20XLh4rEtyISoJB54aV8LknEmJVM5yRG6syq+Kg0AAP6VcJMmNrpMG9jOeDkyoBQ==",
|
|
228
233
|
"name": "Dmytri Kleiner",
|
|
229
|
-
"email": "dev@dmytri.to"
|
|
234
|
+
"email": "dev@dmytri.to",
|
|
235
|
+
"npm": "11.12.1",
|
|
236
|
+
"node": "v25.8.2"
|
|
230
237
|
}
|
|
231
238
|
```
|
package/hipp.js
CHANGED
|
@@ -33,6 +33,14 @@ function getGitUserInfo() {
|
|
|
33
33
|
return { name, email };
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
function sshToHttpsUrl(sshUrl) {
|
|
37
|
+
const match = sshUrl.match(/^git@([^:]+):(.+\.git)$/);
|
|
38
|
+
if (match) {
|
|
39
|
+
return `https://${match[1]}/${match[2]}`;
|
|
40
|
+
}
|
|
41
|
+
return sshUrl;
|
|
42
|
+
}
|
|
43
|
+
|
|
36
44
|
function runCmd(cmd, args, options = {}) {
|
|
37
45
|
const result = spawnSync(cmd, args, {
|
|
38
46
|
encoding: 'utf8',
|
|
@@ -121,8 +129,8 @@ function verifySignature(data, signature, publicKey) {
|
|
|
121
129
|
}, Buffer.from(signature, 'base64'));
|
|
122
130
|
}
|
|
123
131
|
|
|
124
|
-
function buildSignData(hash, origin, tag, name, email) {
|
|
125
|
-
return `${hash}\n${origin}\n${tag}\n${name}\n${email}\n`;
|
|
132
|
+
function buildSignData(hash, origin, tag, revision, name, email) {
|
|
133
|
+
return `${hash}\n${origin}\n${tag}\n${revision}\n${name}\n${email}\n`;
|
|
126
134
|
}
|
|
127
135
|
|
|
128
136
|
function findLastJsonBlock(readmeContent) {
|
|
@@ -463,11 +471,11 @@ async function runVerify(packageSpec) {
|
|
|
463
471
|
|
|
464
472
|
const npmReadme = fs.readFileSync(npmReadmePath, 'utf8');
|
|
465
473
|
manifest = findLastJsonBlock(npmReadme);
|
|
466
|
-
if (!manifest || !manifest.origin || !manifest.tag || !manifest.hash || !manifest.signature || !manifest.name || !manifest.email) {
|
|
474
|
+
if (!manifest || !manifest.origin || !manifest.tag || !manifest.revision || !manifest.hash || !manifest.signature || !manifest.name || !manifest.email) {
|
|
467
475
|
fail(`â Manifest not found or invalid in README`);
|
|
468
476
|
}
|
|
469
477
|
|
|
470
|
-
const { origin: originUrl, tag, signature, name, email } = manifest;
|
|
478
|
+
const { origin: originUrl, tag, revision, signature, name, email, npm: npmVer, node: nodeVer } = manifest;
|
|
471
479
|
|
|
472
480
|
log.info(`đŋ Cloning git origin at tag ${tag}...`);
|
|
473
481
|
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), `hipp-verify-git-`));
|
|
@@ -476,6 +484,12 @@ async function runVerify(packageSpec) {
|
|
|
476
484
|
try {
|
|
477
485
|
git(['clone', '--branch', tag, '--depth', '1', originUrl, tmpDir], { stdio: 'pipe' });
|
|
478
486
|
|
|
487
|
+
const clonedRevision = git(['rev-parse', 'HEAD'], { cwd: tmpDir });
|
|
488
|
+
if (clonedRevision !== revision) {
|
|
489
|
+
fail(`â Revision mismatch: manifest claims ${revision.slice(0, 12)} but tag points to ${clonedRevision.slice(0, 12)}`);
|
|
490
|
+
}
|
|
491
|
+
log.success(`đˇī¸ Revision verified: ${revision.slice(0, 12)}...`);
|
|
492
|
+
|
|
479
493
|
const publicKeyPath = path.join(tmpDir, 'hipp.pub');
|
|
480
494
|
if (!fs.existsSync(publicKeyPath)) {
|
|
481
495
|
fail(`â hipp.pub not found in git at tag ${tag}`);
|
|
@@ -498,7 +512,7 @@ async function runVerify(packageSpec) {
|
|
|
498
512
|
log.success(`đ Manifest hash verified`);
|
|
499
513
|
|
|
500
514
|
log.info(`đ Check 1: Verifying signature...`);
|
|
501
|
-
const signData = buildSignData(manifest.hash, originUrl, tag, name, email);
|
|
515
|
+
const signData = buildSignData(manifest.hash, originUrl, tag, revision, name, email);
|
|
502
516
|
const signatureValid = verifySignature(signData, signature, publicKey);
|
|
503
517
|
if (!signatureValid) {
|
|
504
518
|
fail(`â Signature verification failed`);
|
|
@@ -540,6 +554,9 @@ async function runVerify(packageSpec) {
|
|
|
540
554
|
log.info(`đ Publisher: ${name} <${email}>`);
|
|
541
555
|
log.info(`đ Origin: ${originUrl}`);
|
|
542
556
|
log.info(`đ Tag: ${tag}`);
|
|
557
|
+
if (npmVer && nodeVer) {
|
|
558
|
+
log.info(`âšī¸ npm: ${npmVer} | node: ${nodeVer}`);
|
|
559
|
+
}
|
|
543
560
|
} finally {
|
|
544
561
|
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
545
562
|
fs.rmSync(stageDir, { recursive: true, force: true });
|
|
@@ -638,16 +655,23 @@ async function run() {
|
|
|
638
655
|
}
|
|
639
656
|
|
|
640
657
|
const { name, email } = getGitUserInfo();
|
|
641
|
-
const
|
|
658
|
+
const revision = refInfo.head;
|
|
659
|
+
const npmVersion = runCmd('npm', ['--version']).stdout.trim();
|
|
660
|
+
const nodeVersion = process.version;
|
|
661
|
+
const originUrl = sshToHttpsUrl(provenance.remoteUrl);
|
|
662
|
+
const dataToSign = buildSignData(tarballHash, originUrl, rawTag, revision, name, email);
|
|
642
663
|
const signature = signContent(dataToSign, privateKey);
|
|
643
664
|
|
|
644
665
|
const manifestJson = {
|
|
645
|
-
origin:
|
|
666
|
+
origin: originUrl,
|
|
646
667
|
tag: rawTag,
|
|
668
|
+
revision: revision,
|
|
647
669
|
hash: tarballHash,
|
|
648
670
|
signature: signature,
|
|
649
671
|
name: name,
|
|
650
672
|
email: email,
|
|
673
|
+
npm: npmVersion,
|
|
674
|
+
node: nodeVersion,
|
|
651
675
|
};
|
|
652
676
|
|
|
653
677
|
stagedReadme = stagedReadme.trimEnd() + '\n\n## Verify\n\n' +
|