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.
Files changed (3) hide show
  1. package/README.md +11 -8
  2. package/index.js +50 -14
  3. 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 Maintainers Downloads Age
11
+ Package Risk Score Publishers Downloads Age
12
12
  ──────────────────────────────────────────────────────────────────────────
13
13
  axios 🔴 CRITICAL 89 1 102.0M/wk 11.6y
14
- longevity=25 momentum=25 releases=20 maintainers=4 github=15
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
- longevity=25 momentum=25 releases=18 maintainers=4 github=11
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
- longevity=25 momentum=22 releases=13 maintainers=4 github=11
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 maintainer + >10M weekly downloads (high-value attack target)
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 maintainer, 96M downloads/week) — the exact profile that made it a high-value target.
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
- - **Maintainer Depth** (15 pts) — team size
39
+ - **Publisher Depth** (15 pts) — npm publish-access holders
37
40
  - **GitHub Backing** (15 pts) — organization/team support
38
41
 
39
- **CRITICAL** = sole maintainer + >10M weekly downloads (high-value attack target)
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, 'Maintainers'), COL.maintainers),
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} maintainers=${b.maintainerDepth} github=${b.githubBacking}`
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 maintainer + >10M weekly downloads (high-value attack target)'));
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 maintainer + >10M downloads/wk (high-value attack target)
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 · maintainer depth · GitHub backing
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
- console.log('No results returned. Check package names and try again.');
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.2.0",
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",