@blamejs/exceptd-skills 0.14.2 → 0.14.4
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/CHANGELOG.md +14 -0
- package/bin/exceptd.js +83 -4
- package/data/_indexes/_meta.json +2 -2
- package/lib/citation-resolve.js +51 -13
- package/lib/collectors/citation-hygiene.js +6 -0
- package/lib/cve-cli.js +13 -0
- package/lib/rfc-cli.js +13 -0
- package/manifest.json +44 -44
- package/package.json +1 -1
- package/sbom.cdx.json +22 -22
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.14.4 — 2026-05-27
|
|
4
|
+
|
|
5
|
+
Clearer errors. A case-only playbook typo — `run SECRETS` — now suggests the right id ("Did you mean: secrets?") instead of only printing the id-format rule. Input-validation errors (a bad `--scope`, malformed evidence) are reported plainly rather than dressed as an "internal error" with a file-a-bug pointer. `exceptd ask` now points a question that names a specific CVE or RFC at the direct resolver (`exceptd cve <id>` / `exceptd rfc <n>`). The malformed-CVE message reads accurately for a short year, not only a non-numeric tail, and the RFC resolver's documentation reflects that obsoleted/historic RFCs are now in the local index.
|
|
6
|
+
|
|
7
|
+
## 0.14.3 — 2026-05-27
|
|
8
|
+
|
|
9
|
+
The resolved-citation cache is now integrity-checked. Each cached record carries a content digest (covering its `resolved_at` timestamp) that is verified on read, and freshness is gated on that timestamp rather than the file's modification time. A cache file edited in place — flipping a rejected CVE to "published" — is rejected as tampered instead of trusted, and a touched file can no longer resurrect a stale verdict. This closes a path where a writable cache could launder a rejected or fabricated citation into a passing verdict (which feeds `collect citation-hygiene --resolve` and, in turn, attestations).
|
|
10
|
+
|
|
11
|
+
`exceptd cve` and `exceptd rfc` reject unknown flags instead of silently ignoring them — a mistyped `--json` no longer emits human text into a pipe that asked for JSON.
|
|
12
|
+
|
|
13
|
+
Malformed evidence is rejected at the boundary. A JSON `null`, array, or scalar piped to `run --evidence -` now returns "evidence must be a JSON object" instead of being silently accepted as an empty run or surfacing as an internal error.
|
|
14
|
+
|
|
15
|
+
`collect citation-hygiene --resolve` now flags a cited RFC number that resolves to nothing, matching how it already flags a fabricated CVE. `ci --max-rwep` rejects a non-numeric or negative cap instead of silently coercing it to 0 (which had quietly degenerated the gate to "block everything"). `run --format` notes on stderr when it overrides `--json`. `cve`, `rfc`, `collect`, `watch`, and `report` are now listed in `exceptd help`.
|
|
16
|
+
|
|
3
17
|
## 0.14.2 — 2026-05-27
|
|
4
18
|
|
|
5
19
|
`exceptd collect citation-hygiene --resolve` now resolves the cited CVEs the offline catalog can't confirm — once each, through the shared resolver cache — and flips their verdicts instead of parking them as inconclusive for an agent to chase: a rejected or disputed identifier becomes a hit, a well-formed identifier NVD doesn't know becomes fabricated, and a confirmed one clears. Honors `--air-gap` (catalog and cache only, no network).
|
package/bin/exceptd.js
CHANGED
|
@@ -450,8 +450,19 @@ Canonical verbs
|
|
|
450
450
|
verify-attestation <sid> Alias for \`attest verify\`.
|
|
451
451
|
run-all Alias for \`run --all\`.
|
|
452
452
|
|
|
453
|
+
cve <CVE-ID> Resolve a CVE citation: published | rejected | disputed
|
|
454
|
+
| fabricated | nonexistent (catalog → cache → one NVD
|
|
455
|
+
lookup). --air-gap/--no-network offline-only; exit 2 on
|
|
456
|
+
a citation that won't stand up.
|
|
457
|
+
rfc <number> Resolve an RFC number → title + status from the local
|
|
458
|
+
index (offline). --check "<title>" flags a mismatch.
|
|
459
|
+
collect <playbook> Run a playbook's companion collector; emits submission
|
|
460
|
+
JSON to pipe into \`run --evidence -\`. --resolve
|
|
461
|
+
(citation-hygiene) resolves uncatalogued citations.
|
|
453
462
|
skill <name> Show context for a specific skill.
|
|
454
463
|
framework-gap <fw> <ref> Programmatic gap analysis (one framework, one CVE/scenario).
|
|
464
|
+
watch [--alerts] Forward-watch aggregator across skills.
|
|
465
|
+
report [executive] Structured posture report.
|
|
455
466
|
path Absolute path to the installed package.
|
|
456
467
|
version Package version.
|
|
457
468
|
|
|
@@ -945,6 +956,19 @@ function readJsonFile(filePath) {
|
|
|
945
956
|
}
|
|
946
957
|
}
|
|
947
958
|
|
|
959
|
+
// Evidence must be a JSON object. `null`, an array, or a scalar parse as valid
|
|
960
|
+
// JSON but are not a submission — without this guard `null` NPE'd deep in the
|
|
961
|
+
// runner ("internal error") and `[]` / a wrong-typed field were silently
|
|
962
|
+
// accepted and run as if empty, so an operator believed a malformed submission
|
|
963
|
+
// was evaluated. Reject at the read boundary with an actionable message.
|
|
964
|
+
function asEvidenceObject(parsed) {
|
|
965
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
966
|
+
const got = parsed === null ? "null" : Array.isArray(parsed) ? "array" : typeof parsed;
|
|
967
|
+
throw new Error(`evidence must be a JSON object (e.g. {"artifacts": {...}, "signal_overrides": {...}}); got ${got}. Run \`exceptd brief <playbook>\` for the expected shape.`);
|
|
968
|
+
}
|
|
969
|
+
return parsed;
|
|
970
|
+
}
|
|
971
|
+
|
|
948
972
|
function readEvidence(evidenceFlag) {
|
|
949
973
|
if (!evidenceFlag) return {};
|
|
950
974
|
// v0.12.12: file-path branch enforces a max size to defend against an
|
|
@@ -988,7 +1012,7 @@ function readEvidence(evidenceFlag) {
|
|
|
988
1012
|
);
|
|
989
1013
|
return {};
|
|
990
1014
|
}
|
|
991
|
-
return JSON.parse(text);
|
|
1015
|
+
return asEvidenceObject(JSON.parse(text));
|
|
992
1016
|
}
|
|
993
1017
|
let stat;
|
|
994
1018
|
try { stat = fs.statSync(evidenceFlag); }
|
|
@@ -999,7 +1023,7 @@ function readEvidence(evidenceFlag) {
|
|
|
999
1023
|
// Route through readJsonFile() for UTF-8-BOM / UTF-16 tolerance.
|
|
1000
1024
|
// Windows-tool-emitted JSON commonly carries these markers; the raw "utf8"
|
|
1001
1025
|
// decode in readFileSync chokes on the leading 0xFEFF.
|
|
1002
|
-
return readJsonFile(evidenceFlag);
|
|
1026
|
+
return asEvidenceObject(readJsonFile(evidenceFlag));
|
|
1003
1027
|
}
|
|
1004
1028
|
|
|
1005
1029
|
function loadRunner() {
|
|
@@ -1691,6 +1715,19 @@ function dispatchPlaybook(cmd, argv) {
|
|
|
1691
1715
|
return emitError(`Playbook not found: "${wanted}". ${hint}`, { verb: cmd, wanted, type: "playbook_not_found" }, pretty);
|
|
1692
1716
|
}
|
|
1693
1717
|
}
|
|
1718
|
+
// Distinguish an operator-input validation error from a genuine internal
|
|
1719
|
+
// fault. A validation message ("--scope must be one of […]", "must match
|
|
1720
|
+
// …") is the operator's to fix — emit it plainly instead of labeling it an
|
|
1721
|
+
// "internal error" and inviting a bug report. The NPE/typeerror guards keep
|
|
1722
|
+
// real internal faults (that happen to contain "invalid") on the bug path.
|
|
1723
|
+
const msg = e && e.message ? String(e.message) : String(e);
|
|
1724
|
+
if (
|
|
1725
|
+
/\b(must be|must match|not in accepted set|is not a valid|unrecognized)\b|\binvalid /i.test(msg) &&
|
|
1726
|
+
msg.length < 300 &&
|
|
1727
|
+
!/cannot read prop|is not a function|is not defined|undefined \(reading|maximum call stack/i.test(msg)
|
|
1728
|
+
) {
|
|
1729
|
+
return emitError(`${cmd}: ${msg}`, { verb: cmd, type: "validation_error" }, pretty);
|
|
1730
|
+
}
|
|
1694
1731
|
// Wrap bare e.message so operators see the verb that triggered the
|
|
1695
1732
|
// failure + the next action they can take. Re-running with --pretty
|
|
1696
1733
|
// expands the cause for log-scraping; the GitHub-issues pointer lets
|
|
@@ -2950,9 +2987,26 @@ function validateScopeOrThrow(scope) {
|
|
|
2950
2987
|
function refuseInvalidPlaybookId(verb, playbookId, pretty) {
|
|
2951
2988
|
const r = validateIdComponent(playbookId, "playbook");
|
|
2952
2989
|
if (!r.ok) {
|
|
2990
|
+
// A case-only typo (`run SECRETS`) fails the lowercase-only id regex
|
|
2991
|
+
// before the fuzzy "did you mean" path ever runs. If lowercasing yields a
|
|
2992
|
+
// real playbook, suggest it — the most common id typo shouldn't get the
|
|
2993
|
+
// least helpful error.
|
|
2994
|
+
let suggestion = null;
|
|
2995
|
+
if (typeof playbookId === "string") {
|
|
2996
|
+
const lowered = playbookId.toLowerCase();
|
|
2997
|
+
if (lowered !== playbookId && validateIdComponent(lowered, "playbook").ok) {
|
|
2998
|
+
try {
|
|
2999
|
+
if (fs.existsSync(path.join(PKG_ROOT, "data", "playbooks", `${lowered}.json`))) suggestion = lowered;
|
|
3000
|
+
} catch { /* fall back to no suggestion */ }
|
|
3001
|
+
}
|
|
3002
|
+
}
|
|
2953
3003
|
emitError(
|
|
2954
|
-
`${verb}: invalid <playbook> id — ${r.reason}
|
|
2955
|
-
{
|
|
3004
|
+
`${verb}: invalid <playbook> id — ${r.reason}.${suggestion ? ` Did you mean: ${suggestion}?` : ""}`,
|
|
3005
|
+
{
|
|
3006
|
+
verb,
|
|
3007
|
+
provided: typeof playbookId === "string" ? playbookId.slice(0, 80) : typeof playbookId,
|
|
3008
|
+
...(suggestion ? { did_you_mean: [suggestion] } : {}),
|
|
3009
|
+
},
|
|
2956
3010
|
pretty
|
|
2957
3011
|
);
|
|
2958
3012
|
return true;
|
|
@@ -3576,6 +3630,14 @@ function cmdRun(runner, args, runOpts, pretty) {
|
|
|
3576
3630
|
pretty,
|
|
3577
3631
|
);
|
|
3578
3632
|
}
|
|
3633
|
+
// --format wins over --json (one stdout document). Note it rather than
|
|
3634
|
+
// silently discarding --json — a script that pipes for JSON and later adds
|
|
3635
|
+
// --format markdown for a human would otherwise get non-JSON with no signal.
|
|
3636
|
+
if ((args.json || global.__exceptdWantJson) && requested !== "json") {
|
|
3637
|
+
process.stderr.write(
|
|
3638
|
+
`[exceptd] note: --format "${requested}" overrides --json; stdout is the ${requested} document, not the JSON envelope.\n`
|
|
3639
|
+
);
|
|
3640
|
+
}
|
|
3579
3641
|
// Only one document can be written to stdout. When several --format values
|
|
3580
3642
|
// are given, emit the first and tell the operator where the rest live so
|
|
3581
3643
|
// the extras aren't silently dropped.
|
|
@@ -7632,6 +7694,13 @@ function cmdAsk(runner, args, runOpts, pretty) {
|
|
|
7632
7694
|
if (!question) {
|
|
7633
7695
|
return emitError("ask: usage: exceptd ask \"<plain-English question>\"", null, pretty);
|
|
7634
7696
|
}
|
|
7697
|
+
// ask routes to playbooks, but a question naming a specific CVE / RFC ("is
|
|
7698
|
+
// CVE-… real", "what is RFC 9404") is answered directly by the resolver
|
|
7699
|
+
// verbs — point at them on stderr so the operator gets the right tool.
|
|
7700
|
+
const cveTok = question.match(/\bCVE-\d{4}-\d{3,}\b/i);
|
|
7701
|
+
const rfcTok = question.match(/\bRFC[-\s]?(\d{1,6})\b/i);
|
|
7702
|
+
if (cveTok) process.stderr.write(`[exceptd] tip: to validate that identifier directly, run \`exceptd cve ${cveTok[0].toUpperCase()}\`.\n`);
|
|
7703
|
+
if (rfcTok) process.stderr.write(`[exceptd] tip: to resolve that RFC directly, run \`exceptd rfc ${rfcTok[1]}\`.\n`);
|
|
7635
7704
|
const ids = runner.listPlaybooks();
|
|
7636
7705
|
const q = question.toLowerCase();
|
|
7637
7706
|
|
|
@@ -7831,6 +7900,16 @@ function cmdAsk(runner, args, runOpts, pretty) {
|
|
|
7831
7900
|
function cmdCi(runner, args, runOpts, pretty) {
|
|
7832
7901
|
const scope = args.scope;
|
|
7833
7902
|
const maxRwep = args["max-rwep"] !== undefined ? Number(args["max-rwep"]) : null;
|
|
7903
|
+
// Reject a non-numeric / negative cap rather than silently coercing it.
|
|
7904
|
+
// `--max-rwep abc` previously became Number→NaN→0, degenerating the gate to
|
|
7905
|
+
// "block everything at RWEP 0" with no error — a silently-broken CI gate.
|
|
7906
|
+
if (maxRwep !== null && (!Number.isFinite(maxRwep) || maxRwep < 0)) {
|
|
7907
|
+
return emitError(
|
|
7908
|
+
`ci: --max-rwep must be a non-negative number; got ${JSON.stringify(String(args["max-rwep"]))}.`,
|
|
7909
|
+
{ verb: "ci", provided: args["max-rwep"] },
|
|
7910
|
+
pretty,
|
|
7911
|
+
);
|
|
7912
|
+
}
|
|
7834
7913
|
const blockOnClock = !!args["block-on-jurisdiction-clock"];
|
|
7835
7914
|
|
|
7836
7915
|
// v0.11.9 (#115): --required <playbook,playbook,...> takes precedence over
|
package/data/_indexes/_meta.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema_version": "1.1.0",
|
|
3
|
-
"generated_at": "2026-05-
|
|
3
|
+
"generated_at": "2026-05-27T15:14:36.966Z",
|
|
4
4
|
"generator": "scripts/build-indexes.js",
|
|
5
5
|
"source_count": 54,
|
|
6
6
|
"source_hashes": {
|
|
7
|
-
"manifest.json": "
|
|
7
|
+
"manifest.json": "e9db8b2640844856c72c5e120fe7973d10d3e5d237c38ab38c26bf4c5eaf7af8",
|
|
8
8
|
"data/atlas-ttps.json": "d24bc02859d40ccf1615db75cca68c077585904e41e0d8f6de448121e9b1abb0",
|
|
9
9
|
"data/attack-techniques.json": "fa193f0d2d248176a8beddb641e9fe56ba4faa9e15dc253ff876dbf0c5d58a77",
|
|
10
10
|
"data/cve-catalog.json": "3d451dda7ac0c7d57a4075ae4bafd3148c6184b35dc1bc59d8b81d1f2641e430",
|
package/lib/citation-resolve.js
CHANGED
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
|
|
24
24
|
const fs = require("node:fs");
|
|
25
25
|
const path = require("node:path");
|
|
26
|
+
const crypto = require("node:crypto");
|
|
26
27
|
|
|
27
28
|
const PKG_ROOT = path.join(__dirname, "..");
|
|
28
29
|
const CVE_CATALOG = process.env.EXCEPTD_CVE_CATALOG || path.join(PKG_ROOT, "data", "cve-catalog.json");
|
|
@@ -44,28 +45,64 @@ function rfcIndex() {
|
|
|
44
45
|
return _rfc;
|
|
45
46
|
}
|
|
46
47
|
|
|
47
|
-
// --- resolved-id cache (atomic JSON files, TTL-bounded,
|
|
48
|
+
// --- resolved-id cache (atomic JSON files, TTL-bounded, integrity-checked) ---
|
|
49
|
+
//
|
|
50
|
+
// The cache feeds security verdicts (and, via citation-hygiene --resolve,
|
|
51
|
+
// attestations), so a record is only trusted if it carries a matching content
|
|
52
|
+
// digest AND its own `resolved_at` is within the freshness window. A file an
|
|
53
|
+
// attacker (or a corrupt/half-written process) edits in place without
|
|
54
|
+
// recomputing `_digest` is rejected as a cache-miss — it can never launder a
|
|
55
|
+
// rejected/fabricated citation into "published". This is the resolved-cache
|
|
56
|
+
// analogue of the prefetch cache's sha256+signature model; full maintainer
|
|
57
|
+
// signing isn't possible operator-side (no private key), so the digest binds
|
|
58
|
+
// the record to itself and makes tampering detectable.
|
|
48
59
|
function cachePath(kind, id) {
|
|
49
60
|
// Read the env at call time so tests can isolate the cache per-case.
|
|
50
61
|
const dir = process.env.EXCEPTD_RESOLVE_CACHE_DIR || RESOLVE_CACHE_DIR;
|
|
51
62
|
const safe = id.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
52
63
|
return path.join(dir, kind, `${safe}.json`);
|
|
53
64
|
}
|
|
65
|
+
// sha256 over the record's canonical bytes (sorted keys, `_digest` excluded).
|
|
66
|
+
// `resolved_at` IS covered, so the staleness clock can't be rewritten apart
|
|
67
|
+
// from the verdict.
|
|
68
|
+
function recordDigest(record) {
|
|
69
|
+
const canon = {};
|
|
70
|
+
for (const k of Object.keys(record).sort()) {
|
|
71
|
+
if (k === "_digest") continue;
|
|
72
|
+
canon[k] = record[k];
|
|
73
|
+
}
|
|
74
|
+
return crypto.createHash("sha256").update(JSON.stringify(canon)).digest("hex");
|
|
75
|
+
}
|
|
54
76
|
function cacheGet(kind, id) {
|
|
55
77
|
try {
|
|
56
78
|
const p = cachePath(kind, id);
|
|
57
|
-
const
|
|
58
|
-
if (
|
|
59
|
-
|
|
79
|
+
const record = JSON.parse(fs.readFileSync(p, "utf8"));
|
|
80
|
+
if (!record || typeof record !== "object") return null;
|
|
81
|
+
// Integrity: a record without a matching digest is tampered/corrupt → miss.
|
|
82
|
+
if (typeof record._digest !== "string" || record._digest !== recordDigest(record)) return null;
|
|
83
|
+
// Freshness keyed on the record's own resolved_at (not file mtime, which a
|
|
84
|
+
// `touch` can reset). Reject future-dated records as a poisoning signal,
|
|
85
|
+
// mirroring the prefetch cache's future-date guard.
|
|
86
|
+
const ts = Date.parse(record.resolved_at || "");
|
|
87
|
+
if (!Number.isFinite(ts)) return null;
|
|
88
|
+
const age = Date.now() - ts;
|
|
89
|
+
if (age < -60_000 || age > CACHE_TTL_MS) return null;
|
|
90
|
+
delete record._digest; // internal integrity field — never surface it
|
|
91
|
+
return record;
|
|
60
92
|
} catch { return null; }
|
|
61
93
|
}
|
|
62
94
|
function cachePut(kind, id, record) {
|
|
63
95
|
try {
|
|
64
96
|
const p = cachePath(kind, id);
|
|
65
97
|
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
98
|
+
const signed = { ...record };
|
|
99
|
+
signed._digest = recordDigest(signed);
|
|
100
|
+
// Random suffix (not just pid) so two cachePut calls for the same id in one
|
|
101
|
+
// process — a Promise.all fan-out or worker threads sharing a pid — don't
|
|
102
|
+
// race the same tmp path. Matches lib/prefetch.js writeFileAtomic.
|
|
103
|
+
const tmp = `${p}.${process.pid}.${crypto.randomBytes(4).toString("hex")}.tmp`;
|
|
104
|
+
fs.writeFileSync(tmp, JSON.stringify(signed));
|
|
105
|
+
fs.renameSync(tmp, p); // atomic — concurrent readers never see a half-written file
|
|
69
106
|
} catch { /* cache is an optimization, never fatal */ }
|
|
70
107
|
}
|
|
71
108
|
|
|
@@ -84,7 +121,7 @@ async function resolveCve(id, opts = {}) {
|
|
|
84
121
|
|
|
85
122
|
if (!CVE_RE.test(cveId)) {
|
|
86
123
|
return { ...base, status: "fabricated", from: "format",
|
|
87
|
-
reason: "not the canonical CVE-YYYY-NNNN form
|
|
124
|
+
reason: "not the canonical CVE-YYYY-NNNN form (4-digit year + 4-or-more-digit sequence) — a malformed identifier" };
|
|
88
125
|
}
|
|
89
126
|
|
|
90
127
|
// 1. curated catalog (offline, authoritative for the ids it covers)
|
|
@@ -165,10 +202,11 @@ async function resolveCve(id, opts = {}) {
|
|
|
165
202
|
|
|
166
203
|
/**
|
|
167
204
|
* Resolve an RFC citation. Returns { id, kind:"rfc", number, title, rfc_status,
|
|
168
|
-
* found, from, ... }. The local index covers the whole
|
|
169
|
-
*
|
|
170
|
-
*
|
|
171
|
-
*
|
|
205
|
+
* found, from, ... }. The local index covers the whole RFC series — current
|
|
206
|
+
* AND obsoleted/historic (the latter carry `_obsoleted` + `obsoleted_by`) — so
|
|
207
|
+
* number->title resolution, including "is this RFC superseded?", is fully
|
|
208
|
+
* offline. A number absent from the index is almost certainly nonexistent (or
|
|
209
|
+
* an UNKNOWN-status placeholder); the optional network step confirms.
|
|
172
210
|
*/
|
|
173
211
|
async function resolveRfc(id, opts = {}) {
|
|
174
212
|
const raw = String(id || "").trim();
|
|
@@ -201,7 +239,7 @@ async function resolveRfc(id, opts = {}) {
|
|
|
201
239
|
// 3. offline: report the ambiguity rather than guessing
|
|
202
240
|
if (isAirGap(opts) || opts.noNetwork) {
|
|
203
241
|
return { ...base, number: num, found: false, status: "unknown", from: "offline",
|
|
204
|
-
reason: "not in the local RFC index
|
|
242
|
+
reason: "not in the local RFC index (which includes obsoleted/historic RFCs) — most likely a nonexistent number; confirm at datatracker.ietf.org when online" };
|
|
205
243
|
}
|
|
206
244
|
|
|
207
245
|
// 4. disambiguate obsoleted vs nonexistent via the datatracker, once + cached
|
|
@@ -522,12 +522,18 @@ async function applyResolution(submission, opts = {}) {
|
|
|
522
522
|
signals["cve-citation-needs-external-verification"] = cveUnknown > 0 ? "inconclusive" : "miss";
|
|
523
523
|
}
|
|
524
524
|
|
|
525
|
+
let rfcNonexistentHit = false;
|
|
525
526
|
for (const item of rfcList) {
|
|
526
527
|
const cite = String(item.citation || "");
|
|
527
528
|
const num = (cite.match(/(\d+)/) || [])[1];
|
|
528
529
|
const r = await resolver.resolveRfc(num || cite, { airGap });
|
|
529
530
|
resolved.rfc.push({ citation: cite, file: item.file, status: r.status, found: r.found, from: r.from, title: r.title || null });
|
|
531
|
+
// A cited RFC number that resolves to nothing is a bad citation, same class
|
|
532
|
+
// as a fabricated CVE — surface it instead of discarding the verdict. (An
|
|
533
|
+
// obsoleted/historic RFC that resolves IS a real RFC, so it isn't flagged.)
|
|
534
|
+
if (r.status === "nonexistent") rfcNonexistentHit = true;
|
|
530
535
|
}
|
|
536
|
+
if (rfcNonexistentHit) signals["rfc-number-title-mismatch"] = "hit";
|
|
531
537
|
|
|
532
538
|
const out = { ...submission, signal_overrides: signals };
|
|
533
539
|
out.artifacts = { ...(submission.artifacts || {}) };
|
package/lib/cve-cli.js
CHANGED
|
@@ -15,6 +15,19 @@ const { resolveCve } = require("./citation-resolve.js");
|
|
|
15
15
|
(async () => {
|
|
16
16
|
const argv = process.argv.slice(2);
|
|
17
17
|
const flags = new Set(argv.filter((a) => a.startsWith("--")));
|
|
18
|
+
// Reject unknown flags rather than silently ignoring them — the same
|
|
19
|
+
// contract the in-process verbs enforce. A swallowed `--josn` would emit
|
|
20
|
+
// human text into a pipe that asked for JSON and defeat a CI gate.
|
|
21
|
+
const KNOWN = new Set(["--json", "--pretty", "--air-gap", "--no-network", "--help", "-h"]);
|
|
22
|
+
const unknown = [...flags].filter((f) => !KNOWN.has(f));
|
|
23
|
+
if (unknown.length > 0) {
|
|
24
|
+
process.stderr.write(JSON.stringify({
|
|
25
|
+
ok: false, verb: "cve", error: `cve: unknown flag(s): ${unknown.join(", ")}`,
|
|
26
|
+
unknown_flags: unknown, known_flags: ["--json", "--pretty", "--air-gap", "--no-network"],
|
|
27
|
+
}) + "\n");
|
|
28
|
+
process.exitCode = 1;
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
18
31
|
const id = argv.find((a) => !a.startsWith("--"));
|
|
19
32
|
const pretty = flags.has("--pretty");
|
|
20
33
|
const json = flags.has("--json") || pretty;
|
package/lib/rfc-cli.js
CHANGED
|
@@ -16,6 +16,19 @@ const { resolveRfc } = require("./citation-resolve.js");
|
|
|
16
16
|
(async () => {
|
|
17
17
|
const argv = process.argv.slice(2);
|
|
18
18
|
const flags = new Set(argv.filter((a) => a.startsWith("--")));
|
|
19
|
+
// Reject unknown flags (same contract as the in-process verbs). `--check`
|
|
20
|
+
// consumes the following token as its value; that value is a positional, not
|
|
21
|
+
// a flag, so it isn't checked here.
|
|
22
|
+
const KNOWN = new Set(["--json", "--pretty", "--air-gap", "--no-network", "--check", "--help", "-h"]);
|
|
23
|
+
const unknown = [...flags].filter((f) => !KNOWN.has(f));
|
|
24
|
+
if (unknown.length > 0) {
|
|
25
|
+
process.stderr.write(JSON.stringify({
|
|
26
|
+
ok: false, verb: "rfc", error: `rfc: unknown flag(s): ${unknown.join(", ")}`,
|
|
27
|
+
unknown_flags: unknown, known_flags: ["--json", "--pretty", "--air-gap", "--no-network", "--check"],
|
|
28
|
+
}) + "\n");
|
|
29
|
+
process.exitCode = 1;
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
19
32
|
const positionals = argv.filter((a) => !a.startsWith("--"));
|
|
20
33
|
const id = positionals[0];
|
|
21
34
|
const pretty = flags.has("--pretty");
|
package/manifest.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "exceptd-security",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.4",
|
|
4
4
|
"description": "AI security skills grounded in mid-2026 threat reality, not stale framework documentation",
|
|
5
5
|
"homepage": "https://exceptd.com",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
],
|
|
54
54
|
"last_threat_review": "2026-05-15",
|
|
55
55
|
"signature": "lXhZgoIrrVloO3XaTvo/43AxZn4mwErstd7DR0O/oVhD3AOGODM4HqrageYEou9WKOdMEGP5mJNTjJsXdP5NDA==",
|
|
56
|
-
"signed_at": "2026-05-
|
|
56
|
+
"signed_at": "2026-05-27T15:13:33.851Z",
|
|
57
57
|
"cwe_refs": [
|
|
58
58
|
"CWE-125",
|
|
59
59
|
"CWE-362",
|
|
@@ -123,7 +123,7 @@
|
|
|
123
123
|
],
|
|
124
124
|
"last_threat_review": "2026-05-17",
|
|
125
125
|
"signature": "ztSKk/zFMFbT12qRcEeBKpydBn7fTT86KxMmor0DTCoKQWk5fJ0fSInfP1XMSB6rFk4/SuSjKVxQRMKVJ5a+Cg==",
|
|
126
|
-
"signed_at": "2026-05-
|
|
126
|
+
"signed_at": "2026-05-27T15:13:33.853Z",
|
|
127
127
|
"cwe_refs": [
|
|
128
128
|
"CWE-1039",
|
|
129
129
|
"CWE-1426",
|
|
@@ -196,7 +196,7 @@
|
|
|
196
196
|
],
|
|
197
197
|
"last_threat_review": "2026-05-17",
|
|
198
198
|
"signature": "K6QdPHNK5c4K5QFjrW0QsUhjp71D7SOisSoulwPNSvKRdi2rY+yg0kdckijBMkLMsVPyUvcC9giu93mKJ1OZDg==",
|
|
199
|
-
"signed_at": "2026-05-
|
|
199
|
+
"signed_at": "2026-05-27T15:13:33.854Z",
|
|
200
200
|
"cwe_refs": [
|
|
201
201
|
"CWE-22",
|
|
202
202
|
"CWE-345",
|
|
@@ -248,7 +248,7 @@
|
|
|
248
248
|
"framework_gaps": [],
|
|
249
249
|
"last_threat_review": "2026-05-22",
|
|
250
250
|
"signature": "Qd3SBWmUAaaT++e1Ry2wBIz/dCBmNBMl0+4Rb0etvJLES0fIBEAkU1mTbgNZnT5XOg9J5twdUpymWtmKnDDQCQ==",
|
|
251
|
-
"signed_at": "2026-05-
|
|
251
|
+
"signed_at": "2026-05-27T15:13:33.854Z"
|
|
252
252
|
},
|
|
253
253
|
{
|
|
254
254
|
"name": "compliance-theater",
|
|
@@ -279,7 +279,7 @@
|
|
|
279
279
|
],
|
|
280
280
|
"last_threat_review": "2026-05-22",
|
|
281
281
|
"signature": "F2Shxae0ua0gPtvwzTRVzzHaIgJcFDRT3/akLUAZ4aaMQhkleKkcTaTpkjp+pTVEdPfLeLGNCeAOMs+whVYOBg==",
|
|
282
|
-
"signed_at": "2026-05-
|
|
282
|
+
"signed_at": "2026-05-27T15:13:33.855Z"
|
|
283
283
|
},
|
|
284
284
|
{
|
|
285
285
|
"name": "exploit-scoring",
|
|
@@ -308,7 +308,7 @@
|
|
|
308
308
|
],
|
|
309
309
|
"last_threat_review": "2026-05-18",
|
|
310
310
|
"signature": "NA1hoQycvQhSUoG5rwlXX0mOVmGxoXRVezkELGEA2nZOdGis4gXkHT3O6Sfw7zxE4JuMrsCb65TEeOWk9WEPDg==",
|
|
311
|
-
"signed_at": "2026-05-
|
|
311
|
+
"signed_at": "2026-05-27T15:13:33.855Z"
|
|
312
312
|
},
|
|
313
313
|
{
|
|
314
314
|
"name": "rag-pipeline-security",
|
|
@@ -345,7 +345,7 @@
|
|
|
345
345
|
],
|
|
346
346
|
"last_threat_review": "2026-05-22",
|
|
347
347
|
"signature": "W3pS8lnaCP96TQzsJpG5d5yv5IwgaQyS4Z2Ctcz5BOJf6LbajSIgeDgTZ4f4Bhr5m4E7KsgWGjZS4x7Fwd33BQ==",
|
|
348
|
-
"signed_at": "2026-05-
|
|
348
|
+
"signed_at": "2026-05-27T15:13:33.856Z",
|
|
349
349
|
"cwe_refs": [
|
|
350
350
|
"CWE-1395",
|
|
351
351
|
"CWE-1426"
|
|
@@ -405,7 +405,7 @@
|
|
|
405
405
|
],
|
|
406
406
|
"last_threat_review": "2026-05-17",
|
|
407
407
|
"signature": "/WDGygh1Ck4yWlBWDGtEUVCqKB8d+UaJXoAoBXujtt+GAl8JbMNpaN1TvI0WkEltQ9dTxaAzSn20/eVDqv8iDQ==",
|
|
408
|
-
"signed_at": "2026-05-
|
|
408
|
+
"signed_at": "2026-05-27T15:13:33.856Z",
|
|
409
409
|
"d3fend_refs": [
|
|
410
410
|
"D3-CA",
|
|
411
411
|
"D3-CSPP",
|
|
@@ -440,7 +440,7 @@
|
|
|
440
440
|
"framework_gaps": [],
|
|
441
441
|
"last_threat_review": "2026-05-22",
|
|
442
442
|
"signature": "za1NKBpy9LC91F/ESO/qhUfmvVr8GNItQOjR5OJLeHm+2dQ9HHiFWQK2eo53V/n/0uhubuggURA3yS6kJuWwBg==",
|
|
443
|
-
"signed_at": "2026-05-
|
|
443
|
+
"signed_at": "2026-05-27T15:13:33.857Z",
|
|
444
444
|
"cwe_refs": [
|
|
445
445
|
"CWE-1188"
|
|
446
446
|
],
|
|
@@ -474,7 +474,7 @@
|
|
|
474
474
|
"framework_gaps": [],
|
|
475
475
|
"last_threat_review": "2026-05-18",
|
|
476
476
|
"signature": "xiHAhhdufm9hCKU8PLiPE0MX65ej2F4OZwtlWLGLCiie9/km+Kiqbt192LcMvr94v83C98pb9wIaqFsFWft6AQ==",
|
|
477
|
-
"signed_at": "2026-05-
|
|
477
|
+
"signed_at": "2026-05-27T15:13:33.857Z",
|
|
478
478
|
"forward_watch": [
|
|
479
479
|
"New AI attack classes as ATLAS v6 publishes",
|
|
480
480
|
"Post-quantum adversary capability timeline",
|
|
@@ -513,7 +513,7 @@
|
|
|
513
513
|
"framework_gaps": [],
|
|
514
514
|
"last_threat_review": "2026-05-01",
|
|
515
515
|
"signature": "oYsSk35N2Uzq7MRofACykylcVwkgPhI4luWZ14vmQT+gUKLyZiKVOUJbe1+7lGl6BYPRN0sUDQ0f7S5Eu5w2Ag==",
|
|
516
|
-
"signed_at": "2026-05-
|
|
516
|
+
"signed_at": "2026-05-27T15:13:33.857Z"
|
|
517
517
|
},
|
|
518
518
|
{
|
|
519
519
|
"name": "zeroday-gap-learn",
|
|
@@ -540,7 +540,7 @@
|
|
|
540
540
|
"framework_gaps": [],
|
|
541
541
|
"last_threat_review": "2026-05-18",
|
|
542
542
|
"signature": "igRqYyU1unRFH40BsPyAR62SPrk8QZv8dPGb8S9O9EvLCNOZAzm3t+HdT/NKqzWHwrpomOzkkkyLfYI/0qTUDA==",
|
|
543
|
-
"signed_at": "2026-05-
|
|
543
|
+
"signed_at": "2026-05-27T15:13:33.858Z",
|
|
544
544
|
"forward_watch": [
|
|
545
545
|
"New CISA KEV entries",
|
|
546
546
|
"New ATLAS TTP additions in each ATLAS release",
|
|
@@ -604,7 +604,7 @@
|
|
|
604
604
|
],
|
|
605
605
|
"last_threat_review": "2026-05-22",
|
|
606
606
|
"signature": "i/17u4kJiSpcZAz7LnTyRePFugQOstQ1P4kVoe0oGf4E2/j8oIN9U9DccjUn/YHZhKWIJ2AILG/DMhvMrr3bBg==",
|
|
607
|
-
"signed_at": "2026-05-
|
|
607
|
+
"signed_at": "2026-05-27T15:13:33.858Z",
|
|
608
608
|
"cwe_refs": [
|
|
609
609
|
"CWE-327"
|
|
610
610
|
],
|
|
@@ -652,7 +652,7 @@
|
|
|
652
652
|
],
|
|
653
653
|
"last_threat_review": "2026-05-22",
|
|
654
654
|
"signature": "QuOVaQ4E2Sl39TClbhZ7HA9XrYAyRrDL44HY3RTE7aWLue0hV2cxaBt40ALGmHS++631QGFDlZTLZI77Tr6nAA==",
|
|
655
|
-
"signed_at": "2026-05-
|
|
655
|
+
"signed_at": "2026-05-27T15:13:33.858Z"
|
|
656
656
|
},
|
|
657
657
|
{
|
|
658
658
|
"name": "security-maturity-tiers",
|
|
@@ -689,7 +689,7 @@
|
|
|
689
689
|
],
|
|
690
690
|
"last_threat_review": "2026-05-01",
|
|
691
691
|
"signature": "8Px1s2lDj10/Q6erwEQlXgUHM1+OTruUR8qAHPX7Oo3k/l69N6P9sm0PsafS9wDFtj9l5C/OiLiFgzMlMt6vBw==",
|
|
692
|
-
"signed_at": "2026-05-
|
|
692
|
+
"signed_at": "2026-05-27T15:13:33.859Z",
|
|
693
693
|
"cwe_refs": [
|
|
694
694
|
"CWE-1188"
|
|
695
695
|
]
|
|
@@ -724,7 +724,7 @@
|
|
|
724
724
|
"framework_gaps": [],
|
|
725
725
|
"last_threat_review": "2026-05-11",
|
|
726
726
|
"signature": "urRcataVWg6/utyEkSiOWoNxTL8sABRjPR7ShyDfZGnAozFph/yDktSoaPVxQDXwu9EfJE+qhUW5OYR/yJECBQ==",
|
|
727
|
-
"signed_at": "2026-05-
|
|
727
|
+
"signed_at": "2026-05-27T15:13:33.859Z"
|
|
728
728
|
},
|
|
729
729
|
{
|
|
730
730
|
"name": "attack-surface-pentest",
|
|
@@ -796,7 +796,7 @@
|
|
|
796
796
|
"Pwn2Own Berlin 2026 (disclosed 2026-05-14, embargo ends 2026-08-12) — Microsoft Edge 4-bug sandbox escape by Orange Tsai (DEVCORE); forward-watch only (browser sandbox, out of current playbook scope); track Microsoft Edge security advisory and KEV add"
|
|
797
797
|
],
|
|
798
798
|
"signature": "C7lv65/Ecm8JJgSKxrX5lxx0YFzKWtrIQSKp+vy50I5e8945s1JmifGUUrnQwRQhq/Pkv7EmfiH5XSO8h75bDg==",
|
|
799
|
-
"signed_at": "2026-05-
|
|
799
|
+
"signed_at": "2026-05-27T15:13:33.859Z"
|
|
800
800
|
},
|
|
801
801
|
{
|
|
802
802
|
"name": "fuzz-testing-strategy",
|
|
@@ -856,7 +856,7 @@
|
|
|
856
856
|
"OSS-Fuzz-Gen / AI-assisted harness generation becoming the default expectation for OSS maintainers"
|
|
857
857
|
],
|
|
858
858
|
"signature": "Z7ypCUnXx8JpLtgxxB6RHNi39w74AmrGY1N4ofAGCXhkuM2EaFVm1AU0dvl9UQ1bVLfHKEDGqMO/TwlIY7RABg==",
|
|
859
|
-
"signed_at": "2026-05-
|
|
859
|
+
"signed_at": "2026-05-27T15:13:33.860Z"
|
|
860
860
|
},
|
|
861
861
|
{
|
|
862
862
|
"name": "dlp-gap-analysis",
|
|
@@ -931,7 +931,7 @@
|
|
|
931
931
|
"Quebec Law 25, India DPDPA, KSA PDPL enforcement actions naming AI-tool prompt data as in-scope personal information"
|
|
932
932
|
],
|
|
933
933
|
"signature": "IgEnpHOhCftAyfUNdKsjbrd169T9pJkk/rRM2ZEna+H18y7p5x48+1kME2sJMZjJuyAdQFBJi8PJXZFwLGI+DQ==",
|
|
934
|
-
"signed_at": "2026-05-
|
|
934
|
+
"signed_at": "2026-05-27T15:13:33.860Z"
|
|
935
935
|
},
|
|
936
936
|
{
|
|
937
937
|
"name": "supply-chain-integrity",
|
|
@@ -1010,7 +1010,7 @@
|
|
|
1010
1010
|
"Pwn2Own Berlin 2026 (disclosed 2026-05-14, embargo ends 2026-08-12) — NVIDIA Megatron Bridge path traversal by haehae; AI training-stack file-system trust boundary; track patch and SBOM-attestation impact"
|
|
1011
1011
|
],
|
|
1012
1012
|
"signature": "pcLrM98A3vUSZRjwNAk0aZ9umvOwB41XCLLsCOy/IebB2F/06oIrGUKkMHtHwm4pTVPShMMcKdZQQ3jz30FnCg==",
|
|
1013
|
-
"signed_at": "2026-05-
|
|
1013
|
+
"signed_at": "2026-05-27T15:13:33.861Z"
|
|
1014
1014
|
},
|
|
1015
1015
|
{
|
|
1016
1016
|
"name": "defensive-countermeasure-mapping",
|
|
@@ -1067,7 +1067,7 @@
|
|
|
1067
1067
|
],
|
|
1068
1068
|
"last_threat_review": "2026-05-11",
|
|
1069
1069
|
"signature": "G5q5elh7Q7eu2xcwTVQJGDTGfvZR0OGQaLSLJPb2wjzCHFF8PWuZfCHZdjjqisiRzRWPyLlzgfHeMJqOdy7cBw==",
|
|
1070
|
-
"signed_at": "2026-05-
|
|
1070
|
+
"signed_at": "2026-05-27T15:13:33.861Z"
|
|
1071
1071
|
},
|
|
1072
1072
|
{
|
|
1073
1073
|
"name": "identity-assurance",
|
|
@@ -1134,7 +1134,7 @@
|
|
|
1134
1134
|
"d3fend_refs": [],
|
|
1135
1135
|
"last_threat_review": "2026-05-11",
|
|
1136
1136
|
"signature": "Wv5hGMeHjlaQK1zwicVCA7AvdKgJBgvcjdpGM9Ywahh9tagAKhbkOjybowDQZzu7OZ3bDkbh6pBYc1Sdwr6NAA==",
|
|
1137
|
-
"signed_at": "2026-05-
|
|
1137
|
+
"signed_at": "2026-05-27T15:13:33.861Z"
|
|
1138
1138
|
},
|
|
1139
1139
|
{
|
|
1140
1140
|
"name": "ot-ics-security",
|
|
@@ -1190,7 +1190,7 @@
|
|
|
1190
1190
|
"d3fend_refs": [],
|
|
1191
1191
|
"last_threat_review": "2026-05-11",
|
|
1192
1192
|
"signature": "8t5qKHd3yWi57dvG36YQkLN/X9bQWqtEiYjay4IfSmqhJpM/xXPaQVKNGz3wscrO8OLKUZ0OaX7Mj5kzpgBKBQ==",
|
|
1193
|
-
"signed_at": "2026-05-
|
|
1193
|
+
"signed_at": "2026-05-27T15:13:33.862Z"
|
|
1194
1194
|
},
|
|
1195
1195
|
{
|
|
1196
1196
|
"name": "coordinated-vuln-disclosure",
|
|
@@ -1242,7 +1242,7 @@
|
|
|
1242
1242
|
"NYDFS 23 NYCRR 500 amendments potentially adding explicit CVD program requirements"
|
|
1243
1243
|
],
|
|
1244
1244
|
"signature": "GDGt4UPqBa04PjlpSmpyihGzd3OgfBN7jaAK5tfwp+LRSs3ygKOdbeivUCCHNagTY1hE6hG2Ou40ADfBFuXeAg==",
|
|
1245
|
-
"signed_at": "2026-05-
|
|
1245
|
+
"signed_at": "2026-05-27T15:13:33.862Z"
|
|
1246
1246
|
},
|
|
1247
1247
|
{
|
|
1248
1248
|
"name": "threat-modeling-methodology",
|
|
@@ -1292,7 +1292,7 @@
|
|
|
1292
1292
|
"PASTA v2 updates incorporating AI/ML application threats"
|
|
1293
1293
|
],
|
|
1294
1294
|
"signature": "rFBpOQEJUPpl+v88Lw/WqVJRhTl80vy0VbPAbzQj3Q0suJRRrJg368I9uKu5LXIBKFDvKxnGIcIzbGg9NUtaCA==",
|
|
1295
|
-
"signed_at": "2026-05-
|
|
1295
|
+
"signed_at": "2026-05-27T15:13:33.862Z"
|
|
1296
1296
|
},
|
|
1297
1297
|
{
|
|
1298
1298
|
"name": "webapp-security",
|
|
@@ -1366,7 +1366,7 @@
|
|
|
1366
1366
|
"d3fend_refs": [],
|
|
1367
1367
|
"last_threat_review": "2026-05-11",
|
|
1368
1368
|
"signature": "ux85YI4t2mVHOyt744Yin1HHy+z11JIFygjKfFfQOBBl5QVV3A267jeIy7utix85irMcpZm/T3yx/ooqiK2tBA==",
|
|
1369
|
-
"signed_at": "2026-05-
|
|
1369
|
+
"signed_at": "2026-05-27T15:13:33.863Z",
|
|
1370
1370
|
"forward_watch": [
|
|
1371
1371
|
"NGINX Rift CVE-2026-42945 (disclosed 2026-05-13, source depthfirst) — KEV-watch predicted CISA KEV listing by 2026-05-29; AI-assisted discovery angle; track for active-exploitation confirmation and patch advisory affecting front-door web app deployments"
|
|
1372
1372
|
]
|
|
@@ -1419,7 +1419,7 @@
|
|
|
1419
1419
|
"d3fend_refs": [],
|
|
1420
1420
|
"last_threat_review": "2026-05-15",
|
|
1421
1421
|
"signature": "IIXnkZ5ZNqFwOto5KfytADTLLZLoyXNZACD1ORZ40P1HUAQxe6u2uyXFzzsfuob4Uy06jNkRGr2FFgCphUH1Cw==",
|
|
1422
|
-
"signed_at": "2026-05-
|
|
1422
|
+
"signed_at": "2026-05-27T15:13:33.863Z"
|
|
1423
1423
|
},
|
|
1424
1424
|
{
|
|
1425
1425
|
"name": "sector-healthcare",
|
|
@@ -1479,7 +1479,7 @@
|
|
|
1479
1479
|
"d3fend_refs": [],
|
|
1480
1480
|
"last_threat_review": "2026-05-11",
|
|
1481
1481
|
"signature": "AhF9KF8ZBlDteciV+F8IBSmFVYCvQOn44GmD4rZjgLoPxfIv/QE1/vSkK32zyqDKtHWkLSXExbkkPkxA/V6dDw==",
|
|
1482
|
-
"signed_at": "2026-05-
|
|
1482
|
+
"signed_at": "2026-05-27T15:13:33.864Z"
|
|
1483
1483
|
},
|
|
1484
1484
|
{
|
|
1485
1485
|
"name": "sector-financial",
|
|
@@ -1560,7 +1560,7 @@
|
|
|
1560
1560
|
"TIBER-EU framework v2.0 alignment with DORA TLPT RTS (JC 2024/40); cross-recognition with CBEST and iCAST"
|
|
1561
1561
|
],
|
|
1562
1562
|
"signature": "HQgZvb4ReziEz5rNFr8i/O8/rJEZR+iHRROT7m/D2QUqhrcNISPkYXENsUZlG8xapzy/Ik92ehkseyj4hdmhCQ==",
|
|
1563
|
-
"signed_at": "2026-05-
|
|
1563
|
+
"signed_at": "2026-05-27T15:13:33.864Z"
|
|
1564
1564
|
},
|
|
1565
1565
|
{
|
|
1566
1566
|
"name": "sector-federal-government",
|
|
@@ -1629,7 +1629,7 @@
|
|
|
1629
1629
|
"Australia PSPF 2024 revision and ISM quarterly updates — track for Essential Eight Maturity Level requirements for federal entities"
|
|
1630
1630
|
],
|
|
1631
1631
|
"signature": "linxmsXZiOYtcs71sSWgGCrvb8xQfmxmtTY5PRvZJ0/8FgJulo0tQtejzexYG775s7XhjAmGsDP238BQTQ8ADA==",
|
|
1632
|
-
"signed_at": "2026-05-
|
|
1632
|
+
"signed_at": "2026-05-27T15:13:33.865Z"
|
|
1633
1633
|
},
|
|
1634
1634
|
{
|
|
1635
1635
|
"name": "sector-energy",
|
|
@@ -1694,7 +1694,7 @@
|
|
|
1694
1694
|
"ICS-CERT advisory feed (https://www.cisa.gov/news-events/cybersecurity-advisories/ics-advisories) for vendor CVEs in Siemens, Rockwell, Schneider Electric, ABB, GE Vernova, Hitachi Energy, AVEVA / OSIsoft PI"
|
|
1695
1695
|
],
|
|
1696
1696
|
"signature": "JjBfc0ovta560Clk0x3QGRM5osFJDwcvpy3rT7QEGdCIL827jzE8QCow1C8deXq+4JhY2sA/d7/8IsxikdlkCg==",
|
|
1697
|
-
"signed_at": "2026-05-
|
|
1697
|
+
"signed_at": "2026-05-27T15:13:33.865Z"
|
|
1698
1698
|
},
|
|
1699
1699
|
{
|
|
1700
1700
|
"name": "sector-telecom",
|
|
@@ -1780,7 +1780,7 @@
|
|
|
1780
1780
|
"O-RAN SFG / WG11 security specifications"
|
|
1781
1781
|
],
|
|
1782
1782
|
"signature": "JWVxKFoKrbX4d+Tko1d4OBdwyg25MfFFKn4CT6E/CzH+YwnU3T6Y76uBQIKg3+gIGTvPduqyvQwQQ5FxKDuPBw==",
|
|
1783
|
-
"signed_at": "2026-05-
|
|
1783
|
+
"signed_at": "2026-05-27T15:13:33.865Z"
|
|
1784
1784
|
},
|
|
1785
1785
|
{
|
|
1786
1786
|
"name": "api-security",
|
|
@@ -1849,7 +1849,7 @@
|
|
|
1849
1849
|
"d3fend_refs": [],
|
|
1850
1850
|
"last_threat_review": "2026-05-18",
|
|
1851
1851
|
"signature": "BmCRCestWqr55+fCynEhtAl5NWLT+xLTkpwS0Icp3SaoZOw/ce3Y6TtqjHRSKn4CBJq7YDiLRWxmhO3MStvOAA==",
|
|
1852
|
-
"signed_at": "2026-05-
|
|
1852
|
+
"signed_at": "2026-05-27T15:13:33.866Z",
|
|
1853
1853
|
"forward_watch": [
|
|
1854
1854
|
"NGINX Rift CVE-2026-42945 (disclosed 2026-05-13, source depthfirst) — KEV-watch predicted CISA KEV listing by 2026-05-29; track for active-exploitation confirmation and patch advisory affecting API gateway / reverse-proxy deployments",
|
|
1855
1855
|
"Pwn2Own Berlin 2026 (disclosed 2026-05-14, embargo ends 2026-08-12) — LiteLLM 3-bug SSRF + Code Injection chain by k3vg3n; LLM-proxy API surface; track upstream patch and CVE assignments",
|
|
@@ -1935,7 +1935,7 @@
|
|
|
1935
1935
|
"CISA KEV additions for cloud-control-plane CVEs (IMDSv1 abuses, federation token mishandling, cross-tenant boundary failures); CISA Cybersecurity Advisories for cross-cloud advisories"
|
|
1936
1936
|
],
|
|
1937
1937
|
"signature": "/DV3pmZwrRySrk1OCbyI+0BQESacjupJfUX3eC2NGtXuYOBro0vndIP+z27heFxumnjU3a9sfla7/U9X+pqnDw==",
|
|
1938
|
-
"signed_at": "2026-05-
|
|
1938
|
+
"signed_at": "2026-05-27T15:13:33.866Z"
|
|
1939
1939
|
},
|
|
1940
1940
|
{
|
|
1941
1941
|
"name": "container-runtime-security",
|
|
@@ -1997,7 +1997,7 @@
|
|
|
1997
1997
|
"d3fend_refs": [],
|
|
1998
1998
|
"last_threat_review": "2026-05-15",
|
|
1999
1999
|
"signature": "E2UGSf9ATyYgzBr8uM/0ubOUmDqo1jVA7f9mVxv6LHfWGCNuQNXDyuNou9VAmUCeeXEeUYIi3AFjXkJqpOkxDA==",
|
|
2000
|
-
"signed_at": "2026-05-
|
|
2000
|
+
"signed_at": "2026-05-27T15:13:33.867Z",
|
|
2001
2001
|
"forward_watch": [
|
|
2002
2002
|
"Pwn2Own Berlin 2026 (disclosed 2026-05-14, embargo ends 2026-08-12) — NVIDIA Container Toolkit container escape ($50K award) by chompie / IBM X-Force XOR; high-severity container/hypervisor boundary break; track patch and KEV add post-embargo"
|
|
2003
2003
|
]
|
|
@@ -2071,7 +2071,7 @@
|
|
|
2071
2071
|
"MITRE ATLAS v5.6.0 (released May 2026) shipped the AML.T0010 sub-technique expansion this forecast tracked plus new techniques (\"Publish Poisoned AI Agent Tool\", \"Escape to Host\"); inventory now 16 tactics, 84 techniques, 56 sub-techniques. Forward watch: subsequent ATLAS minor and major releases — track next-cadence updates to agentic-AI TTPs and MLOps-pipeline-specific techniques"
|
|
2072
2072
|
],
|
|
2073
2073
|
"signature": "IL+DlRCDJN/p08iiJCFkasKcoyjcB0uWrJ6ORLjQcS1HrUa5Xt62QxVjYPHzaevlm5y36ZdmfESqsZJmzK3lCg==",
|
|
2074
|
-
"signed_at": "2026-05-
|
|
2074
|
+
"signed_at": "2026-05-27T15:13:33.867Z"
|
|
2075
2075
|
},
|
|
2076
2076
|
{
|
|
2077
2077
|
"name": "incident-response-playbook",
|
|
@@ -2133,7 +2133,7 @@
|
|
|
2133
2133
|
"NYDFS 23 NYCRR 500.17 amendments tightening ransom-payment 24h disclosure operationalization"
|
|
2134
2134
|
],
|
|
2135
2135
|
"signature": "MmjLjlmOMLjhJJ4ZfR8MYlHam+ZB+eSqfh6Nv+DecaG4O5zeo9DBP/iL3cbyDVZxmhnhivgJild2ccYeWTeZAg==",
|
|
2136
|
-
"signed_at": "2026-05-
|
|
2136
|
+
"signed_at": "2026-05-27T15:13:33.867Z"
|
|
2137
2137
|
},
|
|
2138
2138
|
{
|
|
2139
2139
|
"name": "ransomware-response",
|
|
@@ -2213,7 +2213,7 @@
|
|
|
2213
2213
|
],
|
|
2214
2214
|
"last_threat_review": "2026-05-22",
|
|
2215
2215
|
"signature": "ssueL03g9fWlhXpTe+IiY5l7RqQkunN4DTN5QETKE+VOX+qggdjAR8PONxk77ol4xWYmHrM/VcH8CNtXUEvgBA==",
|
|
2216
|
-
"signed_at": "2026-05-
|
|
2216
|
+
"signed_at": "2026-05-27T15:13:33.868Z"
|
|
2217
2217
|
},
|
|
2218
2218
|
{
|
|
2219
2219
|
"name": "email-security-anti-phishing",
|
|
@@ -2266,7 +2266,7 @@
|
|
|
2266
2266
|
"d3fend_refs": [],
|
|
2267
2267
|
"last_threat_review": "2026-05-18",
|
|
2268
2268
|
"signature": "rK+WnuS+9tqEABmwc0jO/PEmxcLjG1/tmUb897HsClQeKzf+TQOlwBE+OsbtuKxpjYNwur62Xxs3TxObkwm8Cw==",
|
|
2269
|
-
"signed_at": "2026-05-
|
|
2269
|
+
"signed_at": "2026-05-27T15:13:33.868Z"
|
|
2270
2270
|
},
|
|
2271
2271
|
{
|
|
2272
2272
|
"name": "age-gates-child-safety",
|
|
@@ -2334,7 +2334,7 @@
|
|
|
2334
2334
|
"US state adult-site age-verification laws — 19+ states by mid-2026 (TX HB 18 upheld by SCOTUS June 2025 in Free Speech Coalition v. Paxton); track ongoing challenges in remaining states"
|
|
2335
2335
|
],
|
|
2336
2336
|
"signature": "Rgho5TOFUL1txOzcVR0kASCNdovSU4yt99JlGilJlJRyg0A+BdeeQYrZrhPF6Vx2reUAVG0BeHfcZtSbi+cwCg==",
|
|
2337
|
-
"signed_at": "2026-05-
|
|
2337
|
+
"signed_at": "2026-05-27T15:13:33.869Z"
|
|
2338
2338
|
},
|
|
2339
2339
|
{
|
|
2340
2340
|
"name": "cloud-iam-incident",
|
|
@@ -2414,7 +2414,7 @@
|
|
|
2414
2414
|
],
|
|
2415
2415
|
"last_threat_review": "2026-05-15",
|
|
2416
2416
|
"signature": "e/kij7GtKaytROyIj7V5RH+FC9WtmVFzrmG2kIlNDNn29ep/CRNlIQKwXLpzo/81AIf634pmdr1qy/+vwIuUDA==",
|
|
2417
|
-
"signed_at": "2026-05-
|
|
2417
|
+
"signed_at": "2026-05-27T15:13:33.869Z",
|
|
2418
2418
|
"forward_watch": [
|
|
2419
2419
|
"AWS IAM Identity Center session-policy refresh and step-up-on-admin enforcement (anticipated 2026-H2 release)",
|
|
2420
2420
|
"GCP Workload Identity Federation principal-set attribute mapping tightening (post-2026 Q3 Federation hardening guide)",
|
|
@@ -2508,7 +2508,7 @@
|
|
|
2508
2508
|
],
|
|
2509
2509
|
"last_threat_review": "2026-05-15",
|
|
2510
2510
|
"signature": "ew9Kglc9fAZzbn0ZIfGP7WSK/j4eV2VhSvpy+s5bEfNEVYIMa2kZjnGBapgUsyGDLes9H9K2ovjQyX17+GKiBw==",
|
|
2511
|
-
"signed_at": "2026-05-
|
|
2511
|
+
"signed_at": "2026-05-27T15:13:33.869Z",
|
|
2512
2512
|
"forward_watch": [
|
|
2513
2513
|
"Entra ID conditional access evolution post-Midnight Blizzard — Microsoft's 2025-2026 commitments on legacy-tenant MFA enforcement and OAuth-app consent gating",
|
|
2514
2514
|
"Okta IPSIE (Interoperability Profile for Secure Identity in the Enterprise) OpenID Foundation working-group output and adoption timeline",
|
|
@@ -2526,6 +2526,6 @@
|
|
|
2526
2526
|
],
|
|
2527
2527
|
"manifest_signature": {
|
|
2528
2528
|
"algorithm": "Ed25519",
|
|
2529
|
-
"signature_base64": "
|
|
2529
|
+
"signature_base64": "SrNc6vocUdcYk7atUeRrn4NtpQvZiV6zV+ji5u98RrvyFXyrYC5WUuNb+vLDw7g2Et+DXoVhrEFYdLBBdu7cAQ=="
|
|
2530
2530
|
}
|
|
2531
2531
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blamejs/exceptd-skills",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.4",
|
|
4
4
|
"description": "AI security skills grounded in mid-2026 threat reality, not stale framework documentation. 42 skills, 11 catalogs (406 CVEs / 171 CWEs / 805 ATT&CK + ICS / 170 ATLAS / 468 D3FEND / 8888 RFCs), 35 jurisdictions, 10-class catalog gap detector + budget gate, real XML parser + canonical-form diff + content-pattern regression detection, Ed25519-signed.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai-security",
|
package/sbom.cdx.json
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"bomFormat": "CycloneDX",
|
|
3
3
|
"specVersion": "1.6",
|
|
4
|
-
"serialNumber": "urn:uuid:
|
|
4
|
+
"serialNumber": "urn:uuid:a194c2f0-f0ce-431b-8f4a-b65b77158b35",
|
|
5
5
|
"version": 1,
|
|
6
6
|
"metadata": {
|
|
7
|
-
"timestamp": "
|
|
7
|
+
"timestamp": "2111-11-27T22:30:08.000Z",
|
|
8
8
|
"tools": [
|
|
9
9
|
{
|
|
10
10
|
"vendor": "blamejs",
|
|
11
11
|
"name": "scripts/refresh-sbom.js",
|
|
12
|
-
"version": "0.14.
|
|
12
|
+
"version": "0.14.4"
|
|
13
13
|
}
|
|
14
14
|
],
|
|
15
15
|
"component": {
|
|
16
|
-
"bom-ref": "pkg:npm/@blamejs/exceptd-skills@0.14.
|
|
16
|
+
"bom-ref": "pkg:npm/@blamejs/exceptd-skills@0.14.4",
|
|
17
17
|
"type": "application",
|
|
18
18
|
"name": "@blamejs/exceptd-skills",
|
|
19
|
-
"version": "0.14.
|
|
19
|
+
"version": "0.14.4",
|
|
20
20
|
"description": "AI security skills grounded in mid-2026 threat reality, not stale framework documentation. 42 skills, 11 catalogs (406 CVEs / 171 CWEs / 805 ATT&CK + ICS / 170 ATLAS / 468 D3FEND / 8888 RFCs), 35 jurisdictions, 10-class catalog gap detector + budget gate, real XML parser + canonical-form diff + content-pattern regression detection, Ed25519-signed.",
|
|
21
21
|
"licenses": [
|
|
22
22
|
{
|
|
@@ -25,17 +25,17 @@
|
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
],
|
|
28
|
-
"purl": "pkg:npm/%40blamejs/exceptd-skills@0.14.
|
|
28
|
+
"purl": "pkg:npm/%40blamejs/exceptd-skills@0.14.4",
|
|
29
29
|
"hashes": [
|
|
30
30
|
{
|
|
31
31
|
"alg": "SHA-256",
|
|
32
|
-
"content": "
|
|
32
|
+
"content": "40d489d13880ac08e06950ab7861942877ff8a58252776f6b09afc7673fe0bb5"
|
|
33
33
|
}
|
|
34
34
|
],
|
|
35
35
|
"externalReferences": [
|
|
36
36
|
{
|
|
37
37
|
"type": "distribution",
|
|
38
|
-
"url": "https://www.npmjs.com/package/@blamejs/exceptd-skills/v/0.14.
|
|
38
|
+
"url": "https://www.npmjs.com/package/@blamejs/exceptd-skills/v/0.14.4"
|
|
39
39
|
},
|
|
40
40
|
{
|
|
41
41
|
"type": "vcs",
|
|
@@ -116,11 +116,11 @@
|
|
|
116
116
|
"hashes": [
|
|
117
117
|
{
|
|
118
118
|
"alg": "SHA-256",
|
|
119
|
-
"content": "
|
|
119
|
+
"content": "514cf558d8176835b4436899c98c9563e5a5ee0bafab302ee7da33c962d94b4b"
|
|
120
120
|
},
|
|
121
121
|
{
|
|
122
122
|
"alg": "SHA3-512",
|
|
123
|
-
"content": "
|
|
123
|
+
"content": "cfb90c6522c178454f8f48f84d5bd0dc7574c0f5625885ff714c7d8d8e62539e1c0c7fc71155e7b8b2ab49e7f16469421bba3f44e7a72c6794f2c76d706d3677"
|
|
124
124
|
}
|
|
125
125
|
]
|
|
126
126
|
},
|
|
@@ -281,11 +281,11 @@
|
|
|
281
281
|
"hashes": [
|
|
282
282
|
{
|
|
283
283
|
"alg": "SHA-256",
|
|
284
|
-
"content": "
|
|
284
|
+
"content": "ef6fce318d6de37570aab08acee1836972f2cb019ff366c7cd21789dbcee8095"
|
|
285
285
|
},
|
|
286
286
|
{
|
|
287
287
|
"alg": "SHA3-512",
|
|
288
|
-
"content": "
|
|
288
|
+
"content": "6005808589c04f5045481044a4b607fc764c7ff34334ff46a108a4c4e10b9bddfbd6b60b6f163c979f3ea704ef7cf3f53bbfcef1c0bad3d884cf8c3699c097f2"
|
|
289
289
|
}
|
|
290
290
|
]
|
|
291
291
|
},
|
|
@@ -881,11 +881,11 @@
|
|
|
881
881
|
"hashes": [
|
|
882
882
|
{
|
|
883
883
|
"alg": "SHA-256",
|
|
884
|
-
"content": "
|
|
884
|
+
"content": "7fc089baec25ccb5a228db85572ccd3dc5029832792edfc624c6ed2c24d8289b"
|
|
885
885
|
},
|
|
886
886
|
{
|
|
887
887
|
"alg": "SHA3-512",
|
|
888
|
-
"content": "
|
|
888
|
+
"content": "2c1f27474cded1a4b063c9e261fc0c0e633d4f4478da43d5ad74f1308c1a28366cd87a0b05e096cd485f00037cc5abcb1250160622363d28c74aea3e3777b449"
|
|
889
889
|
}
|
|
890
890
|
]
|
|
891
891
|
},
|
|
@@ -941,11 +941,11 @@
|
|
|
941
941
|
"hashes": [
|
|
942
942
|
{
|
|
943
943
|
"alg": "SHA-256",
|
|
944
|
-
"content": "
|
|
944
|
+
"content": "b31872b10eb815a53d7753f294dc8409de58b847c296ca2d91c5be4548a3e696"
|
|
945
945
|
},
|
|
946
946
|
{
|
|
947
947
|
"alg": "SHA3-512",
|
|
948
|
-
"content": "
|
|
948
|
+
"content": "46aa9be2df845e10938e5f76a120d42541e3726d92b682504a70dde1d7f6c046606e33ff44bde3ca708d30065d85484d5ab3baa32e93340e6d3f8ac7ac878a1f"
|
|
949
949
|
}
|
|
950
950
|
]
|
|
951
951
|
},
|
|
@@ -1151,11 +1151,11 @@
|
|
|
1151
1151
|
"hashes": [
|
|
1152
1152
|
{
|
|
1153
1153
|
"alg": "SHA-256",
|
|
1154
|
-
"content": "
|
|
1154
|
+
"content": "bacd5bb4d26af7a025bc0e9cc8139551618bd352927acbaeaa5855ae1ea86849"
|
|
1155
1155
|
},
|
|
1156
1156
|
{
|
|
1157
1157
|
"alg": "SHA3-512",
|
|
1158
|
-
"content": "
|
|
1158
|
+
"content": "a5c7a22d905441b487b8cdaaff5744a7f008c00b8629b513ebd059eee3e07c6fccb7ac7c62bad3055a1204a92b493db38ef5fbc033da0fe2eca52a6d4b5d6585"
|
|
1159
1159
|
}
|
|
1160
1160
|
]
|
|
1161
1161
|
},
|
|
@@ -1376,11 +1376,11 @@
|
|
|
1376
1376
|
"hashes": [
|
|
1377
1377
|
{
|
|
1378
1378
|
"alg": "SHA-256",
|
|
1379
|
-
"content": "
|
|
1379
|
+
"content": "96ffa31aaa2239a597eaea9f789777cb33241d13cbd87a295573740912cb1e1a"
|
|
1380
1380
|
},
|
|
1381
1381
|
{
|
|
1382
1382
|
"alg": "SHA3-512",
|
|
1383
|
-
"content": "
|
|
1383
|
+
"content": "dea608cddf51207d631dd3be0700ad313860750ce7f94ae3cd177b578999e499196f7a26ebc11be2524505901ab94efbda6a49ea78f8830fec9404e1bf75c67a"
|
|
1384
1384
|
}
|
|
1385
1385
|
]
|
|
1386
1386
|
},
|
|
@@ -1751,11 +1751,11 @@
|
|
|
1751
1751
|
"hashes": [
|
|
1752
1752
|
{
|
|
1753
1753
|
"alg": "SHA-256",
|
|
1754
|
-
"content": "
|
|
1754
|
+
"content": "e9db8b2640844856c72c5e120fe7973d10d3e5d237c38ab38c26bf4c5eaf7af8"
|
|
1755
1755
|
},
|
|
1756
1756
|
{
|
|
1757
1757
|
"alg": "SHA3-512",
|
|
1758
|
-
"content": "
|
|
1758
|
+
"content": "5c7cf5ee1a9226a81b7abd1a5fc18e536da5996c0d4031846793de843badcb92118901155536aba87cf508456b19a8f1f51992da30f590345faaf8dfb73f112e"
|
|
1759
1759
|
}
|
|
1760
1760
|
]
|
|
1761
1761
|
},
|