@blamejs/exceptd-skills 0.12.28 → 0.12.30
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 +1 -1
- package/CHANGELOG.md +53 -0
- package/bin/exceptd.js +30 -20
- package/data/_indexes/_meta.json +9 -9
- package/data/_indexes/activity-feed.json +7 -7
- package/data/_indexes/chains.json +9 -9
- package/data/_indexes/currency.json +43 -43
- package/data/_indexes/stale-content.json +1 -1
- package/data/atlas-ttps.json +61 -111
- package/data/cve-catalog.json +136 -65
- package/data/cwe-catalog.json +151 -95
- package/data/d3fend-catalog.json +201 -54
- package/data/dlp-controls.json +2 -1
- package/data/framework-control-gaps.json +1214 -110
- package/data/playbooks/crypto-codebase.json +1 -1
- package/data/rfc-references.json +23 -67
- package/lib/exit-codes.js +2 -0
- package/lib/playbook-runner.js +25 -1
- package/manifest-snapshot.json +2 -2
- package/manifest-snapshot.sha256 +1 -1
- package/manifest.json +49 -48
- package/package.json +3 -2
- package/sbom.cdx.json +1853 -10
- package/scripts/backfill-theater-test.js +806 -0
- package/scripts/check-test-coverage.js +18 -4
- package/scripts/refresh-reverse-refs.js +171 -0
- package/scripts/refresh-sbom.js +155 -8
package/AGENTS.md
CHANGED
|
@@ -139,7 +139,7 @@ Cross-cutting playbook `framework` is the natural correlation layer — many pla
|
|
|
139
139
|
|
|
140
140
|
| Verb | What it does |
|
|
141
141
|
|---|---|
|
|
142
|
-
| `exceptd brief --all` | Grouped-by-scope summary of all
|
|
142
|
+
| `exceptd brief --all` | Grouped-by-scope summary of all 16 playbooks. `--scope <type>` filters. `--directives` expands directive IDs/titles per playbook. `--flat` for non-grouped. Legacy alias: `exceptd plan` (deprecated, scheduled for removal in v0.13). |
|
|
143
143
|
| `exceptd brief <pb>` | Phase 2 threat-context briefing — threat context, RWEP thresholds, skill chain, token budget, jurisdiction obligations. |
|
|
144
144
|
| `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. |
|
|
145
145
|
| `exceptd ai-run <pb>` | Streaming variant of `run` for AI agents; emits phase-by-phase NDJSON. |
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,58 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.12.30 — 2026-05-15
|
|
4
|
+
|
|
5
|
+
Catalog scoring honesty pass + diff-coverage gate tightening from the cycle 10 audit. Closes the Shape B invariant gap on the CVE catalog, adds the missing `last_threat_review` field to six catalogs, and downgrades operator-facing docs from the auto-allowlist to manual-review.
|
|
6
|
+
|
|
7
|
+
### Features
|
|
8
|
+
|
|
9
|
+
**Shape B invariant enforced on every CVE.** `lib/scoring.js` documents that `Σ Object.values(rwep_factors) === rwep_score` is an invariant on every catalog entry, but the existing `validate()` function never enforced it — it computed via `scoreCustom()` (clamps `blast_radius` to 30, uses canonical weights) which masked dishonest factor blocks as long as the stored score happened to match the clamped formula. Fourteen entries had non-canonical factor values that summed to a different number than the stored score (CVE-2026-GTIG-AI-2FA, CVE-2026-42945, CVE-2024-3094, CVE-2024-21626, CVE-2023-3519, CVE-2026-20182, CVE-2024-40635, CVE-2025-12686, CVE-2025-62847, CVE-2025-62848, CVE-2025-62849, CVE-2025-59389, MAL-2026-TANSTACK-MINI, MAL-2026-ANTHROPIC-MCP-STDIO). All canonicalized — factor weights now derived from the operational fields (`cisa_kev`, `poc_available`, `ai_discovered`, `active_exploitation`, `blast_radius`, `patch_available`, `live_patch_available`, `patch_required_reboot`) via `lib/scoring.js` `RWEP_WEIGHTS` + `ACTIVE_EXPLOITATION_LADDER`. Where `blast_radius` exceeded the 30 cap (4 entries had values of 40), the value was clamped, which adjusted seven stored `rwep_score` values by ±5; each carries a `rwep_correction_note` documenting the delta. New `tests/cve-rwep-shape-b-invariant.test.js` blocks future drift with an exact-delta assertion.
|
|
10
|
+
|
|
11
|
+
**Operator-facing docs downgraded from auto-allowlist to manual-review.** Cycle 9 P3 finding: `CHANGELOG.md`, `README.md`, `SECURITY.md`, `MIGRATING.md`, and `AGENTS.md` were in the diff-coverage gate's `DOCS_ALWAYS_GREEN` set — a PR could land arbitrary edits to release notes, install instructions, security disclosure policy, or AI-assistant ground truth without triggering any reviewer signal. New `DOCS_MANUAL_REVIEW` set routes them to "manual-review" instead, surfacing the diff in the gate output. Contributor-only / mechanical files (`CONTRIBUTING.md`, `CODE_OF_CONDUCT.md`, `LICENSE`, `NOTICE`, `SUPPORT.md`, `.gitignore`, `.npmrc`, `.editorconfig`, `CLAUDE.md`) stay always-green.
|
|
12
|
+
|
|
13
|
+
**`last_threat_review` mandatory on every catalog _meta.** Cycle 10 finding: `cve-catalog.json`, `cwe-catalog.json`, `d3fend-catalog.json`, `dlp-controls.json`, `rfc-references.json`, and `framework-control-gaps.json` carried only `last_updated` without the more specific `last_threat_review`. Hard Rule #8 makes per-catalog threat-review currency a release-blocker after a stated window; all six catalogs now carry the field. New `tests/threat-review-staleness.test.js` enforces presence + a 30-day staleness window between `manifest.threat_review_date` and every skill's `last_threat_review`.
|
|
14
|
+
|
|
15
|
+
### Bugs
|
|
16
|
+
|
|
17
|
+
- `CVE-2026-42208` `discovery_attribution_note` misattributed discovery to Sysdig Threat Research Team. The actual credited discoverer is Tencent YunDing Security Lab per the LiteLLM GHSA-r75f-5x8p-qvmc advisory; Sysdig published only post-disclosure exploitation telemetry. Attribution corrected; sources updated.
|
|
18
|
+
|
|
19
|
+
### Internal
|
|
20
|
+
|
|
21
|
+
- AI-discovery rate stays at 20% after cycle 10 deep-research pass (24 currently-false CVEs WebSearch'd; zero credible flips found). Methodology block updated: the 40% target reflects the broader 2025 zero-day population (Google Threat Intelligence Group), but the curated exceptd catalog is weighted toward Pwn2Own Ireland 2025 entries, historical anchors (CVE-2020-10148, CVE-2024-3094, etc.), and supply-chain incidents — none of which carry public AI-tool credit. Advancing the ladder from 20% → 30% → 40% will happen as the catalog rotates toward 2026 Big Sleep / AIxCC / GTIG-attributed entries; forcing flips on the current population would violate Hard Rule #1 (no speculation).
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
## 0.12.29 — 2026-05-15
|
|
25
|
+
|
|
26
|
+
Catalog hygiene + pipeline integrity pass. Closes Hard Rule #1, #6, #7, and #8 gaps that had accumulated across the 2025-2026 catalog growth; tightens the SBOM + OpenVEX + exit-code surfaces.
|
|
27
|
+
|
|
28
|
+
### Features
|
|
29
|
+
|
|
30
|
+
**Compliance-theater test on every framework gap.** Every entry in the framework-control-gaps catalog (109 entries spanning NIST 800-53, ISO/IEC 27001/27017/27035/42001, SOC 2, UK CAF, AU ISM/Essential 8, EU DORA, EU NIS2, EU AI Act, HIPAA, PCI DSS, FedRAMP, CMMC, HITRUST, IEC 62443, OWASP, telecom standards, ransomware-class gaps, and OFAC sanctions screening) now carries a `theater_test` field with a falsifiable test that distinguishes paper compliance from actual security. Closes Hard Rule #6. Sample shape: `{claim, test, evidence_required[], verdict_when_failed: "compliance-theater"}`. The test must reference a concrete artifact (audit log, config dump, tabletop exercise stopwatch) whose result is binary.
|
|
31
|
+
|
|
32
|
+
**SBOM per-file SHA-256 + bundle digest.** `sbom.cdx.json` now includes `metadata.component.hashes[]` (bundle digest, SHA-256) and one `components[type=file]` entry per shipped file with its own SHA-256. Downstream supply-chain consumers can verify any individual file against the bundle. Excludes the regenerable `data/_indexes/` cache from per-file inventory (covered by the `Pre-computed indexes freshness` gate instead). Also corrects `metadata.tools` from the placeholder `name: "hand-written"` to the real generator script and bound package version.
|
|
33
|
+
|
|
34
|
+
**OpenVEX `author` threads operator attribution.** Previously hard-pinned to `"exceptd"`, which falsely attributed every disposition statement to the tooling vendor. Now mirrors the CSAF publisher.namespace fallback ladder: `runOpts.publisherNamespace` → `runOpts.operator` → `urn:exceptd:operator:unknown` with a `bundle_publisher_unclaimed` runtime warning. Operators running scans correctly own their dispositions.
|
|
35
|
+
|
|
36
|
+
**Exit code 10: UNKNOWN_COMMAND.** The dispatcher's unknown-command / missing-script / spawn-error paths previously exited 2, colliding with `EXIT_CODES.DETECTED_ESCALATE` semantics. Split into `EXIT_CODES.UNKNOWN_COMMAND = 10`. CI gates wiring `case 2)` for escalation triage no longer false-alarm on operator typos. Same regression class v0.12.24 closed for the SESSION_ID_COLLISION / RAN_NO_EVIDENCE code-3 collision.
|
|
37
|
+
|
|
38
|
+
**Reverse-reference auto-regeneration.** New `npm run refresh-reverse-refs` rebuilds the `skills_referencing` / `exceptd_skills` arrays on `data/atlas-ttps.json`, `data/cwe-catalog.json`, `data/d3fend-catalog.json`, and `data/rfc-references.json` from the manifest forward direction. Idempotent. A new `tests/reverse-ref-drift.test.js` blocks merges that leave the reverse direction out of sync with the manifest — eliminates the one-sided-reference drift class that audits have flagged repeatedly.
|
|
39
|
+
|
|
40
|
+
### Bugs
|
|
41
|
+
|
|
42
|
+
- `crypto-codebase` `feeds_into` condition used the unsupported `contains` operator; the chain to the `secrets` playbook never fired. Replaced with `analyze.classification == 'detected'`. Same class of bug v0.12.28 corrected on the IR-cluster playbooks.
|
|
43
|
+
- Manifest `atlas_version` / `attack_version` had drifted to v5.1.0 / v17 while the data catalogs already pinned v5.4.0 / v19.0. Manifest now matches the catalogs and AGENTS.md ground truth.
|
|
44
|
+
- 14 sites in `bin/exceptd.js` used bare numeric `process.exitCode = 1` / `finish(1)` / `finish(0)` instead of `EXIT_CODES.*` constants. All migrated to the constant.
|
|
45
|
+
- `cmdCi` per-id loop called `runner.loadPlaybook(id)` without first running `validateIdComponent('playbook')` — a defense-in-depth gap relative to `cmdRunMulti`. Now validates before load.
|
|
46
|
+
|
|
47
|
+
### Internal
|
|
48
|
+
|
|
49
|
+
- AI-discovery rate on `data/cve-catalog.json` moves 10% → 20% with three new flag flips backed by citations: CVE-2026-43284 + CVE-2026-43500 (Dirty Frag pair, Hyunwoo Kim with AI-assisted methodology per Sysdig); CVE-2026-46300 (Fragnesia, William Bowling using Zellic.io's AI agentic auditor). All other CVEs gain a `discovery_attribution_note` field citing the human researcher or vendor team. New `_meta.ai_discovery_methodology` block documents the 20%/30%/40% advancement ladder against the AGENTS.md Hard Rule #7 target. Gap to 40% explicitly tracked.
|
|
50
|
+
- AGENTS.md Quick Skill Reference: playbook count "all 13 playbooks" → "all 16 playbooks".
|
|
51
|
+
- `package.json.description`: "38 skills" → "42 skills".
|
|
52
|
+
- 22 reverse-reference entries across 4 catalogs cleaned up by the new regen script (atlas: 30 entries changed, cwe: 46, d3fend: 28, rfc: 22).
|
|
53
|
+
- Test suite 1064 → 1082 (six new test files: framework-gaps-theater-test-coverage, cve-ai-discovery-attribution, sbom-per-file-hash, reverse-ref-drift, plus updates to bin-dispatcher, cli-exit-codes, lib-exit-codes, cve-additions-v0-12-21 for the new contract).
|
|
54
|
+
|
|
55
|
+
|
|
3
56
|
## 0.12.28 — 2026-05-15
|
|
4
57
|
|
|
5
58
|
Incident-response cluster — three new playbooks and skills covering identity-provider tenant compromise, cloud-IAM account takeover, and ransomware response. The existing `incident-response-playbook` skill stays as the generic PICERL backbone; the new surface adds attack-class-specific depth for the three IR scenarios that dominate 2025-2026 breach reporting.
|
package/bin/exceptd.js
CHANGED
|
@@ -516,10 +516,11 @@ function main() {
|
|
|
516
516
|
// Emit a structured JSON error matching the seven-phase verbs so operators
|
|
517
517
|
// piping through `jq` get one consistent shape across the CLI surface.
|
|
518
518
|
// emitError() sets exitCode + returns rather than calling process.exit()
|
|
519
|
-
// so the stderr JSON drains before teardown; promote the exit code to
|
|
520
|
-
//
|
|
519
|
+
// so the stderr JSON drains before teardown; promote the exit code to
|
|
520
|
+
// UNKNOWN_COMMAND (10) afterwards. Cycle 9 split this away from
|
|
521
|
+
// DETECTED_ESCALATE (2) — the two semantics had collided since v0.12.24.
|
|
521
522
|
emitError(`unknown command "${cmd}"`, { hint: "Run `exceptd help` for the list of verbs.", verb: cmd });
|
|
522
|
-
process.exitCode =
|
|
523
|
+
process.exitCode = EXIT_CODES.UNKNOWN_COMMAND;
|
|
523
524
|
return;
|
|
524
525
|
}
|
|
525
526
|
|
|
@@ -530,7 +531,7 @@ function main() {
|
|
|
530
531
|
`command "${cmd}" not available — expected ${path.relative(PKG_ROOT, script)} in the installed package.`,
|
|
531
532
|
{ verb: cmd }
|
|
532
533
|
);
|
|
533
|
-
process.exitCode =
|
|
534
|
+
process.exitCode = EXIT_CODES.UNKNOWN_COMMAND;
|
|
534
535
|
return;
|
|
535
536
|
}
|
|
536
537
|
|
|
@@ -541,7 +542,7 @@ function main() {
|
|
|
541
542
|
if (res.error) {
|
|
542
543
|
// emitError + exitCode rather than stderr + exit() so the JSON drains.
|
|
543
544
|
emitError(`failed to run ${cmd}: ${res.error.message}`, { verb: cmd });
|
|
544
|
-
process.exitCode =
|
|
545
|
+
process.exitCode = EXIT_CODES.UNKNOWN_COMMAND;
|
|
545
546
|
return;
|
|
546
547
|
}
|
|
547
548
|
// Propagate the child's exit status via exitCode so any buffered output
|
|
@@ -615,7 +616,7 @@ function emit(obj, pretty, humanRenderer) {
|
|
|
615
616
|
// and CI gates. The previous fix was per-verb; this is a universal catch
|
|
616
617
|
// so new verbs / new ok:false paths can't regress the contract.
|
|
617
618
|
if (obj && obj.ok === false && !process.exitCode) {
|
|
618
|
-
process.exitCode =
|
|
619
|
+
process.exitCode = EXIT_CODES.GENERIC_FAILURE;
|
|
619
620
|
}
|
|
620
621
|
const wantJson = !!global.__exceptdWantJson || !!process.env.EXCEPTD_RAW_JSON;
|
|
621
622
|
if (humanRenderer && !wantJson && !pretty) {
|
|
@@ -638,7 +639,7 @@ function emitError(msg, extra, pretty) {
|
|
|
638
639
|
const body = Object.assign({ ok: false, error: msg }, extra || {});
|
|
639
640
|
const s = pretty ? JSON.stringify(body, null, 2) : JSON.stringify(body);
|
|
640
641
|
process.stderr.write(s + "\n");
|
|
641
|
-
process.exitCode =
|
|
642
|
+
process.exitCode = EXIT_CODES.GENERIC_FAILURE;
|
|
642
643
|
}
|
|
643
644
|
|
|
644
645
|
/**
|
|
@@ -2107,7 +2108,7 @@ function cmdLint(runner, args, runOpts, pretty) {
|
|
|
2107
2108
|
}
|
|
2108
2109
|
return lines.join("\n");
|
|
2109
2110
|
});
|
|
2110
|
-
if (!ok) process.exitCode =
|
|
2111
|
+
if (!ok) process.exitCode = EXIT_CODES.GENERIC_FAILURE;
|
|
2111
2112
|
}
|
|
2112
2113
|
|
|
2113
2114
|
function cmdBrief(runner, args, runOpts, pretty) {
|
|
@@ -3398,7 +3399,7 @@ function cmdIngest(runner, args, runOpts, pretty) {
|
|
|
3398
3399
|
|
|
3399
3400
|
if (result && result.ok === false) {
|
|
3400
3401
|
process.stderr.write((pretty ? JSON.stringify(result, null, 2) : JSON.stringify(result)) + "\n");
|
|
3401
|
-
process.exitCode =
|
|
3402
|
+
process.exitCode = EXIT_CODES.GENERIC_FAILURE;
|
|
3402
3403
|
return;
|
|
3403
3404
|
}
|
|
3404
3405
|
emit(result, pretty);
|
|
@@ -5276,7 +5277,7 @@ function cmdDoctor(runner, args, runOpts, pretty) {
|
|
|
5276
5277
|
|
|
5277
5278
|
if (wantJson) {
|
|
5278
5279
|
emit(out, indent);
|
|
5279
|
-
if (!allGreen) process.exitCode =
|
|
5280
|
+
if (!allGreen) process.exitCode = EXIT_CODES.GENERIC_FAILURE;
|
|
5280
5281
|
return;
|
|
5281
5282
|
}
|
|
5282
5283
|
|
|
@@ -5367,10 +5368,10 @@ function cmdDoctor(runner, args, runOpts, pretty) {
|
|
|
5367
5368
|
process.stdout.write(`\n[doctor --fix] ${out.summary.fix_applied} — re-run \`exceptd doctor\` to confirm.\n`);
|
|
5368
5369
|
} else if (out.summary.fix_attempted) {
|
|
5369
5370
|
process.stdout.write(`\n[doctor --fix] ${out.summary.fix_attempted} (exit=${out.summary.fix_exit_code}); run \`node lib/sign.js generate-keypair\` manually.\n`);
|
|
5370
|
-
process.exitCode =
|
|
5371
|
+
process.exitCode = EXIT_CODES.GENERIC_FAILURE;
|
|
5371
5372
|
return;
|
|
5372
5373
|
}
|
|
5373
|
-
if (errorList.length > 0) process.exitCode =
|
|
5374
|
+
if (errorList.length > 0) process.exitCode = EXIT_CODES.GENERIC_FAILURE;
|
|
5374
5375
|
// Warnings alone do NOT force exit 1 — CI gates use exit 0 to mean "ran
|
|
5375
5376
|
// successfully" even with informational warnings. Operators reading the
|
|
5376
5377
|
// visible "[!! warn]" line still see the issue.
|
|
@@ -5502,7 +5503,7 @@ function cmdAiRun(runner, args, runOpts, pretty) {
|
|
|
5502
5503
|
// the framed error event so the stdout-only JSONL contract holds — host
|
|
5503
5504
|
// AIs reading this stream must see structured frames, never bare text.
|
|
5504
5505
|
process.stdout.write(JSON.stringify({ event: "error", reason: e.message, phase: "info", playbook_id: playbookId, directive_id: directiveId }) + "\n");
|
|
5505
|
-
process.exitCode =
|
|
5506
|
+
process.exitCode = EXIT_CODES.GENERIC_FAILURE;
|
|
5506
5507
|
return;
|
|
5507
5508
|
}
|
|
5508
5509
|
|
|
@@ -5589,7 +5590,7 @@ function cmdAiRun(runner, args, runOpts, pretty) {
|
|
|
5589
5590
|
// v0.12.12: same exit-after-write anti-pattern as the pre-stream
|
|
5590
5591
|
// load path. Use exitCode + return so stderr drains.
|
|
5591
5592
|
process.stderr.write((pretty ? JSON.stringify(result || {}, null, 2) : JSON.stringify(result || {})) + "\n");
|
|
5592
|
-
process.exitCode =
|
|
5593
|
+
process.exitCode = EXIT_CODES.GENERIC_FAILURE;
|
|
5593
5594
|
return;
|
|
5594
5595
|
}
|
|
5595
5596
|
// v0.12.14: ai-run --no-stream previously emitted a
|
|
@@ -5696,7 +5697,7 @@ function cmdAiRun(runner, args, runOpts, pretty) {
|
|
|
5696
5697
|
catch (e) {
|
|
5697
5698
|
handled = true;
|
|
5698
5699
|
writeLine({ event: "error", reason: `invalid JSON on stdin: ${e.message}`, line_preview: line.slice(0, 120) });
|
|
5699
|
-
return finish(
|
|
5700
|
+
return finish(EXIT_CODES.GENERIC_FAILURE);
|
|
5700
5701
|
}
|
|
5701
5702
|
if (!parsed || parsed.event !== "evidence" || !parsed.payload) {
|
|
5702
5703
|
// Ignore non-evidence chatter so the host AI can interleave its own
|
|
@@ -5710,11 +5711,11 @@ function cmdAiRun(runner, args, runOpts, pretty) {
|
|
|
5710
5711
|
result = runner.run(playbookId, directiveId, submission, runOpts);
|
|
5711
5712
|
} catch (e) {
|
|
5712
5713
|
writeLine({ event: "error", reason: `runner threw: ${e.message}` });
|
|
5713
|
-
return finish(
|
|
5714
|
+
return finish(EXIT_CODES.GENERIC_FAILURE);
|
|
5714
5715
|
}
|
|
5715
5716
|
if (!result || result.ok === false) {
|
|
5716
5717
|
writeLine({ event: "error", reason: result?.reason || "runner returned ok:false", result });
|
|
5717
|
-
return finish(
|
|
5718
|
+
return finish(EXIT_CODES.GENERIC_FAILURE);
|
|
5718
5719
|
}
|
|
5719
5720
|
writeLine({ phase: "detect", ...result.phases?.detect });
|
|
5720
5721
|
writeLine({ phase: "analyze", ...result.phases?.analyze });
|
|
@@ -5759,7 +5760,7 @@ function cmdAiRun(runner, args, runOpts, pretty) {
|
|
|
5759
5760
|
}
|
|
5760
5761
|
}
|
|
5761
5762
|
writeLine({ event: "done", ok: true, session_id: result.session_id, evidence_hash: result.evidence_hash });
|
|
5762
|
-
return finish(
|
|
5763
|
+
return finish(EXIT_CODES.SUCCESS);
|
|
5763
5764
|
};
|
|
5764
5765
|
|
|
5765
5766
|
// Handle empty/closed stdin: emit a hint then exit cleanly so AI agents
|
|
@@ -5767,7 +5768,7 @@ function cmdAiRun(runner, args, runOpts, pretty) {
|
|
|
5767
5768
|
// a hung process.
|
|
5768
5769
|
if (process.stdin.isTTY) {
|
|
5769
5770
|
writeLine({ event: "error", reason: "ai-run streaming mode requires evidence on stdin; pipe {\"event\":\"evidence\",\"payload\":{...}} or use --no-stream." });
|
|
5770
|
-
process.exitCode =
|
|
5771
|
+
process.exitCode = EXIT_CODES.GENERIC_FAILURE;
|
|
5771
5772
|
return;
|
|
5772
5773
|
}
|
|
5773
5774
|
|
|
@@ -5803,7 +5804,7 @@ function cmdAiRun(runner, args, runOpts, pretty) {
|
|
|
5803
5804
|
} catch { /* fall through to error */ }
|
|
5804
5805
|
}
|
|
5805
5806
|
writeLine({ event: "error", reason: "stdin closed without an evidence event. Pipe `{\"event\":\"evidence\",\"payload\":{...}}` for streaming mode, or pass --no-stream + --evidence <file> for single-shot." });
|
|
5806
|
-
process.exitCode =
|
|
5807
|
+
process.exitCode = EXIT_CODES.GENERIC_FAILURE;
|
|
5807
5808
|
return;
|
|
5808
5809
|
}
|
|
5809
5810
|
});
|
|
@@ -6061,6 +6062,15 @@ function cmdCi(runner, args, runOpts, pretty) {
|
|
|
6061
6062
|
let clockStartedReasons = [];
|
|
6062
6063
|
|
|
6063
6064
|
for (const id of ids) {
|
|
6065
|
+
// Cycle 9 B4: defense-in-depth — validate id even though the catalog-iter
|
|
6066
|
+
// upstream is trusted. A corrupt catalog returning a malformed id would
|
|
6067
|
+
// otherwise reach loadPlaybook unchecked. Matches the cmdRunMulti pattern.
|
|
6068
|
+
const idCheck = validateIdComponent(id, "playbook");
|
|
6069
|
+
if (!idCheck.ok) {
|
|
6070
|
+
results.push({ playbook_id: id, ok: false, error: idCheck.reason });
|
|
6071
|
+
fail = true;
|
|
6072
|
+
continue;
|
|
6073
|
+
}
|
|
6064
6074
|
let pb;
|
|
6065
6075
|
try { pb = runner.loadPlaybook(id); }
|
|
6066
6076
|
catch (e) { results.push({ playbook_id: id, ok: false, error: e.message }); fail = true; continue; }
|
package/data/_indexes/_meta.json
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema_version": "1.1.0",
|
|
3
|
-
"generated_at": "2026-05-
|
|
3
|
+
"generated_at": "2026-05-16T02:16:03.581Z",
|
|
4
4
|
"generator": "scripts/build-indexes.js",
|
|
5
5
|
"source_count": 54,
|
|
6
6
|
"source_hashes": {
|
|
7
|
-
"manifest.json": "
|
|
8
|
-
"data/atlas-ttps.json": "
|
|
7
|
+
"manifest.json": "a9d4ea238c6c91f6d12ac8ce8c46fe096c3e448bc53ae1776932bc1c6779984d",
|
|
8
|
+
"data/atlas-ttps.json": "259e76e4252c7a56c17bbe96982a5e37ac89131c2d37a547fe38d64dcacfd763",
|
|
9
9
|
"data/attack-techniques.json": "51f60819aef36e960fd768e44dcc725e137781534fbbb028e5ef6baa21defa1d",
|
|
10
|
-
"data/cve-catalog.json": "
|
|
11
|
-
"data/cwe-catalog.json": "
|
|
12
|
-
"data/d3fend-catalog.json": "
|
|
13
|
-
"data/dlp-controls.json": "
|
|
10
|
+
"data/cve-catalog.json": "0802d09b5783d01ec99d0d629942adbd81844e3cf6ed2f39885b362f57841abd",
|
|
11
|
+
"data/cwe-catalog.json": "f4bdc070b94d5b829f541aab34e21f86b133e9750ce9bef2d0b3e141c880bd33",
|
|
12
|
+
"data/d3fend-catalog.json": "35f076cd65d82ac97db90b72e884ec7ab2895c052567ee7d0c579c1965e6baaf",
|
|
13
|
+
"data/dlp-controls.json": "d2406c482dddd30e49203879999dc4b3a7fd4d0494d6a61d86b91ee76415df19",
|
|
14
14
|
"data/exploit-availability.json": "a9eeda95d24b56c28a0d0178fc601b531653e2ba7dc857160b35ad23ad6c7471",
|
|
15
|
-
"data/framework-control-gaps.json": "
|
|
15
|
+
"data/framework-control-gaps.json": "d7e40e7d5edcdcb1573905a9a01ef31962030b4a16e0a138a4c733f00c8701d1",
|
|
16
16
|
"data/global-frameworks.json": "0168825497e03f079274c9da2e5529310a2ba5bd7c7da7c93acd0b66ed845b8a",
|
|
17
|
-
"data/rfc-references.json": "
|
|
17
|
+
"data/rfc-references.json": "e253a548c8a829d178d5aea601e268724b85c936ccbfa51c2e5d80c5f8efe2b0",
|
|
18
18
|
"data/zeroday-lessons.json": "d960e5f8ca7a83c10194cd60207e13046a7eee1b8793e2f3de79475db283f800",
|
|
19
19
|
"skills/kernel-lpe-triage/skill.md": "8e94bfd38d6db47342fbbe95a0c8df8f7c38743982c13e9de6a1c59cd3783d33",
|
|
20
20
|
"skills/ai-attack-surface/skill.md": "13e543fc92b9b27cdb647dce96a9eeb44919e0fa92ec41e8265a9981a23e7b79",
|
|
@@ -89,6 +89,13 @@
|
|
|
89
89
|
"schema_version": "1.1.0",
|
|
90
90
|
"entry_count": 15
|
|
91
91
|
},
|
|
92
|
+
{
|
|
93
|
+
"date": "2026-05-15",
|
|
94
|
+
"type": "manifest_review",
|
|
95
|
+
"artifact": "manifest.json",
|
|
96
|
+
"path": "manifest.json",
|
|
97
|
+
"note": "manifest threat_review_date — 42 skills, 11 catalogs"
|
|
98
|
+
},
|
|
92
99
|
{
|
|
93
100
|
"date": "2026-05-13",
|
|
94
101
|
"type": "catalog_update",
|
|
@@ -386,13 +393,6 @@
|
|
|
386
393
|
"artifact": "security-maturity-tiers",
|
|
387
394
|
"path": "skills/security-maturity-tiers/skill.md",
|
|
388
395
|
"note": "Three-tier implementation roadmap — MVP (ship this week), Practical (scalable today), Overkill (defense-in-depth)"
|
|
389
|
-
},
|
|
390
|
-
{
|
|
391
|
-
"date": "2026-05-01",
|
|
392
|
-
"type": "manifest_review",
|
|
393
|
-
"artifact": "manifest.json",
|
|
394
|
-
"path": "manifest.json",
|
|
395
|
-
"note": "manifest threat_review_date — 42 skills, 11 catalogs"
|
|
396
396
|
}
|
|
397
397
|
]
|
|
398
398
|
}
|
|
@@ -1367,7 +1367,7 @@
|
|
|
1367
1367
|
},
|
|
1368
1368
|
"CVE-2026-43284": {
|
|
1369
1369
|
"name": "Dirty Frag (ESP/IPsec component)",
|
|
1370
|
-
"rwep":
|
|
1370
|
+
"rwep": 53,
|
|
1371
1371
|
"cvss": 8.8,
|
|
1372
1372
|
"cisa_kev": false,
|
|
1373
1373
|
"epss_score": 0.00007,
|
|
@@ -1577,7 +1577,7 @@
|
|
|
1577
1577
|
},
|
|
1578
1578
|
"CVE-2026-43500": {
|
|
1579
1579
|
"name": "Dirty Frag (RxRPC component)",
|
|
1580
|
-
"rwep":
|
|
1580
|
+
"rwep": 47,
|
|
1581
1581
|
"cvss": 7.6,
|
|
1582
1582
|
"cisa_kev": false,
|
|
1583
1583
|
"epss_score": 0.0001,
|
|
@@ -1821,7 +1821,7 @@
|
|
|
1821
1821
|
},
|
|
1822
1822
|
"CVE-2026-46300": {
|
|
1823
1823
|
"name": "Fragnesia",
|
|
1824
|
-
"rwep":
|
|
1824
|
+
"rwep": 35,
|
|
1825
1825
|
"cvss": 7.8,
|
|
1826
1826
|
"cisa_kev": false,
|
|
1827
1827
|
"referencing_skills": [],
|
|
@@ -1836,7 +1836,7 @@
|
|
|
1836
1836
|
},
|
|
1837
1837
|
"CVE-2024-21626": {
|
|
1838
1838
|
"name": "runc /proc/self/fd leak (Leaky Vessels)",
|
|
1839
|
-
"rwep":
|
|
1839
|
+
"rwep": 80,
|
|
1840
1840
|
"cvss": 8.6,
|
|
1841
1841
|
"cisa_kev": true,
|
|
1842
1842
|
"epss_score": 0.65,
|
|
@@ -1916,7 +1916,7 @@
|
|
|
1916
1916
|
},
|
|
1917
1917
|
"CVE-2023-3519": {
|
|
1918
1918
|
"name": "Citrix NetScaler ADC/Gateway unauth RCE (CitrixBleed precursor)",
|
|
1919
|
-
"rwep":
|
|
1919
|
+
"rwep": 80,
|
|
1920
1920
|
"cvss": 9.8,
|
|
1921
1921
|
"cisa_kev": true,
|
|
1922
1922
|
"epss_score": 0.967,
|
|
@@ -2044,7 +2044,7 @@
|
|
|
2044
2044
|
},
|
|
2045
2045
|
"CVE-2025-12686": {
|
|
2046
2046
|
"name": "Synology BeeStation unauth RCE (Pwn2Own Ireland 2025)",
|
|
2047
|
-
"rwep":
|
|
2047
|
+
"rwep": 45,
|
|
2048
2048
|
"cvss": 9.8,
|
|
2049
2049
|
"cisa_kev": false,
|
|
2050
2050
|
"epss_score": 0.04,
|
|
@@ -2060,7 +2060,7 @@
|
|
|
2060
2060
|
},
|
|
2061
2061
|
"CVE-2025-62847": {
|
|
2062
2062
|
"name": "QNAP QTS/QuTS hero RCE (Pwn2Own Ireland 2025, chain 1/3)",
|
|
2063
|
-
"rwep":
|
|
2063
|
+
"rwep": 40,
|
|
2064
2064
|
"cvss": 9.8,
|
|
2065
2065
|
"cisa_kev": false,
|
|
2066
2066
|
"epss_score": 0.03,
|
|
@@ -2076,7 +2076,7 @@
|
|
|
2076
2076
|
},
|
|
2077
2077
|
"CVE-2025-62848": {
|
|
2078
2078
|
"name": "QNAP QTS/QuTS hero RCE (Pwn2Own Ireland 2025, chain 2/3)",
|
|
2079
|
-
"rwep":
|
|
2079
|
+
"rwep": 40,
|
|
2080
2080
|
"cvss": 9.8,
|
|
2081
2081
|
"cisa_kev": false,
|
|
2082
2082
|
"epss_score": 0.03,
|
|
@@ -2092,7 +2092,7 @@
|
|
|
2092
2092
|
},
|
|
2093
2093
|
"CVE-2025-62849": {
|
|
2094
2094
|
"name": "QNAP QTS/QuTS hero RCE (Pwn2Own Ireland 2025, chain 3/3)",
|
|
2095
|
-
"rwep":
|
|
2095
|
+
"rwep": 35,
|
|
2096
2096
|
"cvss": 8.8,
|
|
2097
2097
|
"cisa_kev": false,
|
|
2098
2098
|
"epss_score": 0.02,
|