proof-of-commitment 1.4.0 → 1.6.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 +16 -4
- package/index.js +145 -18
- package/package.json +7 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# proof-of-commitment
|
|
2
2
|
|
|
3
|
-
> Supply chain risk scorer for npm and
|
|
3
|
+
> Supply chain risk scorer for npm, PyPI, Cargo (Rust), and Go modules. Behavioral signals that can't be faked.
|
|
4
4
|
|
|
5
5
|
```
|
|
6
6
|
npx proof-of-commitment axios zod chalk
|
|
@@ -50,14 +50,26 @@ npx proof-of-commitment axios zod chalk lodash express
|
|
|
50
50
|
# Score PyPI packages
|
|
51
51
|
npx proof-of-commitment --pypi litellm langchain requests numpy
|
|
52
52
|
|
|
53
|
-
#
|
|
54
|
-
npx proof-of-commitment --
|
|
55
|
-
|
|
53
|
+
# Score Rust crates
|
|
54
|
+
npx proof-of-commitment --cargo serde tokio reqwest
|
|
55
|
+
|
|
56
|
+
# Score Go modules (full module path required — host/owner/repo)
|
|
57
|
+
npx proof-of-commitment --golang github.com/gin-gonic/gin golang.org/x/net
|
|
58
|
+
|
|
59
|
+
# Auto-detect from manifest / lock / module file
|
|
60
|
+
npx proof-of-commitment --file package.json # npm
|
|
61
|
+
npx proof-of-commitment --file package-lock.json # full transitive
|
|
62
|
+
npx proof-of-commitment --file requirements.txt # PyPI
|
|
63
|
+
npx proof-of-commitment --file Cargo.toml # Rust direct deps
|
|
64
|
+
npx proof-of-commitment --file go.mod # Go direct + indirect
|
|
65
|
+
npx proof-of-commitment --file go.sum # Go full transitive set
|
|
56
66
|
|
|
57
67
|
# Short alias
|
|
58
68
|
npx poc axios zod chalk
|
|
59
69
|
```
|
|
60
70
|
|
|
71
|
+
**Go modules note:** Go has no centralized download counter and no publisher registry. Scoring is GitHub-primary: longevity, release cadence, GitHub contributor count, OpenSSF Scorecard, and stars (popularity proxy). The "publishers" column in Go output shows GitHub push-access contributors — the closest equivalent to npm publishers.
|
|
72
|
+
|
|
61
73
|
## Zero-install MCP server (for Claude, Cursor, Windsurf)
|
|
62
74
|
|
|
63
75
|
Add to your AI tool's config:
|
package/index.js
CHANGED
|
@@ -87,12 +87,23 @@ function printTable(results, { totalScanned, totalCritical, lockfile } = {}) {
|
|
|
87
87
|
const label = riskLabel(pkg.riskFlags, pkg.score);
|
|
88
88
|
if (pkg.riskFlags && pkg.riskFlags.includes('CRITICAL')) criticalInDisplay++;
|
|
89
89
|
|
|
90
|
+
// Go modules have no download data — show "—" instead. The maintainers
|
|
91
|
+
// column for Go shows GitHub contributor count (the closest equivalent
|
|
92
|
+
// to publish access since Go has no centralized publisher concept).
|
|
93
|
+
const isGo = pkg.ecosystem === 'golang';
|
|
94
|
+
const dlDisplay = isGo
|
|
95
|
+
? '—'
|
|
96
|
+
: fmtDownloads(pkg.weeklyDownloads || 0);
|
|
97
|
+
const maintDisplay = pkg.maintainers === 35
|
|
98
|
+
? '30+'
|
|
99
|
+
: String(pkg.maintainers || '?');
|
|
100
|
+
|
|
90
101
|
const row = [
|
|
91
102
|
padEnd(pkg.name, COL.name),
|
|
92
103
|
padEnd(clr(rc, label), COL.risk),
|
|
93
104
|
padEnd(String(pkg.score), COL.score),
|
|
94
|
-
padEnd(
|
|
95
|
-
padEnd(
|
|
105
|
+
padEnd(maintDisplay, COL.maintainers),
|
|
106
|
+
padEnd(dlDisplay, COL.downloads),
|
|
96
107
|
padEnd((pkg.ageYears || '?').toString().replace(/(\.\d).*/, '$1') + 'y', COL.age),
|
|
97
108
|
].join(' ');
|
|
98
109
|
|
|
@@ -104,13 +115,18 @@ function printTable(results, { totalScanned, totalCritical, lockfile } = {}) {
|
|
|
104
115
|
console.log(clr(c.dim, ` ↳ ${ghCount} GitHub contributors — publish-access concentration risk despite active community`));
|
|
105
116
|
}
|
|
106
117
|
|
|
107
|
-
// Score breakdown if available
|
|
118
|
+
// Score breakdown if available — keys differ across ecosystems
|
|
108
119
|
if (pkg.scoreBreakdown) {
|
|
109
120
|
const b = pkg.scoreBreakdown;
|
|
110
|
-
const breakdown =
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
121
|
+
const breakdown = isGo
|
|
122
|
+
? clr(c.dim,
|
|
123
|
+
` └ longevity=${b.longevity} releases=${b.releaseConsistency} ` +
|
|
124
|
+
`contributors=${b.maintainerDepth} github=${b.githubBacking} stars=${b.popularityProxy}`
|
|
125
|
+
)
|
|
126
|
+
: clr(c.dim,
|
|
127
|
+
` └ longevity=${b.longevity} momentum=${b.downloadMomentum} ` +
|
|
128
|
+
`releases=${b.releaseConsistency} publishers=${b.maintainerDepth} github=${b.githubBacking}`
|
|
129
|
+
);
|
|
114
130
|
console.log(breakdown);
|
|
115
131
|
}
|
|
116
132
|
}
|
|
@@ -138,24 +154,33 @@ function printHelp() {
|
|
|
138
154
|
${clr(c.bold, 'proof-of-commitment')} — supply chain risk scorer
|
|
139
155
|
|
|
140
156
|
${clr(c.bold, 'Usage:')}
|
|
141
|
-
npx proof-of-commitment [packages...]
|
|
142
|
-
npx proof-of-commitment --pypi [pkgs...]
|
|
143
|
-
npx proof-of-commitment --
|
|
144
|
-
npx proof-of-commitment --
|
|
145
|
-
npx proof-of-commitment --file
|
|
146
|
-
npx proof-of-commitment --file
|
|
147
|
-
npx proof-of-commitment --file
|
|
157
|
+
npx proof-of-commitment [packages...] Score npm packages
|
|
158
|
+
npx proof-of-commitment --pypi [pkgs...] Score PyPI packages
|
|
159
|
+
npx proof-of-commitment --cargo [crates...] Score Rust crates
|
|
160
|
+
npx proof-of-commitment --golang [modules...] Score Go modules (full path required)
|
|
161
|
+
npx proof-of-commitment --file package.json Audit direct dependencies
|
|
162
|
+
npx proof-of-commitment --file package-lock.json Audit ALL dependencies (lock file)
|
|
163
|
+
npx proof-of-commitment --file yarn.lock Audit from yarn lock file
|
|
164
|
+
npx proof-of-commitment --file pnpm-lock.yaml Audit from pnpm lock file
|
|
165
|
+
npx proof-of-commitment --file requirements.txt Audit Python packages
|
|
166
|
+
npx proof-of-commitment --file Cargo.toml Audit Rust direct dependencies
|
|
167
|
+
npx proof-of-commitment --file go.mod Audit Go direct + indirect deps
|
|
168
|
+
npx proof-of-commitment --file go.sum Audit Go full transitive set
|
|
148
169
|
|
|
149
170
|
${clr(c.bold, 'Options:')}
|
|
150
171
|
--json Output results as JSON (exits 1 if any CRITICAL found — useful in CI)
|
|
151
172
|
--pypi Score PyPI packages instead of npm
|
|
152
|
-
--
|
|
173
|
+
--cargo Score Rust crates from crates.io
|
|
174
|
+
--golang Score Go modules from proxy.golang.org (use full path: github.com/owner/repo)
|
|
175
|
+
--file, -f Read packages from package.json, lock file, requirements.txt, Cargo.toml, or go.mod/go.sum
|
|
153
176
|
|
|
154
177
|
${clr(c.bold, 'Examples:')}
|
|
155
178
|
npx proof-of-commitment axios zod chalk
|
|
156
179
|
npx proof-of-commitment --pypi litellm langchain requests
|
|
157
|
-
npx proof-of-commitment --
|
|
180
|
+
npx proof-of-commitment --cargo serde tokio reqwest
|
|
181
|
+
npx proof-of-commitment --golang github.com/gin-gonic/gin golang.org/x/net
|
|
158
182
|
npx proof-of-commitment --file package-lock.json # scans ALL transitive deps
|
|
183
|
+
npx proof-of-commitment --file go.sum # scans full Go module graph
|
|
159
184
|
npx proof-of-commitment axios chalk --json | jq '.criticalCount'
|
|
160
185
|
|
|
161
186
|
${clr(c.bold, 'Score meaning:')}
|
|
@@ -165,7 +190,8 @@ ${clr(c.bold, 'Score meaning:')}
|
|
|
165
190
|
🟡 GOOD Score 60–74
|
|
166
191
|
🟢 HEALTHY Score 75+
|
|
167
192
|
|
|
168
|
-
${clr(c.bold, 'Score dimensions:')} longevity · download momentum · release consistency · publisher depth · GitHub backing
|
|
193
|
+
${clr(c.bold, 'Score dimensions (npm/PyPI/Cargo):')} longevity · download momentum · release consistency · publisher depth · GitHub backing
|
|
194
|
+
${clr(c.bold, 'Score dimensions (Go):')} longevity · release consistency · maintainer depth · GitHub backing · stars (Go has no centralized download counter)
|
|
169
195
|
|
|
170
196
|
${clr(c.bold, 'Web:')} ${WEB}
|
|
171
197
|
${clr(c.bold, 'MCP:')} ${clr(c.dim, 'Add to Claude Desktop / Cursor for AI-assisted auditing')}
|
|
@@ -274,7 +300,106 @@ async function readPackagesFromFile(filePath) {
|
|
|
274
300
|
return { packages: pkgs, ecosystem: 'pypi', lockfile: false };
|
|
275
301
|
}
|
|
276
302
|
|
|
277
|
-
|
|
303
|
+
// Cargo.toml
|
|
304
|
+
if (basename === 'cargo.toml') {
|
|
305
|
+
// Crude TOML parse — extract [dependencies] section keys
|
|
306
|
+
const pkgs = parseCargoToml(content);
|
|
307
|
+
return { packages: pkgs, ecosystem: 'cargo', lockfile: false };
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// go.mod — Go module file
|
|
311
|
+
if (basename === 'go.mod') {
|
|
312
|
+
const pkgs = parseGoMod(content);
|
|
313
|
+
return { packages: pkgs, ecosystem: 'golang', lockfile: false, totalInFile: pkgs.length };
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// go.sum — Go module checksum file (lockfile-equivalent — captures full transitive set)
|
|
317
|
+
if (basename === 'go.sum') {
|
|
318
|
+
const pkgs = parseGoSum(content);
|
|
319
|
+
return { packages: pkgs, ecosystem: 'golang', lockfile: true, totalInFile: pkgs.length };
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
throw new Error(`Unsupported file: ${basename}. Supported: package.json, package-lock.json, yarn.lock, pnpm-lock.yaml, requirements.txt, Cargo.toml, go.mod, go.sum`);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Parse a Cargo.toml — extract direct deps from [dependencies] / [dev-dependencies].
|
|
327
|
+
* Crude — only handles the common "name = version" / "name = { ... }" lines under
|
|
328
|
+
* [dependencies] / [dev-dependencies]. Doesn't expand workspace members.
|
|
329
|
+
*/
|
|
330
|
+
function parseCargoToml(content) {
|
|
331
|
+
const pkgs = new Set();
|
|
332
|
+
const lines = content.split('\n');
|
|
333
|
+
let inDeps = false;
|
|
334
|
+
for (const raw of lines) {
|
|
335
|
+
const line = raw.trim();
|
|
336
|
+
if (line.startsWith('[')) {
|
|
337
|
+
inDeps = /^\[(dev-)?dependencies\]/.test(line);
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
if (!inDeps || !line || line.startsWith('#')) continue;
|
|
341
|
+
const match = line.match(/^([a-zA-Z0-9_-]+)\s*=/);
|
|
342
|
+
if (match) pkgs.add(match[1]);
|
|
343
|
+
}
|
|
344
|
+
return [...pkgs];
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Parse a go.mod file — extract direct + indirect dependencies from require blocks.
|
|
349
|
+
* Format:
|
|
350
|
+
* require (
|
|
351
|
+
* github.com/gin-gonic/gin v1.9.1
|
|
352
|
+
* golang.org/x/net v0.20.0 // indirect
|
|
353
|
+
* )
|
|
354
|
+
* require github.com/foo/bar v1.0.0
|
|
355
|
+
*/
|
|
356
|
+
function parseGoMod(content) {
|
|
357
|
+
const pkgs = new Set();
|
|
358
|
+
const lines = content.split('\n');
|
|
359
|
+
let inRequireBlock = false;
|
|
360
|
+
|
|
361
|
+
for (const raw of lines) {
|
|
362
|
+
const line = raw.trim();
|
|
363
|
+
if (!line || line.startsWith('//')) continue;
|
|
364
|
+
|
|
365
|
+
// Block-form require: "require ("
|
|
366
|
+
if (/^require\s*\(\s*$/.test(line)) {
|
|
367
|
+
inRequireBlock = true;
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
if (inRequireBlock && line === ')') {
|
|
371
|
+
inRequireBlock = false;
|
|
372
|
+
continue;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Inside a block: "<modulepath> <version>" optionally followed by comments
|
|
376
|
+
if (inRequireBlock) {
|
|
377
|
+
const match = line.match(/^([^\s]+)\s+v[^\s]+/);
|
|
378
|
+
if (match) pkgs.add(match[1]);
|
|
379
|
+
continue;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Single-line require: "require <modulepath> <version>"
|
|
383
|
+
const single = line.match(/^require\s+([^\s]+)\s+v[^\s]+/);
|
|
384
|
+
if (single) pkgs.add(single[1]);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
return [...pkgs];
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Parse a go.sum file — module path is the first column, version + suffix follow.
|
|
392
|
+
* Each module appears multiple times (once per artifact); dedupe.
|
|
393
|
+
*/
|
|
394
|
+
function parseGoSum(content) {
|
|
395
|
+
const pkgs = new Set();
|
|
396
|
+
for (const raw of content.split('\n')) {
|
|
397
|
+
const line = raw.trim();
|
|
398
|
+
if (!line) continue;
|
|
399
|
+
const match = line.match(/^([^\s]+)\s+v[^\s/]+/);
|
|
400
|
+
if (match) pkgs.add(match[1]);
|
|
401
|
+
}
|
|
402
|
+
return [...pkgs];
|
|
278
403
|
}
|
|
279
404
|
|
|
280
405
|
/**
|
|
@@ -340,6 +465,8 @@ async function main() {
|
|
|
340
465
|
const a = args[i];
|
|
341
466
|
if (a === '--pypi') { ecosystem = 'pypi'; i++; }
|
|
342
467
|
else if (a === '--npm') { ecosystem = 'npm'; i++; }
|
|
468
|
+
else if (a === '--cargo') { ecosystem = 'cargo'; i++; }
|
|
469
|
+
else if (a === '--golang' || a === '--go') { ecosystem = 'golang'; i++; }
|
|
343
470
|
else if (a === '--json') { jsonOutput = true; i++; }
|
|
344
471
|
else if (a === '--file' || a === '-f') {
|
|
345
472
|
filePath = args[++i];
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "proof-of-commitment",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Supply chain risk scorer for npm and
|
|
3
|
+
"version": "1.6.0",
|
|
4
|
+
"description": "Supply chain risk scorer for npm, PyPI, Cargo, and Go packages — behavioral signals that can't be faked",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"proof-of-commitment": "./index.js",
|
|
@@ -17,6 +17,11 @@
|
|
|
17
17
|
"security",
|
|
18
18
|
"npm",
|
|
19
19
|
"pypi",
|
|
20
|
+
"cargo",
|
|
21
|
+
"rust",
|
|
22
|
+
"golang",
|
|
23
|
+
"go",
|
|
24
|
+
"go-modules",
|
|
20
25
|
"dependencies",
|
|
21
26
|
"audit",
|
|
22
27
|
"risk",
|