@blamejs/exceptd-skills 0.14.0 → 0.14.2
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/AGENTS.md +3 -1
- package/CHANGELOG.md +16 -0
- package/README.md +31 -0
- package/bin/exceptd.js +31 -1
- package/data/_indexes/_meta.json +3 -3
- package/data/_indexes/activity-feed.json +8 -8
- package/data/_indexes/catalog-summaries.json +2 -2
- package/data/_indexes/frequency.json +1413 -1
- package/data/playbooks/citation-hygiene.json +1 -1
- package/data/rfc-references.json +55757 -146
- package/lib/citation-resolve.js +226 -0
- package/lib/collectors/citation-hygiene.js +81 -1
- package/lib/cve-cli.js +51 -0
- package/lib/flag-suggest.js +1 -1
- package/lib/rfc-cli.js +68 -0
- package/lib/schemas/cve-catalog.schema.json +13 -0
- package/lib/source-ghsa.js +3 -0
- package/lib/source-osv.js +4 -0
- package/lib/validate-package.js +7 -2
- package/manifest.json +44 -44
- package/package.json +2 -2
- package/sbom.cdx.json +84 -39
- package/scripts/refresh-upstream-catalogs.js +12 -2
- package/sources/validators/cve-validator.js +46 -1
package/AGENTS.md
CHANGED
|
@@ -8,7 +8,7 @@ Also read [CONTEXT.md](CONTEXT.md) for a complete orientation to the skill syste
|
|
|
8
8
|
|
|
9
9
|
Each rule below carries a **Forcing function** annotation declaring whether it is mechanically enforced by a script in the predeploy / CI gate sequence, or whether it is policy-only (reviewer trust). Policy-only rules are not weaker — they are auditable through reviewer judgment, not via a script — but operators should know which class a given rule sits in.
|
|
10
10
|
|
|
11
|
-
1. **No stale threat intel** — Every CVE reference must include: CVSS score, KEV status, PoC availability, AI-discovery flag, active exploitation status, and patch/live-patch availability. No theoretical vulnerabilities without real-world grounding.
|
|
11
|
+
1. **No stale threat intel** — Every CVE reference must include: CVSS score, KEV status, PoC availability, AI-discovery flag, active exploitation status, and patch/live-patch availability. No theoretical vulnerabilities without real-world grounding. When validating a CVE or RFC citation during security work, the canonical move is `exceptd cve <CVE-ID>` / `exceptd rfc <number>` — it resolves the citation and caches the result, so a multi-agent fan-out resolves each id once rather than re-researching it independently against NVD/the datatracker.
|
|
12
12
|
*Forcing function:* enforced by `lib/validate-cve-catalog.js` (predeploy gate).
|
|
13
13
|
|
|
14
14
|
2. **Framework lag is a first-class concept** — Every skill must explicitly declare which framework controls are insufficient for the threats it covers. Never imply a framework control is adequate when current TTPs bypass it.
|
|
@@ -169,6 +169,8 @@ Cross-cutting playbook `framework` is the natural correlation layer — many pla
|
|
|
169
169
|
| `exceptd attest list` | Inventory `.exceptd/attestations/` — newest first. `--playbook <id>` filters. |
|
|
170
170
|
| `exceptd attest show <sid>` | Print the attestation body. |
|
|
171
171
|
| `exceptd doctor` | Health checks. `--signatures` verifies Ed25519 chains; `--cves` / `--rfcs` check catalog currency; `--fix` repairs recoverable state; `--ai-config` audits AI-assistant config-file permissions (`~/.claude`, `~/.cursor`, `~/.codeium`, `~/.aider`, `~/.continue`) and flags sensitive files not at mode `0o600` on POSIX (NEW-CTRL-050). |
|
|
172
|
+
| `exceptd cve <CVE-ID>` | Resolve a single CVE citation — returns status (`published`/`rejected`/`disputed`/`fabricated`/`nonexistent`/`unknown`) plus cvss/kev/product. Resolution order: curated catalog (offline) → resolved cache (`.cache/upstream/resolved/`, 7-day TTL) → one NVD lookup, then cached. `--air-gap`/`--no-network`/`EXCEPTD_AIR_GAP=1` force offline-only (returns `unknown` with a reason). Exit 2 when the citation won't stand up (rejected/fabricated/nonexistent/withdrawn). |
|
|
173
|
+
| `exceptd rfc <number>` | Resolve an RFC number → title + status from the local index (whole current series, offline). `--check "<title>"` reports `title_match` true/false, exit 2 on mismatch (catches e.g. RFC 9404 cited as the Sieve spec — it's JMAP Blob Management). Not-found numbers are likely obsoleted/historic or nonexistent; with network it disambiguates via the datatracker. |
|
|
172
174
|
| `exceptd lint` | Skill format lint — frontmatter completeness, required body sections, signature presence. |
|
|
173
175
|
| `exceptd refresh --check-advisories` | Poll 15 primary-source advisory feeds — 8 advisory/coordinated-disclosure venues (Qualys TRU, Red Hat RHSA, Ubuntu USN, ZDI, kernel.org commits, oss-security mailing list, JFrog SecOps, CISA current advisories), 4 vendor security research blogs (Microsoft Security Blog, Sysdig, Trail of Bits, Embrace the Red — added in v0.13.14 after DirtyDecrypt fell through the advisory-only set), and 3 sources added in v0.13.17 (BleepingComputer security, The Hacker News, Nightmare-Eclipse GitLab activity-feed tracker, migrated from GitHub after the account was removed — closes the researcher-drop class anchored by MiniPlasma / YellowKey / GreenPlasma / UnDefend, NEW-CTRL-073). Pairs with `lib/cve-regression-watcher.js` (NEW-CTRL-074) which cross-checks poller diffs for historical-CVE references that may indicate silent vendor regression — the class anchored by MiniPlasma re-breaking CVE-2020-17103. Report-only; emits structured `diffs[]` without mutating the catalog. Route promising IDs through `refresh --advisory <CVE-ID> --apply` to enrich. |
|
|
174
176
|
| `exceptd watchlist` | Default: aggregate every skill's `forward_watch` entries. `--by-skill` inverts grouping. `--alerts` switches to CVE-catalog pattern alerts (5 patterns: `kernel_lpe_with_poc`, `supply_chain_family`, `ai_discovered_kev`, `active_exploitation_unpatched`, `recent_poc_no_kev_yet`); sorts critical-first, then by RWEP. `--org-scan --org <login>` probes GitHub Search for repos matching threat-actor naming patterns ("A Gift From TeamPCP", "Shai-Hulud", "TeamPCP"); custom patterns via repeatable `--pattern <s>`; set `GITHUB_TOKEN` for private-repo + rate-limit headroom (NEW-CTRL-052). |
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.14.2 — 2026-05-27
|
|
4
|
+
|
|
5
|
+
`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).
|
|
6
|
+
|
|
7
|
+
The RFC index now includes obsoleted and historic RFCs (8888 entries, up from 7476). `exceptd rfc <number>` resolves a superseded RFC entirely offline — RFC 2616, for example, returns "Hypertext Transfer Protocol -- HTTP/1.1, obsoleted by RFC 7230–7235" — so confirming whether a cited RFC is still current no longer requires an IETF datatracker lookup.
|
|
8
|
+
|
|
9
|
+
## 0.14.1 — 2026-05-27
|
|
10
|
+
|
|
11
|
+
Two citation resolvers — `exceptd cve <id>` and `exceptd rfc <number>` — answer "is this CVE/RFC citation valid?" so an agent gets the answer from exceptd instead of researching each identifier against NVD or the IETF datatracker by hand. A fan-out of agents auditing a codebase previously re-researched the same citations independently; these resolvers do it once and cache the result for the rest.
|
|
12
|
+
|
|
13
|
+
`exceptd cve <id>` returns a structured status — published, rejected, disputed, fabricated, nonexistent, or unknown — alongside CVSS / KEV / product. It resolves offline-first: the curated catalog, then a resolved cache, then a single NVD lookup whose result is cached under `.cache/upstream/resolved/` (7-day TTL). The first lookup of an uncatalogued identifier serves every later agent and every offline run. NVD's authoritative `vulnStatus` and `cveTags` are now read — they were previously fetched and discarded — so a rejected or disputed CVE is flagged rather than treated as valid (the class that lets a withdrawn identifier sit cited in a codebase unnoticed). A non-canonical identifier such as `CVE-2024-XXXX` is caught as fabricated with no network call. Network is opt-out: `--air-gap`, `--no-network`, or `EXCEPTD_AIR_GAP=1` keep resolution offline-only and return `unknown` with a reason. Exit code 2 when a citation will not stand up.
|
|
14
|
+
|
|
15
|
+
`exceptd rfc <number>` resolves an RFC number to its title and status from the local index — the whole current RFC series, fully offline. `--check "<claimed title>"` reports whether a claimed title matches the real one (exit code 2 on mismatch), catching an RFC number cited under the wrong specification.
|
|
16
|
+
|
|
17
|
+
Catalog entries may now carry a structured `status` field (`published` / `rejected` / `disputed` / `withdrawn` / `reserved`), sourced from NVD `vulnStatus` / `cveTags` or OSV / GHSA `withdrawn`, replacing the prior free-text heuristic. The `citation-hygiene` playbook now routes its "needs external verification" guidance through `exceptd cve` / `exceptd rfc`.
|
|
18
|
+
|
|
3
19
|
## 0.14.0 — 2026-05-26
|
|
4
20
|
|
|
5
21
|
New playbook — `citation-hygiene`. Validates a codebase's own cited security references: it scans source, comments, and docs for CVE and RFC citations and flags fabricated CVE IDs (the non-numeric `CVE-2024-XXXX` form), catalog-rejected/disputed CVEs, and RFC number-vs-title mismatches. Well-formed CVE IDs absent from the curated catalog are routed to an inconclusive "needs external verification" result rather than a false clear or a false fabrication flag. Ships with a companion collector — `exceptd collect citation-hygiene | exceptd run citation-hygiene --evidence -`. The catalog now holds 24 playbooks.
|
package/README.md
CHANGED
|
@@ -349,6 +349,35 @@ exceptd lint <pb> <evidence> Pre-flight check submission shape vs
|
|
|
349
349
|
playbook (preconditions / artifacts /
|
|
350
350
|
indicators) without executing phases 4-7.
|
|
351
351
|
|
|
352
|
+
exceptd cve <CVE-ID> Resolve one CVE citation → status
|
|
353
|
+
(published / rejected / disputed /
|
|
354
|
+
fabricated / nonexistent / unknown) plus
|
|
355
|
+
cvss / kev / product. Order: curated
|
|
356
|
+
catalog (offline) → resolved cache
|
|
357
|
+
(7-day TTL, warmed by a prior lookup) →
|
|
358
|
+
one NVD lookup, then cached. Lets a
|
|
359
|
+
fan-out of agents share one answer
|
|
360
|
+
instead of each researching the same id.
|
|
361
|
+
--air-gap | --no-network Offline-only (also EXCEPTD_AIR_GAP=1).
|
|
362
|
+
Returns unknown + a reason when the id
|
|
363
|
+
isn't in catalog/cache.
|
|
364
|
+
--json | --pretty Machine output.
|
|
365
|
+
Exit 2 when the citation won't stand up
|
|
366
|
+
(rejected / fabricated / nonexistent /
|
|
367
|
+
withdrawn).
|
|
368
|
+
|
|
369
|
+
exceptd rfc <number> Resolve an RFC number → title + status
|
|
370
|
+
from the local index (whole current
|
|
371
|
+
series, fully offline).
|
|
372
|
+
--check "<title>" Report title_match true/false; exit 2 on
|
|
373
|
+
mismatch (e.g. RFC 9404 cited as the
|
|
374
|
+
Sieve spec — it's JMAP Blob Management).
|
|
375
|
+
--air-gap Offline-only. Not-found numbers are
|
|
376
|
+
likely obsoleted/historic or nonexistent;
|
|
377
|
+
with network it disambiguates via the
|
|
378
|
+
datatracker.
|
|
379
|
+
--json | --pretty Machine output.
|
|
380
|
+
|
|
352
381
|
exceptd refresh Refresh upstream catalogs + indexes.
|
|
353
382
|
Replaces prefetch + refresh + build-indexes.
|
|
354
383
|
--apply Write diffs back + rebuild indexes.
|
|
@@ -549,6 +578,8 @@ The `agents/` directory ships markdown role cards documenting authoring conventi
|
|
|
549
578
|
|
|
550
579
|
All skills pull from `data/`. Cross-validated against canonical upstream sources via `exceptd refresh` / `exceptd doctor --cves` / `exceptd doctor --rfcs`.
|
|
551
580
|
|
|
581
|
+
To resolve a single citation rather than refresh the whole catalog, `exceptd cve <CVE-ID>` and `exceptd rfc <number>` return a status verdict for one id (catalog → resolved cache → one NVD / datatracker lookup, offline-capable). The lookup caches, so a fan-out of agents shares the answer instead of each independently re-researching the same citation.
|
|
582
|
+
|
|
552
583
|
- `cve-catalog.json` — CVE metadata with RWEP scores, CISA KEV status, PoC availability, live-patch info
|
|
553
584
|
- `atlas-ttps.json` — MITRE ATLAS v5.6.0 TTPs with gap flags and exploitation examples. Each TTP now carries a `cve_refs[]` back-edge — operators reading an ATLAS entry see the catalogued CVEs that cite it without grepping `cve-catalog.json`. The same back-edge is populated on `attack-techniques.json`, and each playbook carries a `_meta.fed_by[]` reverse field naming the upstream playbooks that chain into it.
|
|
554
585
|
- `framework-control-gaps.json` — Per-framework, per-control: what it was designed for vs. what it misses
|
package/bin/exceptd.js
CHANGED
|
@@ -154,6 +154,10 @@ const COMMANDS = {
|
|
|
154
154
|
watch: () => path.join(PKG_ROOT, "orchestrator", "index.js"),
|
|
155
155
|
"framework-gap": () => path.join(PKG_ROOT, "orchestrator", "index.js"),
|
|
156
156
|
"framework-gap-analysis": () => path.join(PKG_ROOT, "orchestrator", "index.js"),
|
|
157
|
+
// Citation resolvers — answer "is this CVE/RFC citation valid?" offline-first
|
|
158
|
+
// (catalog/index -> resolved cache -> opt-in single network lookup, cached).
|
|
159
|
+
cve: () => path.join(PKG_ROOT, "lib", "cve-cli.js"),
|
|
160
|
+
rfc: () => path.join(PKG_ROOT, "lib", "rfc-cli.js"),
|
|
157
161
|
// Seven-phase playbook verbs — handled in-process via lib/playbook-runner.js.
|
|
158
162
|
plan: null,
|
|
159
163
|
govern: null,
|
|
@@ -738,6 +742,8 @@ function main() {
|
|
|
738
742
|
skill: "exceptd skill <name> Show the full context document for one skill.",
|
|
739
743
|
"framework-gap": "exceptd framework-gap <framework> <cve-or-scenario> One-framework gap analysis.",
|
|
740
744
|
"framework-gap-analysis": "exceptd framework-gap <framework> <cve-or-scenario> One-framework gap analysis.",
|
|
745
|
+
cve: "exceptd cve <CVE-ID> [--json] [--air-gap|--no-network] Resolve a CVE: published/rejected/disputed/fabricated/nonexistent (catalog -> cache -> NVD).",
|
|
746
|
+
rfc: "exceptd rfc <number> [--check \"<title>\"] [--json] [--air-gap] Resolve an RFC number -> title + status (local index, offline).",
|
|
741
747
|
};
|
|
742
748
|
if ((effectiveRest.includes("--help") || effectiveRest.includes("-h")) && SPAWN_HELP_USAGE[effectiveCmd]) {
|
|
743
749
|
process.stdout.write(SPAWN_HELP_USAGE[effectiveCmd] + "\n Full reference: exceptd help\n");
|
|
@@ -2331,7 +2337,7 @@ Flags (selected — see \`exceptd run --help\` for the full list):
|
|
|
2331
2337
|
* its evidence JSON before going through phases 4-7. Returns a categorized
|
|
2332
2338
|
* list: ok / missing_required / unknown_keys / type_mismatch / suggestions.
|
|
2333
2339
|
*/
|
|
2334
|
-
function cmdCollect(runner, args, runOpts, pretty) {
|
|
2340
|
+
async function cmdCollect(runner, args, runOpts, pretty) {
|
|
2335
2341
|
const playbookId = args._[0];
|
|
2336
2342
|
if (!playbookId) {
|
|
2337
2343
|
return emitError(
|
|
@@ -2433,6 +2439,30 @@ function cmdCollect(runner, args, runOpts, pretty) {
|
|
|
2433
2439
|
try { pbMetaAirGap = !!(runner.loadPlaybook(playbookId)?._meta?.air_gap_mode); }
|
|
2434
2440
|
catch { /* playbook load shouldn't fail here — collector exists — but be defensive */ }
|
|
2435
2441
|
const collectAirGap = !!(runOpts.airGap || process.env.EXCEPTD_AIR_GAP === "1" || pbMetaAirGap);
|
|
2442
|
+
|
|
2443
|
+
// --resolve: resolve the citations the offline catalog couldn't confirm,
|
|
2444
|
+
// flipping their parked signals instead of leaving them inconclusive for the
|
|
2445
|
+
// operator to research. Opt-in, collector-specific (only citation-hygiene
|
|
2446
|
+
// exposes applyResolution). Honors the collect air-gap disposition.
|
|
2447
|
+
if (args.resolve) {
|
|
2448
|
+
if (typeof mod.applyResolution !== "function") {
|
|
2449
|
+
return emitError(
|
|
2450
|
+
`collect: --resolve is not supported by the "${playbookId}" collector (no resolution step).`,
|
|
2451
|
+
{ verb: "collect", playbook_id: playbookId },
|
|
2452
|
+
pretty,
|
|
2453
|
+
);
|
|
2454
|
+
}
|
|
2455
|
+
try {
|
|
2456
|
+
submission = await mod.applyResolution(submission, { airGap: collectAirGap });
|
|
2457
|
+
} catch (e) {
|
|
2458
|
+
return emitError(
|
|
2459
|
+
`collect: --resolve failed for "${playbookId}": ${e.message}`,
|
|
2460
|
+
{ verb: "collect", playbook_id: playbookId, exit_code: 2 },
|
|
2461
|
+
pretty,
|
|
2462
|
+
);
|
|
2463
|
+
}
|
|
2464
|
+
}
|
|
2465
|
+
|
|
2436
2466
|
// Spread `submission` first, then explicit fields, so a submission key
|
|
2437
2467
|
// named `air_gap_mode` (currently always undefined but defensive against
|
|
2438
2468
|
// future collector contracts) can't clobber the envelope marker.
|
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-27T13:10:02.958Z",
|
|
4
4
|
"generator": "scripts/build-indexes.js",
|
|
5
5
|
"source_count": 54,
|
|
6
6
|
"source_hashes": {
|
|
7
|
-
"manifest.json": "
|
|
7
|
+
"manifest.json": "e3c6dc62608d025f8f60ae232bdd54da7433eb8c1d29bdef81deee51122dae4c",
|
|
8
8
|
"data/atlas-ttps.json": "d24bc02859d40ccf1615db75cca68c077585904e41e0d8f6de448121e9b1abb0",
|
|
9
9
|
"data/attack-techniques.json": "fa193f0d2d248176a8beddb641e9fe56ba4faa9e15dc253ff876dbf0c5d58a77",
|
|
10
10
|
"data/cve-catalog.json": "3d451dda7ac0c7d57a4075ae4bafd3148c6184b35dc1bc59d8b81d1f2641e430",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"data/exploit-availability.json": "ec2656f0d9a893610e27b43eb6035fe9b18e057c9f6dfaac7e7d4959bbcbb795",
|
|
15
15
|
"data/framework-control-gaps.json": "46094ad9b9584f454daddd28f4c5d27faf0ea0510daafd38fb60bf9d30f6a305",
|
|
16
16
|
"data/global-frameworks.json": "9ba563a85f7f8d6c3c957de64945e20925a89d0ed6ea6fc561cf093811acf558",
|
|
17
|
-
"data/rfc-references.json": "
|
|
17
|
+
"data/rfc-references.json": "b21d03b948c41bc8a854e2f057948ecf844bd8c105848aeb141d1eadf8192c31",
|
|
18
18
|
"data/zeroday-lessons.json": "258252b9bff1fc11b05b76e10b659c1195971884bd44c92af5fefe17a5ca9512",
|
|
19
19
|
"skills/kernel-lpe-triage/skill.md": "08b3e9815ba481c57c80f5fc0ccbf5bb7cbb41f570c235ba6ff9596b8c07354d",
|
|
20
20
|
"skills/ai-attack-surface/skill.md": "c4c1eb22a38ca7a959b5725222bab8fbd4f4044a548a93f3e288e6f698334b72",
|
|
@@ -5,6 +5,14 @@
|
|
|
5
5
|
"event_count": 54
|
|
6
6
|
},
|
|
7
7
|
"events": [
|
|
8
|
+
{
|
|
9
|
+
"date": "2026-05-27",
|
|
10
|
+
"type": "catalog_update",
|
|
11
|
+
"artifact": "data/rfc-references.json",
|
|
12
|
+
"path": "data/rfc-references.json",
|
|
13
|
+
"schema_version": "1.0.0",
|
|
14
|
+
"entry_count": 8888
|
|
15
|
+
},
|
|
8
16
|
{
|
|
9
17
|
"date": "2026-05-22",
|
|
10
18
|
"type": "skill_review",
|
|
@@ -100,14 +108,6 @@
|
|
|
100
108
|
"schema_version": "1.0.0",
|
|
101
109
|
"entry_count": 468
|
|
102
110
|
},
|
|
103
|
-
{
|
|
104
|
-
"date": "2026-05-19",
|
|
105
|
-
"type": "catalog_update",
|
|
106
|
-
"artifact": "data/rfc-references.json",
|
|
107
|
-
"path": "data/rfc-references.json",
|
|
108
|
-
"schema_version": "1.0.0",
|
|
109
|
-
"entry_count": 7476
|
|
110
|
-
},
|
|
111
111
|
{
|
|
112
112
|
"date": "2026-05-18",
|
|
113
113
|
"type": "skill_review",
|
|
@@ -207,7 +207,7 @@
|
|
|
207
207
|
"path": "data/rfc-references.json",
|
|
208
208
|
"purpose": "IETF RFCs + active Internet-Drafts cited by skills (TLS, IPsec, PQ crypto migration, HTTP/3, CT). Cross-validated against IETF Datatracker via validate-rfcs.",
|
|
209
209
|
"schema_version": "1.0.0",
|
|
210
|
-
"last_updated": "2026-05-
|
|
210
|
+
"last_updated": "2026-05-27",
|
|
211
211
|
"tlp": "CLEAR",
|
|
212
212
|
"source_confidence_default": "A1",
|
|
213
213
|
"freshness_policy": {
|
|
@@ -216,7 +216,7 @@
|
|
|
216
216
|
"rebuild_after_days": 365,
|
|
217
217
|
"note": "Per-entry last_verified governs decay. Skills depending on this catalog must check entry freshness before high-stakes use."
|
|
218
218
|
},
|
|
219
|
-
"entry_count":
|
|
219
|
+
"entry_count": 8888,
|
|
220
220
|
"sample_keys": [
|
|
221
221
|
"RFC-4301",
|
|
222
222
|
"RFC-4303",
|