proof-of-commitment 1.25.0 → 1.26.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 +7 -7
- package/index.js +239 -11
- package/package.json +12 -2
package/README.md
CHANGED
|
@@ -11,15 +11,15 @@ An MCP server and web tool that scores npm packages, PyPI packages, Rust crates,
|
|
|
11
11
|
26 of the 91 npm packages with >10M weekly downloads have a **single npm publisher**. Together they account for over 3 billion downloads per week. `npm audit` doesn't surface this. Stars don't either.
|
|
12
12
|
|
|
13
13
|
Four packages in a typical Node.js project are CRITICAL right now:
|
|
14
|
-
- **chalk** —
|
|
15
|
-
- **zod** —
|
|
16
|
-
- **lodash** —
|
|
17
|
-
- **axios** —
|
|
14
|
+
- **chalk** — 432M downloads/week, **1 npm publisher**
|
|
15
|
+
- **zod** — 185M downloads/week, **1 npm publisher** (30+ GitHub contributors)
|
|
16
|
+
- **lodash** — 156M downloads/week, **1 npm publisher**
|
|
17
|
+
- **axios** — 113M downloads/week, **1 npm publisher** (attacked March 30, 2026)
|
|
18
18
|
|
|
19
19
|
They won't appear in your `package.json` either — but these are in almost every project:
|
|
20
|
-
- **minimatch** —
|
|
21
|
-
- **glob** —
|
|
22
|
-
- **cross-spawn** —
|
|
20
|
+
- **minimatch** — 625M downloads/week, **1 npm publisher**
|
|
21
|
+
- **glob** — 366M downloads/week, **1 npm publisher**
|
|
22
|
+
- **cross-spawn** — 215M downloads/week, **1 npm publisher**
|
|
23
23
|
|
|
24
24
|
Behavioral signals surface this. Stars and READMEs don't.
|
|
25
25
|
|
package/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* proof-of-commitment CLI v1.
|
|
3
|
+
* proof-of-commitment CLI v1.26.0
|
|
4
4
|
* Scores npm/PyPI/Cargo/Go packages on behavioral commitment signals.
|
|
5
5
|
* Usage: npx proof-of-commitment [packages...] [options]
|
|
6
6
|
*/
|
|
@@ -98,9 +98,14 @@ async function handle429(res) {
|
|
|
98
98
|
const partial = Array.isArray(data.packages_already_scored)
|
|
99
99
|
? data.packages_already_scored
|
|
100
100
|
: [];
|
|
101
|
+
// Authenticated keys: retry_after (seconds, used by worker auth-middleware quota path).
|
|
102
|
+
// Anonymous IPs: retry_after_seconds (legacy / overshoot rescue path).
|
|
103
|
+
// Read both so both paths surface a reset-time hint.
|
|
101
104
|
const retryAfter = Number.isFinite(data.retry_after_seconds)
|
|
102
105
|
? data.retry_after_seconds
|
|
103
|
-
:
|
|
106
|
+
: Number.isFinite(data.retry_after)
|
|
107
|
+
? data.retry_after
|
|
108
|
+
: null;
|
|
104
109
|
// Backend signals "you've blown past the free wall, Developer $15/mo is the
|
|
105
110
|
// right fix" via overshoot=true / tier_suggestion="developer" (added
|
|
106
111
|
// backend-side 2026-06-04). When set, backend routes instantKeyUrl to
|
|
@@ -109,6 +114,25 @@ async function handle429(res) {
|
|
|
109
114
|
// Mismatched CTA text + destination kills trust and conversion. This branch
|
|
110
115
|
// aligns label + URL + skips the inline email prompt. (Dogfood, 2026-06-06.)
|
|
111
116
|
const overshoot = data.overshoot === true || data.tier_suggestion === 'developer';
|
|
117
|
+
// Authenticated-key quota path (added 2026-06-10): when the user already
|
|
118
|
+
// owns an API key and burns through their daily allowance, the backend
|
|
119
|
+
// auth-middleware (worker.ts resolveApiKey) returns a NESTED upgrade object:
|
|
120
|
+
// { error, message, tier, upgrade: { url, plan, price, limit, message }, retry_after }
|
|
121
|
+
// The legacy handle429() shape only knew the FLAT anonymous-IP shape
|
|
122
|
+
// (instant_key_url, upgrade_url, overshoot, tier_suggestion). On a free-tier
|
|
123
|
+
// key quota hit, all those flat fields were undefined → handler fell back
|
|
124
|
+
// to "Get a free key" + inline email prompt → user (who already has a key)
|
|
125
|
+
// got bait-and-switched at their highest-intent moment: invested in setup,
|
|
126
|
+
// used the key all day, ready to upgrade — and we offered them to create
|
|
127
|
+
// ANOTHER free key. Detect via `data.upgrade?.url && data.upgrade?.plan` +
|
|
128
|
+
// a non-anonymous `data.tier`, route to dedicated upgrade UX. Aligns CLI
|
|
129
|
+
// CTA + URL + skips email prompt symmetric to the overshoot branch.
|
|
130
|
+
const keyUpgrade =
|
|
131
|
+
data.upgrade &&
|
|
132
|
+
typeof data.upgrade.url === 'string' &&
|
|
133
|
+
typeof data.upgrade.plan === 'string' &&
|
|
134
|
+
typeof data.tier === 'string' &&
|
|
135
|
+
data.tier !== 'anonymous';
|
|
112
136
|
|
|
113
137
|
// Forward-compat: if backend ever returns partial scoring on 429,
|
|
114
138
|
// print what we have BEFORE the rescue message. Falls back to JSON
|
|
@@ -138,6 +162,36 @@ async function handle429(res) {
|
|
|
138
162
|
}
|
|
139
163
|
console.error('');
|
|
140
164
|
|
|
165
|
+
// Authenticated-key quota path: user already has a key, hit their daily
|
|
166
|
+
// allowance. Free-key inline prompt is the wrong tool — surface upgrade.
|
|
167
|
+
// (Diagnosis: 2026-06-10 idle-mode dogfood — see comment block above.)
|
|
168
|
+
if (keyUpgrade) {
|
|
169
|
+
const planLabel = data.upgrade.plan.charAt(0).toUpperCase() + data.upgrade.plan.slice(1);
|
|
170
|
+
const price = data.upgrade.price || '';
|
|
171
|
+
const limit = data.upgrade.limit || '';
|
|
172
|
+
const pitch = data.upgrade.message || `Upgrade to ${planLabel}.`;
|
|
173
|
+
// URL already carries utm_campaign=key-upgrade + utm_source=key +
|
|
174
|
+
// utm_medium=quota from backend buildUpgradeUrl — no rewrite needed,
|
|
175
|
+
// /pricing key-upgrade banner reads these and pre-selects the tier.
|
|
176
|
+
console.error(
|
|
177
|
+
clr(
|
|
178
|
+
c.cyan + c.bold,
|
|
179
|
+
` → ${planLabel} (${price}${limit ? ' · ' + limit : ''}): ${data.upgrade.url}`
|
|
180
|
+
)
|
|
181
|
+
);
|
|
182
|
+
if (pitch) {
|
|
183
|
+
console.error(clr(c.dim, ` ${pitch}`));
|
|
184
|
+
}
|
|
185
|
+
if (retryAfter && retryAfter > 0) {
|
|
186
|
+
const hours = Math.floor(retryAfter / 3600);
|
|
187
|
+
const mins = Math.floor((retryAfter % 3600) / 60);
|
|
188
|
+
const resetIn = hours > 0 ? `${hours}h ${mins}m` : `${mins}m`;
|
|
189
|
+
console.error(clr(c.dim, ` or wait — your free-tier quota resets in ${resetIn}.`));
|
|
190
|
+
}
|
|
191
|
+
console.error('');
|
|
192
|
+
process.exit(1);
|
|
193
|
+
}
|
|
194
|
+
|
|
141
195
|
// Overshoot path: free key is the wrong tool. Surface a URL aligned with
|
|
142
196
|
// the backend's Developer recommendation, skip the email prompt, exit.
|
|
143
197
|
// Without this branch, the CLI would say "Free API key in 30 seconds (no
|
|
@@ -256,6 +310,157 @@ function riskLabel(flags, score) {
|
|
|
256
310
|
return '🟢 HEALTHY';
|
|
257
311
|
}
|
|
258
312
|
|
|
313
|
+
/**
|
|
314
|
+
* Format audit results as SARIF 2.1.0 for GitHub Code Scanning / security dashboards.
|
|
315
|
+
*
|
|
316
|
+
* Maps risk levels: CRITICAL → error, HIGH (score<40) → warning,
|
|
317
|
+
* MODERATE (score<60) → note. Each package produces one result entry.
|
|
318
|
+
* Compromised packages get a separate "compromised" rule.
|
|
319
|
+
*
|
|
320
|
+
* When --file was used, locations point to that file at line 1.
|
|
321
|
+
* Otherwise, a logical package-name location is used.
|
|
322
|
+
*/
|
|
323
|
+
function formatSarif(results, { filePath, ecosystem, version } = {}) {
|
|
324
|
+
const rules = [];
|
|
325
|
+
const ruleIndex = {};
|
|
326
|
+
|
|
327
|
+
function ensureRule(id, shortDescription, fullDescription, level) {
|
|
328
|
+
if (ruleIndex[id] != null) return ruleIndex[id];
|
|
329
|
+
const idx = rules.length;
|
|
330
|
+
ruleIndex[id] = idx;
|
|
331
|
+
rules.push({
|
|
332
|
+
id,
|
|
333
|
+
shortDescription: { text: shortDescription },
|
|
334
|
+
fullDescription: { text: fullDescription },
|
|
335
|
+
defaultConfiguration: { level },
|
|
336
|
+
helpUri: 'https://getcommit.dev/docs/',
|
|
337
|
+
});
|
|
338
|
+
return idx;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Pre-define rules
|
|
342
|
+
ensureRule(
|
|
343
|
+
'commit/critical',
|
|
344
|
+
'CRITICAL: sole publisher with high download volume',
|
|
345
|
+
'Package has a single npm/registry publisher controlling millions of weekly downloads — the exact attack surface exploited in the axios and LiteLLM supply chain compromises.',
|
|
346
|
+
'error'
|
|
347
|
+
);
|
|
348
|
+
ensureRule(
|
|
349
|
+
'commit/high',
|
|
350
|
+
'HIGH: behavioral risk score below 40',
|
|
351
|
+
'Package scores below 40 on behavioral commitment signals, indicating elevated supply chain risk from low maintenance activity, publisher concentration, or rapid adoption without established track record.',
|
|
352
|
+
'warning'
|
|
353
|
+
);
|
|
354
|
+
ensureRule(
|
|
355
|
+
'commit/moderate',
|
|
356
|
+
'MODERATE: behavioral risk score below 60',
|
|
357
|
+
'Package scores below 60 on behavioral commitment signals. Not immediately dangerous but worth monitoring.',
|
|
358
|
+
'note'
|
|
359
|
+
);
|
|
360
|
+
ensureRule(
|
|
361
|
+
'commit/compromised',
|
|
362
|
+
'COMPROMISED: confirmed supply chain attack',
|
|
363
|
+
'Package was involved in a confirmed supply chain attack. Verify you are on a clean version.',
|
|
364
|
+
'error'
|
|
365
|
+
);
|
|
366
|
+
|
|
367
|
+
const sarifResults = [];
|
|
368
|
+
|
|
369
|
+
for (const pkg of results) {
|
|
370
|
+
const isCritical = hasCritical(pkg.riskFlags);
|
|
371
|
+
const score = typeof pkg.score === 'number' ? pkg.score : null;
|
|
372
|
+
|
|
373
|
+
// Determine primary rule
|
|
374
|
+
let ruleId, level;
|
|
375
|
+
if (isCritical) {
|
|
376
|
+
ruleId = 'commit/critical';
|
|
377
|
+
level = 'error';
|
|
378
|
+
} else if (score !== null && score < 40) {
|
|
379
|
+
ruleId = 'commit/high';
|
|
380
|
+
level = 'warning';
|
|
381
|
+
} else if (score !== null && score < 60) {
|
|
382
|
+
ruleId = 'commit/moderate';
|
|
383
|
+
level = 'note';
|
|
384
|
+
} else {
|
|
385
|
+
// Healthy — skip unless compromised
|
|
386
|
+
if (!pkg.compromised) continue;
|
|
387
|
+
ruleId = 'commit/compromised';
|
|
388
|
+
level = 'error';
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
const dlStr = pkg.weeklyDownloads
|
|
392
|
+
? ` (${fmtDl(pkg.weeklyDownloads)} downloads/week)`
|
|
393
|
+
: '';
|
|
394
|
+
const pubStr = pkg.maintainers
|
|
395
|
+
? `, ${pkg.maintainers} publisher${pkg.maintainers > 1 ? 's' : ''}`
|
|
396
|
+
: '';
|
|
397
|
+
const scoreStr = score !== null ? `Score: ${score}/100` : '';
|
|
398
|
+
|
|
399
|
+
const messageText = `${pkg.name}: ${scoreStr}${pubStr}${dlStr}. ` +
|
|
400
|
+
`${isCritical ? 'Sole publisher with high download volume — publish-access concentration risk.' : ''} ` +
|
|
401
|
+
`https://getcommit.dev/${pkg.ecosystem || ecosystem || 'npm'}/${encodeURIComponent(pkg.name)}`;
|
|
402
|
+
|
|
403
|
+
const location = filePath
|
|
404
|
+
? { physicalLocation: { artifactLocation: { uri: filePath }, region: { startLine: 1 } } }
|
|
405
|
+
: { logicalLocations: [{ name: pkg.name, kind: 'module' }] };
|
|
406
|
+
|
|
407
|
+
sarifResults.push({
|
|
408
|
+
ruleId,
|
|
409
|
+
ruleIndex: ruleIndex[ruleId],
|
|
410
|
+
level,
|
|
411
|
+
message: { text: messageText.trim() },
|
|
412
|
+
locations: [location],
|
|
413
|
+
properties: {
|
|
414
|
+
ecosystem: pkg.ecosystem || ecosystem || 'npm',
|
|
415
|
+
score: pkg.score,
|
|
416
|
+
maintainers: pkg.maintainers,
|
|
417
|
+
weeklyDownloads: pkg.weeklyDownloads,
|
|
418
|
+
ageYears: pkg.ageYears,
|
|
419
|
+
hasProvenance: pkg.hasProvenance || false,
|
|
420
|
+
riskFlags: pkg.riskFlags || [],
|
|
421
|
+
},
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
// Separate result for compromised packages
|
|
425
|
+
if (pkg.compromised && ruleId !== 'commit/compromised') {
|
|
426
|
+
const atk = pkg.compromised;
|
|
427
|
+
sarifResults.push({
|
|
428
|
+
ruleId: 'commit/compromised',
|
|
429
|
+
ruleIndex: ruleIndex['commit/compromised'],
|
|
430
|
+
level: 'error',
|
|
431
|
+
message: {
|
|
432
|
+
text: `${pkg.name}: confirmed supply chain attack — ${atk.attack || 'unknown'} (${atk.date || '?'}). ${atk.url || ''}`,
|
|
433
|
+
},
|
|
434
|
+
locations: [location],
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
return {
|
|
440
|
+
$schema: 'https://json.schemastore.org/sarif-2.1.0.json',
|
|
441
|
+
version: '2.1.0',
|
|
442
|
+
runs: [{
|
|
443
|
+
tool: {
|
|
444
|
+
driver: {
|
|
445
|
+
name: 'Commit',
|
|
446
|
+
semanticVersion: version || '1.25.0',
|
|
447
|
+
informationUri: 'https://getcommit.dev',
|
|
448
|
+
rules,
|
|
449
|
+
},
|
|
450
|
+
},
|
|
451
|
+
results: sarifResults,
|
|
452
|
+
}],
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// Short download formatter for SARIF messages (no /wk suffix)
|
|
457
|
+
function fmtDl(n) {
|
|
458
|
+
if (n >= 1e9) return (n / 1e9).toFixed(1) + 'B';
|
|
459
|
+
if (n >= 1e6) return (n / 1e6).toFixed(1) + 'M';
|
|
460
|
+
if (n >= 1e3) return (n / 1e3).toFixed(0) + 'K';
|
|
461
|
+
return String(n);
|
|
462
|
+
}
|
|
463
|
+
|
|
259
464
|
function fmtDownloads(n) {
|
|
260
465
|
if (n >= 1e9) return (n / 1e9).toFixed(1) + 'B/wk';
|
|
261
466
|
if (n >= 1e6) return (n / 1e6).toFixed(1) + 'M/wk';
|
|
@@ -578,6 +783,7 @@ ${clr(c.bold, 'Monitoring (free: 3 packages weekly · Developer $15/mo: 15 daily
|
|
|
578
783
|
|
|
579
784
|
${clr(c.bold, 'Options:')}
|
|
580
785
|
--json Output results as JSON
|
|
786
|
+
--sarif Output results as SARIF 2.1.0 (for GitHub Code Scanning)
|
|
581
787
|
--fail-on=<level> Exit 1 when findings meet the threshold. Levels:
|
|
582
788
|
critical any CRITICAL package (publish-access concentration)
|
|
583
789
|
risky any CRITICAL or HIGH (score < 40) package
|
|
@@ -607,6 +813,7 @@ ${clr(c.bold, 'Examples:')}
|
|
|
607
813
|
npx proof-of-commitment --file package-lock.json # scans ALL transitive deps
|
|
608
814
|
npx proof-of-commitment --file go.sum # scans full Go module graph
|
|
609
815
|
npx proof-of-commitment axios chalk --json | jq '.criticalCount'
|
|
816
|
+
npx proof-of-commitment --sarif > results.sarif # GitHub Code Scanning format
|
|
610
817
|
npx proof-of-commitment --fail-on=critical # CI-friendly hard gate
|
|
611
818
|
|
|
612
819
|
${clr(c.bold, 'CI integration (GitHub Actions):')}
|
|
@@ -2265,8 +2472,11 @@ async function main() {
|
|
|
2265
2472
|
let isLockfile = false;
|
|
2266
2473
|
let totalInFile = 0;
|
|
2267
2474
|
let jsonOutput = false;
|
|
2475
|
+
let sarifOutput = false;
|
|
2268
2476
|
// null means "default later" — depends on output mode and CI env.
|
|
2269
2477
|
let failOn = null;
|
|
2478
|
+
// Set after arg-parse: true when JSON or SARIF suppresses interactive output.
|
|
2479
|
+
let structuredOutput = false;
|
|
2270
2480
|
|
|
2271
2481
|
let i = 0;
|
|
2272
2482
|
while (i < args.length) {
|
|
@@ -2276,6 +2486,7 @@ async function main() {
|
|
|
2276
2486
|
else if (a === '--cargo') { ecosystem = 'cargo'; i++; }
|
|
2277
2487
|
else if (a === '--golang' || a === '--go') { ecosystem = 'golang'; i++; }
|
|
2278
2488
|
else if (a === '--json') { jsonOutput = true; i++; }
|
|
2489
|
+
else if (a === '--sarif') { sarifOutput = true; i++; }
|
|
2279
2490
|
else if (a.startsWith('--fail-on=')) {
|
|
2280
2491
|
try { failOn = parseFailOn(a.slice('--fail-on='.length)); }
|
|
2281
2492
|
catch (err) { console.error(err.message); process.exit(2); }
|
|
@@ -2297,12 +2508,14 @@ async function main() {
|
|
|
2297
2508
|
else { packages.push(a); i++; }
|
|
2298
2509
|
}
|
|
2299
2510
|
|
|
2511
|
+
structuredOutput = jsonOutput || sarifOutput;
|
|
2512
|
+
|
|
2300
2513
|
// Zero-arg auto-detect: if no positional packages and no --file, look for a manifest in cwd.
|
|
2301
2514
|
if (!filePath && packages.length === 0) {
|
|
2302
2515
|
const detected = await autodetectManifest(process.cwd());
|
|
2303
2516
|
if (detected) {
|
|
2304
2517
|
filePath = detected;
|
|
2305
|
-
if (!
|
|
2518
|
+
if (!structuredOutput) console.log(clr(c.dim, `Auto-detected manifest: ${detected}`));
|
|
2306
2519
|
} else {
|
|
2307
2520
|
// No positional packages, no --file, and no manifest in cwd → print help.
|
|
2308
2521
|
// This preserves the prior "bare invocation" UX rather than failing silently.
|
|
@@ -2318,7 +2531,7 @@ async function main() {
|
|
|
2318
2531
|
ecosystem = result.ecosystem;
|
|
2319
2532
|
isLockfile = result.lockfile || false;
|
|
2320
2533
|
totalInFile = result.totalInFile || packages.length;
|
|
2321
|
-
if (!
|
|
2534
|
+
if (!structuredOutput) console.log(clr(c.dim, `Detected ${totalInFile} packages from ${filePath} (${ecosystem})`));
|
|
2322
2535
|
} catch (err) {
|
|
2323
2536
|
console.error(`Error reading ${filePath}: ${err.message}`);
|
|
2324
2537
|
process.exit(1);
|
|
@@ -2333,12 +2546,12 @@ async function main() {
|
|
|
2333
2546
|
// Resolve fail-on default.
|
|
2334
2547
|
// - User passed --fail-on=X → use X (already set).
|
|
2335
2548
|
// - CI env (CI=true or =1) → 'critical' (hard gate by default in CI).
|
|
2336
|
-
// - --json output (no CI)
|
|
2549
|
+
// - --json/--sarif output (no CI) → 'critical' (machine-readable = CI-like).
|
|
2337
2550
|
// - interactive table output → 'none' (backward-compatible for casual users).
|
|
2338
2551
|
if (failOn === null) {
|
|
2339
2552
|
const ciEnv = process.env.CI;
|
|
2340
2553
|
const inCI = ciEnv === 'true' || ciEnv === '1';
|
|
2341
|
-
if (inCI ||
|
|
2554
|
+
if (inCI || structuredOutput) failOn = 'critical';
|
|
2342
2555
|
else failOn = 'none';
|
|
2343
2556
|
}
|
|
2344
2557
|
|
|
@@ -2348,7 +2561,7 @@ async function main() {
|
|
|
2348
2561
|
let apiCta = null;
|
|
2349
2562
|
|
|
2350
2563
|
if (packages.length <= 20) {
|
|
2351
|
-
if (!
|
|
2564
|
+
if (!structuredOutput) process.stdout.write(clr(c.dim, `Scoring ${packages.length} ${ecosystem} package${packages.length > 1 ? 's' : ''}...`));
|
|
2352
2565
|
|
|
2353
2566
|
try {
|
|
2354
2567
|
const res = await fetch(API, {
|
|
@@ -2370,11 +2583,11 @@ async function main() {
|
|
|
2370
2583
|
}
|
|
2371
2584
|
|
|
2372
2585
|
const elapsed = ((Date.now() - t0) / 1000).toFixed(1);
|
|
2373
|
-
if (!
|
|
2586
|
+
if (!structuredOutput) process.stdout.write(clr(c.dim, ` done in ${elapsed}s\n`));
|
|
2374
2587
|
|
|
2375
2588
|
} else {
|
|
2376
2589
|
const batches = Math.ceil(packages.length / 20);
|
|
2377
|
-
if (!
|
|
2590
|
+
if (!structuredOutput) process.stdout.write(clr(c.dim, `Scanning ${packages.length} packages (${batches} batches in parallel)...`));
|
|
2378
2591
|
|
|
2379
2592
|
let lastPct = 0;
|
|
2380
2593
|
try {
|
|
@@ -2395,7 +2608,13 @@ async function main() {
|
|
|
2395
2608
|
}
|
|
2396
2609
|
|
|
2397
2610
|
const elapsed = ((Date.now() - t0) / 1000).toFixed(1);
|
|
2398
|
-
if (!
|
|
2611
|
+
if (!structuredOutput) process.stdout.write(clr(c.dim, ` done in ${elapsed}s\n`));
|
|
2612
|
+
|
|
2613
|
+
if (sarifOutput) {
|
|
2614
|
+
const sarif = formatSarif(allResults, { filePath, ecosystem, version: '1.26.0' });
|
|
2615
|
+
console.log(JSON.stringify(sarif, null, 2));
|
|
2616
|
+
process.exit(shouldFail(allResults, failOn) ? 1 : 0);
|
|
2617
|
+
}
|
|
2399
2618
|
|
|
2400
2619
|
if (jsonOutput) {
|
|
2401
2620
|
const criticalCount = allResults.filter(r => hasCritical(r.riskFlags)).length;
|
|
@@ -2425,7 +2644,10 @@ async function main() {
|
|
|
2425
2644
|
}
|
|
2426
2645
|
|
|
2427
2646
|
if (!allResults || allResults.length === 0) {
|
|
2428
|
-
if (
|
|
2647
|
+
if (sarifOutput) {
|
|
2648
|
+
const sarif = formatSarif([], { filePath, ecosystem, version: '1.26.0' });
|
|
2649
|
+
console.log(JSON.stringify(sarif, null, 2));
|
|
2650
|
+
} else if (jsonOutput) {
|
|
2429
2651
|
console.log(JSON.stringify({ totalScanned: 0, criticalCount: 0, provenanceCount: 0, failOn, results: [] }, null, 2));
|
|
2430
2652
|
} else {
|
|
2431
2653
|
console.log('No results returned. Check package names and try again.');
|
|
@@ -2433,6 +2655,12 @@ async function main() {
|
|
|
2433
2655
|
process.exit(0);
|
|
2434
2656
|
}
|
|
2435
2657
|
|
|
2658
|
+
if (sarifOutput) {
|
|
2659
|
+
const sarif = formatSarif(allResults, { filePath, ecosystem, version: '1.26.0' });
|
|
2660
|
+
console.log(JSON.stringify(sarif, null, 2));
|
|
2661
|
+
process.exit(shouldFail(allResults, failOn) ? 1 : 0);
|
|
2662
|
+
}
|
|
2663
|
+
|
|
2436
2664
|
if (jsonOutput) {
|
|
2437
2665
|
const criticalCount = allResults.filter(r => hasCritical(r.riskFlags)).length;
|
|
2438
2666
|
const provenanceCount = allResults.filter(r => r.hasProvenance).length;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "proof-of-commitment",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.26.0",
|
|
4
4
|
"mcpName": "io.github.piiiico/proof-of-commitment",
|
|
5
5
|
"description": "Supply chain security risk scorer for npm, PyPI, Cargo, and Go packages — behavioral signals that can't be faked",
|
|
6
6
|
"type": "module",
|
|
@@ -34,7 +34,17 @@
|
|
|
34
34
|
"maintainer",
|
|
35
35
|
"publisher",
|
|
36
36
|
"provenance",
|
|
37
|
-
"trusted-publishing"
|
|
37
|
+
"trusted-publishing",
|
|
38
|
+
"mcp",
|
|
39
|
+
"mcp-server",
|
|
40
|
+
"vulnerability",
|
|
41
|
+
"sca",
|
|
42
|
+
"dependency-audit",
|
|
43
|
+
"lockfile",
|
|
44
|
+
"devsecops",
|
|
45
|
+
"ci",
|
|
46
|
+
"sarif",
|
|
47
|
+
"code-scanning"
|
|
38
48
|
],
|
|
39
49
|
"author": "piiiico",
|
|
40
50
|
"license": "MIT",
|