@blamejs/exceptd-skills 0.12.40 → 0.13.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 +17 -0
- package/ARCHITECTURE.md +7 -4
- package/CHANGELOG.md +215 -248
- package/CONTEXT.md +2 -2
- package/README.md +2 -8
- package/agents/threat-researcher.md +2 -2
- package/bin/exceptd.js +179 -81
- package/data/_indexes/_meta.json +50 -50
- package/data/_indexes/activity-feed.json +1 -1
- package/data/_indexes/catalog-summaries.json +1 -1
- package/data/_indexes/chains.json +485 -13
- package/data/_indexes/frequency.json +4 -0
- package/data/_indexes/jurisdiction-map.json +15 -4
- package/data/_indexes/section-offsets.json +1224 -1224
- package/data/_indexes/token-budget.json +170 -170
- package/data/atlas-ttps.json +54 -11
- package/data/attack-techniques.json +113 -17
- package/data/cve-catalog.json +38 -52
- package/data/cwe-catalog.json +8 -2
- package/data/exploit-availability.json +1 -0
- package/data/framework-control-gaps.json +149 -6
- package/data/global-frameworks.json +1 -0
- package/data/playbooks/ai-api.json +5 -0
- package/data/playbooks/cicd-pipeline-compromise.json +970 -0
- package/data/playbooks/cloud-iam-incident.json +4 -1
- package/data/playbooks/cred-stores.json +10 -0
- package/data/playbooks/crypto-codebase.json +13 -0
- package/data/playbooks/framework.json +16 -0
- package/data/playbooks/hardening.json +4 -0
- package/data/playbooks/identity-sso-compromise.json +951 -0
- package/data/playbooks/idp-incident.json +3 -0
- package/data/playbooks/kernel.json +6 -0
- package/data/playbooks/llm-tool-use-exfil.json +963 -0
- package/data/playbooks/mcp.json +6 -0
- package/data/playbooks/runtime.json +4 -0
- package/data/playbooks/sbom.json +13 -0
- package/data/playbooks/secrets.json +6 -0
- package/data/playbooks/webhook-callback-abuse.json +916 -0
- package/data/zeroday-lessons.json +1 -0
- package/lib/cross-ref-api.js +33 -13
- package/lib/cve-curation.js +12 -1
- package/lib/exit-codes.js +29 -0
- package/lib/lint-skills.js +25 -3
- package/lib/playbook-runner.js +8 -4
- package/lib/refresh-external.js +10 -1
- package/lib/scoring.js +64 -1
- package/lib/sign.js +40 -7
- package/lib/verify.js +5 -5
- package/manifest.json +83 -83
- package/orchestrator/README.md +7 -7
- package/orchestrator/index.js +46 -25
- package/orchestrator/scheduler.js +2 -2
- package/package.json +1 -1
- package/sbom.cdx.json +135 -91
- package/scripts/check-test-coverage.js +6 -6
- package/scripts/predeploy.js +7 -13
- package/scripts/refresh-reverse-refs.js +107 -20
- package/scripts/refresh-sbom.js +21 -4
- package/skills/age-gates-child-safety/skill.md +1 -5
- package/skills/ai-attack-surface/skill.md +11 -4
- package/skills/ai-c2-detection/skill.md +11 -2
- package/skills/ai-risk-management/skill.md +4 -2
- package/skills/api-security/skill.md +7 -8
- package/skills/attack-surface-pentest/skill.md +2 -2
- package/skills/cloud-iam-incident/skill.md +1 -5
- package/skills/cloud-security/skill.md +0 -4
- package/skills/compliance-theater/skill.md +10 -2
- package/skills/container-runtime-security/skill.md +1 -3
- package/skills/dlp-gap-analysis/skill.md +3 -4
- package/skills/email-security-anti-phishing/skill.md +1 -8
- package/skills/exploit-scoring/skill.md +7 -2
- package/skills/framework-gap-analysis/skill.md +1 -1
- package/skills/fuzz-testing-strategy/skill.md +1 -2
- package/skills/global-grc/skill.md +3 -2
- package/skills/identity-assurance/skill.md +1 -3
- package/skills/idp-incident-response/skill.md +1 -4
- package/skills/incident-response-playbook/skill.md +1 -5
- package/skills/kernel-lpe-triage/skill.md +2 -2
- package/skills/mcp-agent-trust/skill.md +13 -3
- package/skills/mlops-security/skill.md +3 -4
- package/skills/ot-ics-security/skill.md +0 -3
- package/skills/policy-exception-gen/skill.md +11 -3
- package/skills/pqc-first/skill.md +4 -2
- package/skills/rag-pipeline-security/skill.md +2 -0
- package/skills/ransomware-response/skill.md +1 -5
- package/skills/researcher/skill.md +4 -3
- package/skills/sector-energy/skill.md +0 -4
- package/skills/sector-federal-government/skill.md +2 -3
- package/skills/sector-financial/skill.md +1 -4
- package/skills/sector-healthcare/skill.md +0 -5
- package/skills/sector-telecom/skill.md +0 -4
- package/skills/security-maturity-tiers/skill.md +1 -2
- package/skills/skill-update-loop/skill.md +4 -3
- package/skills/supply-chain-integrity/skill.md +4 -3
- package/skills/threat-model-currency/skill.md +1 -1
- package/skills/threat-modeling-methodology/skill.md +2 -1
- package/skills/webapp-security/skill.md +0 -5
package/CONTEXT.md
CHANGED
|
@@ -114,7 +114,7 @@ Skills and playbooks read from `data/`. Authoritative catalog inventory:
|
|
|
114
114
|
| File | Entries | Purpose |
|
|
115
115
|
|------|---------|---------|
|
|
116
116
|
| `cve-catalog.json` | 10 | CVEs with CVSS, RWEP score, EPSS estimates, CISA KEV flags, PoC and live-patch availability |
|
|
117
|
-
| `atlas-ttps.json` | 15 | MITRE ATLAS v5.
|
|
117
|
+
| `atlas-ttps.json` | 15 | MITRE ATLAS v5.4.0 (February 2026) techniques with framework gap flags |
|
|
118
118
|
| `attack-techniques.json` | 79 | MITRE ATT&CK techniques with framework coverage mappings |
|
|
119
119
|
| `framework-control-gaps.json` | 62 | Framework control gap entries: designed-for vs. what each control misses |
|
|
120
120
|
| `exploit-availability.json` | 10 | Per-CVE PoC locations, weaponization stage, AI-acceleration factor, live-patch status |
|
|
@@ -245,7 +245,7 @@ The `researcher` **skill** (front-door dispatcher) and `threat-researcher` **age
|
|
|
245
245
|
|------|------------|
|
|
246
246
|
| RWEP | Real-World Exploit Priority — risk score beyond CVSS |
|
|
247
247
|
| KEV | CISA Known Exploited Vulnerabilities catalog |
|
|
248
|
-
| ATLAS | MITRE ATLAS v5.
|
|
248
|
+
| ATLAS | MITRE ATLAS v5.4.0 — AI threat framework |
|
|
249
249
|
| MCP | Model Context Protocol — AI tool integration standard |
|
|
250
250
|
| HNDL | Harvest-Now-Decrypt-Later — quantum threat to current crypto |
|
|
251
251
|
| Framework lag | Gap between what a framework requires and what current TTPs demand |
|
package/README.md
CHANGED
|
@@ -30,13 +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)
|
|
34
|
-
|
|
35
|
-
**v0.10.0 introduced the seven-phase playbook contract** — exceptd ships playbooks under `data/playbooks/*.json` that host AIs (Claude Code, Cursor, Gemini CLI, Codex) execute through seven phases: `govern → direct → look → detect → analyze → validate → close`. exceptd owns govern / direct / analyze / validate / close (knowledge + GRC layer); the host AI owns look / detect (artifact collection + indicator evaluation with its native Bash/Read/Grep/Glob).
|
|
36
|
-
|
|
37
|
-
**v0.11.0 collapses the 21-verb CLI into 11 canonical verbs** + flips the default output to human-readable. The new surface: `discover` (scan cwd → recommend playbooks), `brief` (unified info doc, replaces plan + govern + direct + look), `run` (phases 4-7, with flat or nested submission shape, auto-detect cwd context), `ai-run` (JSONL streaming variant for AI conversational flow), `attest` (subverbs: list / show / export / verify / diff — replaces reattest + list-attestations), `doctor` (one-shot health check — signatures + currency + cve/rfc validation + signing status), `ci` (one-shot CI gate, exit-2 on detected or rwep ≥ escalate), `ask` (plain-English routing), `lint` (pre-flight submission shape check). Attestation root moved from cwd-relative `.exceptd/` to `~/.exceptd/attestations/<repo-or-host-tag>/`. v0.10.x verbs (`plan`/`govern`/`direct`/`look`/`scan`/`dispatch`/`currency`/`verify`/`validate-cves`/`validate-rfcs`/`watchlist`/`prefetch`/`build-indexes`/`ingest`/`reattest`/`list-attestations`) still work via one-time deprecation banner — scheduled for removal in v0.13.
|
|
38
|
-
|
|
39
|
-
**v0.11 series** — CLI ergonomics and signature-verify hardening: mutex filesystem lockfile, `--vex` filter, `--ci` exit-code gating, `--diff-from-latest`, `--operator`/`--ack` attestation binding, `--format <fmt>` transforms output for `run` and `ci`, `ask` synonym routing, `lint` shares the normalize contract with the runner, CSAF/SARIF/OpenVEX bundles include indicator hits and framework gaps for posture-only playbooks, CSAF `current_release_date` populated, SARIF rule definitions for every ruleId, `doctor --fix` repairs a missing private key, `--strict-preconditions` flag, default human output for `attest list` and `lint` on TTY. Regression coverage at `tests/operator-bugs.test.js` catches re-introductions at `npm test`.
|
|
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, 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 into auto-PRs for editorial review.
|
|
40
34
|
|
|
41
35
|
---
|
|
42
36
|
|
|
@@ -178,7 +172,7 @@ You're adding a skill, updating a catalog, or cutting a release. Clone + bootstr
|
|
|
178
172
|
git clone https://github.com/blamejs/exceptd-skills
|
|
179
173
|
cd exceptd-skills
|
|
180
174
|
npm run bootstrap # auto-detects: verify-only / re-sign / first-init
|
|
181
|
-
npm run predeploy # full
|
|
175
|
+
npm run predeploy # full predeploy gate sequence locally
|
|
182
176
|
```
|
|
183
177
|
|
|
184
178
|
`bootstrap` auto-detects the right mode based on which keys exist on disk:
|
|
@@ -52,9 +52,9 @@ Research and validate new threat intelligence — CVEs, attack campaigns, new AT
|
|
|
52
52
|
- Distinguish: "CISA KEV confirmed" vs. "suspected" vs. "no evidence"
|
|
53
53
|
|
|
54
54
|
6. **Map to ATLAS/ATT&CK**
|
|
55
|
-
- Identify which ATLAS v5.
|
|
55
|
+
- Identify which ATLAS v5.4.0 TTPs are relevant to this CVE's attack vector
|
|
56
56
|
- Identify which ATT&CK techniques are relevant
|
|
57
|
-
- Flag any ATLAS gaps (attack pattern not in ATLAS v5.
|
|
57
|
+
- Flag any ATLAS gaps (attack pattern not in ATLAS v5.4.0)
|
|
58
58
|
|
|
59
59
|
7. **Identify affected skills**
|
|
60
60
|
- Which skills cover the CVE's technology domain?
|
package/bin/exceptd.js
CHANGED
|
@@ -207,31 +207,46 @@ function suggestVerb(cmd, known) {
|
|
|
207
207
|
// v0.11.0 introduces: brief (collapses plan/govern/direct/look), discover (scan + dispatch),
|
|
208
208
|
// doctor (currency + verify + validate-cves + validate-rfcs), ci (CI gate),
|
|
209
209
|
// ai-run (streaming JSONL), ask (plain-English routing).
|
|
210
|
+
//
|
|
211
|
+
// v0.13.0 removed the v0.10.x phase-name aliases (plan, govern, direct,
|
|
212
|
+
// look, ingest). They were deprecation-bannered since v0.11.0 and
|
|
213
|
+
// slated-for-removal-in-v0.13 since v0.12.0; v0.13 honors that contract.
|
|
214
|
+
// REMOVED_VERBS below carries the rename map for operator-facing refusal
|
|
215
|
+
// hints. `reattest` and `list-attestations` are preserved as canonical
|
|
216
|
+
// routings — they're short forms of `attest diff` / `attest list` that
|
|
217
|
+
// remain operationally useful and have substantial test coverage.
|
|
210
218
|
const PLAYBOOK_VERBS = new Set([
|
|
211
|
-
// v0.11.0 canonical surface:
|
|
212
219
|
"brief", "run", "ai-run", "attest", "discover", "doctor", "ci", "ask",
|
|
213
220
|
"verify-attestation", "run-all", "lint",
|
|
214
|
-
|
|
215
|
-
"plan", "govern", "direct", "look", "ingest", "reattest", "list-attestations",
|
|
221
|
+
"reattest", "list-attestations",
|
|
216
222
|
]);
|
|
217
223
|
|
|
218
|
-
//
|
|
219
|
-
//
|
|
220
|
-
|
|
224
|
+
// v0.13.0: hard-removed legacy verbs. The dispatcher refuses the verb
|
|
225
|
+
// with an actionable replacement hint instead of routing it. Pre-v0.13
|
|
226
|
+
// these were soft-deprecated (banner + still functional); v0.13 removes
|
|
227
|
+
// the routing entirely. Operators upgrading from v0.10.x → v0.13 see
|
|
228
|
+
// the same hint that the deprecation banner previously surfaced, but
|
|
229
|
+
// non-zero exit so scripts noticing pinned-name use fail loudly instead
|
|
230
|
+
// of silently invoking the alias.
|
|
231
|
+
const REMOVED_VERBS = {
|
|
221
232
|
plan: "brief --all",
|
|
222
233
|
govern: "brief <pb> --phase govern",
|
|
223
234
|
direct: "brief <pb> --phase direct",
|
|
224
235
|
look: "brief <pb> --phase look",
|
|
225
236
|
ingest: "run",
|
|
226
|
-
|
|
227
|
-
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
// Renamed but functionally-routed verbs (orchestrator-side dispatch still
|
|
240
|
+
// handles them as of v0.13). Distinct from REMOVED_VERBS — these aren't
|
|
241
|
+
// refused; they're just a soft hint that the rename happened. No banner
|
|
242
|
+
// is emitted post-v0.13.
|
|
243
|
+
const RENAMED_VERBS_HINT = {
|
|
228
244
|
scan: "discover --scan-only",
|
|
229
245
|
dispatch: "discover",
|
|
230
246
|
currency: "doctor --currency",
|
|
231
247
|
verify: "doctor --signatures",
|
|
232
248
|
"validate-cves": "doctor --cves",
|
|
233
249
|
"validate-rfcs": "doctor --rfcs",
|
|
234
|
-
watchlist: "watch",
|
|
235
250
|
prefetch: "refresh --no-network",
|
|
236
251
|
"build-indexes": "refresh --indexes-only",
|
|
237
252
|
};
|
|
@@ -498,40 +513,23 @@ function main() {
|
|
|
498
513
|
process.exit(0);
|
|
499
514
|
}
|
|
500
515
|
|
|
501
|
-
// v0.
|
|
502
|
-
//
|
|
503
|
-
//
|
|
504
|
-
//
|
|
505
|
-
//
|
|
506
|
-
//
|
|
507
|
-
if (
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
const markerFile = path.join(markerDir, `exceptd-deprecation-shown-v${ver}`);
|
|
519
|
-
let alreadyShown = false;
|
|
520
|
-
try { alreadyShown = fs.existsSync(markerFile); } catch { /* tmpdir unwritable; degrade to per-process */ }
|
|
521
|
-
if (!alreadyShown) {
|
|
522
|
-
const haveBrief = ver !== "unknown" && ver.match(/^(\d+)\.(\d+)/) && (parseInt(RegExp.$1, 10) > 0 || parseInt(RegExp.$2, 10) >= 11);
|
|
523
|
-
process.stderr.write(
|
|
524
|
-
`[exceptd] DEPRECATION: \`${cmd}\` is a v0.10.x verb. ` +
|
|
525
|
-
(haveBrief
|
|
526
|
-
? `Prefer \`${LEGACY_VERB_REPLACEMENTS[cmd]}\` (available in this install, v${ver}). `
|
|
527
|
-
: `Upgrade to v0.11.0+ then use \`${LEGACY_VERB_REPLACEMENTS[cmd]}\` (currently installed: v${ver}). `) +
|
|
528
|
-
`Legacy verbs remain functional through this release; they will be removed in v0.13. ` +
|
|
529
|
-
`This banner shows once per exceptd version per host (re-shown on upgrade). Permanent suppress: export EXCEPTD_DEPRECATION_SHOWN=1.\n`
|
|
530
|
-
);
|
|
531
|
-
try { fs.writeFileSync(markerFile, `shown_at=${new Date().toISOString()}\nversion=${ver}\n`); }
|
|
532
|
-
catch { /* tmpdir unwritable; the env-var guard below keeps the per-process suppression intact */ }
|
|
533
|
-
}
|
|
534
|
-
process.env.EXCEPTD_DEPRECATION_SHOWN = "1";
|
|
516
|
+
// v0.13.0: hard-refuse the v0.10.x legacy verbs that were
|
|
517
|
+
// deprecation-bannered since v0.11.0. Pre-v0.13 these silently routed
|
|
518
|
+
// to their v0.11+ replacements with a soft banner; v0.13 honors the
|
|
519
|
+
// long-advertised removal. Operators upgrading from v0.10.x get a
|
|
520
|
+
// structured error with the replacement command, suitable for
|
|
521
|
+
// grep / scripted handling.
|
|
522
|
+
if (REMOVED_VERBS[cmd]) {
|
|
523
|
+
emitError(
|
|
524
|
+
`'${cmd}' was removed in v0.13.0. Use \`exceptd ${REMOVED_VERBS[cmd]}\` instead.`,
|
|
525
|
+
{
|
|
526
|
+
verb: cmd,
|
|
527
|
+
removed_in: "0.13.0",
|
|
528
|
+
replacement: REMOVED_VERBS[cmd],
|
|
529
|
+
deprecation_history: "Deprecated in v0.11.0 with a soft banner; slated-for-removal-in-v0.13 announced in v0.12.0; removed in v0.13.0.",
|
|
530
|
+
}
|
|
531
|
+
);
|
|
532
|
+
return;
|
|
535
533
|
}
|
|
536
534
|
|
|
537
535
|
// Seven-phase playbook verbs run in-process — they emit JSON to stdout
|
|
@@ -714,6 +712,16 @@ function emit(obj, pretty, humanRenderer) {
|
|
|
714
712
|
if (obj && obj.ok === false && !process.exitCode) {
|
|
715
713
|
process.exitCode = EXIT_CODES.GENERIC_FAILURE;
|
|
716
714
|
}
|
|
715
|
+
// v0.13.0 envelope harmonization: every emitted body has a top-level
|
|
716
|
+
// `ok` field — defaults to true when not set, matching the symmetric
|
|
717
|
+
// ok:false → exitCode=1 fallback above. Consumers that parse stdout
|
|
718
|
+
// can now assume the envelope shape regardless of which verb produced
|
|
719
|
+
// the body. Per-site `verb: "<name>"` is set at the call site; this
|
|
720
|
+
// helper guarantees the `ok` field's presence but does not synthesize
|
|
721
|
+
// verb (the caller knows its own name).
|
|
722
|
+
if (obj && typeof obj === 'object' && !('ok' in obj)) {
|
|
723
|
+
obj = { ok: true, ...obj };
|
|
724
|
+
}
|
|
717
725
|
const wantJson = !!global.__exceptdWantJson || !!process.env.EXCEPTD_RAW_JSON;
|
|
718
726
|
if (humanRenderer && !wantJson && !pretty) {
|
|
719
727
|
process.stdout.write(humanRenderer(obj) + "\n");
|
|
@@ -2858,8 +2866,10 @@ function cmdRun(runner, args, runOpts, pretty) {
|
|
|
2858
2866
|
// exit-code expectations regardless of which verb they call. Without
|
|
2859
2867
|
// --ci the legacy exit 1 is preserved (ok:false bodies are framework
|
|
2860
2868
|
// signals when no CI gating is requested).
|
|
2861
|
-
|
|
2869
|
+
// Set exitCode BEFORE emit(): emit's ok:false fallback only fires when
|
|
2870
|
+
// exitCode is not already set, so the BLOCKED override survives.
|
|
2862
2871
|
process.exitCode = args.ci ? EXIT_CODES.BLOCKED : EXIT_CODES.GENERIC_FAILURE;
|
|
2872
|
+
emit(result, pretty);
|
|
2863
2873
|
return;
|
|
2864
2874
|
}
|
|
2865
2875
|
|
|
@@ -3535,8 +3545,7 @@ function cmdIngest(runner, args, runOpts, pretty) {
|
|
|
3535
3545
|
}
|
|
3536
3546
|
|
|
3537
3547
|
if (result && result.ok === false) {
|
|
3538
|
-
|
|
3539
|
-
process.exitCode = EXIT_CODES.GENERIC_FAILURE;
|
|
3548
|
+
emit(result, pretty);
|
|
3540
3549
|
return;
|
|
3541
3550
|
}
|
|
3542
3551
|
emit(result, pretty);
|
|
@@ -3897,13 +3906,18 @@ function maybeSignAttestation(filePath) {
|
|
|
3897
3906
|
algorithm: "Ed25519",
|
|
3898
3907
|
signature_base64: sig.toString("base64"),
|
|
3899
3908
|
note: "Ed25519 signature covers the attestation file bytes only. Use filesystem mtime for freshness; use the attestation's `captured_at` for the signed timestamp.",
|
|
3900
|
-
}, null, 2));
|
|
3909
|
+
}, null, 2), { mode: 0o600 });
|
|
3910
|
+
// Mirror the v0.12.38 attestation.json hardening: 0o600 on POSIX +
|
|
3911
|
+
// icacls inheritance strip on win32. The sidecar carries the
|
|
3912
|
+
// signature payload; multi-tenant hosts shouldn't leak it.
|
|
3913
|
+
try { require("./../lib/sign.js").restrictWindowsAcl(sigPath); } catch { /* best-effort */ }
|
|
3901
3914
|
} else {
|
|
3902
3915
|
fs.writeFileSync(sigPath, JSON.stringify({
|
|
3903
3916
|
algorithm: "unsigned",
|
|
3904
3917
|
signed: false,
|
|
3905
|
-
note: "No private key at .keys/private.pem — attestation is hash-stable but unsigned. Run `
|
|
3906
|
-
}, null, 2));
|
|
3918
|
+
note: "No private key at .keys/private.pem — attestation is hash-stable but unsigned. Run `exceptd doctor --fix` to enable signing.",
|
|
3919
|
+
}, null, 2), { mode: 0o600 });
|
|
3920
|
+
try { require("./../lib/sign.js").restrictWindowsAcl(sigPath); } catch { /* best-effort */ }
|
|
3907
3921
|
}
|
|
3908
3922
|
} catch { /* non-fatal — signing failure shouldn't block the run */ }
|
|
3909
3923
|
}
|
|
@@ -4477,6 +4491,23 @@ function cmdAttest(runner, args, runOpts, pretty) {
|
|
|
4477
4491
|
if (!subverb) {
|
|
4478
4492
|
return emitError("attest: missing subverb. Usage: attest list | show <sid> | export <sid> | verify <sid> | diff <sid>", null, pretty);
|
|
4479
4493
|
}
|
|
4494
|
+
// Validate subverb membership BEFORE the session-id branch so a typo
|
|
4495
|
+
// (`attest verfy sid`) gets the did-you-mean response, not the
|
|
4496
|
+
// misleading "no session dir for sid" downstream. Pre-fix the
|
|
4497
|
+
// session-id resolution ran first and a valid-but-unrecognized
|
|
4498
|
+
// subverb collapsed into a session-lookup failure.
|
|
4499
|
+
const ATTEST_SUBVERBS = ["list", "show", "export", "verify", "diff"];
|
|
4500
|
+
if (!ATTEST_SUBVERBS.includes(subverb)) {
|
|
4501
|
+
const dym = suggestVerb(subverb, ATTEST_SUBVERBS);
|
|
4502
|
+
const hint = dym.length > 0
|
|
4503
|
+
? `Did you mean: ${dym.join(" | ")}? Accepted: ${ATTEST_SUBVERBS.join(" | ")}.`
|
|
4504
|
+
: `Accepted: ${ATTEST_SUBVERBS.join(" | ")}.`;
|
|
4505
|
+
return emitError(
|
|
4506
|
+
`attest: unknown subverb "${subverb}". ${hint}`,
|
|
4507
|
+
{ verb: "attest", subverb_input: subverb, did_you_mean: dym, accepted_subverbs: ATTEST_SUBVERBS },
|
|
4508
|
+
pretty
|
|
4509
|
+
);
|
|
4510
|
+
}
|
|
4480
4511
|
// `list` doesn't require a session-id positional.
|
|
4481
4512
|
if (subverb === "list") {
|
|
4482
4513
|
return cmdListAttestations(runner, args, runOpts, pretty);
|
|
@@ -4521,7 +4552,7 @@ function cmdAttest(runner, args, runOpts, pretty) {
|
|
|
4521
4552
|
}
|
|
4522
4553
|
|
|
4523
4554
|
if (subverb === "show") {
|
|
4524
|
-
emit({ session_id: sessionId, attestations, attestation_replays: replays }, pretty);
|
|
4555
|
+
emit({ verb: "attest show", session_id: sessionId, attestations, attestation_replays: replays }, pretty);
|
|
4525
4556
|
return;
|
|
4526
4557
|
}
|
|
4527
4558
|
|
|
@@ -4568,6 +4599,15 @@ function cmdAttest(runner, args, runOpts, pretty) {
|
|
|
4568
4599
|
return emitError(`attest diff --against ${args.against}: no attestations under that session id.`, null, pretty);
|
|
4569
4600
|
}
|
|
4570
4601
|
const self = attestations[0];
|
|
4602
|
+
if (!self) {
|
|
4603
|
+
// Session dir contains only replay records, no attestation —
|
|
4604
|
+
// diff has nothing to compare on the A side.
|
|
4605
|
+
return emitError(
|
|
4606
|
+
`attest diff ${sessionId}: no attestation found in session dir (only replay records). The session may be replay-only; verify with \`exceptd attest show ${sessionId}\`.`,
|
|
4607
|
+
{ verb: "attest diff", session_id: sessionId, attestation_count: 0, replay_count: replays.length },
|
|
4608
|
+
pretty
|
|
4609
|
+
);
|
|
4610
|
+
}
|
|
4571
4611
|
emit({
|
|
4572
4612
|
verb: "attest diff",
|
|
4573
4613
|
a_session: sessionId,
|
|
@@ -4599,10 +4639,6 @@ function cmdAttest(runner, args, runOpts, pretty) {
|
|
|
4599
4639
|
return cmdReattest(runner, args, {}, pretty);
|
|
4600
4640
|
}
|
|
4601
4641
|
|
|
4602
|
-
if (subverb === "list") {
|
|
4603
|
-
return cmdListAttestations(runner, args, {}, pretty);
|
|
4604
|
-
}
|
|
4605
|
-
|
|
4606
4642
|
if (subverb === "verify") {
|
|
4607
4643
|
const crypto = require("crypto");
|
|
4608
4644
|
const pubKeyPath = path.join(PKG_ROOT, "keys", "public.pem");
|
|
@@ -4796,7 +4832,10 @@ function cmdAttest(runner, args, runOpts, pretty) {
|
|
|
4796
4832
|
return;
|
|
4797
4833
|
}
|
|
4798
4834
|
|
|
4799
|
-
|
|
4835
|
+
// Unreachable — front-loaded subverb membership check above handles
|
|
4836
|
+
// unknown subverbs. Defensive return so future refactors that move
|
|
4837
|
+
// the gate don't silently fall through.
|
|
4838
|
+
return emitError(`attest: unknown subverb "${subverb}".`, { verb: "attest", subverb_input: subverb }, pretty);
|
|
4800
4839
|
}
|
|
4801
4840
|
|
|
4802
4841
|
/**
|
|
@@ -5335,7 +5374,7 @@ function cmdDoctor(runner, args, runOpts, pretty) {
|
|
|
5335
5374
|
severity: present ? "info" : "warn",
|
|
5336
5375
|
private_key_present: present,
|
|
5337
5376
|
can_sign_attestations: present,
|
|
5338
|
-
...(present ? {} : { hint: "run `node lib/sign.js generate-keypair`
|
|
5377
|
+
...(present ? {} : { hint: "run `exceptd doctor --fix` to generate an Ed25519 keypair and sign skills (or `node $(exceptd path)/lib/sign.js generate-keypair` from a contributor checkout)" }),
|
|
5339
5378
|
};
|
|
5340
5379
|
} catch (e) {
|
|
5341
5380
|
checks.signing = { ok: false, error: e.message };
|
|
@@ -5410,27 +5449,78 @@ function cmdDoctor(runner, args, runOpts, pretty) {
|
|
|
5410
5449
|
},
|
|
5411
5450
|
};
|
|
5412
5451
|
|
|
5413
|
-
//
|
|
5414
|
-
//
|
|
5415
|
-
//
|
|
5452
|
+
// --fix runs BEFORE the JSON early-return so `exceptd doctor --fix --json`
|
|
5453
|
+
// actually fixes (was a no-op pre-v0.11.6). Re-runs the signing check
|
|
5454
|
+
// after fix so the returned JSON reflects the post-fix state.
|
|
5455
|
+
//
|
|
5456
|
+
// Safety: lib/sign.js generateKeypair() refuses if keys/public.pem
|
|
5457
|
+
// already exists (overwriting it would orphan every shipped signature —
|
|
5458
|
+
// the v0.11.x regression class). Surface that refusal as a distinct
|
|
5459
|
+
// fix_attempted reason so operators see WHY the fix declined.
|
|
5460
|
+
// After successful key generation, chain sign-all so the manifest +
|
|
5461
|
+
// every shipped skill carries a signature paired with the new public
|
|
5462
|
+
// key. Without this chain, `doctor --fix` succeeds but the very next
|
|
5463
|
+
// `exceptd doctor` (signatures check) reports 0/N passing.
|
|
5416
5464
|
if (args.fix && checks.signing && !checks.signing.private_key_present) {
|
|
5417
|
-
|
|
5418
|
-
|
|
5465
|
+
const pubKeyExists = fs.existsSync(path.join(PKG_ROOT, "keys", "public.pem"));
|
|
5466
|
+
if (pubKeyExists) {
|
|
5467
|
+
out.summary.fix_attempted = "ed25519_keypair_generation_declined";
|
|
5468
|
+
out.summary.fix_decline_reason = "keys/public.pem already exists but no matching private key. Generating a fresh keypair would overwrite the public key and orphan every shipped signature. If you intend to establish a new signing identity, run `node $(exceptd path)/lib/sign.js generate-keypair --rotate` followed by sign-all.";
|
|
5469
|
+
process.stderr.write("[doctor --fix] refused: keys/public.pem present without matching private key. Pass --rotate via the underlying lib/sign.js if a new identity is intended.\n");
|
|
5470
|
+
} else {
|
|
5471
|
+
process.stderr.write("[doctor --fix] generating Ed25519 keypair...\n");
|
|
5472
|
+
const r = require("child_process").spawnSync(process.execPath, [path.join(PKG_ROOT, "lib", "sign.js"), "generate-keypair"], {
|
|
5473
|
+
stdio: ["ignore", "pipe", "pipe"], cwd: PKG_ROOT,
|
|
5474
|
+
});
|
|
5475
|
+
if (r.status === 0) {
|
|
5476
|
+
// Chain sign-all so the manifest + skills carry signatures paired
|
|
5477
|
+
// with the new keypair. Without this every shipped signature is
|
|
5478
|
+
// invalid against the new public key.
|
|
5479
|
+
process.stderr.write("[doctor --fix] keypair generated — signing skills + manifest...\n");
|
|
5480
|
+
const s = require("child_process").spawnSync(process.execPath, [path.join(PKG_ROOT, "lib", "sign.js"), "sign-all"], {
|
|
5481
|
+
stdio: ["ignore", "pipe", "pipe"], cwd: PKG_ROOT,
|
|
5482
|
+
});
|
|
5483
|
+
const keyPath = path.join(PKG_ROOT, ".keys", "private.pem");
|
|
5484
|
+
const present = fs.existsSync(keyPath);
|
|
5485
|
+
checks.signing = { ok: present, severity: present ? "info" : "warn", private_key_present: present, can_sign_attestations: present };
|
|
5486
|
+
out.checks = checks;
|
|
5487
|
+
if (s.status === 0) {
|
|
5488
|
+
out.summary.fix_applied = "ed25519_keypair_generated_and_skills_signed";
|
|
5489
|
+
process.stderr.write("[doctor --fix] keypair + sign-all complete — re-checking signing status.\n");
|
|
5490
|
+
} else {
|
|
5491
|
+
out.summary.fix_applied = "ed25519_keypair_generated";
|
|
5492
|
+
out.summary.fix_partial = "sign_all_failed";
|
|
5493
|
+
out.summary.sign_all_exit_code = s.status;
|
|
5494
|
+
process.stderr.write(`[doctor --fix] WARNING: keypair generated but sign-all failed (exit=${s.status}). Skills carry signatures from a different key; verify will report mismatches.\n`);
|
|
5495
|
+
}
|
|
5496
|
+
} else {
|
|
5497
|
+
out.summary.fix_attempted = "ed25519_keypair_generation_failed";
|
|
5498
|
+
out.summary.fix_exit_code = r.status;
|
|
5499
|
+
process.stderr.write(`[doctor --fix] generation failed (exit=${r.status}); run \`node $(exceptd path)/lib/sign.js generate-keypair\` manually.\n`);
|
|
5500
|
+
}
|
|
5501
|
+
}
|
|
5502
|
+
}
|
|
5503
|
+
|
|
5504
|
+
// Second --fix path: private key IS present but the signatures check
|
|
5505
|
+
// FAILED. This is the post-rotation case (codex P2 v0.12.41): operator
|
|
5506
|
+
// ran `node $(exceptd path)/lib/sign.js generate-keypair --rotate`,
|
|
5507
|
+
// got a fresh keypair, but the manifest + skills still carry signatures
|
|
5508
|
+
// from the OLD keypair. Pre-fix doctor --fix's signing path only fired
|
|
5509
|
+
// when the private key was missing, so the rotation flow's remediation
|
|
5510
|
+
// step was a no-op. Chain sign-all here so the post-rotate doctor --fix
|
|
5511
|
+
// converges to a fully-verified state.
|
|
5512
|
+
if (args.fix && checks.signing && checks.signing.private_key_present && checks.signatures && checks.signatures.ok === false && !out.summary.fix_applied && !out.summary.fix_attempted) {
|
|
5513
|
+
process.stderr.write("[doctor --fix] private key present, signatures failing — running sign-all to re-sign skills + manifest...\n");
|
|
5514
|
+
const s = require("child_process").spawnSync(process.execPath, [path.join(PKG_ROOT, "lib", "sign.js"), "sign-all"], {
|
|
5419
5515
|
stdio: ["ignore", "pipe", "pipe"], cwd: PKG_ROOT,
|
|
5420
5516
|
});
|
|
5421
|
-
if (
|
|
5422
|
-
|
|
5423
|
-
|
|
5424
|
-
const keyPath = path.join(PKG_ROOT, ".keys", "private.pem");
|
|
5425
|
-
const present = fs.existsSync(keyPath);
|
|
5426
|
-
checks.signing = { ok: present, severity: present ? "info" : "warn", private_key_present: present, can_sign_attestations: present };
|
|
5427
|
-
out.checks = checks;
|
|
5428
|
-
out.summary.fix_applied = "ed25519_keypair_generated";
|
|
5429
|
-
process.stderr.write("[doctor --fix] keypair generated — re-checking signing status.\n");
|
|
5517
|
+
if (s.status === 0) {
|
|
5518
|
+
out.summary.fix_applied = "skills_resigned_against_current_keypair";
|
|
5519
|
+
process.stderr.write("[doctor --fix] sign-all complete — re-run `exceptd doctor` to confirm.\n");
|
|
5430
5520
|
} else {
|
|
5431
|
-
out.summary.fix_attempted = "
|
|
5432
|
-
out.summary.
|
|
5433
|
-
process.stderr.write(`[doctor --fix]
|
|
5521
|
+
out.summary.fix_attempted = "sign_all_failed";
|
|
5522
|
+
out.summary.sign_all_exit_code = s.status;
|
|
5523
|
+
process.stderr.write(`[doctor --fix] sign-all failed (exit=${s.status}); run \`node $(exceptd path)/lib/sign.js sign-all\` manually.\n`);
|
|
5434
5524
|
}
|
|
5435
5525
|
}
|
|
5436
5526
|
|
|
@@ -5509,7 +5599,7 @@ function cmdDoctor(runner, args, runOpts, pretty) {
|
|
|
5509
5599
|
if (checks.signing.private_key_present) {
|
|
5510
5600
|
lines.push(` [ok] attestation signing: private key present (.keys/private.pem)`);
|
|
5511
5601
|
} else {
|
|
5512
|
-
lines.push(` [!!] attestation signing: private key MISSING (.keys/private.pem) — run \`
|
|
5602
|
+
lines.push(` [!!] attestation signing: private key MISSING (.keys/private.pem) — run \`exceptd doctor --fix\` to enable`);
|
|
5513
5603
|
}
|
|
5514
5604
|
}
|
|
5515
5605
|
lines.push("");
|
|
@@ -5526,7 +5616,11 @@ function cmdDoctor(runner, args, runOpts, pretty) {
|
|
|
5526
5616
|
if (out.summary.fix_applied) {
|
|
5527
5617
|
process.stdout.write(`\n[doctor --fix] ${out.summary.fix_applied} — re-run \`exceptd doctor\` to confirm.\n`);
|
|
5528
5618
|
} else if (out.summary.fix_attempted) {
|
|
5529
|
-
|
|
5619
|
+
if (out.summary.fix_decline_reason) {
|
|
5620
|
+
process.stdout.write(`\n[doctor --fix] ${out.summary.fix_attempted}: ${out.summary.fix_decline_reason}\n`);
|
|
5621
|
+
} else {
|
|
5622
|
+
process.stdout.write(`\n[doctor --fix] ${out.summary.fix_attempted} (exit=${out.summary.fix_exit_code}); run \`node $(exceptd path)/lib/sign.js generate-keypair\` from a contributor checkout if needed.\n`);
|
|
5623
|
+
}
|
|
5530
5624
|
process.exitCode = EXIT_CODES.GENERIC_FAILURE;
|
|
5531
5625
|
return;
|
|
5532
5626
|
}
|
|
@@ -5764,10 +5858,11 @@ function cmdAiRun(runner, args, runOpts, pretty) {
|
|
|
5764
5858
|
);
|
|
5765
5859
|
}
|
|
5766
5860
|
if (!result || result.ok === false) {
|
|
5767
|
-
//
|
|
5768
|
-
//
|
|
5769
|
-
|
|
5770
|
-
|
|
5861
|
+
// Route through emit() so the body lands on stdout (per v0.12.39
|
|
5862
|
+
// envelope contracts) and exitCode is set by the shared ok:false
|
|
5863
|
+
// fallback. Pre-fix the body went to stderr, which split it from
|
|
5864
|
+
// the success path and made consumers parse two streams.
|
|
5865
|
+
emit(result || { ok: false, error: 'ai-run returned empty result' }, pretty);
|
|
5771
5866
|
return;
|
|
5772
5867
|
}
|
|
5773
5868
|
// v0.12.14: ai-run --no-stream previously emitted a
|
|
@@ -6132,7 +6227,10 @@ function cmdAsk(runner, args, runOpts, pretty) {
|
|
|
6132
6227
|
routed_to: [],
|
|
6133
6228
|
hint: "No playbook matched. Try `exceptd brief --all` to see what's available, or `exceptd discover` to detect what's in your cwd.",
|
|
6134
6229
|
};
|
|
6135
|
-
|
|
6230
|
+
// Honor --pretty as an implicit opt-in to structured output, matching
|
|
6231
|
+
// the discover/doctor convention. Pre-fix `ask "..." --pretty` fell
|
|
6232
|
+
// into the human-text branch and silently ignored the flag.
|
|
6233
|
+
if (args.json || args.pretty) return emit(result, pretty);
|
|
6136
6234
|
process.stdout.write(`ask: ${question}\n no playbook matched.\n try: exceptd discover (auto-detect what's in your cwd)\n`);
|
|
6137
6235
|
return;
|
|
6138
6236
|
}
|
|
@@ -6145,7 +6243,7 @@ function cmdAsk(runner, args, runOpts, pretty) {
|
|
|
6145
6243
|
next_step: `exceptd run ${top[0].id} # or: exceptd brief ${top[0].id} to learn first`,
|
|
6146
6244
|
full_match_list: top,
|
|
6147
6245
|
};
|
|
6148
|
-
if (args.json) return emit(result, pretty);
|
|
6246
|
+
if (args.json || args.pretty) return emit(result, pretty);
|
|
6149
6247
|
process.stdout.write(`ask: ${question}\n top match: ${top[0].id} (score ${top[0].score})\n next: ${result.next_step}\n alternates: ${top.slice(1).map(t => t.id).join(", ") || "(none)"}\n`);
|
|
6150
6248
|
}
|
|
6151
6249
|
|
package/data/_indexes/_meta.json
CHANGED
|
@@ -1,63 +1,63 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema_version": "1.1.0",
|
|
3
|
-
"generated_at": "2026-05-
|
|
3
|
+
"generated_at": "2026-05-17T23:47:25.094Z",
|
|
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": "e4db72d2b307adb316fad1af15322392587c50c4de5b7592c015b93631f6f26b",
|
|
8
|
+
"data/atlas-ttps.json": "1301e6d053be91ac41ec38e64c72ac726e478b7fd6482884c3e2219c8fa16173",
|
|
9
|
+
"data/attack-techniques.json": "4afc226cc7359771a54aab1a1ff53c5530d1ef54eabc1d2fbd3b1caeda2f64a6",
|
|
10
|
+
"data/cve-catalog.json": "d4d2ecc24a0b010d5e83fea9850130278bea120ef130c99dcbb03eff64008f2d",
|
|
11
|
+
"data/cwe-catalog.json": "7a8e01c6f5538125b81ddb29f1b658f0fd407682bc2998aafc6b759e3b1afe48",
|
|
12
12
|
"data/d3fend-catalog.json": "a1fc2827ceb344669e148d55197dbf1b0e5b20bcc618e90517639c17d67ee82d",
|
|
13
13
|
"data/dlp-controls.json": "d2406c482dddd30e49203879999dc4b3a7fd4d0494d6a61d86b91ee76415df19",
|
|
14
|
-
"data/exploit-availability.json": "
|
|
15
|
-
"data/framework-control-gaps.json": "
|
|
16
|
-
"data/global-frameworks.json": "
|
|
14
|
+
"data/exploit-availability.json": "003a400f5ae5b15527589571679ccdb9b3a62e60073627b5fbdeb2a9fe330a7a",
|
|
15
|
+
"data/framework-control-gaps.json": "2c37446ad38270ebe51c0eaf340b26f19367c4eae47886d6310f16a4185354bf",
|
|
16
|
+
"data/global-frameworks.json": "9ba563a85f7f8d6c3c957de64945e20925a89d0ed6ea6fc561cf093811acf558",
|
|
17
17
|
"data/rfc-references.json": "e253a548c8a829d178d5aea601e268724b85c936ccbfa51c2e5d80c5f8efe2b0",
|
|
18
|
-
"data/zeroday-lessons.json": "
|
|
19
|
-
"skills/kernel-lpe-triage/skill.md": "
|
|
20
|
-
"skills/ai-attack-surface/skill.md": "
|
|
21
|
-
"skills/mcp-agent-trust/skill.md": "
|
|
22
|
-
"skills/framework-gap-analysis/skill.md": "
|
|
23
|
-
"skills/compliance-theater/skill.md": "
|
|
24
|
-
"skills/exploit-scoring/skill.md": "
|
|
25
|
-
"skills/rag-pipeline-security/skill.md": "
|
|
26
|
-
"skills/ai-c2-detection/skill.md": "
|
|
27
|
-
"skills/policy-exception-gen/skill.md": "
|
|
28
|
-
"skills/threat-model-currency/skill.md": "
|
|
29
|
-
"skills/global-grc/skill.md": "
|
|
18
|
+
"data/zeroday-lessons.json": "27d46a0e09a3edbe97dfbb070c3991348567cf93c86a3e94c767c5ad2dfb653e",
|
|
19
|
+
"skills/kernel-lpe-triage/skill.md": "ae4a0af924d0078ffc6cd051a3ef9fce75a6a3f9c0c15d1c07900ae5faf80502",
|
|
20
|
+
"skills/ai-attack-surface/skill.md": "dcca7d92a1ab4d1e4c46356b614a138b1c1f79b65a6a290eccf2095d8d443993",
|
|
21
|
+
"skills/mcp-agent-trust/skill.md": "6821f6d38f6e23bbed953f8f86a279597b0b95a2d0548b5383e851bca7442531",
|
|
22
|
+
"skills/framework-gap-analysis/skill.md": "3b139eaefbedd36b2379cfe22dceef71e97d0e34404b0009b7afbfe0a8dc39e6",
|
|
23
|
+
"skills/compliance-theater/skill.md": "a1387c523f7aa2481a199f6288e0152b94aa5a6644600eb39dbb3ea9ee9af6bd",
|
|
24
|
+
"skills/exploit-scoring/skill.md": "fba9e27722d361cc6ed5992d9aaeaa397598b417fc5a0d6fe0bee2993942e7e8",
|
|
25
|
+
"skills/rag-pipeline-security/skill.md": "ff07e48918090247aef71def4150b0df372a24bcdaa34eb6e11d246b9e71e1ee",
|
|
26
|
+
"skills/ai-c2-detection/skill.md": "3da9f549f5c62e6163cddd70c8edccbef7be622d5a45fa89c90c6550e68c6b2e",
|
|
27
|
+
"skills/policy-exception-gen/skill.md": "a7d886f7fa99a150b040f158b09045ba45e107439315389aea785311b0013395",
|
|
28
|
+
"skills/threat-model-currency/skill.md": "ecc6441cb47ef2bc24547e47be018098228c956a41d61ddb50de7e7b37114a37",
|
|
29
|
+
"skills/global-grc/skill.md": "1dca534cce7612c1d26a7b1bfd088a811081555ecfa25b1f68cff2ca2ba28c98",
|
|
30
30
|
"skills/zeroday-gap-learn/skill.md": "59a0d7cd85b923b3f5633bdc15c1a88eef7dea6332480d93b0bb0ae93a4cd0fe",
|
|
31
|
-
"skills/pqc-first/skill.md": "
|
|
32
|
-
"skills/skill-update-loop/skill.md": "
|
|
33
|
-
"skills/security-maturity-tiers/skill.md": "
|
|
34
|
-
"skills/researcher/skill.md": "
|
|
35
|
-
"skills/attack-surface-pentest/skill.md": "
|
|
36
|
-
"skills/fuzz-testing-strategy/skill.md": "
|
|
37
|
-
"skills/dlp-gap-analysis/skill.md": "
|
|
38
|
-
"skills/supply-chain-integrity/skill.md": "
|
|
31
|
+
"skills/pqc-first/skill.md": "a7131b65d0ceee47887b16679ee4e4b065d32d8751fe59921762388703662913",
|
|
32
|
+
"skills/skill-update-loop/skill.md": "cf2b996cb18a5146614c06e3a50f4734a07d02b5be36bbdf492583f9cdcfed4d",
|
|
33
|
+
"skills/security-maturity-tiers/skill.md": "ed962937c45f3d95f325f231b787d272fe45c4cb91d4c5a2d982493d722c2acf",
|
|
34
|
+
"skills/researcher/skill.md": "b47daaa26fdac07aa23e7becaa18487c5302e65c654f99fecab3689f23ec1bd2",
|
|
35
|
+
"skills/attack-surface-pentest/skill.md": "0d301beb9fb8e247ec80256a7e647804b5f9a41c7156e5724555ca9f93ccb986",
|
|
36
|
+
"skills/fuzz-testing-strategy/skill.md": "51acb746cd63366ca62567588c700a9eb3f37c43250bd9ae4e1477ccb71c5b6d",
|
|
37
|
+
"skills/dlp-gap-analysis/skill.md": "1c4e1d7da2421b82f202eaf2c9e21876af34ab5c76ce1359166842ee473f02dd",
|
|
38
|
+
"skills/supply-chain-integrity/skill.md": "ad69b72f5c5df095f8618b977fbc8f0fbff396eebd4a8448b44c3f93309f63f9",
|
|
39
39
|
"skills/defensive-countermeasure-mapping/skill.md": "e62c71ba3be2b4d0f7dfa529fec007cba6bee3013f76b93756e3e6310f2d22ab",
|
|
40
|
-
"skills/identity-assurance/skill.md": "
|
|
41
|
-
"skills/ot-ics-security/skill.md": "
|
|
40
|
+
"skills/identity-assurance/skill.md": "4ee7096fd82997c66b0f9e825ea3c04c3aa84768b74e6f668c1a9104104138cf",
|
|
41
|
+
"skills/ot-ics-security/skill.md": "9ece7b1fb7f24e37dbdd8618b94b2a4434e182e3426e15f17e26464c0a1fdfd1",
|
|
42
42
|
"skills/coordinated-vuln-disclosure/skill.md": "0e875953bb8a38a89c8ec5d2a9ef967b12e9a9f166dc9356723f10304fd0535e",
|
|
43
|
-
"skills/threat-modeling-methodology/skill.md": "
|
|
44
|
-
"skills/webapp-security/skill.md": "
|
|
45
|
-
"skills/ai-risk-management/skill.md": "
|
|
46
|
-
"skills/sector-healthcare/skill.md": "
|
|
47
|
-
"skills/sector-financial/skill.md": "
|
|
48
|
-
"skills/sector-federal-government/skill.md": "
|
|
49
|
-
"skills/sector-energy/skill.md": "
|
|
50
|
-
"skills/sector-telecom/skill.md": "
|
|
51
|
-
"skills/api-security/skill.md": "
|
|
52
|
-
"skills/cloud-security/skill.md": "
|
|
53
|
-
"skills/container-runtime-security/skill.md": "
|
|
54
|
-
"skills/mlops-security/skill.md": "
|
|
55
|
-
"skills/incident-response-playbook/skill.md": "
|
|
56
|
-
"skills/ransomware-response/skill.md": "
|
|
57
|
-
"skills/email-security-anti-phishing/skill.md": "
|
|
58
|
-
"skills/age-gates-child-safety/skill.md": "
|
|
59
|
-
"skills/cloud-iam-incident/skill.md": "
|
|
60
|
-
"skills/idp-incident-response/skill.md": "
|
|
43
|
+
"skills/threat-modeling-methodology/skill.md": "ac623f61585de66c9ef5ed63e9c6059faef77e525abc672ac6d435c616a7268f",
|
|
44
|
+
"skills/webapp-security/skill.md": "fdb07324b69a3a724e3eaba17bf687d72d4bd9d5c4f440be816bc9b08b8aef04",
|
|
45
|
+
"skills/ai-risk-management/skill.md": "67e62791f60231f2ff53408922fa7137a9060de72097769c630f838a1c227c45",
|
|
46
|
+
"skills/sector-healthcare/skill.md": "a18e11d25524cdbf40df3798f4c2aa3cb51a4db1b088242ea53fa2885e86b64c",
|
|
47
|
+
"skills/sector-financial/skill.md": "023b5440d614e6b83ba7294219bcac3cdbffd28fdfdd5f0ec23abbeea71b8230",
|
|
48
|
+
"skills/sector-federal-government/skill.md": "c63cf1c7c98e920f968cfe60f14e718ea71b120c1b01616af22f64a796963bbe",
|
|
49
|
+
"skills/sector-energy/skill.md": "643fd951359c2602d9b029a244fe66c1e23f726e711141a06c09cc760a479534",
|
|
50
|
+
"skills/sector-telecom/skill.md": "862f9482af88e5409e011a6981a5d719863deeb646e41cd4df63e5d6597c50b1",
|
|
51
|
+
"skills/api-security/skill.md": "2bdfa3dbe534efa3df245e0da37998ad7ab2da4a3171d5000d3346513c10bceb",
|
|
52
|
+
"skills/cloud-security/skill.md": "c9fad9ed3663cf2faec74ad8f06d62eb86e6636f79933560d8c8d50e0e82d1da",
|
|
53
|
+
"skills/container-runtime-security/skill.md": "605a8e8eb1af09835b967ec7179456015ec116c6b9051af3a8d225866cc2f7af",
|
|
54
|
+
"skills/mlops-security/skill.md": "ca3fd922b43fc57aeb5e65c2d5a2823e6bc438167d6afa3a767cee83e4af1f96",
|
|
55
|
+
"skills/incident-response-playbook/skill.md": "2017515d899c1b2bcb878bc6731e4059623ac52345b2cebbd92204583657bf60",
|
|
56
|
+
"skills/ransomware-response/skill.md": "2e4fc488f86ed1ba7791ab0e7021160d8ca5ad33a02cdf92a5b916c8afecaa54",
|
|
57
|
+
"skills/email-security-anti-phishing/skill.md": "e4e9e5a820c0ed3fde9483282e7a0ecaf79284cd2e9923ce66f2b0fb1fc44626",
|
|
58
|
+
"skills/age-gates-child-safety/skill.md": "66e7773d29c179ab62f409007c05e05993e04a19273225a1e520f2481fd9a90d",
|
|
59
|
+
"skills/cloud-iam-incident/skill.md": "6494ee3856edeb212e65fe5cdb208357c1a832eb8ac374b26055586bfc71f629",
|
|
60
|
+
"skills/idp-incident-response/skill.md": "e67a2576e7f1c3bf89f499f5c977bc470ef29e8b3e3e45f4cb5bd45a82674282"
|
|
61
61
|
},
|
|
62
62
|
"skill_count": 42,
|
|
63
63
|
"catalog_count": 11,
|
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
"handoff_dag_nodes": 42,
|
|
79
79
|
"summary_cards": 42,
|
|
80
80
|
"section_offsets_skills": 42,
|
|
81
|
-
"token_budget_total_approx":
|
|
81
|
+
"token_budget_total_approx": 402643,
|
|
82
82
|
"recipes": 8,
|
|
83
83
|
"jurisdiction_clocks": 29,
|
|
84
84
|
"did_ladders": 8,
|