@blamejs/exceptd-skills 0.12.11 → 0.12.15
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/CHANGELOG.md +243 -0
- package/bin/exceptd.js +299 -48
- package/data/_indexes/_meta.json +49 -48
- package/data/_indexes/activity-feed.json +13 -5
- package/data/_indexes/catalog-summaries.json +51 -29
- package/data/_indexes/chains.json +3238 -3210
- package/data/_indexes/frequency.json +3 -0
- package/data/_indexes/jurisdiction-map.json +5 -3
- package/data/_indexes/section-offsets.json +712 -685
- package/data/_indexes/theater-fingerprints.json +1 -1
- package/data/_indexes/token-budget.json +355 -340
- package/data/atlas-ttps.json +144 -129
- package/data/attack-techniques.json +339 -0
- package/data/cve-catalog.json +515 -475
- package/data/cwe-catalog.json +1081 -759
- package/data/exploit-availability.json +63 -15
- package/data/framework-control-gaps.json +867 -843
- package/data/rfc-references.json +276 -276
- package/keys/EXPECTED_FINGERPRINT +1 -0
- package/lib/auto-discovery.js +21 -4
- package/lib/cross-ref-api.js +39 -6
- package/lib/cve-curation.js +505 -47
- package/lib/lint-skills.js +217 -15
- package/lib/playbook-runner.js +1224 -183
- package/lib/prefetch.js +121 -8
- package/lib/refresh-external.js +261 -95
- package/lib/refresh-network.js +208 -18
- package/lib/schemas/manifest.schema.json +16 -0
- package/lib/scoring.js +83 -7
- package/lib/sign.js +112 -3
- package/lib/source-ghsa.js +219 -37
- package/lib/source-osv.js +381 -122
- package/lib/validate-catalog-meta.js +64 -9
- package/lib/validate-cve-catalog.js +213 -7
- package/lib/validate-indexes.js +88 -37
- package/lib/validate-playbooks.js +469 -0
- package/lib/verify.js +313 -16
- package/manifest-snapshot.json +1 -1
- package/manifest-snapshot.sha256 +1 -0
- package/manifest.json +73 -73
- package/orchestrator/dispatcher.js +21 -1
- package/orchestrator/event-bus.js +52 -8
- package/orchestrator/index.js +279 -20
- package/orchestrator/pipeline.js +63 -2
- package/orchestrator/scanner.js +32 -10
- package/orchestrator/scheduler.js +196 -20
- package/package.json +3 -1
- package/sbom.cdx.json +9 -9
- package/scripts/check-manifest-snapshot.js +32 -0
- package/scripts/check-sbom-currency.js +65 -3
- package/scripts/check-test-coverage.js +142 -19
- package/scripts/predeploy.js +110 -40
- package/scripts/refresh-manifest-snapshot.js +55 -4
- package/scripts/validate-vendor-online.js +169 -0
- package/scripts/verify-shipped-tarball.js +106 -3
- package/skills/ai-attack-surface/skill.md +18 -10
- package/skills/ai-c2-detection/skill.md +7 -2
- package/skills/ai-risk-management/skill.md +5 -4
- package/skills/api-security/skill.md +3 -3
- package/skills/attack-surface-pentest/skill.md +5 -5
- package/skills/cloud-security/skill.md +1 -1
- package/skills/compliance-theater/skill.md +8 -8
- package/skills/container-runtime-security/skill.md +1 -1
- package/skills/dlp-gap-analysis/skill.md +5 -1
- package/skills/email-security-anti-phishing/skill.md +1 -1
- package/skills/exploit-scoring/skill.md +18 -18
- package/skills/framework-gap-analysis/skill.md +6 -6
- package/skills/global-grc/skill.md +3 -2
- package/skills/identity-assurance/skill.md +2 -2
- package/skills/incident-response-playbook/skill.md +4 -4
- package/skills/kernel-lpe-triage/skill.md +21 -2
- package/skills/mcp-agent-trust/skill.md +17 -10
- package/skills/mlops-security/skill.md +2 -1
- package/skills/ot-ics-security/skill.md +1 -1
- package/skills/policy-exception-gen/skill.md +3 -3
- package/skills/pqc-first/skill.md +1 -1
- package/skills/rag-pipeline-security/skill.md +7 -3
- package/skills/researcher/skill.md +20 -3
- package/skills/sector-energy/skill.md +1 -1
- package/skills/sector-federal-government/skill.md +1 -1
- package/skills/sector-financial/skill.md +3 -3
- package/skills/sector-healthcare/skill.md +2 -2
- package/skills/security-maturity-tiers/skill.md +7 -7
- package/skills/skill-update-loop/skill.md +19 -3
- package/skills/supply-chain-integrity/skill.md +1 -1
- package/skills/threat-model-currency/skill.md +11 -11
- package/skills/threat-modeling-methodology/skill.md +3 -3
- package/skills/webapp-security/skill.md +1 -1
- package/skills/zeroday-gap-learn/skill.md +51 -7
- package/vendor/blamejs/_PROVENANCE.json +4 -1
- package/vendor/blamejs/worker-pool.js +38 -0
package/bin/exceptd.js
CHANGED
|
@@ -541,10 +541,16 @@ function emit(obj, pretty, humanRenderer) {
|
|
|
541
541
|
}
|
|
542
542
|
|
|
543
543
|
function emitError(msg, extra, pretty) {
|
|
544
|
+
// v0.12.14 (audit A P1-2): the v0.11.13 emit() fix used exitCode + return
|
|
545
|
+
// to defend stdout-buffered writes from truncation under piped consumers.
|
|
546
|
+
// emitError() (stderr) kept process.exit(1), which has the same truncation
|
|
547
|
+
// class — CLAUDE.md's "fix the class, not the instance." Now: write to
|
|
548
|
+
// stderr, set exitCode = 1, return. Every caller already uses
|
|
549
|
+
// `return emitError(...)` so the return-value propagation is clean.
|
|
544
550
|
const body = Object.assign({ ok: false, error: msg }, extra || {});
|
|
545
551
|
const s = pretty ? JSON.stringify(body, null, 2) : JSON.stringify(body);
|
|
546
552
|
process.stderr.write(s + "\n");
|
|
547
|
-
process.
|
|
553
|
+
process.exitCode = 1;
|
|
548
554
|
}
|
|
549
555
|
|
|
550
556
|
function readEvidence(evidenceFlag) {
|
|
@@ -554,6 +560,17 @@ function readEvidence(evidenceFlag) {
|
|
|
554
560
|
if (!buf.trim()) return {};
|
|
555
561
|
return JSON.parse(buf);
|
|
556
562
|
}
|
|
563
|
+
// v0.12.12: read enforces a max size to defend against an operator
|
|
564
|
+
// accidentally passing a multi-gigabyte file (binary, log, or
|
|
565
|
+
// adversarial JSON bomb). 32 MB is well beyond any legitimate
|
|
566
|
+
// submission and still drains in a single read on modern hardware.
|
|
567
|
+
const MAX_EVIDENCE_BYTES = 32 * 1024 * 1024;
|
|
568
|
+
let stat;
|
|
569
|
+
try { stat = fs.statSync(evidenceFlag); }
|
|
570
|
+
catch (e) { throw new Error(`evidence path not readable: ${e.message}`); }
|
|
571
|
+
if (stat.size > MAX_EVIDENCE_BYTES) {
|
|
572
|
+
throw new Error(`evidence file too large: ${stat.size} bytes > ${MAX_EVIDENCE_BYTES} byte limit. Reduce the submission or split into multiple playbook runs.`);
|
|
573
|
+
}
|
|
557
574
|
return JSON.parse(fs.readFileSync(evidenceFlag, "utf8"));
|
|
558
575
|
}
|
|
559
576
|
|
|
@@ -607,8 +624,39 @@ function dispatchPlaybook(cmd, argv) {
|
|
|
607
624
|
airGap: !!args["air-gap"],
|
|
608
625
|
forceStale: !!args["force-stale"],
|
|
609
626
|
};
|
|
610
|
-
if (args["session-id"])
|
|
611
|
-
|
|
627
|
+
if (args["session-id"]) {
|
|
628
|
+
// v0.12.12: --session-id is a filesystem path component (resolves to
|
|
629
|
+
// .exceptd/attestations/<id>/attestation.json). Operator-supplied input
|
|
630
|
+
// with `..` or path separators escapes the attestation root. Validate
|
|
631
|
+
// strict allowlist before propagating.
|
|
632
|
+
const sid = args["session-id"];
|
|
633
|
+
if (typeof sid !== "string" || !/^[A-Za-z0-9._-]{1,64}$/.test(sid)) {
|
|
634
|
+
return emitError(
|
|
635
|
+
"run: --session-id must match /^[A-Za-z0-9._-]{1,64}$/ (alphanumeric, dot, underscore, hyphen; up to 64 chars). Path separators and '..' are rejected.",
|
|
636
|
+
{ provided: typeof sid === "string" ? sid.slice(0, 80) : typeof sid },
|
|
637
|
+
pretty
|
|
638
|
+
);
|
|
639
|
+
}
|
|
640
|
+
runOpts.session_id = sid;
|
|
641
|
+
}
|
|
642
|
+
if (args["attestation-root"]) {
|
|
643
|
+
// v0.12.12: --attestation-root must resolve to an absolute path the
|
|
644
|
+
// operator owns. Reject `..`-bearing relatives at input so a misconfigured
|
|
645
|
+
// env doesn't write outside the intended root. Final resolution still
|
|
646
|
+
// happens in resolveAttestationRoot — this is the input-validation layer.
|
|
647
|
+
const ar = args["attestation-root"];
|
|
648
|
+
if (typeof ar !== "string" || ar.length === 0) {
|
|
649
|
+
return emitError("run: --attestation-root must be a non-empty string.", { provided: typeof ar }, pretty);
|
|
650
|
+
}
|
|
651
|
+
if (ar.split(/[\\/]/).some(seg => seg === "..")) {
|
|
652
|
+
return emitError(
|
|
653
|
+
"run: --attestation-root must not contain '..' path segments. Pass an absolute path under your home directory or an explicit project-relative path without traversal.",
|
|
654
|
+
{ provided: ar.slice(0, 200) },
|
|
655
|
+
pretty
|
|
656
|
+
);
|
|
657
|
+
}
|
|
658
|
+
runOpts.attestationRoot = path.resolve(ar);
|
|
659
|
+
}
|
|
612
660
|
if (args["session-key"]) {
|
|
613
661
|
// Bug #33: validate that --session-key is hex. Previously any string was
|
|
614
662
|
// silently accepted; HMAC signing then either failed silently or produced
|
|
@@ -1395,7 +1443,26 @@ function cmdPlan(runner, args, runOpts, pretty) {
|
|
|
1395
1443
|
emit(plan, pretty);
|
|
1396
1444
|
}
|
|
1397
1445
|
|
|
1446
|
+
// v0.12.15 (audit L F1, F2): --scope must validate against the accepted
|
|
1447
|
+
// set. The prior shape silently returned [] for any unknown scope, which
|
|
1448
|
+
// in `run --scope nonsense` produced `count: 0` + exit 0 (cmd reports
|
|
1449
|
+
// "ran 0 playbooks") and in `ci --scope nonsense` silently ran only the
|
|
1450
|
+
// cross-cutting set (the union with `framework` produced a false-positive
|
|
1451
|
+
// PASS). Both are operator-intent loss patterns CLAUDE.md flags as the
|
|
1452
|
+
// "field-present, content-wrong" class.
|
|
1453
|
+
const VALID_SCOPES = ["system", "code", "service", "cross-cutting", "all"];
|
|
1454
|
+
|
|
1455
|
+
function validateScopeOrThrow(scope) {
|
|
1456
|
+
if (typeof scope !== "string" || !VALID_SCOPES.includes(scope)) {
|
|
1457
|
+
throw new Error(
|
|
1458
|
+
`--scope must be one of ${JSON.stringify(VALID_SCOPES)}; got ${JSON.stringify(scope)}.`
|
|
1459
|
+
);
|
|
1460
|
+
}
|
|
1461
|
+
return scope;
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1398
1464
|
function filterPlaybooksByScope(runner, scope) {
|
|
1465
|
+
validateScopeOrThrow(scope);
|
|
1399
1466
|
const ids = runner.listPlaybooks();
|
|
1400
1467
|
return ids.filter(id => {
|
|
1401
1468
|
try {
|
|
@@ -1471,7 +1538,8 @@ function cmdRun(runner, args, runOpts, pretty) {
|
|
|
1471
1538
|
if (args.all) {
|
|
1472
1539
|
ids = runner.listPlaybooks();
|
|
1473
1540
|
} else {
|
|
1474
|
-
ids = filterPlaybooksByScope(runner, args.scope);
|
|
1541
|
+
try { ids = filterPlaybooksByScope(runner, args.scope); }
|
|
1542
|
+
catch (e) { return emitError(`run: ${e.message}`, { provided_scope: args.scope }, pretty); }
|
|
1475
1543
|
}
|
|
1476
1544
|
return cmdRunMulti(runner, ids, args, runOpts, pretty, { trigger: args.all ? "--all" : `--scope ${args.scope}` });
|
|
1477
1545
|
}
|
|
@@ -1651,8 +1719,11 @@ function cmdRun(runner, args, runOpts, pretty) {
|
|
|
1651
1719
|
hint: "Pass --force-overwrite to replace, or supply a fresh --session-id (omit the flag for an auto-generated hex).",
|
|
1652
1720
|
verb: "run",
|
|
1653
1721
|
};
|
|
1722
|
+
// v0.12.14 (audit A P1-2): exitCode + return instead of process.exit
|
|
1723
|
+
// so the stderr line drains under piped CI consumers.
|
|
1654
1724
|
process.stderr.write(JSON.stringify(err) + "\n");
|
|
1655
|
-
process.
|
|
1725
|
+
process.exitCode = 3;
|
|
1726
|
+
return;
|
|
1656
1727
|
}
|
|
1657
1728
|
if (persistResult.prior_session_id) {
|
|
1658
1729
|
// Force-overwrite happened — surface the prior_session_id in the
|
|
@@ -1665,8 +1736,10 @@ function cmdRun(runner, args, runOpts, pretty) {
|
|
|
1665
1736
|
}
|
|
1666
1737
|
|
|
1667
1738
|
if (result && result.ok === false) {
|
|
1739
|
+
// v0.12.14: exitCode + return; matches the emitError class fix.
|
|
1668
1740
|
process.stderr.write((pretty ? JSON.stringify(result, null, 2) : JSON.stringify(result)) + "\n");
|
|
1669
|
-
process.
|
|
1741
|
+
process.exitCode = 1;
|
|
1742
|
+
return;
|
|
1670
1743
|
}
|
|
1671
1744
|
|
|
1672
1745
|
// v0.11.6 (#96): --strict-preconditions escalates warn-level preflight
|
|
@@ -1678,6 +1751,14 @@ function cmdRun(runner, args, runOpts, pretty) {
|
|
|
1678
1751
|
i.kind === "precondition_unverified" || i.kind === "precondition_warn"
|
|
1679
1752
|
);
|
|
1680
1753
|
if (warnIssues.length > 0) {
|
|
1754
|
+
// v0.12.12: surface the contract violation in the emitted body so
|
|
1755
|
+
// downstream consumers grepping the JSON see WHY the exit is non-zero.
|
|
1756
|
+
// result.ok stays true (the playbook executed) but the explicit flag
|
|
1757
|
+
// makes the strict-preconditions contract observable, not just inferable
|
|
1758
|
+
// from exit code + stderr line.
|
|
1759
|
+
result.strict_preconditions_violated = warnIssues.map(i => ({
|
|
1760
|
+
id: i.id, kind: i.kind, message: i.message || null, on_fail: i.on_fail || null,
|
|
1761
|
+
}));
|
|
1681
1762
|
process.stderr.write(`[exceptd run] --strict-preconditions: ${warnIssues.length} unverified/warn precondition(s) — exit 1.\n`);
|
|
1682
1763
|
emit(result, pretty);
|
|
1683
1764
|
// v0.11.11: exitCode + return so emit()'s stdout flushes (process.exit
|
|
@@ -1922,13 +2003,28 @@ function cmdRunMulti(runner, ids, args, runOpts, pretty, meta) {
|
|
|
1922
2003
|
// contract in one pass.
|
|
1923
2004
|
if (args["evidence-dir"]) {
|
|
1924
2005
|
const dir = args["evidence-dir"];
|
|
2006
|
+
if (typeof dir !== "string" || dir.length === 0) {
|
|
2007
|
+
return emitError("run: --evidence-dir must be a non-empty string.", null, pretty);
|
|
2008
|
+
}
|
|
1925
2009
|
if (!fs.existsSync(dir)) {
|
|
1926
2010
|
return emitError(`run: --evidence-dir ${dir} does not exist.`, null, pretty);
|
|
1927
2011
|
}
|
|
2012
|
+
const resolvedDir = path.resolve(dir);
|
|
2013
|
+
// v0.12.12: only `<playbook-id>.json` entries are honored. Reject
|
|
2014
|
+
// anything where the filename strip leaves traversal segments — npm
|
|
2015
|
+
// refuses to write such filenames so the realistic risk is an operator
|
|
2016
|
+
// symlink/junction inside the dir, but the filter is cheap.
|
|
1928
2017
|
for (const f of fs.readdirSync(dir).filter(x => x.endsWith(".json"))) {
|
|
1929
2018
|
const pbId = f.replace(/\.json$/, "");
|
|
2019
|
+
if (!/^[A-Za-z0-9_.-]+$/.test(pbId)) {
|
|
2020
|
+
return emitError(`run: --evidence-dir entry ${JSON.stringify(f)} has unsafe playbook-id segment.`, null, pretty);
|
|
2021
|
+
}
|
|
2022
|
+
const entryPath = path.resolve(path.join(resolvedDir, f));
|
|
2023
|
+
if (!entryPath.startsWith(resolvedDir + path.sep)) {
|
|
2024
|
+
return emitError(`run: --evidence-dir entry ${f} resolves outside the directory; refusing.`, null, pretty);
|
|
2025
|
+
}
|
|
1930
2026
|
try {
|
|
1931
|
-
bundle[pbId] = JSON.parse(fs.readFileSync(
|
|
2027
|
+
bundle[pbId] = JSON.parse(fs.readFileSync(entryPath, "utf8"));
|
|
1932
2028
|
} catch (e) {
|
|
1933
2029
|
return emitError(`run: failed to parse --evidence-dir entry ${f}: ${e.message}`, null, pretty);
|
|
1934
2030
|
}
|
|
@@ -2128,47 +2224,86 @@ function deriveRunTag() {
|
|
|
2128
2224
|
function persistAttestation(args) {
|
|
2129
2225
|
const { sessionId, playbookId, directiveId, evidenceHash, operator,
|
|
2130
2226
|
operatorConsent, submission, runOpts, forceOverwrite, filename } = args;
|
|
2227
|
+
// v0.12.12: session-id is supposed to be sanitized at input. Defense in
|
|
2228
|
+
// depth: reject anything that path-traverses out of the attestation root.
|
|
2229
|
+
if (!/^[A-Za-z0-9._-]{1,64}$/.test(sessionId || "")) {
|
|
2230
|
+
return {
|
|
2231
|
+
ok: false,
|
|
2232
|
+
error: `Refusing to persist attestation with unsafe session-id: ${JSON.stringify(sessionId).slice(0, 80)}. Must match /^[A-Za-z0-9._-]{1,64}$/.`,
|
|
2233
|
+
existingPath: null,
|
|
2234
|
+
};
|
|
2235
|
+
}
|
|
2236
|
+
if (!/^[A-Za-z0-9._-]{1,64}\.json$/.test(filename || "")) {
|
|
2237
|
+
return {
|
|
2238
|
+
ok: false,
|
|
2239
|
+
error: `Refusing to persist attestation with unsafe filename: ${JSON.stringify(filename).slice(0, 80)}.`,
|
|
2240
|
+
existingPath: null,
|
|
2241
|
+
};
|
|
2242
|
+
}
|
|
2131
2243
|
const root = resolveAttestationRoot(runOpts);
|
|
2132
2244
|
const dir = path.join(root, sessionId);
|
|
2133
2245
|
const filePath = path.join(dir, filename);
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
if (
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
existingPath: path.relative(process.cwd(), filePath),
|
|
2143
|
-
};
|
|
2144
|
-
}
|
|
2246
|
+
// Final-resolution check: dir must remain inside root after normalization.
|
|
2247
|
+
const normRoot = path.resolve(root) + path.sep;
|
|
2248
|
+
if (!(path.resolve(dir) + path.sep).startsWith(normRoot)) {
|
|
2249
|
+
return {
|
|
2250
|
+
ok: false,
|
|
2251
|
+
error: `Refusing to persist attestation outside root. session_id=${sessionId} root=${root}`,
|
|
2252
|
+
existingPath: null,
|
|
2253
|
+
};
|
|
2145
2254
|
}
|
|
2146
2255
|
|
|
2147
2256
|
try {
|
|
2148
2257
|
fs.mkdirSync(dir, { recursive: true });
|
|
2149
|
-
const
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2258
|
+
const writeAttestation = (priorEvidenceHash, priorCapturedAt, flag) => {
|
|
2259
|
+
const attestation = {
|
|
2260
|
+
session_id: sessionId,
|
|
2261
|
+
playbook_id: playbookId,
|
|
2262
|
+
directive_id: directiveId,
|
|
2263
|
+
evidence_hash: evidenceHash,
|
|
2264
|
+
operator: operator || null,
|
|
2265
|
+
operator_consent: operatorConsent || null,
|
|
2266
|
+
submission,
|
|
2267
|
+
run_opts: { airGap: runOpts.airGap, forceStale: runOpts.forceStale, mode: runOpts.mode },
|
|
2268
|
+
captured_at: new Date().toISOString(),
|
|
2269
|
+
// When overwriting (with --force-overwrite), link to the prior content
|
|
2270
|
+
// by evidence_hash + capture timestamp. session_id is the same (that's
|
|
2271
|
+
// why we collided), so it's the hash + timestamp that distinguish.
|
|
2272
|
+
prior_evidence_hash: priorEvidenceHash,
|
|
2273
|
+
prior_captured_at: priorCapturedAt,
|
|
2274
|
+
};
|
|
2275
|
+
// Atomic-create via O_EXCL ('wx' flag) eliminates the TOCTOU window
|
|
2276
|
+
// between existsSync and writeFileSync. Two concurrent run-with-same-
|
|
2277
|
+
// session-id invocations now produce one winner + one EEXIST loser,
|
|
2278
|
+
// not silent last-write-wins.
|
|
2279
|
+
fs.writeFileSync(filePath, JSON.stringify(attestation, null, 2), { flag });
|
|
2280
|
+
maybeSignAttestation(filePath);
|
|
2171
2281
|
};
|
|
2282
|
+
|
|
2283
|
+
try {
|
|
2284
|
+
writeAttestation(null, null, "wx");
|
|
2285
|
+
return { ok: true, prior_session_id: null, overwrote_at: null };
|
|
2286
|
+
} catch (eExcl) {
|
|
2287
|
+
if (eExcl.code !== "EEXIST") throw eExcl;
|
|
2288
|
+
// Slot already taken — read prior to chain audit trail, then decide.
|
|
2289
|
+
let prior = null;
|
|
2290
|
+
try { prior = JSON.parse(fs.readFileSync(filePath, "utf8")); } catch { /* malformed prior — proceed */ }
|
|
2291
|
+
if (!forceOverwrite) {
|
|
2292
|
+
return {
|
|
2293
|
+
ok: false,
|
|
2294
|
+
error: `Attestation already exists at ${path.relative(process.cwd(), filePath)}. Session-id collision (${sessionId}) — refusing to overwrite to preserve audit trail.`,
|
|
2295
|
+
existingPath: path.relative(process.cwd(), filePath),
|
|
2296
|
+
};
|
|
2297
|
+
}
|
|
2298
|
+
writeAttestation(prior ? (prior.evidence_hash || null) : null,
|
|
2299
|
+
prior ? (prior.captured_at || null) : null,
|
|
2300
|
+
"w");
|
|
2301
|
+
return {
|
|
2302
|
+
ok: true,
|
|
2303
|
+
prior_session_id: prior ? sessionId : null,
|
|
2304
|
+
overwrote_at: prior ? prior.captured_at : null,
|
|
2305
|
+
};
|
|
2306
|
+
}
|
|
2172
2307
|
} catch (e) {
|
|
2173
2308
|
return { ok: false, error: `Failed to write attestation: ${e.message}`, existingPath: null };
|
|
2174
2309
|
}
|
|
@@ -2243,12 +2378,43 @@ function maybeSignAttestation(filePath) {
|
|
|
2243
2378
|
* default root and the legacy cwd-relative root; returns whichever exists.
|
|
2244
2379
|
* Returns null if neither has the session.
|
|
2245
2380
|
*/
|
|
2381
|
+
/**
|
|
2382
|
+
* v0.12.14 (audit A P1-1): session-id validation — applied at every READ
|
|
2383
|
+
* site, not just writes. The write path (persistAttestation) was hardened
|
|
2384
|
+
* in v0.12.12, but the read paths (findSessionDir / cmdAttest / cmdReattest)
|
|
2385
|
+
* accepted arbitrary strings and joined them into path.join(root, id) with
|
|
2386
|
+
* no normalization. Reproducer that exfiltrated $HOME/.claude.json:
|
|
2387
|
+
* exceptd attest show '../../..'
|
|
2388
|
+
*
|
|
2389
|
+
* Validation regex + root-confinement check matches persistAttestation.
|
|
2390
|
+
*/
|
|
2391
|
+
function validateSessionIdForRead(sessionId) {
|
|
2392
|
+
if (typeof sessionId !== "string" || !/^[A-Za-z0-9._-]{1,64}$/.test(sessionId)) {
|
|
2393
|
+
throw new Error(
|
|
2394
|
+
`Invalid session-id: ${JSON.stringify(sessionId).slice(0, 80)}. Must match /^[A-Za-z0-9._-]{1,64}$/.`
|
|
2395
|
+
);
|
|
2396
|
+
}
|
|
2397
|
+
return sessionId;
|
|
2398
|
+
}
|
|
2399
|
+
|
|
2246
2400
|
function findSessionDir(sessionId, runOpts) {
|
|
2401
|
+
// v0.12.14 (audit A P1-1): validate the session-id at every read path.
|
|
2402
|
+
try { validateSessionIdForRead(sessionId); }
|
|
2403
|
+
catch { return null; }
|
|
2247
2404
|
const candidates = [
|
|
2248
2405
|
path.join(resolveAttestationRoot(runOpts), sessionId),
|
|
2249
2406
|
path.join(process.cwd(), ".exceptd", "attestations", sessionId),
|
|
2250
2407
|
];
|
|
2251
|
-
for (const c of candidates)
|
|
2408
|
+
for (const c of candidates) {
|
|
2409
|
+
// Final-resolution check: the resolved candidate must stay strictly
|
|
2410
|
+
// inside its parent root after normalization. Defense in depth on top
|
|
2411
|
+
// of the regex check above — catches anything that survives the
|
|
2412
|
+
// string-level filter.
|
|
2413
|
+
const parent = path.dirname(c);
|
|
2414
|
+
const resolved = path.resolve(c);
|
|
2415
|
+
if (!resolved.startsWith(path.resolve(parent) + path.sep)) continue;
|
|
2416
|
+
if (fs.existsSync(c)) return c;
|
|
2417
|
+
}
|
|
2252
2418
|
return null;
|
|
2253
2419
|
}
|
|
2254
2420
|
|
|
@@ -3274,6 +3440,26 @@ function cmdDoctor(runner, args, runOpts, pretty) {
|
|
|
3274
3440
|
}
|
|
3275
3441
|
|
|
3276
3442
|
function cmdListAttestations(runner, args, runOpts, pretty) {
|
|
3443
|
+
// v0.12.14 (audit A P2-3): --playbook is registered as `multi:` so
|
|
3444
|
+
// `--playbook a --playbook b` lands as an array. The prior filter used
|
|
3445
|
+
// strict equality (`j.playbook_id !== args.playbook`) — always false for
|
|
3446
|
+
// array, silently producing count: 0. Normalize to a Set up-front.
|
|
3447
|
+
const playbookFilter = (() => {
|
|
3448
|
+
if (args.playbook == null) return null;
|
|
3449
|
+
const list = Array.isArray(args.playbook) ? args.playbook : [args.playbook];
|
|
3450
|
+
return new Set(list.filter(x => typeof x === "string" && x.length > 0));
|
|
3451
|
+
})();
|
|
3452
|
+
// v0.12.14 (audit A P2-6): --since must be a parseable ISO-8601 timestamp.
|
|
3453
|
+
// Prior behavior silently accepted any string and lexically compared to
|
|
3454
|
+
// captured_at, producing 0-result or full-result depending on the string.
|
|
3455
|
+
if (args.since != null) {
|
|
3456
|
+
if (typeof args.since !== "string" || isNaN(Date.parse(args.since))) {
|
|
3457
|
+
return emitError(
|
|
3458
|
+
`attest list: --since must be a parseable ISO-8601 timestamp (e.g. 2026-05-01 or 2026-05-01T00:00:00Z). Got: ${JSON.stringify(String(args.since)).slice(0, 80)}`,
|
|
3459
|
+
null, pretty
|
|
3460
|
+
);
|
|
3461
|
+
}
|
|
3462
|
+
}
|
|
3277
3463
|
// Enumerate sessions across both v0.11.0 default root and legacy cwd-
|
|
3278
3464
|
// relative root, so operators with prior attestations still see them.
|
|
3279
3465
|
const roots = [resolveAttestationRoot(runOpts), path.join(process.cwd(), ".exceptd", "attestations")];
|
|
@@ -3291,7 +3477,8 @@ function cmdListAttestations(runner, args, runOpts, pretty) {
|
|
|
3291
3477
|
for (const f of files) {
|
|
3292
3478
|
try {
|
|
3293
3479
|
const j = JSON.parse(fs.readFileSync(path.join(sdir, f), "utf8"));
|
|
3294
|
-
|
|
3480
|
+
// v0.12.14: normalized array-set filter (see top of fn).
|
|
3481
|
+
if (playbookFilter && !playbookFilter.has(j.playbook_id)) continue;
|
|
3295
3482
|
if (args.since && (j.captured_at || "") < args.since) continue;
|
|
3296
3483
|
entries.push({
|
|
3297
3484
|
session_id: sid,
|
|
@@ -3311,7 +3498,7 @@ function cmdListAttestations(runner, args, runOpts, pretty) {
|
|
|
3311
3498
|
ok: true,
|
|
3312
3499
|
attestations: entries,
|
|
3313
3500
|
count: entries.length,
|
|
3314
|
-
filter: { playbook:
|
|
3501
|
+
filter: { playbook: playbookFilter ? [...playbookFilter] : null, since: args.since || null },
|
|
3315
3502
|
roots_searched: [...seenRoots],
|
|
3316
3503
|
}, pretty, (obj) => {
|
|
3317
3504
|
// v0.11.6 (#95) human renderer for attest list: one row per session.
|
|
@@ -3367,8 +3554,14 @@ function cmdAiRun(runner, args, runOpts, pretty) {
|
|
|
3367
3554
|
directPhase = runner.direct(playbookId, directiveId);
|
|
3368
3555
|
lookPhase = runner.look(playbookId, directiveId, runOpts);
|
|
3369
3556
|
} catch (e) {
|
|
3370
|
-
|
|
3371
|
-
|
|
3557
|
+
// v0.12.12 (T8): process.exit(1) immediately after a stdout write can
|
|
3558
|
+
// truncate buffered output under piped consumers (same class as v0.11.10
|
|
3559
|
+
// #100). Use exitCode+return so the JSONL error frame drains. Also write
|
|
3560
|
+
// the framed error event so the stdout-only JSONL contract holds — host
|
|
3561
|
+
// AIs reading this stream must see structured frames, never bare text.
|
|
3562
|
+
process.stdout.write(JSON.stringify({ event: "error", reason: e.message, phase: "info", playbook_id: playbookId, directive_id: directiveId }) + "\n");
|
|
3563
|
+
process.exitCode = 1;
|
|
3564
|
+
return;
|
|
3372
3565
|
}
|
|
3373
3566
|
|
|
3374
3567
|
const governEvent = {
|
|
@@ -3444,8 +3637,41 @@ function cmdAiRun(runner, args, runOpts, pretty) {
|
|
|
3444
3637
|
return emitError(`ai-run: runner threw: ${e.message}`, { playbook: playbookId }, pretty);
|
|
3445
3638
|
}
|
|
3446
3639
|
if (!result || result.ok === false) {
|
|
3640
|
+
// v0.12.12: same exit-after-write anti-pattern as the pre-stream
|
|
3641
|
+
// load path. Use exitCode + return so stderr drains.
|
|
3447
3642
|
process.stderr.write((pretty ? JSON.stringify(result || {}, null, 2) : JSON.stringify(result || {})) + "\n");
|
|
3448
|
-
process.
|
|
3643
|
+
process.exitCode = 1;
|
|
3644
|
+
return;
|
|
3645
|
+
}
|
|
3646
|
+
// v0.12.14 (audit A P2-1): ai-run --no-stream previously emitted a
|
|
3647
|
+
// session_id but never persisted the attestation, so the AI agent
|
|
3648
|
+
// calling ai-run couldn't chain into `attest show / verify / diff`
|
|
3649
|
+
// or `reattest` with the returned id. Now: same persistAttestation
|
|
3650
|
+
// shape as cmdRun, so AI-facing flow round-trips cleanly.
|
|
3651
|
+
if (result.session_id) {
|
|
3652
|
+
const persistResult = persistAttestation({
|
|
3653
|
+
sessionId: result.session_id,
|
|
3654
|
+
playbookId: result.playbook_id || playbookId,
|
|
3655
|
+
directiveId: result.directive_id || directiveId,
|
|
3656
|
+
evidenceHash: result.evidence_hash,
|
|
3657
|
+
operator: runOpts.operator,
|
|
3658
|
+
operatorConsent: runOpts.operator_consent,
|
|
3659
|
+
submission,
|
|
3660
|
+
runOpts,
|
|
3661
|
+
forceOverwrite: !!args["force-overwrite"],
|
|
3662
|
+
filename: "attestation.json",
|
|
3663
|
+
});
|
|
3664
|
+
if (!persistResult.ok && !args["force-overwrite"]) {
|
|
3665
|
+
// Collision without --force-overwrite. AI agents typically pass
|
|
3666
|
+
// unique session ids each run, so this path is rare but surface
|
|
3667
|
+
// it cleanly via the same JSONL contract.
|
|
3668
|
+
process.stdout.write(JSON.stringify({
|
|
3669
|
+
event: "error", reason: persistResult.error,
|
|
3670
|
+
existing_attestation: persistResult.existingPath,
|
|
3671
|
+
}) + "\n");
|
|
3672
|
+
process.exitCode = 3;
|
|
3673
|
+
return;
|
|
3674
|
+
}
|
|
3449
3675
|
}
|
|
3450
3676
|
// v0.11.8 (#101): unify ai-run --no-stream shape with `run`. Pre-0.11.8
|
|
3451
3677
|
// ai-run flattened phases to top-level (`govern`, `direct`, `look`, ...),
|
|
@@ -3523,6 +3749,28 @@ function cmdAiRun(runner, args, runOpts, pretty) {
|
|
|
3523
3749
|
writeLine({ phase: "analyze", ...result.phases?.analyze });
|
|
3524
3750
|
writeLine({ phase: "validate", ...result.phases?.validate });
|
|
3525
3751
|
writeLine({ phase: "close", ...result.phases?.close });
|
|
3752
|
+
// v0.12.14 (audit A P2-1): persist the attestation in streaming mode
|
|
3753
|
+
// too. Without this, the session_id emitted in the `done` frame
|
|
3754
|
+
// can't be resolved by `attest show / verify / diff` or `reattest`.
|
|
3755
|
+
if (result.session_id) {
|
|
3756
|
+
const persistResult = persistAttestation({
|
|
3757
|
+
sessionId: result.session_id,
|
|
3758
|
+
playbookId: result.playbook_id || playbookId,
|
|
3759
|
+
directiveId: result.directive_id || directiveId,
|
|
3760
|
+
evidenceHash: result.evidence_hash,
|
|
3761
|
+
operator: runOpts.operator,
|
|
3762
|
+
operatorConsent: runOpts.operator_consent,
|
|
3763
|
+
submission,
|
|
3764
|
+
runOpts,
|
|
3765
|
+
forceOverwrite: !!args["force-overwrite"],
|
|
3766
|
+
filename: "attestation.json",
|
|
3767
|
+
});
|
|
3768
|
+
if (!persistResult.ok && !args["force-overwrite"]) {
|
|
3769
|
+
writeLine({ event: "error", reason: persistResult.error,
|
|
3770
|
+
existing_attestation: persistResult.existingPath });
|
|
3771
|
+
return finish(3);
|
|
3772
|
+
}
|
|
3773
|
+
}
|
|
3526
3774
|
writeLine({ event: "done", ok: true, session_id: result.session_id, evidence_hash: result.evidence_hash });
|
|
3527
3775
|
return finish(0);
|
|
3528
3776
|
};
|
|
@@ -3766,7 +4014,8 @@ function cmdCi(runner, args, runOpts, pretty) {
|
|
|
3766
4014
|
} else if (args.all) {
|
|
3767
4015
|
ids = runner.listPlaybooks();
|
|
3768
4016
|
} else if (scope) {
|
|
3769
|
-
ids = filterPlaybooksByScope(runner, scope);
|
|
4017
|
+
try { ids = filterPlaybooksByScope(runner, scope); }
|
|
4018
|
+
catch (e) { return emitError(`ci: ${e.message}`, { provided_scope: scope }, pretty); }
|
|
3770
4019
|
// Always include cross-cutting playbooks regardless of scope choice.
|
|
3771
4020
|
const cross = filterPlaybooksByScope(runner, "cross-cutting");
|
|
3772
4021
|
ids = [...new Set([...ids, ...cross])];
|
|
@@ -3963,8 +4212,10 @@ function cmdCi(runner, args, runOpts, pretty) {
|
|
|
3963
4212
|
emit({ verb: "ci", session_id: sessionId, format: fmt, bundles_count: bundles.length, bundles }, pretty);
|
|
3964
4213
|
} else if (fmt && fmt !== "json") {
|
|
3965
4214
|
// v0.11.4 (#76): garbage format rejected with structured error, not silent empty stdout.
|
|
4215
|
+
// v0.12.14: exitCode + return; matches the emitError class fix.
|
|
3966
4216
|
process.stderr.write(JSON.stringify({ ok: false, error: `ci: --format "${fmt}" not in accepted set ["summary","markdown","csaf-2.0","sarif","openvex","json"].`, verb: "ci" }) + "\n");
|
|
3967
|
-
process.
|
|
4217
|
+
process.exitCode = 2;
|
|
4218
|
+
return;
|
|
3968
4219
|
} else {
|
|
3969
4220
|
emit({ verb: "ci", session_id: sessionId, playbooks_run: ids, summary, results }, pretty);
|
|
3970
4221
|
}
|
package/data/_indexes/_meta.json
CHANGED
|
@@ -1,61 +1,62 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema_version": "1.1.0",
|
|
3
|
-
"generated_at": "2026-05-
|
|
3
|
+
"generated_at": "2026-05-14T16:47:17.975Z",
|
|
4
4
|
"generator": "scripts/build-indexes.js",
|
|
5
|
-
"source_count":
|
|
5
|
+
"source_count": 50,
|
|
6
6
|
"source_hashes": {
|
|
7
|
-
"manifest.json": "
|
|
8
|
-
"data/atlas-ttps.json": "
|
|
9
|
-
"data/
|
|
10
|
-
"data/
|
|
7
|
+
"manifest.json": "e0db0f6421e782c796a972277b1ac6774222fa245ae109b87a29f5122a6eb972",
|
|
8
|
+
"data/atlas-ttps.json": "20339e0ae3cd89c06f1385be31c50f408f827edc2e8ab8aef026ade3bcf0a917",
|
|
9
|
+
"data/attack-techniques.json": "6db08a8e8a4d03d9309b1d185112de7f3c9595d2cd3d24566b7ce0b3b8aa5d1a",
|
|
10
|
+
"data/cve-catalog.json": "54e04fc72a1b85dd75d46dbbf646bed5f489f867df752800c62498fc0d4ee428",
|
|
11
|
+
"data/cwe-catalog.json": "19893d2a7139d86ff3fcf296b0e6cda10e357727a1d1ffb56af282104e99157a",
|
|
11
12
|
"data/d3fend-catalog.json": "d219520c8d3eb61a270b25ea60f64721035e98a8d5d51d1a4e1f1140d9a586f9",
|
|
12
13
|
"data/dlp-controls.json": "8ea8d907aea0a2cfd772b048a62122a322ba3284a5c36a272ad5e9d392564cb5",
|
|
13
|
-
"data/exploit-availability.json": "
|
|
14
|
-
"data/framework-control-gaps.json": "
|
|
14
|
+
"data/exploit-availability.json": "24352ffa23c9f319624452497d9dcfc5c0a1d16255ad9557990acb4652ec5e1f",
|
|
15
|
+
"data/framework-control-gaps.json": "182417e662e36cd75a4c74f91c650131b58067fc412878094ff71eff3c1053cb",
|
|
15
16
|
"data/global-frameworks.json": "84fd19061f052e4ccf66308a7b8d3fd38e00325e97e9e5e19e4d9b302c128957",
|
|
16
|
-
"data/rfc-references.json": "
|
|
17
|
+
"data/rfc-references.json": "c0b684e586269bdb6864c55ae0e802742c6c103e81c7fff1613796bd460e727b",
|
|
17
18
|
"data/zeroday-lessons.json": "d670e73dfd5237ceb71a56326676d90c05387b9547f8ed6f3a60a153854b444b",
|
|
18
|
-
"skills/kernel-lpe-triage/skill.md": "
|
|
19
|
-
"skills/ai-attack-surface/skill.md": "
|
|
20
|
-
"skills/mcp-agent-trust/skill.md": "
|
|
21
|
-
"skills/framework-gap-analysis/skill.md": "
|
|
22
|
-
"skills/compliance-theater/skill.md": "
|
|
23
|
-
"skills/exploit-scoring/skill.md": "
|
|
24
|
-
"skills/rag-pipeline-security/skill.md": "
|
|
25
|
-
"skills/ai-c2-detection/skill.md": "
|
|
26
|
-
"skills/policy-exception-gen/skill.md": "
|
|
27
|
-
"skills/threat-model-currency/skill.md": "
|
|
28
|
-
"skills/global-grc/skill.md": "
|
|
29
|
-
"skills/zeroday-gap-learn/skill.md": "
|
|
30
|
-
"skills/pqc-first/skill.md": "
|
|
31
|
-
"skills/skill-update-loop/skill.md": "
|
|
32
|
-
"skills/security-maturity-tiers/skill.md": "
|
|
33
|
-
"skills/researcher/skill.md": "
|
|
34
|
-
"skills/attack-surface-pentest/skill.md": "
|
|
19
|
+
"skills/kernel-lpe-triage/skill.md": "1da5a85a8728768055cba2e19f5c1a6cbb568a3dd49985a2cf1cf381f6ee30b0",
|
|
20
|
+
"skills/ai-attack-surface/skill.md": "922a36632ebb6026c97369168046972f9cd6e634c09fcb97facc830bebe25558",
|
|
21
|
+
"skills/mcp-agent-trust/skill.md": "c604074050a75c401d50d2d495129022ab4bd2fd5c1ca66bb648c26bc9bde301",
|
|
22
|
+
"skills/framework-gap-analysis/skill.md": "9add77ac4dd7d36090bae81d19d3be2b55ed9753dce75f176a7e7d205e2afd12",
|
|
23
|
+
"skills/compliance-theater/skill.md": "7c319cf78946d213eef6be9a1582c0f24658428ea7fddd0bd14ac81e6fa1f2fa",
|
|
24
|
+
"skills/exploit-scoring/skill.md": "f0e71ad7d9597088001b625e8b1ae18d936c527f48e9c12bacdbfbb8580444b6",
|
|
25
|
+
"skills/rag-pipeline-security/skill.md": "78f00a39e66f08da2894e28eeedb32137295ca019eba7110ab28282d613a97eb",
|
|
26
|
+
"skills/ai-c2-detection/skill.md": "095cab9daa072bfabc87152aea1b61ccd6da8f531753b05c181629f04014b5ca",
|
|
27
|
+
"skills/policy-exception-gen/skill.md": "79db45ba722a6dd9bba25bf84e0b52cf659b56b662193cef80a8273337e41df9",
|
|
28
|
+
"skills/threat-model-currency/skill.md": "694dbf0f8ec2d4ffbf893a507d054643620ab2618b56f87ade32f500345ec41b",
|
|
29
|
+
"skills/global-grc/skill.md": "e0487de49679172347653d8c191d1f269193de6f444f6b0c6396d326e45bd72e",
|
|
30
|
+
"skills/zeroday-gap-learn/skill.md": "cb11bbbec9fadf152d8f30bded22c40f29d63074a6729cd45a1628ee3cfbb181",
|
|
31
|
+
"skills/pqc-first/skill.md": "a5eb776e1ea3bb422a4c18a3bdf39ad2ec1651b3c25e65c89428ba319141b275",
|
|
32
|
+
"skills/skill-update-loop/skill.md": "95268eb083a22b164661c14db401f5c57995fdd1ca86b35fb399b0c8419c4273",
|
|
33
|
+
"skills/security-maturity-tiers/skill.md": "817f0bca44297d03fb206c446fbf3f93aa3a64c309d6ef5efd046e6e47874030",
|
|
34
|
+
"skills/researcher/skill.md": "51d03d9eaea52d2bbbdd67709035db494d44819ce58931ca025cab3025c9fad7",
|
|
35
|
+
"skills/attack-surface-pentest/skill.md": "8d404f6662d9bc3765d716ec7b38e302f17574a2267245fd68eeedfdbf212f42",
|
|
35
36
|
"skills/fuzz-testing-strategy/skill.md": "83b1929a0d1e09a58908b91125ebc91ff14323ab9acc9bab6c4b04903b69b837",
|
|
36
|
-
"skills/dlp-gap-analysis/skill.md": "
|
|
37
|
-
"skills/supply-chain-integrity/skill.md": "
|
|
37
|
+
"skills/dlp-gap-analysis/skill.md": "11a299cdcf8902e22b8662e55369da9d8b5ae804edf237412df0ddbe684a04d0",
|
|
38
|
+
"skills/supply-chain-integrity/skill.md": "32c8c31b07caedf6146ab548a67a25408d9ae6ca1365edc6703782a3265de108",
|
|
38
39
|
"skills/defensive-countermeasure-mapping/skill.md": "e62c71ba3be2b4d0f7dfa529fec007cba6bee3013f76b93756e3e6310f2d22ab",
|
|
39
|
-
"skills/identity-assurance/skill.md": "
|
|
40
|
-
"skills/ot-ics-security/skill.md": "
|
|
40
|
+
"skills/identity-assurance/skill.md": "6fd734d5cf8eed031537c9ccb1ad11c09ec4e88d31c45d86046a2154a6770990",
|
|
41
|
+
"skills/ot-ics-security/skill.md": "d239ed497816e00ad14568e9fcca68ffdc7cb0c2a2cbd4960b35fab2065cce31",
|
|
41
42
|
"skills/coordinated-vuln-disclosure/skill.md": "c96fd2254abf8a29819f8175da85094bea1afe589fecc92abcf1289b30895030",
|
|
42
|
-
"skills/threat-modeling-methodology/skill.md": "
|
|
43
|
-
"skills/webapp-security/skill.md": "
|
|
44
|
-
"skills/ai-risk-management/skill.md": "
|
|
45
|
-
"skills/sector-healthcare/skill.md": "
|
|
46
|
-
"skills/sector-financial/skill.md": "
|
|
47
|
-
"skills/sector-federal-government/skill.md": "
|
|
48
|
-
"skills/sector-energy/skill.md": "
|
|
49
|
-
"skills/api-security/skill.md": "
|
|
50
|
-
"skills/cloud-security/skill.md": "
|
|
51
|
-
"skills/container-runtime-security/skill.md": "
|
|
52
|
-
"skills/mlops-security/skill.md": "
|
|
53
|
-
"skills/incident-response-playbook/skill.md": "
|
|
54
|
-
"skills/email-security-anti-phishing/skill.md": "
|
|
43
|
+
"skills/threat-modeling-methodology/skill.md": "d57d1acc46851d4f1580858c60a90cc20732ca8a5a46da2c50e71c9bdf4cc0b4",
|
|
44
|
+
"skills/webapp-security/skill.md": "0e4726311edf96444773d84b8c0842678fe73f7625d415f860bd26fd4568f888",
|
|
45
|
+
"skills/ai-risk-management/skill.md": "4c46cce244bf22cf3814fcd8836da3725bc0c44f573846e49039827045096340",
|
|
46
|
+
"skills/sector-healthcare/skill.md": "97b4486419ab4480266bf2e938564d52bb1cdd70faae09697f695772adf02029",
|
|
47
|
+
"skills/sector-financial/skill.md": "db728a79cbd2ad149c45b34c0466452df7f4321ca968595042323b23ef7649f4",
|
|
48
|
+
"skills/sector-federal-government/skill.md": "48c3c019502c8b758598331dbad8a9b121f8dd3dc6fc68bfaf506eba7e3843e5",
|
|
49
|
+
"skills/sector-energy/skill.md": "875799aa2ad88744b646583fef0a3399abd42a979541dc99bf39825a5ef48ce9",
|
|
50
|
+
"skills/api-security/skill.md": "3ee3edd244a9240e42138edbec339ca28e01a662dfb83317af2d758ce355fb7a",
|
|
51
|
+
"skills/cloud-security/skill.md": "e0574c153aefbb0fc4581c78bc2d708ab7c49d6b5a45a985e51967b8ea740eb9",
|
|
52
|
+
"skills/container-runtime-security/skill.md": "921a7ac163e04fe8415986b0f54c1b3c8c4656576d72ccb6665dff3869c63003",
|
|
53
|
+
"skills/mlops-security/skill.md": "e3bb447033ec94b5ddc621e4b3c3ca7e971cde51584ab9653fb121a899a0eb81",
|
|
54
|
+
"skills/incident-response-playbook/skill.md": "c1033410b479f33a7a0e60a75ad02965fb70ba88f57203fa36e9c2418789e098",
|
|
55
|
+
"skills/email-security-anti-phishing/skill.md": "b5a7693b3ddbd6cd83303d092bc5e324db431245d25c4945d9f65fcffa1995e7",
|
|
55
56
|
"skills/age-gates-child-safety/skill.md": "c741d7dca9da0abb09bdebb8a02e803ce4ae9fb9a6904fb8df3ec19cae83917d"
|
|
56
57
|
},
|
|
57
58
|
"skill_count": 38,
|
|
58
|
-
"catalog_count":
|
|
59
|
+
"catalog_count": 11,
|
|
59
60
|
"index_stats": {
|
|
60
61
|
"xref_entries": {
|
|
61
62
|
"cwe_refs": 34,
|
|
@@ -68,20 +69,20 @@
|
|
|
68
69
|
},
|
|
69
70
|
"trigger_table_entries": 453,
|
|
70
71
|
"chains_cve_entries": 8,
|
|
71
|
-
"chains_cwe_entries":
|
|
72
|
+
"chains_cwe_entries": 55,
|
|
72
73
|
"jurisdictions_indexed": 29,
|
|
73
74
|
"handoff_dag_nodes": 38,
|
|
74
75
|
"summary_cards": 38,
|
|
75
76
|
"section_offsets_skills": 38,
|
|
76
|
-
"token_budget_total_approx":
|
|
77
|
+
"token_budget_total_approx": 351655,
|
|
77
78
|
"recipes": 8,
|
|
78
79
|
"jurisdiction_clocks": 29,
|
|
79
80
|
"did_ladders": 8,
|
|
80
81
|
"theater_fingerprints": 7,
|
|
81
82
|
"currency_action_required": 0,
|
|
82
83
|
"frequency_fields": 7,
|
|
83
|
-
"activity_feed_events":
|
|
84
|
-
"catalog_summaries":
|
|
84
|
+
"activity_feed_events": 50,
|
|
85
|
+
"catalog_summaries": 11,
|
|
85
86
|
"stale_content_findings": 0
|
|
86
87
|
},
|
|
87
88
|
"invalidation_note": "If any source file in source_hashes has a different SHA-256 than recorded here, the indexes are stale. Re-run `npm run build-indexes`."
|