@blamejs/exceptd-skills 0.13.126 → 0.14.1
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 +5 -3
- package/CHANGELOG.md +30 -0
- package/README.md +43 -9
- package/bin/exceptd.js +148 -35
- package/data/_indexes/_meta.json +2 -2
- package/data/playbooks/citation-hygiene.json +820 -0
- package/lib/citation-resolve.js +226 -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/cve-cli.js +51 -0
- package/lib/flag-suggest.js +2 -2
- package/lib/refresh-external.js +15 -0
- 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 +1 -1
- package/sbom.cdx.json +134 -44
- package/scripts/check-agents-md-collectors.js +8 -0
- 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.
|
|
@@ -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. |
|
|
@@ -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). |
|
|
@@ -372,7 +374,7 @@ This split costs every consumer the same translation work on every invocation. C
|
|
|
372
374
|
exceptd collect secrets | exceptd run secrets --evidence -
|
|
373
375
|
```
|
|
374
376
|
|
|
375
|
-
The collector library is small and grows as playbooks are touched.
|
|
377
|
+
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
378
|
|
|
377
379
|
### Precision target for new `look.artifacts[].source` strings
|
|
378
380
|
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.14.1 — 2026-05-27
|
|
4
|
+
|
|
5
|
+
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.
|
|
6
|
+
|
|
7
|
+
`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.
|
|
8
|
+
|
|
9
|
+
`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.
|
|
10
|
+
|
|
11
|
+
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`.
|
|
12
|
+
|
|
13
|
+
## 0.14.0 — 2026-05-26
|
|
14
|
+
|
|
15
|
+
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.
|
|
16
|
+
|
|
17
|
+
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.
|
|
18
|
+
|
|
19
|
+
`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.
|
|
20
|
+
|
|
21
|
+
`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`.
|
|
22
|
+
|
|
23
|
+
`exceptd refresh --check-advisories` polls the primary-source advisory feeds as documented (report-only; emits `diffs[]`) instead of being silently ignored.
|
|
24
|
+
|
|
25
|
+
`skill --help` and `framework-gap --help` print usage instead of erroring.
|
|
26
|
+
|
|
27
|
+
`exceptd attest list --limit <n>` caps the inventory; the JSON envelope reports the unfiltered total alongside the shown count.
|
|
28
|
+
|
|
29
|
+
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.
|
|
30
|
+
|
|
31
|
+
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.
|
|
32
|
+
|
|
3
33
|
## 0.13.126 — 2026-05-26
|
|
4
34
|
|
|
5
35
|
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.
|
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.
|
|
@@ -346,6 +349,35 @@ exceptd lint <pb> <evidence> Pre-flight check submission shape vs
|
|
|
346
349
|
playbook (preconditions / artifacts /
|
|
347
350
|
indicators) without executing phases 4-7.
|
|
348
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
|
+
|
|
349
381
|
exceptd refresh Refresh upstream catalogs + indexes.
|
|
350
382
|
Replaces prefetch + refresh + build-indexes.
|
|
351
383
|
--apply Write diffs back + rebuild indexes.
|
|
@@ -385,7 +417,7 @@ Packages dataset (`MAL-*` keys). New IDs land as drafts that the catalog
|
|
|
385
417
|
validator treats as warnings, not errors — editorial review (framework
|
|
386
418
|
gaps, IoCs, ATLAS/ATT&CK refs) is still required.
|
|
387
419
|
|
|
388
|
-
exceptd
|
|
420
|
+
exceptd watch Default mode: aggregate every skill's
|
|
389
421
|
forward_watch entries (upcoming standards,
|
|
390
422
|
RFC publications, new TTPs to monitor).
|
|
391
423
|
`--by-skill` inverts the grouping.
|
|
@@ -536,7 +568,7 @@ If your tool has a conventional auto-load filename not listed here and you'd lik
|
|
|
536
568
|
- **`theater-fingerprints.json`** — structured records for the 7 compliance theater patterns: claim, audit evidence, reality, fast detection test, controls implicated.
|
|
537
569
|
- **`_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
570
|
|
|
539
|
-
Regenerate with `exceptd
|
|
571
|
+
Regenerate with `exceptd refresh --indexes-only`.
|
|
540
572
|
|
|
541
573
|
## For skill authors — `agents/`
|
|
542
574
|
|
|
@@ -546,6 +578,8 @@ The `agents/` directory ships markdown role cards documenting authoring conventi
|
|
|
546
578
|
|
|
547
579
|
All skills pull from `data/`. Cross-validated against canonical upstream sources via `exceptd refresh` / `exceptd doctor --cves` / `exceptd doctor --rfcs`.
|
|
548
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
|
+
|
|
549
583
|
- `cve-catalog.json` — CVE metadata with RWEP scores, CISA KEV status, PoC availability, live-patch info
|
|
550
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.
|
|
551
585
|
- `framework-control-gaps.json` — Per-framework, per-control: what it was designed for vs. what it misses
|
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
|
|
@@ -144,6 +154,10 @@ const COMMANDS = {
|
|
|
144
154
|
watch: () => path.join(PKG_ROOT, "orchestrator", "index.js"),
|
|
145
155
|
"framework-gap": () => path.join(PKG_ROOT, "orchestrator", "index.js"),
|
|
146
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"),
|
|
147
161
|
// Seven-phase playbook verbs — handled in-process via lib/playbook-runner.js.
|
|
148
162
|
plan: null,
|
|
149
163
|
govern: null,
|
|
@@ -384,7 +398,7 @@ Canonical verbs
|
|
|
384
398
|
--evidence <file|-> flat or nested submission
|
|
385
399
|
--evidence-dir <dir> per-playbook submission files
|
|
386
400
|
--vex <file> CycloneDX / OpenVEX filter
|
|
387
|
-
--format <fmt> ... csaf-2.0 | sarif | openvex | markdown | summary
|
|
401
|
+
--format <fmt> ... csaf-2.0 | sarif | openvex | markdown | summary | json
|
|
388
402
|
--diff-from-latest drift vs prior attestation
|
|
389
403
|
--ci exit-code gate (use \`exceptd ci\` instead)
|
|
390
404
|
--operator <name> bind attestation to identity
|
|
@@ -458,8 +472,10 @@ Canonical verbs
|
|
|
458
472
|
--prefetch populate offline cache
|
|
459
473
|
--from-cache consume offline cache
|
|
460
474
|
--indexes-only rebuild indexes only
|
|
461
|
-
Sources: kev|epss|nvd|rfc|pins|ghsa
|
|
475
|
+
Sources: kev|epss|nvd|rfc|pins|ghsa|osv.
|
|
462
476
|
ghsa drafts pass validator as warnings.
|
|
477
|
+
--check-advisories poll primary-source advisory
|
|
478
|
+
feeds; report-only diffs[].
|
|
463
479
|
|
|
464
480
|
Removed verbs (refused — these now error with a pointer to the replacement)
|
|
465
481
|
───────────────────────────────────────────────────────────────────────────
|
|
@@ -476,12 +492,10 @@ here so old scripts know where each moved:
|
|
|
476
492
|
Deprecated aliases (still work — prefer the canonical verb)
|
|
477
493
|
───────────────────────────────────────────────────────────
|
|
478
494
|
|
|
479
|
-
These still run
|
|
480
|
-
|
|
481
|
-
|
|
495
|
+
These still run. The [DEPRECATED] prefix keeps them out of the active-verbs
|
|
496
|
+
list that \`exceptd help | grep '^ [a-z]'\` surfaces. Each maps to a canonical
|
|
497
|
+
verb:
|
|
482
498
|
|
|
483
|
-
[DEPRECATED] reattest <sid> → attest diff <sid>
|
|
484
|
-
[DEPRECATED] list-attestations → attest list
|
|
485
499
|
[DEPRECATED] scan → discover --scan-only
|
|
486
500
|
[DEPRECATED] dispatch → discover
|
|
487
501
|
[DEPRECATED] currency → doctor --currency
|
|
@@ -492,6 +506,11 @@ prefix keeps them out of the active-verbs list that
|
|
|
492
506
|
[DEPRECATED] prefetch → refresh --no-network
|
|
493
507
|
[DEPRECATED] build-indexes → refresh --indexes-only
|
|
494
508
|
|
|
509
|
+
Accepted short forms (canonical — not deprecated):
|
|
510
|
+
|
|
511
|
+
reattest <sid> short form of \`attest diff <sid>\`
|
|
512
|
+
list-attestations short form of \`attest list\`
|
|
513
|
+
|
|
495
514
|
Output: default human-readable (v0.11.0). --json for machine output.
|
|
496
515
|
--pretty for indented JSON.
|
|
497
516
|
|
|
@@ -711,6 +730,26 @@ function main() {
|
|
|
711
730
|
return;
|
|
712
731
|
}
|
|
713
732
|
|
|
733
|
+
// `skill` and `framework-gap` are spawned subcommands that never reach the
|
|
734
|
+
// in-process per-verb --help, and (unlike `refresh`/`prefetch`, which print
|
|
735
|
+
// their own help) the orchestrator forwards `--help` as a positional —
|
|
736
|
+
// `skill --help` tried to resolve a skill literally named "--help"
|
|
737
|
+
// ("Skill not found: --help") and `framework-gap --help` errored on missing
|
|
738
|
+
// args. Intercept --help for just these so they honor it. Scoped to the
|
|
739
|
+
// verbs that lack their own help handler, so spawns that do (refresh,
|
|
740
|
+
// prefetch) keep their detailed usage.
|
|
741
|
+
const SPAWN_HELP_USAGE = {
|
|
742
|
+
skill: "exceptd skill <name> Show the full context document for one skill.",
|
|
743
|
+
"framework-gap": "exceptd framework-gap <framework> <cve-or-scenario> One-framework gap analysis.",
|
|
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).",
|
|
747
|
+
};
|
|
748
|
+
if ((effectiveRest.includes("--help") || effectiveRest.includes("-h")) && SPAWN_HELP_USAGE[effectiveCmd]) {
|
|
749
|
+
process.stdout.write(SPAWN_HELP_USAGE[effectiveCmd] + "\n Full reference: exceptd help\n");
|
|
750
|
+
return;
|
|
751
|
+
}
|
|
752
|
+
|
|
714
753
|
// Orchestrator subcommands need the subcommand name preserved as argv[0]
|
|
715
754
|
// for orchestrator/index.js's switch statement.
|
|
716
755
|
const finalArgs = ORCHESTRATOR_PASSTHROUGH.has(effectiveCmd) ? [script, effectiveCmd, ...effectiveRest] : [script, ...effectiveRest];
|
|
@@ -1203,6 +1242,7 @@ function dispatchPlaybook(cmd, argv) {
|
|
|
1203
1242
|
"publisher-namespace", "mode", "scope", "playbook", "phase", "tlp",
|
|
1204
1243
|
"against", "since", "bundle-epoch", "attestation-root", "format",
|
|
1205
1244
|
"cwd", // exceptd collect <pb> --cwd <path>
|
|
1245
|
+
"limit", // exceptd attest list --limit <n>
|
|
1206
1246
|
]);
|
|
1207
1247
|
const verbAllowlist = flagsFor(cmd);
|
|
1208
1248
|
const allowlistSet = new Set(verbAllowlist);
|
|
@@ -1242,21 +1282,29 @@ function dispatchPlaybook(cmd, argv) {
|
|
|
1242
1282
|
}
|
|
1243
1283
|
continue;
|
|
1244
1284
|
}
|
|
1245
|
-
//
|
|
1246
|
-
//
|
|
1247
|
-
//
|
|
1248
|
-
//
|
|
1249
|
-
|
|
1250
|
-
//
|
|
1251
|
-
//
|
|
1285
|
+
// A flag valid on SOME other verb (e.g. `--csaf-status` on `brief`) is
|
|
1286
|
+
// cross-verb misuse — fall through so the verb handler can emit its
|
|
1287
|
+
// tailored "that flag belongs on a run-class verb" guidance rather than
|
|
1288
|
+
// a blanket refusal here.
|
|
1289
|
+
if (ALL_KNOWN_FLAGS.has(key)) continue;
|
|
1290
|
+
// Unknown everywhere — refuse it as a typo / unsupported flag. Silently
|
|
1291
|
+
// ignoring an unrecognized flag let a mistyped cap or output-format flag
|
|
1292
|
+
// look like it applied when it did nothing. Surface a suggestion
|
|
1293
|
+
// when one is close, and always list the accepted flags so the operator
|
|
1294
|
+
// can self-correct. Adding a new flag to a verb means appending it to
|
|
1295
|
+
// that verb's allowlist (or PASSTHROUGH_FLAGS) — the test suite exercises
|
|
1296
|
+
// every shipped flag, so a missing registration fails CI rather than
|
|
1297
|
+
// silently breaking the flag.
|
|
1252
1298
|
const suggestion = suggestFlag(key, verbAllowlist);
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1299
|
+
return emitError(
|
|
1300
|
+
`${cmd}: unknown flag --${key}`,
|
|
1301
|
+
{
|
|
1302
|
+
verb: cmd,
|
|
1303
|
+
unknown_flags: [{ flag: `--${key}`, did_you_mean: suggestion ? [`--${suggestion}`] : [] }],
|
|
1304
|
+
known_flags: verbAllowlist.filter((f) => typeof f === "string").sort().map((f) => `--${f}`),
|
|
1305
|
+
},
|
|
1306
|
+
pretty
|
|
1307
|
+
);
|
|
1260
1308
|
}
|
|
1261
1309
|
const runOpts = {
|
|
1262
1310
|
// Air-gap can be requested via the explicit flag OR the
|
|
@@ -1798,10 +1846,14 @@ Flags:
|
|
|
1798
1846
|
or not_affected | fixed (OpenVEX) drop out of
|
|
1799
1847
|
analyze.matched_cves. The disposition is preserved
|
|
1800
1848
|
under analyze.vex.dropped_cves.
|
|
1801
|
-
--format <fmt> ...
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1849
|
+
--format <fmt> ... Transform stdout. Supported: summary | markdown |
|
|
1850
|
+
csaf-2.0 | csaf | sarif | openvex | json (json = the
|
|
1851
|
+
full run result). Standardized bundles (csaf/sarif/
|
|
1852
|
+
openvex) are emitted as spec-conformant documents.
|
|
1853
|
+
Repeatable, but only ONE document goes to stdout — the
|
|
1854
|
+
first; every requested bundle is embedded under
|
|
1855
|
+
close.evidence_package.bundles_by_format (see via
|
|
1856
|
+
--json). Passing several prints a note to stderr.
|
|
1805
1857
|
--explain Dry-run: emit preconditions, required artifacts,
|
|
1806
1858
|
recognized signal keys, and a submission skeleton.
|
|
1807
1859
|
Does not run detect/analyze/validate/close.
|
|
@@ -2390,7 +2442,14 @@ function cmdCollect(runner, args, runOpts, pretty) {
|
|
|
2390
2442
|
// Spread `submission` first, then explicit fields, so a submission key
|
|
2391
2443
|
// named `air_gap_mode` (currently always undefined but defensive against
|
|
2392
2444
|
// future collector contracts) can't clobber the envelope marker.
|
|
2393
|
-
|
|
2445
|
+
const collectBody = { verb: "collect", playbook_id: playbookId, ...submission, air_gap_mode: collectAirGap };
|
|
2446
|
+
// collect's primary purpose is the pipe `exceptd collect <pb> | exceptd run
|
|
2447
|
+
// <pb> --evidence -`. When stdout is NOT a TTY (a pipe / redirect), emit JSON
|
|
2448
|
+
// so that one-liner just works; the human summary is only for an interactive
|
|
2449
|
+
// operator at a terminal. Explicit --json / --pretty force JSON regardless.
|
|
2450
|
+
// Without this gate, emit()'s default-human behavior printed a prose summary
|
|
2451
|
+
// into the pipe and the downstream `run --evidence -` failed to parse it.
|
|
2452
|
+
const collectHuman = process.stdout.isTTY ? (obj) => {
|
|
2394
2453
|
const lines = [];
|
|
2395
2454
|
const meta = obj.collector_meta || {};
|
|
2396
2455
|
lines.push(`collect: ${obj.playbook_id} (${meta.collector_version || "?"} on ${meta.platform || "?"})`);
|
|
@@ -2427,10 +2486,11 @@ function cmdCollect(runner, args, runOpts, pretty) {
|
|
|
2427
2486
|
}
|
|
2428
2487
|
if (errs.length > 5) lines.push(` … ${errs.length - 5} more`);
|
|
2429
2488
|
}
|
|
2430
|
-
lines.push(`\n→ next: exceptd collect ${obj.playbook_id}
|
|
2489
|
+
lines.push(`\n→ next: exceptd collect ${obj.playbook_id} | exceptd run ${obj.playbook_id} --evidence -`);
|
|
2431
2490
|
lines.push(`Full structured result: --json (or --pretty for indented JSON).`);
|
|
2432
2491
|
return lines.join("\n");
|
|
2433
|
-
}
|
|
2492
|
+
} : undefined;
|
|
2493
|
+
emit(collectBody, pretty, collectHuman);
|
|
2434
2494
|
}
|
|
2435
2495
|
|
|
2436
2496
|
function cmdLint(runner, args, runOpts, pretty) {
|
|
@@ -3480,7 +3540,8 @@ function cmdRun(runner, args, runOpts, pretty) {
|
|
|
3480
3540
|
// --format csaf-2.0/sarif/openvex → the corresponding bundle from close
|
|
3481
3541
|
// (default — no --format) → full JSON result as before
|
|
3482
3542
|
if (args.format) {
|
|
3483
|
-
const
|
|
3543
|
+
const requestedAll = Array.isArray(args.format) ? args.format : [args.format];
|
|
3544
|
+
const requested = requestedAll[0];
|
|
3484
3545
|
const VALID = ["summary", "markdown", "csaf-2.0", "csaf", "sarif", "openvex", "json"];
|
|
3485
3546
|
if (!VALID.includes(requested)) {
|
|
3486
3547
|
const dym = suggestFlag(String(requested), VALID);
|
|
@@ -3491,6 +3552,25 @@ function cmdRun(runner, args, runOpts, pretty) {
|
|
|
3491
3552
|
pretty,
|
|
3492
3553
|
);
|
|
3493
3554
|
}
|
|
3555
|
+
// Only one document can be written to stdout. When several --format values
|
|
3556
|
+
// are given, emit the first and tell the operator where the rest live so
|
|
3557
|
+
// the extras aren't silently dropped.
|
|
3558
|
+
if (requestedAll.length > 1) {
|
|
3559
|
+
process.stderr.write(
|
|
3560
|
+
`[exceptd] note: ${requestedAll.length} --format values given; emitting "${requested}" to stdout. ` +
|
|
3561
|
+
`All requested bundles are embedded under phases.close.evidence_package.bundles_by_format — ` +
|
|
3562
|
+
`re-run with --json to see them.\n`
|
|
3563
|
+
);
|
|
3564
|
+
}
|
|
3565
|
+
// `json` means "the full run result as JSON" — the same body the default
|
|
3566
|
+
// (no --format) path emits. Without this it fell through to the bundle
|
|
3567
|
+
// lookup, found the runner's "unknown format" stub under
|
|
3568
|
+
// bundles_by_format.json, and emitted that 150-byte stub instead of the
|
|
3569
|
+
// scan — silently discarding the result with a success exit code.
|
|
3570
|
+
if (requested === "json") {
|
|
3571
|
+
emit(result, pretty);
|
|
3572
|
+
return;
|
|
3573
|
+
}
|
|
3494
3574
|
if (requested === "summary") {
|
|
3495
3575
|
const cls = result.phases?.detect?.classification;
|
|
3496
3576
|
const rwep = result.phases?.analyze?.rwep?.adjusted ?? 0;
|
|
@@ -3546,7 +3626,15 @@ function cmdRun(runner, args, runOpts, pretty) {
|
|
|
3546
3626
|
const bbf = result.phases?.close?.evidence_package?.bundles_by_format || {};
|
|
3547
3627
|
const body = bbf[formatNorm] || result.phases?.close?.evidence_package?.bundle_body;
|
|
3548
3628
|
if (body) {
|
|
3549
|
-
|
|
3629
|
+
// SARIF / CSAF / OpenVEX are self-describing standard documents. Write
|
|
3630
|
+
// them verbatim rather than through emit(), which prepends the tool's
|
|
3631
|
+
// own `ok` envelope key — `ok` is not a permitted top-level property in
|
|
3632
|
+
// any of these schemas and makes strict validators (GitHub code-scanning
|
|
3633
|
+
// SARIF upload, a CSAF trusted-provider check) reject the output. The
|
|
3634
|
+
// namespaced `exceptd_extension` block (CSAF vendor extension carrying
|
|
3635
|
+
// publisher-namespace provenance) is preserved intentionally.
|
|
3636
|
+
const { ok: _ok, ...spec } = body;
|
|
3637
|
+
process.stdout.write(JSON.stringify(spec, null, pretty ? 2 : 0) + "\n");
|
|
3550
3638
|
return;
|
|
3551
3639
|
}
|
|
3552
3640
|
// Fallback: full result
|
|
@@ -7040,10 +7128,28 @@ function cmdListAttestations(runner, args, runOpts, pretty) {
|
|
|
7040
7128
|
}
|
|
7041
7129
|
}
|
|
7042
7130
|
entries.sort((a, b) => (b.captured_at || "").localeCompare(a.captured_at || ""));
|
|
7131
|
+
const total = entries.length;
|
|
7132
|
+
// --limit caps the inventory (newest first). Without it, JSON returns every
|
|
7133
|
+
// session and the human table shows the first 50 with an "… and N more"
|
|
7134
|
+
// footer. With it, both surfaces honor the cap and report `total`.
|
|
7135
|
+
let limitN = null;
|
|
7136
|
+
if (args.limit != null) {
|
|
7137
|
+
limitN = Number(args.limit);
|
|
7138
|
+
if (!Number.isInteger(limitN) || limitN < 0) {
|
|
7139
|
+
return emitError(
|
|
7140
|
+
`attest list: --limit must be a non-negative integer; got ${JSON.stringify(String(args.limit))}.`,
|
|
7141
|
+
{ verb: "attest list", provided: args.limit },
|
|
7142
|
+
pretty,
|
|
7143
|
+
);
|
|
7144
|
+
}
|
|
7145
|
+
}
|
|
7146
|
+
const shown = limitN != null ? entries.slice(0, limitN) : entries;
|
|
7043
7147
|
emit({
|
|
7044
7148
|
ok: true,
|
|
7045
|
-
attestations:
|
|
7046
|
-
count:
|
|
7149
|
+
attestations: shown,
|
|
7150
|
+
count: total,
|
|
7151
|
+
shown: shown.length,
|
|
7152
|
+
limit: limitN,
|
|
7047
7153
|
filter: { playbook: playbookFilter ? [...playbookFilter] : null, since: args.since || null },
|
|
7048
7154
|
roots_searched: [...seenRoots],
|
|
7049
7155
|
// Cycle 11 F5 (v0.12.32): every candidate root + whether it existed,
|
|
@@ -7068,10 +7174,17 @@ function cmdListAttestations(runner, args, runOpts, pretty) {
|
|
|
7068
7174
|
}
|
|
7069
7175
|
lines.push(` ${"session-id".padEnd(20)} ${"playbook".padEnd(16)} ${"captured-at".padEnd(20)} evidence-hash`);
|
|
7070
7176
|
lines.push(` ${"-".repeat(20)} ${"-".repeat(16)} ${"-".repeat(20)} ${"-".repeat(20)}`);
|
|
7071
|
-
|
|
7177
|
+
// When --limit was given, obj.attestations is already capped; show all of
|
|
7178
|
+
// it. Otherwise show the first 50 and footer the remainder.
|
|
7179
|
+
const rows = obj.limit != null ? obj.attestations : obj.attestations.slice(0, 50);
|
|
7180
|
+
for (const e of rows) {
|
|
7072
7181
|
lines.push(` ${(e.session_id || "?").padEnd(20)} ${(e.playbook_id || "?").padEnd(16)} ${(e.captured_at || "").slice(0, 19).padEnd(20)} ${e.evidence_hash || ""}`);
|
|
7073
7182
|
}
|
|
7074
|
-
if (obj.
|
|
7183
|
+
if (obj.limit != null) {
|
|
7184
|
+
if (obj.count > rows.length) lines.push(` showing ${rows.length} of ${obj.count} (raise --limit or use --json for the full list)`);
|
|
7185
|
+
} else if (obj.count > 50) {
|
|
7186
|
+
lines.push(` … and ${obj.count - 50} more (use --limit <n> or --json for the full list)`);
|
|
7187
|
+
}
|
|
7075
7188
|
return lines.join("\n");
|
|
7076
7189
|
});
|
|
7077
7190
|
}
|
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-27T12:31:54.921Z",
|
|
4
4
|
"generator": "scripts/build-indexes.js",
|
|
5
5
|
"source_count": 54,
|
|
6
6
|
"source_hashes": {
|
|
7
|
-
"manifest.json": "
|
|
7
|
+
"manifest.json": "cc72d668222e9cd18eaef7928e173ad17434ff4b411235bbba2b13fca722db9e",
|
|
8
8
|
"data/atlas-ttps.json": "d24bc02859d40ccf1615db75cca68c077585904e41e0d8f6de448121e9b1abb0",
|
|
9
9
|
"data/attack-techniques.json": "fa193f0d2d248176a8beddb641e9fe56ba4faa9e15dc253ff876dbf0c5d58a77",
|
|
10
10
|
"data/cve-catalog.json": "3d451dda7ac0c7d57a4075ae4bafd3148c6184b35dc1bc59d8b81d1f2641e430",
|