@blamejs/exceptd-skills 0.16.22 → 0.16.23
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/ARCHITECTURE.md +2 -2
- package/CHANGELOG.md +25 -1
- package/CONTEXT.md +9 -9
- package/README.md +3 -3
- package/agents/skill-updater.md +1 -1
- package/agents/source-validator.md +3 -4
- package/agents/threat-researcher.md +1 -1
- package/bin/exceptd.js +19 -7
- package/data/_indexes/_meta.json +10 -10
- package/data/_indexes/activity-feed.json +12 -12
- package/data/_indexes/chains.json +70084 -3852
- package/data/_indexes/frequency.json +492 -163
- package/data/_indexes/section-offsets.json +16 -16
- package/data/_indexes/summary-cards.json +272 -106
- package/data/_indexes/token-budget.json +10 -10
- package/data/_indexes/trigger-table.json +15 -6
- package/data/_indexes/xref.json +218 -26
- package/data/cve-catalog.json +10 -10
- package/data/cwe-catalog.json +1 -0
- package/lib/auto-discovery.js +39 -1
- package/lib/collectors/scan-excludes.js +4 -1
- package/lib/cve-cli.js +9 -1
- package/lib/cve-curation.js +8 -1
- package/lib/exit-codes.js +2 -0
- package/lib/flag-suggest.js +1 -1
- package/lib/lint-skills.js +70 -0
- package/lib/playbook-runner.js +59 -11
- package/lib/prefetch.js +24 -1
- package/lib/refresh-external.js +32 -3
- package/lib/rfc-cli.js +8 -1
- package/lib/scoring.js +36 -8
- package/lib/validate-cve-catalog.js +36 -14
- package/lib/validate-package.js +8 -0
- package/lib/validate-playbooks.js +42 -0
- package/lib/verify.js +4 -3
- package/manifest-snapshot.json +4 -2
- package/manifest-snapshot.sha256 +1 -1
- package/manifest.json +57 -54
- package/orchestrator/index.js +48 -4
- package/orchestrator/scanner.js +53 -5
- package/package.json +1 -1
- package/sbom.cdx.json +78 -78
- package/scripts/build-indexes.js +42 -8
- package/scripts/check-sbom-currency.js +72 -0
- package/scripts/release.js +22 -15
- package/skills/exploit-scoring/skill.md +8 -8
|
@@ -31,6 +31,39 @@ function resolveRoot(argv) {
|
|
|
31
31
|
return path.join(__dirname, "..");
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
// Entry count for a data/*.json catalog: keys minus the _meta sentinel. The
|
|
35
|
+
// catalogs are objects keyed by entry id (CVE-…, CWE-…, T…, AML.T…, D3-…,
|
|
36
|
+
// RFC-…) with a single _meta block, so the live entry total is the key count
|
|
37
|
+
// excluding _meta.
|
|
38
|
+
function catalogEntryCount(dataDir, file) {
|
|
39
|
+
const p = path.join(dataDir, file);
|
|
40
|
+
// A --root pointed at a partial tree (no such catalog file) skips that
|
|
41
|
+
// token's check rather than crashing — catalog PRESENCE is asserted by
|
|
42
|
+
// the cardinality check above and the per-component hash check below,
|
|
43
|
+
// not by the description parser.
|
|
44
|
+
if (!fs.existsSync(p)) return null;
|
|
45
|
+
const j = JSON.parse(fs.readFileSync(p, "utf8"));
|
|
46
|
+
if (Array.isArray(j)) return j.length;
|
|
47
|
+
if (j && typeof j === "object") {
|
|
48
|
+
return Object.keys(j).filter((k) => k !== "_meta").length;
|
|
49
|
+
}
|
|
50
|
+
return 0;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// The description string embeds per-catalog ENTRY counts as free text, e.g.
|
|
54
|
+
// "11 catalogs (439 CVEs / 177 CWEs / 805 ATT&CK + ICS / 170 ATLAS /
|
|
55
|
+
// 468 D3FEND / 8888 RFCs)". Each token maps to one data/*.json catalog whose
|
|
56
|
+
// live entry count must match. `label` is the regex-escaped text that follows
|
|
57
|
+
// the number in the description.
|
|
58
|
+
const DESCRIPTION_ENTRY_TOKENS = [
|
|
59
|
+
{ file: "cve-catalog.json", label: "CVEs" },
|
|
60
|
+
{ file: "cwe-catalog.json", label: "CWEs" },
|
|
61
|
+
{ file: "attack-techniques.json", label: "ATT&CK \\+ ICS" },
|
|
62
|
+
{ file: "atlas-ttps.json", label: "ATLAS" },
|
|
63
|
+
{ file: "d3fend-catalog.json", label: "D3FEND" },
|
|
64
|
+
{ file: "rfc-references.json", label: "RFCs" },
|
|
65
|
+
];
|
|
66
|
+
|
|
34
67
|
function checkSbomCurrency(root) {
|
|
35
68
|
const sbomPath = path.join(root, "sbom.cdx.json");
|
|
36
69
|
const manifestPath = path.join(root, "manifest.json");
|
|
@@ -64,6 +97,45 @@ function checkSbomCurrency(root) {
|
|
|
64
97
|
errors.push("SBOM is not CycloneDX 1.6");
|
|
65
98
|
}
|
|
66
99
|
|
|
100
|
+
// The SBOM ships per-catalog entry counts and a skill count embedded as free
|
|
101
|
+
// text in metadata.component.description (propagated verbatim from
|
|
102
|
+
// package.json). The numeric properties above only cover catalog/skill
|
|
103
|
+
// CARDINALITY (file count + skill count), so a catalog's entry total can
|
|
104
|
+
// drift past the count baked into the description while the dedicated SBOM
|
|
105
|
+
// gate still passes. Parse each token out of the description and assert it
|
|
106
|
+
// against the live entry count so a stale published-SBOM description fails
|
|
107
|
+
// the gate.
|
|
108
|
+
const description =
|
|
109
|
+
(sbom.metadata && sbom.metadata.component && sbom.metadata.component.description) || "";
|
|
110
|
+
for (const { file, label } of DESCRIPTION_ENTRY_TOKENS) {
|
|
111
|
+
const live = catalogEntryCount(dataDir, file);
|
|
112
|
+
if (live === null) continue;
|
|
113
|
+
const m = description.match(new RegExp("(\\d+)\\s+" + label + "\\b"));
|
|
114
|
+
if (!m) {
|
|
115
|
+
errors.push(
|
|
116
|
+
`SBOM description is missing the "${file.replace(/\.json$/, "")}" entry-count token (${label}) — regenerate via \`npm run refresh-sbom\``
|
|
117
|
+
);
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
const stated = Number(m[1]);
|
|
121
|
+
if (stated !== live) {
|
|
122
|
+
errors.push(
|
|
123
|
+
`SBOM description entry count for ${label} is ${stated} but live ${file} has ${live} — description is stale; update package.json.description and \`npm run refresh-sbom\``
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
// The skill count is embedded in the same description string ("N skills").
|
|
128
|
+
const skillMatch = description.match(/(\d+)\s+skills\b/);
|
|
129
|
+
if (!skillMatch) {
|
|
130
|
+
errors.push(
|
|
131
|
+
"SBOM description is missing the skill-count token (N skills) — regenerate via `npm run refresh-sbom`"
|
|
132
|
+
);
|
|
133
|
+
} else if (Number(skillMatch[1]) !== liveSkills) {
|
|
134
|
+
errors.push(
|
|
135
|
+
`SBOM description skill count is ${Number(skillMatch[1])} but live manifest has ${liveSkills} skills — description is stale; update package.json.description and \`npm run refresh-sbom\``
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
67
139
|
// component-level cross-check. A renamed or version-bumped
|
|
68
140
|
// skill that never made it into the SBOM refresh will pass the count
|
|
69
141
|
// check (the cardinality is unchanged) but the per-component name +
|
package/scripts/release.js
CHANGED
|
@@ -503,21 +503,27 @@ function cmdRelease() {
|
|
|
503
503
|
if (runId) {
|
|
504
504
|
_run("gh", ["run", "watch", runId, "--exit-status"], { allowFail: true });
|
|
505
505
|
var concl = _capture("gh", ["run", "view", runId, "--json", "conclusion", "--jq", ".conclusion"]).stdout;
|
|
506
|
+
// A non-success conclusion is a hard failure: the publish either failed or
|
|
507
|
+
// is unconfirmable, and either way the release is not done. Warning-and-
|
|
508
|
+
// continuing let a stalled publish read as a clean release.
|
|
506
509
|
if (concl !== "success") {
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
_ok("release.yml: success");
|
|
510
|
+
throw new Error("release: release.yml conclusion=" + (concl || "(unknown)") +
|
|
511
|
+
" — the publish workflow did not finish successfully; re-check release.yml before treating the release as done");
|
|
510
512
|
}
|
|
513
|
+
_ok("release.yml: success");
|
|
511
514
|
} else {
|
|
512
|
-
|
|
515
|
+
throw new Error("release: no release.yml run found for the tag — the publish workflow has not started; " +
|
|
516
|
+
"confirm the tag was pushed and the workflow fired before treating the release as done");
|
|
513
517
|
}
|
|
514
518
|
|
|
515
519
|
_section("verify npm");
|
|
516
520
|
var npmVersion = _capture("npm", ["view", PKG_NAME, "version"]).stdout;
|
|
517
521
|
console.log("npm " + PKG_NAME + ": " + (npmVersion || "(unable to query)") + " (expected " + next + ")");
|
|
522
|
+
// Require a POSITIVE confirmation: the queried npm version must equal `next`.
|
|
523
|
+
// The hard failure is asserted at the end of the phase (after the tarball
|
|
524
|
+
// verify). An empty stdout (registry/auth/network failure) is treated as a
|
|
525
|
+
// mismatch — an unconfirmable publish is a failure, not a success.
|
|
518
526
|
if (npmVersion === next) _ok("npm matches " + next);
|
|
519
|
-
// A mismatch is asserted as a hard failure at the end of the phase (after
|
|
520
|
-
// the tarball verify), so a stalled publish can't read as a clean release.
|
|
521
527
|
|
|
522
528
|
_section("fresh-tarball signature verify");
|
|
523
529
|
// Verify against the EXACT bytes a downstream consumer installs — the
|
|
@@ -535,18 +541,19 @@ function cmdRelease() {
|
|
|
535
541
|
throw new Error("release: scripts/verify-shipped-tarball.js missing — cannot verify the shipped artifact");
|
|
536
542
|
}
|
|
537
543
|
|
|
538
|
-
//
|
|
539
|
-
//
|
|
540
|
-
//
|
|
541
|
-
//
|
|
542
|
-
//
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
544
|
+
// Require a positive npm confirmation after the workflow finished. A version
|
|
545
|
+
// that is empty (query failed) OR != next is not mere propagation lag — fail
|
|
546
|
+
// so a stalled/failed/unconfirmable publish can't read as a completed
|
|
547
|
+
// release. (A genuinely in-flight publish is caught by the workflow-
|
|
548
|
+
// conclusion check above; by the time we query npm post-watch the version
|
|
549
|
+
// should be live.) The message reports the value actually queried.
|
|
550
|
+
if (npmVersion !== next) {
|
|
551
|
+
throw new Error("release: npm shows " + (npmVersion || "(unable to query)") + " but expected " + next +
|
|
552
|
+
" — publish did not complete or could not be confirmed; re-check release.yml before treating the release as done");
|
|
546
553
|
}
|
|
547
554
|
|
|
548
555
|
console.log("\nThe landing site auto-injects the version from jsDelivr @latest — no manual deploy.");
|
|
549
|
-
console.log("Release complete: npm shows " +
|
|
556
|
+
console.log("Release complete: npm shows " + npmVersion + " and the shipped tarball verifies.");
|
|
550
557
|
}
|
|
551
558
|
|
|
552
559
|
function cmdAll(opts) {
|
|
@@ -109,7 +109,7 @@ RWEP = min(100, max(0,
|
|
|
109
109
|
(poc_public × 20) +
|
|
110
110
|
(ai_assisted × 15) +
|
|
111
111
|
(active_expl × 20) +
|
|
112
|
-
(blast_radius ×
|
|
112
|
+
(blast_radius × 30) -
|
|
113
113
|
(patch_avail × 15) -
|
|
114
114
|
(live_patch × 10) +
|
|
115
115
|
(reboot_req × 5)
|
|
@@ -135,11 +135,11 @@ RWEP = min(100, max(0,
|
|
|
135
135
|
- Score contribution: +20 points if confirmed
|
|
136
136
|
- Rationale: Confirmed exploitation means the threat is not theoretical. Treat as an incident-level response trigger.
|
|
137
137
|
|
|
138
|
-
**blast_radius** (0.0 to 1.0 scaled to 0–
|
|
139
|
-
-
|
|
140
|
-
-
|
|
141
|
-
-
|
|
142
|
-
-
|
|
138
|
+
**blast_radius** (0.0 to 1.0 scaled to 0–30): How broad is the affected population?
|
|
139
|
+
- 30 points: Affects all Linux systems since a specific kernel version (e.g., Copy Fail: all 4.14+)
|
|
140
|
+
- 20 points: Affects a major distribution's default configuration
|
|
141
|
+
- 14 points: Affects a specific distribution or configuration
|
|
142
|
+
- 6 points: Affects a narrow software version range
|
|
143
143
|
- 0 points: Affects only highly specific configurations
|
|
144
144
|
|
|
145
145
|
**patch_avail** (0 or 1): Is a patch available?
|
|
@@ -290,7 +290,7 @@ For a CVE not in the pre-calculated catalog, collect:
|
|
|
290
290
|
|
|
291
291
|
### Step 2: Apply RWEP formula
|
|
292
292
|
|
|
293
|
-
Calculate factor values (binary 0/1 or scaled 0–
|
|
293
|
+
Calculate factor values (binary 0/1, or scaled 0–30 for blast radius) and apply formula.
|
|
294
294
|
|
|
295
295
|
### Step 3: Generate remediation timeline
|
|
296
296
|
|
|
@@ -336,7 +336,7 @@ The skill produces a per-CVE Exploit Priority Assessment showing the RWEP score,
|
|
|
336
336
|
| PoC Public | Yes/No | +20/0 |
|
|
337
337
|
| AI-Assisted | Yes/No | +15/0 |
|
|
338
338
|
| Active Exploitation | Confirmed/Suspected/No | +20/+10/0 |
|
|
339
|
-
| Blast Radius | [description] | [0-
|
|
339
|
+
| Blast Radius | [description] | [0-30] |
|
|
340
340
|
| Patch Available | Yes/No | -15/0 |
|
|
341
341
|
| Live Patch Available | Yes/No | -10/0 |
|
|
342
342
|
| Reboot Required | Yes/No | +5/0 |
|