proof-of-commitment 1.2.0 → 1.4.0
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 +11 -8
- package/index.js +50 -14
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -8,18 +8,21 @@ npx proof-of-commitment axios zod chalk
|
|
|
8
8
|
|
|
9
9
|
```
|
|
10
10
|
──────────────────────────────────────────────────────────────────────────
|
|
11
|
-
Package Risk Score
|
|
11
|
+
Package Risk Score Publishers Downloads Age
|
|
12
12
|
──────────────────────────────────────────────────────────────────────────
|
|
13
13
|
axios 🔴 CRITICAL 89 1 102.0M/wk 11.6y
|
|
14
|
-
|
|
14
|
+
↳ 30+ GitHub contributors — publish-access concentration risk despite active community
|
|
15
|
+
└ longevity=25 momentum=25 releases=20 publishers=4 github=15
|
|
15
16
|
zod 🔴 CRITICAL 83 1 154.0M/wk 6.1y
|
|
16
|
-
|
|
17
|
+
↳ 30+ GitHub contributors — publish-access concentration risk despite active community
|
|
18
|
+
└ longevity=25 momentum=25 releases=18 publishers=4 github=11
|
|
17
19
|
chalk 🔴 CRITICAL 75 1 414.6M/wk 12.7y
|
|
18
|
-
|
|
20
|
+
↳ 30+ GitHub contributors — publish-access concentration risk despite active community
|
|
21
|
+
└ longevity=25 momentum=22 releases=13 publishers=4 github=11
|
|
19
22
|
──────────────────────────────────────────────────────────────────────────
|
|
20
23
|
|
|
21
24
|
⚠ 3 CRITICAL packages found.
|
|
22
|
-
CRITICAL = sole
|
|
25
|
+
CRITICAL = sole npm publisher + >10M weekly downloads (publish-access concentration risk)
|
|
23
26
|
Full breakdown: https://getcommit.dev/audit?packages=axios,zod,chalk
|
|
24
27
|
```
|
|
25
28
|
|
|
@@ -27,16 +30,16 @@ chalk 🔴 CRITICAL 75 1 414.6M/wk 12.
|
|
|
27
30
|
|
|
28
31
|
`npm audit` finds *known* CVEs — vulnerabilities already catalogued in a database. This scores *structural risk before it becomes a CVE*.
|
|
29
32
|
|
|
30
|
-
The axios attack on April 1st, 2026: `npm audit` showed zero issues beforehand. Proof of Commitment flagged axios as CRITICAL (1
|
|
33
|
+
The axios attack on April 1st, 2026: `npm audit` showed zero issues beforehand. Proof of Commitment flagged axios as CRITICAL (1 npm publisher, 96M downloads/week) — the exact publish-access concentration profile that made it a high-value target.
|
|
31
34
|
|
|
32
35
|
**Score dimensions:**
|
|
33
36
|
- **Longevity** (25 pts) — years in production
|
|
34
37
|
- **Download Momentum** (25 pts) — weekly download trend
|
|
35
38
|
- **Release Consistency** (20 pts) — days since last release
|
|
36
|
-
- **
|
|
39
|
+
- **Publisher Depth** (15 pts) — npm publish-access holders
|
|
37
40
|
- **GitHub Backing** (15 pts) — organization/team support
|
|
38
41
|
|
|
39
|
-
**CRITICAL** = sole
|
|
42
|
+
**CRITICAL** = sole npm publisher + >10M weekly downloads (publish-access concentration risk)
|
|
40
43
|
|
|
41
44
|
## Usage
|
|
42
45
|
|
package/index.js
CHANGED
|
@@ -65,7 +65,7 @@ function printTable(results, { totalScanned, totalCritical, lockfile } = {}) {
|
|
|
65
65
|
padEnd(clr(c.bold, 'Package'), COL.name),
|
|
66
66
|
padEnd(clr(c.bold, 'Risk'), COL.risk),
|
|
67
67
|
padEnd(clr(c.bold, 'Score'), COL.score),
|
|
68
|
-
padEnd(clr(c.bold, '
|
|
68
|
+
padEnd(clr(c.bold, 'Publishers'), COL.maintainers),
|
|
69
69
|
padEnd(clr(c.bold, 'Downloads'), COL.downloads),
|
|
70
70
|
padEnd(clr(c.bold, 'Age'), COL.age),
|
|
71
71
|
].join(' ');
|
|
@@ -98,12 +98,18 @@ function printTable(results, { totalScanned, totalCritical, lockfile } = {}) {
|
|
|
98
98
|
|
|
99
99
|
console.log(row);
|
|
100
100
|
|
|
101
|
+
// Show GitHub contributor context for CRITICAL packages with active communities
|
|
102
|
+
if (pkg.riskFlags && pkg.riskFlags.includes('CRITICAL') && pkg.githubContributors && pkg.githubContributors > 1) {
|
|
103
|
+
const ghCount = pkg.githubContributors === 35 ? '30+' : pkg.githubContributors;
|
|
104
|
+
console.log(clr(c.dim, ` ↳ ${ghCount} GitHub contributors — publish-access concentration risk despite active community`));
|
|
105
|
+
}
|
|
106
|
+
|
|
101
107
|
// Score breakdown if available
|
|
102
108
|
if (pkg.scoreBreakdown) {
|
|
103
109
|
const b = pkg.scoreBreakdown;
|
|
104
110
|
const breakdown = clr(c.dim,
|
|
105
111
|
` └ longevity=${b.longevity} momentum=${b.downloadMomentum} ` +
|
|
106
|
-
`releases=${b.releaseConsistency}
|
|
112
|
+
`releases=${b.releaseConsistency} publishers=${b.maintainerDepth} github=${b.githubBacking}`
|
|
107
113
|
);
|
|
108
114
|
console.log(breakdown);
|
|
109
115
|
}
|
|
@@ -115,7 +121,7 @@ function printTable(results, { totalScanned, totalCritical, lockfile } = {}) {
|
|
|
115
121
|
if (effectiveCritical > 0) {
|
|
116
122
|
const suffix = totalScanned ? ` (in ${totalScanned} packages scanned)` : '';
|
|
117
123
|
console.log('\n' + clr(c.red + c.bold, `⚠ ${effectiveCritical} CRITICAL package${effectiveCritical > 1 ? 's' : ''} found${suffix}.`));
|
|
118
|
-
console.log(clr(c.dim, ' CRITICAL = sole
|
|
124
|
+
console.log(clr(c.dim, ' CRITICAL = sole npm publisher + >10M weekly downloads (publish-access concentration risk)'));
|
|
119
125
|
} else {
|
|
120
126
|
const suffix = totalScanned ? ` (${totalScanned} packages scanned)` : '';
|
|
121
127
|
console.log('\n' + clr(c.green, `✓ No CRITICAL packages found${suffix}.`));
|
|
@@ -140,20 +146,26 @@ ${clr(c.bold, 'Usage:')}
|
|
|
140
146
|
npx proof-of-commitment --file pnpm-lock.yaml Audit from pnpm lock file
|
|
141
147
|
npx proof-of-commitment --file requirements.txt Audit Python packages
|
|
142
148
|
|
|
149
|
+
${clr(c.bold, 'Options:')}
|
|
150
|
+
--json Output results as JSON (exits 1 if any CRITICAL found — useful in CI)
|
|
151
|
+
--pypi Score PyPI packages instead of npm
|
|
152
|
+
--file, -f Read packages from package.json, lock file, or requirements.txt
|
|
153
|
+
|
|
143
154
|
${clr(c.bold, 'Examples:')}
|
|
144
155
|
npx proof-of-commitment axios zod chalk
|
|
145
156
|
npx proof-of-commitment --pypi litellm langchain requests
|
|
146
157
|
npx proof-of-commitment --file package.json
|
|
147
158
|
npx proof-of-commitment --file package-lock.json # scans ALL transitive deps
|
|
159
|
+
npx proof-of-commitment axios chalk --json | jq '.criticalCount'
|
|
148
160
|
|
|
149
161
|
${clr(c.bold, 'Score meaning:')}
|
|
150
|
-
🔴 CRITICAL Sole
|
|
162
|
+
🔴 CRITICAL Sole npm publisher + >10M downloads/wk (publish-access concentration risk)
|
|
151
163
|
🟠 HIGH Score < 40
|
|
152
164
|
🟡 MODERATE Score 40–59
|
|
153
165
|
🟡 GOOD Score 60–74
|
|
154
166
|
🟢 HEALTHY Score 75+
|
|
155
167
|
|
|
156
|
-
${clr(c.bold, 'Score dimensions:')} longevity · download momentum · release consistency ·
|
|
168
|
+
${clr(c.bold, 'Score dimensions:')} longevity · download momentum · release consistency · publisher depth · GitHub backing
|
|
157
169
|
|
|
158
170
|
${clr(c.bold, 'Web:')} ${WEB}
|
|
159
171
|
${clr(c.bold, 'MCP:')} ${clr(c.dim, 'Add to Claude Desktop / Cursor for AI-assisted auditing')}
|
|
@@ -321,12 +333,14 @@ async function main() {
|
|
|
321
333
|
let filePath = null;
|
|
322
334
|
let isLockfile = false;
|
|
323
335
|
let totalInFile = 0;
|
|
336
|
+
let jsonOutput = false;
|
|
324
337
|
|
|
325
338
|
let i = 0;
|
|
326
339
|
while (i < args.length) {
|
|
327
340
|
const a = args[i];
|
|
328
341
|
if (a === '--pypi') { ecosystem = 'pypi'; i++; }
|
|
329
342
|
else if (a === '--npm') { ecosystem = 'npm'; i++; }
|
|
343
|
+
else if (a === '--json') { jsonOutput = true; i++; }
|
|
330
344
|
else if (a === '--file' || a === '-f') {
|
|
331
345
|
filePath = args[++i];
|
|
332
346
|
i++;
|
|
@@ -345,7 +359,7 @@ async function main() {
|
|
|
345
359
|
ecosystem = result.ecosystem;
|
|
346
360
|
isLockfile = result.lockfile || false;
|
|
347
361
|
totalInFile = result.totalInFile || packages.length;
|
|
348
|
-
console.log(clr(c.dim, `Detected ${totalInFile} packages from ${filePath} (${ecosystem})`));
|
|
362
|
+
if (!jsonOutput) console.log(clr(c.dim, `Detected ${totalInFile} packages from ${filePath} (${ecosystem})`));
|
|
349
363
|
} catch (err) {
|
|
350
364
|
console.error(`Error reading ${filePath}: ${err.message}`);
|
|
351
365
|
process.exit(1);
|
|
@@ -363,7 +377,7 @@ async function main() {
|
|
|
363
377
|
|
|
364
378
|
if (packages.length <= 20) {
|
|
365
379
|
// Single batch — existing behavior
|
|
366
|
-
process.stdout.write(clr(c.dim, `Scoring ${packages.length} ${ecosystem} package${packages.length > 1 ? 's' : ''}...`));
|
|
380
|
+
if (!jsonOutput) process.stdout.write(clr(c.dim, `Scoring ${packages.length} ${ecosystem} package${packages.length > 1 ? 's' : ''}...`));
|
|
367
381
|
|
|
368
382
|
try {
|
|
369
383
|
const res = await fetch(API, {
|
|
@@ -383,12 +397,12 @@ async function main() {
|
|
|
383
397
|
}
|
|
384
398
|
|
|
385
399
|
const elapsed = ((Date.now() - t0) / 1000).toFixed(1);
|
|
386
|
-
process.stdout.write(clr(c.dim, ` done in ${elapsed}s\n`));
|
|
400
|
+
if (!jsonOutput) process.stdout.write(clr(c.dim, ` done in ${elapsed}s\n`));
|
|
387
401
|
|
|
388
402
|
} else {
|
|
389
403
|
// Multi-batch for lock files
|
|
390
404
|
const batches = Math.ceil(packages.length / 20);
|
|
391
|
-
process.stdout.write(clr(c.dim, `Scanning ${packages.length} packages (${batches} batches in parallel)...`));
|
|
405
|
+
if (!jsonOutput) process.stdout.write(clr(c.dim, `Scanning ${packages.length} packages (${batches} batches in parallel)...`));
|
|
392
406
|
|
|
393
407
|
let lastPct = 0;
|
|
394
408
|
try {
|
|
@@ -407,24 +421,46 @@ async function main() {
|
|
|
407
421
|
}
|
|
408
422
|
|
|
409
423
|
const elapsed = ((Date.now() - t0) / 1000).toFixed(1);
|
|
410
|
-
process.stdout.write(clr(c.dim, ` done in ${elapsed}s\n`));
|
|
424
|
+
if (!jsonOutput) process.stdout.write(clr(c.dim, ` done in ${elapsed}s\n`));
|
|
425
|
+
|
|
426
|
+
// JSON output: emit all results with summary
|
|
427
|
+
if (jsonOutput) {
|
|
428
|
+
const criticalCount = allResults.filter(r => r.riskFlags?.includes('CRITICAL')).length;
|
|
429
|
+
console.log(JSON.stringify({
|
|
430
|
+
totalScanned: allResults.length,
|
|
431
|
+
criticalCount,
|
|
432
|
+
results: allResults,
|
|
433
|
+
}, null, 2));
|
|
434
|
+
process.exit(criticalCount > 0 ? 1 : 0);
|
|
435
|
+
}
|
|
411
436
|
|
|
412
437
|
// For lock files: show top 25 highest-risk packages
|
|
413
438
|
const MAX_DISPLAY = 25;
|
|
414
439
|
const displayed = allResults.slice(0, MAX_DISPLAY);
|
|
415
|
-
const criticalCount = allResults.filter(r => r.riskFlags?.includes('CRITICAL')).length;
|
|
416
|
-
const highRiskCount = allResults.filter(r => !r.riskFlags?.includes('CRITICAL') && r.score < 40).length;
|
|
417
|
-
|
|
418
440
|
const criticalTotal = allResults.filter(r => r.riskFlags?.includes('CRITICAL')).length;
|
|
419
441
|
printTable(displayed, { totalScanned: allResults.length, totalCritical: criticalTotal, lockfile: true });
|
|
420
442
|
return;
|
|
421
443
|
}
|
|
422
444
|
|
|
423
445
|
if (!allResults || allResults.length === 0) {
|
|
424
|
-
|
|
446
|
+
if (jsonOutput) {
|
|
447
|
+
console.log(JSON.stringify({ totalScanned: 0, criticalCount: 0, results: [] }, null, 2));
|
|
448
|
+
} else {
|
|
449
|
+
console.log('No results returned. Check package names and try again.');
|
|
450
|
+
}
|
|
425
451
|
process.exit(0);
|
|
426
452
|
}
|
|
427
453
|
|
|
454
|
+
if (jsonOutput) {
|
|
455
|
+
const criticalCount = allResults.filter(r => r.riskFlags?.includes('CRITICAL')).length;
|
|
456
|
+
console.log(JSON.stringify({
|
|
457
|
+
totalScanned: allResults.length,
|
|
458
|
+
criticalCount,
|
|
459
|
+
results: allResults,
|
|
460
|
+
}, null, 2));
|
|
461
|
+
process.exit(criticalCount > 0 ? 1 : 0);
|
|
462
|
+
}
|
|
463
|
+
|
|
428
464
|
printTable(allResults);
|
|
429
465
|
}
|
|
430
466
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "proof-of-commitment",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "Supply chain risk scorer for npm and PyPI packages — behavioral signals that can't be faked",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -22,7 +22,8 @@
|
|
|
22
22
|
"risk",
|
|
23
23
|
"behavioral",
|
|
24
24
|
"commitment",
|
|
25
|
-
"maintainer"
|
|
25
|
+
"maintainer",
|
|
26
|
+
"publisher"
|
|
26
27
|
],
|
|
27
28
|
"author": "piiiico",
|
|
28
29
|
"license": "MIT",
|