@blamejs/exceptd-skills 0.13.125 → 0.14.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/AGENTS.md +2 -2
- package/CHANGELOG.md +24 -0
- package/README.md +12 -9
- package/bin/exceptd.js +142 -35
- package/data/_indexes/_meta.json +9 -9
- package/data/_indexes/activity-feed.json +2 -2
- package/data/_indexes/catalog-summaries.json +2 -2
- package/data/_indexes/chains.json +375 -0
- package/data/atlas-ttps.json +2 -1
- package/data/attack-techniques.json +4 -2
- package/data/cve-catalog.json +104 -0
- package/data/cwe-catalog.json +4 -2
- package/data/framework-control-gaps.json +18 -9
- package/data/playbooks/citation-hygiene.json +820 -0
- package/data/zeroday-lessons.json +50 -0
- package/lib/collectors/cicd-pipeline-compromise.js +10 -1
- package/lib/collectors/citation-hygiene.js +465 -0
- package/lib/collectors/containers.js +12 -7
- package/lib/collectors/crypto-codebase.js +11 -5
- package/lib/collectors/library-author.js +82 -10
- package/lib/collectors/scan-excludes.js +85 -0
- package/lib/collectors/secrets.js +10 -6
- package/lib/flag-suggest.js +2 -2
- package/lib/refresh-external.js +15 -0
- package/manifest.json +44 -44
- package/package.json +1 -1
- package/sbom.cdx.json +91 -46
- package/scripts/check-agents-md-collectors.js +8 -0
package/AGENTS.md
CHANGED
|
@@ -156,7 +156,7 @@ Cross-cutting playbook `framework` is the natural correlation layer — many pla
|
|
|
156
156
|
|
|
157
157
|
| Verb | What it does |
|
|
158
158
|
|---|---|
|
|
159
|
-
| `exceptd brief --all` | Grouped-by-scope summary of all
|
|
159
|
+
| `exceptd brief --all` | Grouped-by-scope summary of all 24 playbooks. `--scope <type>` filters. `--directives` expands directive IDs/titles per playbook. `--flat` for non-grouped. `exceptd plan` was removed in v0.13.0; invoking it returns a structured `ok:false` refusal pointing at this command. |
|
|
160
160
|
| `exceptd brief <pb>` | Phase 2 threat-context briefing — threat context, RWEP thresholds, skill chain, token budget, jurisdiction obligations. |
|
|
161
161
|
| `exceptd run <pb> --evidence <file>` | Phases 5-7 (analyze + validate + close) from agent evidence. Auto-detect cwd when no playbook positional. `--vex <file>` drops CycloneDX/OpenVEX `not_affected` CVEs. `--diff-from-latest` for drift mode. `--force-stale` overrides currency hard-block. |
|
|
162
162
|
| `exceptd ai-run <pb>` | Streaming variant of `run` for AI agents; emits phase-by-phase NDJSON. |
|
|
@@ -372,7 +372,7 @@ This split costs every consumer the same translation work on every invocation. C
|
|
|
372
372
|
exceptd collect secrets | exceptd run secrets --evidence -
|
|
373
373
|
```
|
|
374
374
|
|
|
375
|
-
The collector library is small and grows as playbooks are touched.
|
|
375
|
+
The collector library is small and grows as playbooks are touched. Fourteen reference collectors ship today (`lib/collectors/secrets.js`, `lib/collectors/kernel.js`, `lib/collectors/sbom.js`, `lib/collectors/containers.js`, `lib/collectors/library-author.js`, `lib/collectors/crypto-codebase.js`, `lib/collectors/cred-stores.js`, `lib/collectors/hardening.js`, `lib/collectors/runtime.js`, `lib/collectors/ai-api.js`, `lib/collectors/mcp.js`, `lib/collectors/crypto.js`, `lib/collectors/cicd-pipeline-compromise.js`, `lib/collectors/citation-hygiene.js`); the rest are written when each playbook needs them. Code-scope collectors share `lib/collectors/scan-excludes.js`, which skips dependency/build caches, agent scratch (`.claude`), and linked git worktrees so a tree holding detached worktree copies isn't scanned N times. Until a playbook has a collector, the AI/operator owns evidence collection as before.
|
|
376
376
|
|
|
377
377
|
### Precision target for new `look.artifacts[].source` strings
|
|
378
378
|
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.14.0 — 2026-05-26
|
|
4
|
+
|
|
5
|
+
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.
|
|
6
|
+
|
|
7
|
+
Unknown flags are refused on every verb. Previously only `doctor` rejected an unrecognized flag; every other verb silently ignored it, so a typo like `--max-rweap 70` or `--fromat sarif` looked like it applied a cap or a format when it did nothing. Each verb now exits 1 with the accepted-flag list and a did-you-mean suggestion. A flag that is valid on another verb (e.g. `--csaf-status` on `brief`) still gets its tailored "that flag belongs on a run-class verb" guidance instead of a blanket refusal.
|
|
8
|
+
|
|
9
|
+
`exceptd run --format json` now emits the full run result. It previously discarded the result and printed a short "unknown format" stub with a success exit code. SARIF, CSAF, and OpenVEX bundles are now emitted as spec-conformant documents — the internal `ok` envelope key is no longer prepended, so strict validators (GitHub code-scanning SARIF upload, CSAF trusted-provider checks) accept the output. Passing several `--format` values prints a note to stderr pointing at `bundles_by_format` rather than silently dropping all but the first.
|
|
10
|
+
|
|
11
|
+
`exceptd collect <pb> | exceptd run <pb> --evidence -` works again. `collect` now emits JSON when its stdout is a pipe (the human summary is reserved for an interactive terminal), so the documented one-liner no longer feeds a prose summary into `run`.
|
|
12
|
+
|
|
13
|
+
`exceptd refresh --check-advisories` polls the primary-source advisory feeds as documented (report-only; emits `diffs[]`) instead of being silently ignored.
|
|
14
|
+
|
|
15
|
+
`skill --help` and `framework-gap --help` print usage instead of erroring.
|
|
16
|
+
|
|
17
|
+
`exceptd attest list --limit <n>` caps the inventory; the JSON envelope reports the unfiltered total alongside the shown count.
|
|
18
|
+
|
|
19
|
+
Code-scope collectors skip agent scratch (`.claude`) and linked git worktrees. A working tree holding detached worktree copies was scanned once per copy, inflating hash and secret counts with duplicates of the same files. The `library-author` collector now recognizes release-time SBOM generation, npm provenance, and sigstore/cosign signing, and `id-token: write` declared at job scope — so a well-run publisher no longer gets false "SBOM absent" or "no OIDC" findings from artifacts that are produced at release time.
|
|
20
|
+
|
|
21
|
+
Documentation: the `ci` exit-code contract (0–5), the `osv` refresh source, and the full `--format` vocabulary are corrected in `--help` and README; the deprecated-alias help no longer claims a one-time banner it does not emit, and `reattest` / `list-attestations` are documented as canonical short forms rather than deprecated aliases.
|
|
22
|
+
|
|
23
|
+
## 0.13.126 — 2026-05-26
|
|
24
|
+
|
|
25
|
+
CVE catalog — n8n Git-node RCE. Adds **CVE-2026-21877**, completing the n8n critical cluster. In versions ≥ 0.123.0 and < 1.121.3, an authenticated user abuses the Git node to write a file of a dangerous type to an arbitrary path, which is then executed — yielding remote code execution and full compromise of both self-hosted and Cloud instances (CWE-434 unrestricted upload chained to CWE-94; GitHub CNA CVSS v3.1 9.9). Fixed in 1.121.3. Reuses the AI-app-builder execution-endpoint auth-and-sandbox control (NEW-CTRL-103), which now also covers file-writing workflow nodes as code-execution sinks. CVE count 419 → 420.
|
|
26
|
+
|
|
3
27
|
## 0.13.125 — 2026-05-26
|
|
4
28
|
|
|
5
29
|
CVE catalog — SGLang unauthenticated IPC-deserialization RCE cluster. Adds two unauthenticated RCEs in SGLang (lmsys), the unauth siblings of the already-catalogued authenticated weight-update flaw. **CVE-2026-3059** (CNA CVSS v3.1 9.8) — the multimodal generation module's ZMQ broker deserializes untrusted serialized objects from unauthenticated peers (CWE-502). **CVE-2026-3060** (CNA CVSS v3.1 9.8) — the encoder-parallel disaggregation module does the same. Both yield unauthenticated remote code execution on the serving host and are fixed in 0.5.10 (PR #20904). Both reuse the AI-inference IPC deserialization-safety control (NEW-CTRL-086), shared with the vLLM ZeroMQ-transport and TensorRT-LLM deserialization class — the lesson being that inference-engine IPC channels must use a safe serializer + peer authentication and never deserialize untrusted objects. CVE count 417 → 419.
|
package/README.md
CHANGED
|
@@ -30,7 +30,7 @@ This platform surfaces what is actually happening right now. Every skill explici
|
|
|
30
30
|
|
|
31
31
|
## Status
|
|
32
32
|
|
|
33
|
-
Pre-1.0. Latest release lives on [GitHub Releases](https://github.com/blamejs/exceptd-skills/releases) and on npm as [`@blamejs/exceptd-skills`](https://www.npmjs.com/package/@blamejs/exceptd-skills) with signed npm provenance attestation and Ed25519-signed skill bodies. The package ships 42 skills across kernel LPE, MCP supply chain, AI-as-C2, prompt injection, post-quantum crypto, SBOM integrity, identity-incident response, and 35 other AI/security domains, plus 10 intelligence catalogs (CVE / ATLAS / ATT&CK / CWE / D3FEND / DLP / RFC / framework gaps / global frameworks / zero-day lessons) covering 35 jurisdictions — the CVE catalog has grown past 400 entries, its size anchored by a v0.13.17 CISA KEV bulk-intake of `dateAdded >= 2024-01-01` actively-exploited vulnerabilities that took it from 68 to 312 in a single pass.
|
|
33
|
+
Pre-1.0. Latest release lives on [GitHub Releases](https://github.com/blamejs/exceptd-skills/releases) and on npm as [`@blamejs/exceptd-skills`](https://www.npmjs.com/package/@blamejs/exceptd-skills) with signed npm provenance attestation and Ed25519-signed skill bodies. The package ships 42 skills across kernel LPE, MCP supply chain, AI-as-C2, prompt injection, post-quantum crypto, SBOM integrity, identity-incident response, and 35 other AI/security domains, plus 10 intelligence catalogs (CVE / ATLAS / ATT&CK / CWE / D3FEND / DLP / RFC / framework gaps / global frameworks / zero-day lessons) covering 35 jurisdictions — the CVE catalog has grown past 400 entries, its size anchored by a v0.13.17 CISA KEV bulk-intake of `dateAdded >= 2024-01-01` actively-exploited vulnerabilities that took it from 68 to 312 in a single pass. 24 investigation playbooks (kernel, MCP, AI-API, framework, SBOM, runtime, hardening, secrets, cred-stores, containers, crypto, plus `webhook-callback-abuse`, `cicd-pipeline-compromise`, `identity-sso-compromise`, `llm-tool-use-exfil`, `post-quantum-migration`, `ai-discovered-cve-triage`, `supply-chain-recovery`, `citation-hygiene`, and more), a CLI for discovery and seven-phase investigation runs (`govern → direct → look → detect → analyze → validate → close`), and a nightly auto-refresh job that pulls KEV / NVD / EPSS / GHSA / OSV / IETF deltas plus 15 primary-source advisory + research-blog + tech-press feeds (Qualys TRU, Red Hat RHSA, Ubuntu USN, ZDI, kernel.org, oss-security, JFrog, CISA, Microsoft Security Blog, Sysdig, Trail of Bits, Embrace the Red, BleepingComputer security, The Hacker News, and a GitLab activity-feed tracker for the Nightmare-Eclipse researcher handle that anchors NEW-CTRL-073) into auto-PRs for editorial review. v0.13.17 also ships `lib/cve-regression-watcher.js` (NEW-CTRL-074) — a complementary detection method that surfaces poller-diff historical-CVE references as candidate silent-regression cases, the class anchored by MiniPlasma (a 2026 PoC drop that re-broke CVE-2020-17103 without any new ID being assigned).
|
|
34
34
|
|
|
35
35
|
---
|
|
36
36
|
|
|
@@ -156,13 +156,13 @@ Fresh-disclosure workflow (v0.12.0): the nightly auto-PR job pulls KEV / NVD / E
|
|
|
156
156
|
|
|
157
157
|
Primary-source advisory polling: `exceptd refresh --check-advisories` polls 15 vendor and coordinated-disclosure feeds — 8 advisory/coordinated-disclosure venues (Qualys TRU, Red Hat RHSA, Ubuntu USN, Zero Day Initiative, kernel.org commits, oss-security mailing list, JFrog SecOps, CISA current advisories), 4 vendor security research blogs added in v0.13.14 (Microsoft Security Blog, Sysdig, Trail of Bits, Embrace the Red), and 3 additions in v0.13.17 (BleepingComputer security, The Hacker News, and a GitLab activity-feed tracker for the Nightmare-Eclipse researcher handle that anchors NEW-CTRL-073). Combined coverage publishes CVE IDs at T+0 to T+1 — typically 3–14 days ahead of NVD enrichment. The command is report-only: it returns a structured `diffs[]` listing each newly-seen CVE ID with its source attributions and advisory URLs, but does not mutate the catalog. v0.13.17 also adds a complementary detection method (NEW-CTRL-074 / `lib/cve-regression-watcher.js`): the watcher cross-checks poller diffs for historical-CVE references (year ≤ currentYear − 2) and surfaces candidate silent-regression cases — the class anchored by MiniPlasma (a 2026 PoC drop that re-broke CVE-2020-17103 without any new ID being assigned). Operators triage the output and route promising IDs through `exceptd refresh --advisory <CVE-ID> --apply`. Pairs naturally with the daily scheduled remote agent below.
|
|
158
158
|
|
|
159
|
-
CVE-class alert surfacing: `exceptd
|
|
159
|
+
CVE-class alert surfacing: `exceptd watch --alerts` matches the live `cve-catalog.json` against five operational patterns (`kernel_lpe_with_poc`, `supply_chain_family`, `ai_discovered_kev`, `active_exploitation_unpatched`, `recent_poc_no_kev_yet`) and returns the matches sorted critical-severity-first, then by RWEP. Use as a fast operational triage on a refreshed catalog without scanning every entry by hand.
|
|
160
160
|
|
|
161
|
-
GitHub repo-pattern monitoring: `exceptd
|
|
161
|
+
GitHub repo-pattern monitoring: `exceptd watch --org-scan --org <login>` probes GitHub Search for repositories matching known threat-actor naming patterns ("A Gift From TeamPCP", "Shai-Hulud", "TeamPCP") scoped to one org. Custom patterns via repeatable `--pattern <s>`. Implements the canonical detection for the Shai-Hulud / TeamPCP supply-chain framework class — the attacker uses GitHub itself as the exfil channel. Set `GITHUB_TOKEN` for private-repo coverage and rate-limit headroom; public-repo search works without auth.
|
|
162
162
|
|
|
163
163
|
AI-assistant config-file audit: `exceptd doctor --ai-config` walks `~/.claude`, `~/.cursor`, `~/.codeium`, `~/.aider`, and `~/.continue`, flagging sensitive files (`settings.json`, `mcp.json`, `*.mcp_config.json`, `api_key*`, `*.token`, `*.credentials`) not at mode 0600 on POSIX. On Windows the mode bits aren't load-bearing; each finding is surfaced with an info-level "manual ACL review" note. Catches the AI-config-credential-exfil class that the Shai-Hulud framework targets. Opt-in — does not run as part of the default no-flag `doctor` pass.
|
|
164
164
|
|
|
165
|
-
Evidence-collection layer: `exceptd collect <playbook>` invokes a companion script under `lib/collectors/<playbook>.js` that walks cwd, applies the catalogued regex set, stats permissions, and emits the submission JSON in the same shape `exceptd run --evidence -` accepts.
|
|
165
|
+
Evidence-collection layer: `exceptd collect <playbook>` invokes a companion script under `lib/collectors/<playbook>.js` that walks cwd, applies the catalogued regex set, stats permissions, and emits the submission JSON in the same shape `exceptd run --evidence -` accepts. 14 of 24 playbooks have collectors today (`ai-api`, `cicd-pipeline-compromise`, `citation-hygiene`, `containers`, `cred-stores`, `crypto`, `crypto-codebase`, `hardening`, `kernel`, `library-author`, `mcp`, `runtime`, `sbom`, `secrets`); the remaining 10 are policy-skipped per AGENTS.md (judgement-shaped incident / governance / pure-analyze playbooks where AI-driven evidence collection is the design). Canonical operator pipe: `exceptd collect <pb> | exceptd run <pb> --evidence -`. `exceptd doctor --collectors` enumerates the layer; `exceptd discover` tags applicable playbooks with `[collector]` when one ships. `cicd-pipeline-compromise` requires `--attest-ownership` on the collect call (the playbook's `operator-owns-ci-fleet` precondition is opt-in to prevent unauthorized CI assessments).
|
|
166
166
|
|
|
167
167
|
Daily scheduled threat intake: a `routine: exceptd-threat-intake` (claude.ai remote agent) runs daily at 14:00 UTC. Sequence: `npm install` → `refresh --check-advisories` → `watchlist --alerts` → `refresh --apply` → `refresh --advisory <CVE-ID>` for up to 5 new CVE IDs from the primary-source feeds → re-sign + rebuild-indexes if the catalog mutated → commit on `intake/<YYYY-MM-DD>` branch with the full diff in the report. Closes the cadence gap that previously left fresh disclosures dependent on operator-triggered intake. Operator-managed at <https://claude.ai/code/routines>.
|
|
168
168
|
|
|
@@ -281,7 +281,7 @@ exceptd collect <playbook> Walk cwd + invoke the companion collector
|
|
|
281
281
|
under lib/collectors/<playbook>.js. Emits
|
|
282
282
|
a submission JSON ready to pipe into
|
|
283
283
|
`exceptd run <playbook> --evidence -`.
|
|
284
|
-
|
|
284
|
+
14/24 playbooks have collectors; the rest
|
|
285
285
|
are AI-driven by design (incident /
|
|
286
286
|
governance / pure-analyze — see
|
|
287
287
|
AGENTS.md).
|
|
@@ -330,8 +330,11 @@ exceptd doctor One-shot health check.
|
|
|
330
330
|
Opt-in; not part of the default doctor
|
|
331
331
|
pass.
|
|
332
332
|
|
|
333
|
-
exceptd ci One-shot CI gate.
|
|
334
|
-
|
|
333
|
+
exceptd ci One-shot CI gate. Exit codes: 0 PASS,
|
|
334
|
+
1 framework error, 2 detected/escalate
|
|
335
|
+
(or rwep ≥ rwep_threshold.escalate),
|
|
336
|
+
3 ran-but-no-evidence, 4 blocked
|
|
337
|
+
(ok:false), 5 jurisdiction clock started.
|
|
335
338
|
--all | --scope <type> Pick playbooks; auto-detect if neither.
|
|
336
339
|
--max-rwep <n> Cap below playbook default.
|
|
337
340
|
--block-on-jurisdiction-clock Fail when notification clock fires.
|
|
@@ -385,7 +388,7 @@ Packages dataset (`MAL-*` keys). New IDs land as drafts that the catalog
|
|
|
385
388
|
validator treats as warnings, not errors — editorial review (framework
|
|
386
389
|
gaps, IoCs, ATLAS/ATT&CK refs) is still required.
|
|
387
390
|
|
|
388
|
-
exceptd
|
|
391
|
+
exceptd watch Default mode: aggregate every skill's
|
|
389
392
|
forward_watch entries (upcoming standards,
|
|
390
393
|
RFC publications, new TTPs to monitor).
|
|
391
394
|
`--by-skill` inverts the grouping.
|
|
@@ -536,7 +539,7 @@ If your tool has a conventional auto-load filename not listed here and you'd lik
|
|
|
536
539
|
- **`theater-fingerprints.json`** — structured records for the 7 compliance theater patterns: claim, audit evidence, reality, fast detection test, controls implicated.
|
|
537
540
|
- **`_meta.json`** — sha256 of every source file. The `validate-indexes` predeploy gate fails if any source changed after the last build; `build-indexes --changed` reads this to know what to rebuild.
|
|
538
541
|
|
|
539
|
-
Regenerate with `exceptd
|
|
542
|
+
Regenerate with `exceptd refresh --indexes-only`.
|
|
540
543
|
|
|
541
544
|
## For skill authors — `agents/`
|
|
542
545
|
|
package/bin/exceptd.js
CHANGED
|
@@ -65,7 +65,17 @@ const PKG_ROOT = path.resolve(__dirname, "..");
|
|
|
65
65
|
// behavior share the same source of truth.
|
|
66
66
|
const { EXIT_CODES, listExitCodes } = require(path.join(PKG_ROOT, "lib", "exit-codes.js"));
|
|
67
67
|
const { validateIdComponent } = require(path.join(PKG_ROOT, "lib", "id-validation.js"));
|
|
68
|
-
const { suggestFlag, flagsFor } = require(path.join(PKG_ROOT, "lib", "flag-suggest.js"));
|
|
68
|
+
const { suggestFlag, flagsFor, VERB_FLAG_ALLOWLIST } = require(path.join(PKG_ROOT, "lib", "flag-suggest.js"));
|
|
69
|
+
|
|
70
|
+
// Union of every flag known to ANY verb. A flag that is valid somewhere but
|
|
71
|
+
// not on the active verb (e.g. `--csaf-status` on `brief`) is cross-verb
|
|
72
|
+
// misuse, not a typo — it falls through to the verb handler, which emits a
|
|
73
|
+
// tailored "that flag belongs on a run-class verb" message. Only a flag that
|
|
74
|
+
// is unknown EVERYWHERE is refused outright as a typo/garbage at the
|
|
75
|
+
// dispatcher. Kept module-scope so it is computed once.
|
|
76
|
+
const ALL_KNOWN_FLAGS = new Set(
|
|
77
|
+
Object.values(VERB_FLAG_ALLOWLIST).flat()
|
|
78
|
+
);
|
|
69
79
|
|
|
70
80
|
/**
|
|
71
81
|
* Factor the EXPECTED_FINGERPRINT pin check used by
|
|
@@ -384,7 +394,7 @@ Canonical verbs
|
|
|
384
394
|
--evidence <file|-> flat or nested submission
|
|
385
395
|
--evidence-dir <dir> per-playbook submission files
|
|
386
396
|
--vex <file> CycloneDX / OpenVEX filter
|
|
387
|
-
--format <fmt> ... csaf-2.0 | sarif | openvex | markdown | summary
|
|
397
|
+
--format <fmt> ... csaf-2.0 | sarif | openvex | markdown | summary | json
|
|
388
398
|
--diff-from-latest drift vs prior attestation
|
|
389
399
|
--ci exit-code gate (use \`exceptd ci\` instead)
|
|
390
400
|
--operator <name> bind attestation to identity
|
|
@@ -458,8 +468,10 @@ Canonical verbs
|
|
|
458
468
|
--prefetch populate offline cache
|
|
459
469
|
--from-cache consume offline cache
|
|
460
470
|
--indexes-only rebuild indexes only
|
|
461
|
-
Sources: kev|epss|nvd|rfc|pins|ghsa
|
|
471
|
+
Sources: kev|epss|nvd|rfc|pins|ghsa|osv.
|
|
462
472
|
ghsa drafts pass validator as warnings.
|
|
473
|
+
--check-advisories poll primary-source advisory
|
|
474
|
+
feeds; report-only diffs[].
|
|
463
475
|
|
|
464
476
|
Removed verbs (refused — these now error with a pointer to the replacement)
|
|
465
477
|
───────────────────────────────────────────────────────────────────────────
|
|
@@ -476,12 +488,10 @@ here so old scripts know where each moved:
|
|
|
476
488
|
Deprecated aliases (still work — prefer the canonical verb)
|
|
477
489
|
───────────────────────────────────────────────────────────
|
|
478
490
|
|
|
479
|
-
These still run
|
|
480
|
-
|
|
481
|
-
|
|
491
|
+
These still run. The [DEPRECATED] prefix keeps them out of the active-verbs
|
|
492
|
+
list that \`exceptd help | grep '^ [a-z]'\` surfaces. Each maps to a canonical
|
|
493
|
+
verb:
|
|
482
494
|
|
|
483
|
-
[DEPRECATED] reattest <sid> → attest diff <sid>
|
|
484
|
-
[DEPRECATED] list-attestations → attest list
|
|
485
495
|
[DEPRECATED] scan → discover --scan-only
|
|
486
496
|
[DEPRECATED] dispatch → discover
|
|
487
497
|
[DEPRECATED] currency → doctor --currency
|
|
@@ -492,6 +502,11 @@ prefix keeps them out of the active-verbs list that
|
|
|
492
502
|
[DEPRECATED] prefetch → refresh --no-network
|
|
493
503
|
[DEPRECATED] build-indexes → refresh --indexes-only
|
|
494
504
|
|
|
505
|
+
Accepted short forms (canonical — not deprecated):
|
|
506
|
+
|
|
507
|
+
reattest <sid> short form of \`attest diff <sid>\`
|
|
508
|
+
list-attestations short form of \`attest list\`
|
|
509
|
+
|
|
495
510
|
Output: default human-readable (v0.11.0). --json for machine output.
|
|
496
511
|
--pretty for indented JSON.
|
|
497
512
|
|
|
@@ -711,6 +726,24 @@ function main() {
|
|
|
711
726
|
return;
|
|
712
727
|
}
|
|
713
728
|
|
|
729
|
+
// `skill` and `framework-gap` are spawned subcommands that never reach the
|
|
730
|
+
// in-process per-verb --help, and (unlike `refresh`/`prefetch`, which print
|
|
731
|
+
// their own help) the orchestrator forwards `--help` as a positional —
|
|
732
|
+
// `skill --help` tried to resolve a skill literally named "--help"
|
|
733
|
+
// ("Skill not found: --help") and `framework-gap --help` errored on missing
|
|
734
|
+
// args. Intercept --help for just these so they honor it. Scoped to the
|
|
735
|
+
// verbs that lack their own help handler, so spawns that do (refresh,
|
|
736
|
+
// prefetch) keep their detailed usage.
|
|
737
|
+
const SPAWN_HELP_USAGE = {
|
|
738
|
+
skill: "exceptd skill <name> Show the full context document for one skill.",
|
|
739
|
+
"framework-gap": "exceptd framework-gap <framework> <cve-or-scenario> One-framework gap analysis.",
|
|
740
|
+
"framework-gap-analysis": "exceptd framework-gap <framework> <cve-or-scenario> One-framework gap analysis.",
|
|
741
|
+
};
|
|
742
|
+
if ((effectiveRest.includes("--help") || effectiveRest.includes("-h")) && SPAWN_HELP_USAGE[effectiveCmd]) {
|
|
743
|
+
process.stdout.write(SPAWN_HELP_USAGE[effectiveCmd] + "\n Full reference: exceptd help\n");
|
|
744
|
+
return;
|
|
745
|
+
}
|
|
746
|
+
|
|
714
747
|
// Orchestrator subcommands need the subcommand name preserved as argv[0]
|
|
715
748
|
// for orchestrator/index.js's switch statement.
|
|
716
749
|
const finalArgs = ORCHESTRATOR_PASSTHROUGH.has(effectiveCmd) ? [script, effectiveCmd, ...effectiveRest] : [script, ...effectiveRest];
|
|
@@ -1203,6 +1236,7 @@ function dispatchPlaybook(cmd, argv) {
|
|
|
1203
1236
|
"publisher-namespace", "mode", "scope", "playbook", "phase", "tlp",
|
|
1204
1237
|
"against", "since", "bundle-epoch", "attestation-root", "format",
|
|
1205
1238
|
"cwd", // exceptd collect <pb> --cwd <path>
|
|
1239
|
+
"limit", // exceptd attest list --limit <n>
|
|
1206
1240
|
]);
|
|
1207
1241
|
const verbAllowlist = flagsFor(cmd);
|
|
1208
1242
|
const allowlistSet = new Set(verbAllowlist);
|
|
@@ -1242,21 +1276,29 @@ function dispatchPlaybook(cmd, argv) {
|
|
|
1242
1276
|
}
|
|
1243
1277
|
continue;
|
|
1244
1278
|
}
|
|
1245
|
-
//
|
|
1246
|
-
//
|
|
1247
|
-
//
|
|
1248
|
-
//
|
|
1249
|
-
|
|
1250
|
-
//
|
|
1251
|
-
//
|
|
1279
|
+
// A flag valid on SOME other verb (e.g. `--csaf-status` on `brief`) is
|
|
1280
|
+
// cross-verb misuse — fall through so the verb handler can emit its
|
|
1281
|
+
// tailored "that flag belongs on a run-class verb" guidance rather than
|
|
1282
|
+
// a blanket refusal here.
|
|
1283
|
+
if (ALL_KNOWN_FLAGS.has(key)) continue;
|
|
1284
|
+
// Unknown everywhere — refuse it as a typo / unsupported flag. Silently
|
|
1285
|
+
// ignoring an unrecognized flag let a mistyped cap or output-format flag
|
|
1286
|
+
// look like it applied when it did nothing. Surface a suggestion
|
|
1287
|
+
// when one is close, and always list the accepted flags so the operator
|
|
1288
|
+
// can self-correct. Adding a new flag to a verb means appending it to
|
|
1289
|
+
// that verb's allowlist (or PASSTHROUGH_FLAGS) — the test suite exercises
|
|
1290
|
+
// every shipped flag, so a missing registration fails CI rather than
|
|
1291
|
+
// silently breaking the flag.
|
|
1252
1292
|
const suggestion = suggestFlag(key, verbAllowlist);
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1293
|
+
return emitError(
|
|
1294
|
+
`${cmd}: unknown flag --${key}`,
|
|
1295
|
+
{
|
|
1296
|
+
verb: cmd,
|
|
1297
|
+
unknown_flags: [{ flag: `--${key}`, did_you_mean: suggestion ? [`--${suggestion}`] : [] }],
|
|
1298
|
+
known_flags: verbAllowlist.filter((f) => typeof f === "string").sort().map((f) => `--${f}`),
|
|
1299
|
+
},
|
|
1300
|
+
pretty
|
|
1301
|
+
);
|
|
1260
1302
|
}
|
|
1261
1303
|
const runOpts = {
|
|
1262
1304
|
// Air-gap can be requested via the explicit flag OR the
|
|
@@ -1798,10 +1840,14 @@ Flags:
|
|
|
1798
1840
|
or not_affected | fixed (OpenVEX) drop out of
|
|
1799
1841
|
analyze.matched_cves. The disposition is preserved
|
|
1800
1842
|
under analyze.vex.dropped_cves.
|
|
1801
|
-
--format <fmt> ...
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1843
|
+
--format <fmt> ... Transform stdout. Supported: summary | markdown |
|
|
1844
|
+
csaf-2.0 | csaf | sarif | openvex | json (json = the
|
|
1845
|
+
full run result). Standardized bundles (csaf/sarif/
|
|
1846
|
+
openvex) are emitted as spec-conformant documents.
|
|
1847
|
+
Repeatable, but only ONE document goes to stdout — the
|
|
1848
|
+
first; every requested bundle is embedded under
|
|
1849
|
+
close.evidence_package.bundles_by_format (see via
|
|
1850
|
+
--json). Passing several prints a note to stderr.
|
|
1805
1851
|
--explain Dry-run: emit preconditions, required artifacts,
|
|
1806
1852
|
recognized signal keys, and a submission skeleton.
|
|
1807
1853
|
Does not run detect/analyze/validate/close.
|
|
@@ -2390,7 +2436,14 @@ function cmdCollect(runner, args, runOpts, pretty) {
|
|
|
2390
2436
|
// Spread `submission` first, then explicit fields, so a submission key
|
|
2391
2437
|
// named `air_gap_mode` (currently always undefined but defensive against
|
|
2392
2438
|
// future collector contracts) can't clobber the envelope marker.
|
|
2393
|
-
|
|
2439
|
+
const collectBody = { verb: "collect", playbook_id: playbookId, ...submission, air_gap_mode: collectAirGap };
|
|
2440
|
+
// collect's primary purpose is the pipe `exceptd collect <pb> | exceptd run
|
|
2441
|
+
// <pb> --evidence -`. When stdout is NOT a TTY (a pipe / redirect), emit JSON
|
|
2442
|
+
// so that one-liner just works; the human summary is only for an interactive
|
|
2443
|
+
// operator at a terminal. Explicit --json / --pretty force JSON regardless.
|
|
2444
|
+
// Without this gate, emit()'s default-human behavior printed a prose summary
|
|
2445
|
+
// into the pipe and the downstream `run --evidence -` failed to parse it.
|
|
2446
|
+
const collectHuman = process.stdout.isTTY ? (obj) => {
|
|
2394
2447
|
const lines = [];
|
|
2395
2448
|
const meta = obj.collector_meta || {};
|
|
2396
2449
|
lines.push(`collect: ${obj.playbook_id} (${meta.collector_version || "?"} on ${meta.platform || "?"})`);
|
|
@@ -2427,10 +2480,11 @@ function cmdCollect(runner, args, runOpts, pretty) {
|
|
|
2427
2480
|
}
|
|
2428
2481
|
if (errs.length > 5) lines.push(` … ${errs.length - 5} more`);
|
|
2429
2482
|
}
|
|
2430
|
-
lines.push(`\n→ next: exceptd collect ${obj.playbook_id}
|
|
2483
|
+
lines.push(`\n→ next: exceptd collect ${obj.playbook_id} | exceptd run ${obj.playbook_id} --evidence -`);
|
|
2431
2484
|
lines.push(`Full structured result: --json (or --pretty for indented JSON).`);
|
|
2432
2485
|
return lines.join("\n");
|
|
2433
|
-
}
|
|
2486
|
+
} : undefined;
|
|
2487
|
+
emit(collectBody, pretty, collectHuman);
|
|
2434
2488
|
}
|
|
2435
2489
|
|
|
2436
2490
|
function cmdLint(runner, args, runOpts, pretty) {
|
|
@@ -3480,7 +3534,8 @@ function cmdRun(runner, args, runOpts, pretty) {
|
|
|
3480
3534
|
// --format csaf-2.0/sarif/openvex → the corresponding bundle from close
|
|
3481
3535
|
// (default — no --format) → full JSON result as before
|
|
3482
3536
|
if (args.format) {
|
|
3483
|
-
const
|
|
3537
|
+
const requestedAll = Array.isArray(args.format) ? args.format : [args.format];
|
|
3538
|
+
const requested = requestedAll[0];
|
|
3484
3539
|
const VALID = ["summary", "markdown", "csaf-2.0", "csaf", "sarif", "openvex", "json"];
|
|
3485
3540
|
if (!VALID.includes(requested)) {
|
|
3486
3541
|
const dym = suggestFlag(String(requested), VALID);
|
|
@@ -3491,6 +3546,25 @@ function cmdRun(runner, args, runOpts, pretty) {
|
|
|
3491
3546
|
pretty,
|
|
3492
3547
|
);
|
|
3493
3548
|
}
|
|
3549
|
+
// Only one document can be written to stdout. When several --format values
|
|
3550
|
+
// are given, emit the first and tell the operator where the rest live so
|
|
3551
|
+
// the extras aren't silently dropped.
|
|
3552
|
+
if (requestedAll.length > 1) {
|
|
3553
|
+
process.stderr.write(
|
|
3554
|
+
`[exceptd] note: ${requestedAll.length} --format values given; emitting "${requested}" to stdout. ` +
|
|
3555
|
+
`All requested bundles are embedded under phases.close.evidence_package.bundles_by_format — ` +
|
|
3556
|
+
`re-run with --json to see them.\n`
|
|
3557
|
+
);
|
|
3558
|
+
}
|
|
3559
|
+
// `json` means "the full run result as JSON" — the same body the default
|
|
3560
|
+
// (no --format) path emits. Without this it fell through to the bundle
|
|
3561
|
+
// lookup, found the runner's "unknown format" stub under
|
|
3562
|
+
// bundles_by_format.json, and emitted that 150-byte stub instead of the
|
|
3563
|
+
// scan — silently discarding the result with a success exit code.
|
|
3564
|
+
if (requested === "json") {
|
|
3565
|
+
emit(result, pretty);
|
|
3566
|
+
return;
|
|
3567
|
+
}
|
|
3494
3568
|
if (requested === "summary") {
|
|
3495
3569
|
const cls = result.phases?.detect?.classification;
|
|
3496
3570
|
const rwep = result.phases?.analyze?.rwep?.adjusted ?? 0;
|
|
@@ -3546,7 +3620,15 @@ function cmdRun(runner, args, runOpts, pretty) {
|
|
|
3546
3620
|
const bbf = result.phases?.close?.evidence_package?.bundles_by_format || {};
|
|
3547
3621
|
const body = bbf[formatNorm] || result.phases?.close?.evidence_package?.bundle_body;
|
|
3548
3622
|
if (body) {
|
|
3549
|
-
|
|
3623
|
+
// SARIF / CSAF / OpenVEX are self-describing standard documents. Write
|
|
3624
|
+
// them verbatim rather than through emit(), which prepends the tool's
|
|
3625
|
+
// own `ok` envelope key — `ok` is not a permitted top-level property in
|
|
3626
|
+
// any of these schemas and makes strict validators (GitHub code-scanning
|
|
3627
|
+
// SARIF upload, a CSAF trusted-provider check) reject the output. The
|
|
3628
|
+
// namespaced `exceptd_extension` block (CSAF vendor extension carrying
|
|
3629
|
+
// publisher-namespace provenance) is preserved intentionally.
|
|
3630
|
+
const { ok: _ok, ...spec } = body;
|
|
3631
|
+
process.stdout.write(JSON.stringify(spec, null, pretty ? 2 : 0) + "\n");
|
|
3550
3632
|
return;
|
|
3551
3633
|
}
|
|
3552
3634
|
// Fallback: full result
|
|
@@ -7040,10 +7122,28 @@ function cmdListAttestations(runner, args, runOpts, pretty) {
|
|
|
7040
7122
|
}
|
|
7041
7123
|
}
|
|
7042
7124
|
entries.sort((a, b) => (b.captured_at || "").localeCompare(a.captured_at || ""));
|
|
7125
|
+
const total = entries.length;
|
|
7126
|
+
// --limit caps the inventory (newest first). Without it, JSON returns every
|
|
7127
|
+
// session and the human table shows the first 50 with an "… and N more"
|
|
7128
|
+
// footer. With it, both surfaces honor the cap and report `total`.
|
|
7129
|
+
let limitN = null;
|
|
7130
|
+
if (args.limit != null) {
|
|
7131
|
+
limitN = Number(args.limit);
|
|
7132
|
+
if (!Number.isInteger(limitN) || limitN < 0) {
|
|
7133
|
+
return emitError(
|
|
7134
|
+
`attest list: --limit must be a non-negative integer; got ${JSON.stringify(String(args.limit))}.`,
|
|
7135
|
+
{ verb: "attest list", provided: args.limit },
|
|
7136
|
+
pretty,
|
|
7137
|
+
);
|
|
7138
|
+
}
|
|
7139
|
+
}
|
|
7140
|
+
const shown = limitN != null ? entries.slice(0, limitN) : entries;
|
|
7043
7141
|
emit({
|
|
7044
7142
|
ok: true,
|
|
7045
|
-
attestations:
|
|
7046
|
-
count:
|
|
7143
|
+
attestations: shown,
|
|
7144
|
+
count: total,
|
|
7145
|
+
shown: shown.length,
|
|
7146
|
+
limit: limitN,
|
|
7047
7147
|
filter: { playbook: playbookFilter ? [...playbookFilter] : null, since: args.since || null },
|
|
7048
7148
|
roots_searched: [...seenRoots],
|
|
7049
7149
|
// Cycle 11 F5 (v0.12.32): every candidate root + whether it existed,
|
|
@@ -7068,10 +7168,17 @@ function cmdListAttestations(runner, args, runOpts, pretty) {
|
|
|
7068
7168
|
}
|
|
7069
7169
|
lines.push(` ${"session-id".padEnd(20)} ${"playbook".padEnd(16)} ${"captured-at".padEnd(20)} evidence-hash`);
|
|
7070
7170
|
lines.push(` ${"-".repeat(20)} ${"-".repeat(16)} ${"-".repeat(20)} ${"-".repeat(20)}`);
|
|
7071
|
-
|
|
7171
|
+
// When --limit was given, obj.attestations is already capped; show all of
|
|
7172
|
+
// it. Otherwise show the first 50 and footer the remainder.
|
|
7173
|
+
const rows = obj.limit != null ? obj.attestations : obj.attestations.slice(0, 50);
|
|
7174
|
+
for (const e of rows) {
|
|
7072
7175
|
lines.push(` ${(e.session_id || "?").padEnd(20)} ${(e.playbook_id || "?").padEnd(16)} ${(e.captured_at || "").slice(0, 19).padEnd(20)} ${e.evidence_hash || ""}`);
|
|
7073
7176
|
}
|
|
7074
|
-
if (obj.
|
|
7177
|
+
if (obj.limit != null) {
|
|
7178
|
+
if (obj.count > rows.length) lines.push(` showing ${rows.length} of ${obj.count} (raise --limit or use --json for the full list)`);
|
|
7179
|
+
} else if (obj.count > 50) {
|
|
7180
|
+
lines.push(` … and ${obj.count - 50} more (use --limit <n> or --json for the full list)`);
|
|
7181
|
+
}
|
|
7075
7182
|
return lines.join("\n");
|
|
7076
7183
|
});
|
|
7077
7184
|
}
|
package/data/_indexes/_meta.json
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema_version": "1.1.0",
|
|
3
|
-
"generated_at": "2026-05-
|
|
3
|
+
"generated_at": "2026-05-27T06:54:45.872Z",
|
|
4
4
|
"generator": "scripts/build-indexes.js",
|
|
5
5
|
"source_count": 54,
|
|
6
6
|
"source_hashes": {
|
|
7
|
-
"manifest.json": "
|
|
8
|
-
"data/atlas-ttps.json": "
|
|
9
|
-
"data/attack-techniques.json": "
|
|
10
|
-
"data/cve-catalog.json": "
|
|
11
|
-
"data/cwe-catalog.json": "
|
|
7
|
+
"manifest.json": "b6388ed9b3b0e87f6f65d050c2aef4fb3121e2135892e59313ba05b6fa5090e7",
|
|
8
|
+
"data/atlas-ttps.json": "d24bc02859d40ccf1615db75cca68c077585904e41e0d8f6de448121e9b1abb0",
|
|
9
|
+
"data/attack-techniques.json": "fa193f0d2d248176a8beddb641e9fe56ba4faa9e15dc253ff876dbf0c5d58a77",
|
|
10
|
+
"data/cve-catalog.json": "3d451dda7ac0c7d57a4075ae4bafd3148c6184b35dc1bc59d8b81d1f2641e430",
|
|
11
|
+
"data/cwe-catalog.json": "0b82f1ba29024720dcafb66aaa6c7b59b535badd28dbac8a5c08737c5f25e9a9",
|
|
12
12
|
"data/d3fend-catalog.json": "9a54bccb9f24f84b32024216cc3f53819a053721ac8ab43c326859e68fc0ffaf",
|
|
13
13
|
"data/dlp-controls.json": "d2406c482dddd30e49203879999dc4b3a7fd4d0494d6a61d86b91ee76415df19",
|
|
14
14
|
"data/exploit-availability.json": "ec2656f0d9a893610e27b43eb6035fe9b18e057c9f6dfaac7e7d4959bbcbb795",
|
|
15
|
-
"data/framework-control-gaps.json": "
|
|
15
|
+
"data/framework-control-gaps.json": "46094ad9b9584f454daddd28f4c5d27faf0ea0510daafd38fb60bf9d30f6a305",
|
|
16
16
|
"data/global-frameworks.json": "9ba563a85f7f8d6c3c957de64945e20925a89d0ed6ea6fc561cf093811acf558",
|
|
17
17
|
"data/rfc-references.json": "66ef2e1f444a2cf0c2700a754f0a66030bb8a91d9e68394b9537ea1fe8b904fe",
|
|
18
|
-
"data/zeroday-lessons.json": "
|
|
18
|
+
"data/zeroday-lessons.json": "258252b9bff1fc11b05b76e10b659c1195971884bd44c92af5fefe17a5ca9512",
|
|
19
19
|
"skills/kernel-lpe-triage/skill.md": "08b3e9815ba481c57c80f5fc0ccbf5bb7cbb41f570c235ba6ff9596b8c07354d",
|
|
20
20
|
"skills/ai-attack-surface/skill.md": "c4c1eb22a38ca7a959b5725222bab8fbd4f4044a548a93f3e288e6f698334b72",
|
|
21
21
|
"skills/mcp-agent-trust/skill.md": "89ac89084391d2341b6513fefb1be2d36b93de1c130f057696219c1c59440f13",
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
"dlp_refs": 0
|
|
73
73
|
},
|
|
74
74
|
"trigger_table_entries": 538,
|
|
75
|
-
"chains_cve_entries":
|
|
75
|
+
"chains_cve_entries": 409,
|
|
76
76
|
"chains_cwe_entries": 172,
|
|
77
77
|
"jurisdictions_indexed": 29,
|
|
78
78
|
"handoff_dag_nodes": 42,
|
|
@@ -149,7 +149,7 @@
|
|
|
149
149
|
"artifact": "data/cve-catalog.json",
|
|
150
150
|
"path": "data/cve-catalog.json",
|
|
151
151
|
"schema_version": "1.0.0",
|
|
152
|
-
"entry_count":
|
|
152
|
+
"entry_count": 420
|
|
153
153
|
},
|
|
154
154
|
{
|
|
155
155
|
"date": "2026-05-18",
|
|
@@ -165,7 +165,7 @@
|
|
|
165
165
|
"artifact": "data/zeroday-lessons.json",
|
|
166
166
|
"path": "data/zeroday-lessons.json",
|
|
167
167
|
"schema_version": "1.1.0",
|
|
168
|
-
"entry_count":
|
|
168
|
+
"entry_count": 415
|
|
169
169
|
},
|
|
170
170
|
{
|
|
171
171
|
"date": "2026-05-17",
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
"rebuild_after_days": 365,
|
|
63
63
|
"note": "Per-entry last_verified governs decay. Skills depending on this catalog must check entry freshness before high-stakes use."
|
|
64
64
|
},
|
|
65
|
-
"entry_count":
|
|
65
|
+
"entry_count": 420,
|
|
66
66
|
"sample_keys": [
|
|
67
67
|
"CVE-2025-53773",
|
|
68
68
|
"CVE-2026-30615",
|
|
@@ -238,7 +238,7 @@
|
|
|
238
238
|
"rebuild_after_days": 365,
|
|
239
239
|
"note": "Per-entry last_verified governs decay. Skills depending on this catalog must check entry freshness before high-stakes use."
|
|
240
240
|
},
|
|
241
|
-
"entry_count":
|
|
241
|
+
"entry_count": 415,
|
|
242
242
|
"sample_keys": [
|
|
243
243
|
"CVE-2026-31431",
|
|
244
244
|
"CVE-2025-53773",
|