@blamejs/exceptd-skills 0.12.41 → 0.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/CHANGELOG.md +124 -0
  2. package/bin/exceptd.js +52 -44
  3. package/data/_indexes/_meta.json +49 -49
  4. package/data/_indexes/activity-feed.json +2 -2
  5. package/data/_indexes/catalog-summaries.json +2 -2
  6. package/data/_indexes/chains.json +1531 -575
  7. package/data/_indexes/jurisdiction-map.json +15 -4
  8. package/data/_indexes/section-offsets.json +1244 -1244
  9. package/data/_indexes/token-budget.json +173 -173
  10. package/data/atlas-ttps.json +55 -11
  11. package/data/attack-techniques.json +124 -19
  12. package/data/cve-catalog.json +194 -27
  13. package/data/cwe-catalog.json +15 -5
  14. package/data/framework-control-gaps.json +32 -10
  15. package/data/playbooks/ai-api.json +5 -0
  16. package/data/playbooks/cicd-pipeline-compromise.json +970 -0
  17. package/data/playbooks/cloud-iam-incident.json +4 -1
  18. package/data/playbooks/cred-stores.json +10 -0
  19. package/data/playbooks/framework.json +16 -0
  20. package/data/playbooks/hardening.json +4 -0
  21. package/data/playbooks/identity-sso-compromise.json +951 -0
  22. package/data/playbooks/idp-incident.json +3 -0
  23. package/data/playbooks/kernel.json +6 -0
  24. package/data/playbooks/llm-tool-use-exfil.json +963 -0
  25. package/data/playbooks/mcp.json +6 -0
  26. package/data/playbooks/runtime.json +4 -0
  27. package/data/playbooks/sbom.json +13 -0
  28. package/data/playbooks/secrets.json +6 -0
  29. package/data/playbooks/webhook-callback-abuse.json +916 -0
  30. package/data/zeroday-lessons.json +178 -0
  31. package/lib/cross-ref-api.js +33 -13
  32. package/lib/cve-curation.js +12 -1
  33. package/lib/exit-codes.js +29 -0
  34. package/lib/lint-skills.js +24 -2
  35. package/lib/refresh-external.js +17 -1
  36. package/lib/scoring.js +55 -0
  37. package/lib/source-advisories.js +281 -0
  38. package/manifest.json +83 -83
  39. package/orchestrator/index.js +207 -24
  40. package/package.json +1 -1
  41. package/sbom.cdx.json +134 -79
  42. package/scripts/predeploy.js +7 -13
  43. package/scripts/refresh-reverse-refs.js +86 -0
  44. package/scripts/refresh-sbom.js +21 -4
  45. package/skills/age-gates-child-safety/skill.md +1 -5
  46. package/skills/ai-attack-surface/skill.md +11 -4
  47. package/skills/ai-c2-detection/skill.md +11 -2
  48. package/skills/ai-risk-management/skill.md +4 -2
  49. package/skills/api-security/skill.md +7 -8
  50. package/skills/attack-surface-pentest/skill.md +2 -2
  51. package/skills/cloud-iam-incident/skill.md +1 -5
  52. package/skills/cloud-security/skill.md +0 -4
  53. package/skills/compliance-theater/skill.md +10 -2
  54. package/skills/container-runtime-security/skill.md +1 -3
  55. package/skills/dlp-gap-analysis/skill.md +3 -4
  56. package/skills/email-security-anti-phishing/skill.md +1 -8
  57. package/skills/exploit-scoring/skill.md +7 -2
  58. package/skills/framework-gap-analysis/skill.md +1 -1
  59. package/skills/fuzz-testing-strategy/skill.md +1 -2
  60. package/skills/global-grc/skill.md +3 -2
  61. package/skills/identity-assurance/skill.md +1 -3
  62. package/skills/idp-incident-response/skill.md +1 -4
  63. package/skills/incident-response-playbook/skill.md +1 -5
  64. package/skills/kernel-lpe-triage/skill.md +2 -2
  65. package/skills/mcp-agent-trust/skill.md +13 -3
  66. package/skills/mlops-security/skill.md +2 -3
  67. package/skills/ot-ics-security/skill.md +0 -3
  68. package/skills/policy-exception-gen/skill.md +11 -3
  69. package/skills/pqc-first/skill.md +4 -2
  70. package/skills/rag-pipeline-security/skill.md +2 -0
  71. package/skills/ransomware-response/skill.md +1 -5
  72. package/skills/researcher/skill.md +4 -3
  73. package/skills/sector-energy/skill.md +0 -4
  74. package/skills/sector-federal-government/skill.md +2 -3
  75. package/skills/sector-financial/skill.md +1 -4
  76. package/skills/sector-healthcare/skill.md +0 -5
  77. package/skills/sector-telecom/skill.md +0 -4
  78. package/skills/security-maturity-tiers/skill.md +1 -2
  79. package/skills/skill-update-loop/skill.md +4 -3
  80. package/skills/supply-chain-integrity/skill.md +4 -3
  81. package/skills/threat-model-currency/skill.md +1 -1
  82. package/skills/threat-modeling-methodology/skill.md +2 -1
  83. package/skills/webapp-security/skill.md +0 -5
@@ -27,6 +27,7 @@ const { dispatch, routeQuery, getSkillContext } = require('./dispatcher');
27
27
  const { currencyCheck, initPipeline } = require('./pipeline');
28
28
  const { bus, EVENT_TYPES } = require('./event-bus');
29
29
  const { start: startScheduler, stop: stopScheduler, runCurrencyNow } = require('./scheduler');
30
+ const { EXIT_CODES, safeExit } = require('../lib/exit-codes');
30
31
 
31
32
  const cmd = process.argv[2];
32
33
  const args = process.argv.slice(3);
@@ -145,9 +146,11 @@ Examples:
145
146
  exceptd framework-gap NIST-800-53 CVE-2026-31431
146
147
  exceptd framework-gap PCI-DSS-4.0 "prompt injection"
147
148
  exceptd framework-gap all CVE-2025-53773 --json`);
148
- // Pinned exit 2 by operator contract. Envelope harmonization
149
- // across orchestrator + CLI is a v0.13 concern.
150
- process.exitCode = 2;
149
+ // v0.13 exit-code class fix: usage error is GENERIC_FAILURE (1),
150
+ // not DETECTED_ESCALATE (2). Pre-v0.13 the orchestrator emitted
151
+ // exit 2 for usage errors, colliding with CI gates that branch on
152
+ // exit 2 to mean "verb ran + detected escalation-worthy finding".
153
+ safeExit(EXIT_CODES.GENERIC_FAILURE);
151
154
  return;
152
155
  }
153
156
 
@@ -158,7 +161,7 @@ Examples:
158
161
  cveCatalog = JSON.parse(fs.readFileSync(path.join(root, 'data', 'cve-catalog.json'), 'utf8'));
159
162
  } catch (err) {
160
163
  console.error(`[framework-gap] cannot read catalog: ${err.message}`);
161
- process.exitCode = 2;
164
+ safeExit(EXIT_CODES.GENERIC_FAILURE);
162
165
  return;
163
166
  }
164
167
 
@@ -296,23 +299,19 @@ async function runDispatch() {
296
299
 
297
300
  function runSkillContext(skillName) {
298
301
  if (!skillName) {
299
- // v0.12.40: operator-facing surface uses the canonical `exceptd skill
300
- // <name>` form, not the orchestrator path that's an implementation
301
- // detail.
302
302
  console.error('Usage: exceptd skill <skill-name>');
303
303
  console.error(' (Lists available skills: exceptd brief --all)');
304
- process.exitCode = 1;
304
+ safeExit(EXIT_CODES.GENERIC_FAILURE);
305
305
  return;
306
306
  }
307
307
 
308
308
  const context = getSkillContext(skillName);
309
309
  if (!context) {
310
- // Unified error shape across the CLI surface — see v0.10.3 bug #18.
311
- // Stderr is the documented stream for ok:false bodies emitted by
312
- // orchestrator dispatch; envelope harmonization across the CLI is
313
- // a v0.13 concern.
314
- process.stderr.write(JSON.stringify({ ok: false, error: `Skill not found: ${skillName}`, verb: "skill", hint: "Run `exceptd brief --all` or check skills/ for available skill IDs." }) + "\n");
315
- process.exitCode = 1;
310
+ // v0.13 envelope harmonization: ok:false bodies land on stdout
311
+ // alongside successful results so a single consumer can parse the
312
+ // verb's envelope without splitting across two streams.
313
+ process.stdout.write(JSON.stringify({ ok: false, verb: "skill", error: `Skill not found: ${skillName}`, hint: "Run `exceptd brief --all` or check skills/ for available skill IDs." }) + "\n");
314
+ safeExit(EXIT_CODES.GENERIC_FAILURE);
316
315
  return;
317
316
  }
318
317
 
@@ -376,15 +375,17 @@ async function runReport(format) {
376
375
  // string. Now: reject with structured JSON error matching other verbs.
377
376
  const VALID_REPORT_FORMATS = ['executive', 'technical', 'compliance', 'csaf'];
378
377
  if (!VALID_REPORT_FORMATS.includes(format)) {
379
- // Pinned exit 2 + stderr-stream by operator contract. Envelope
380
- // harmonization across orchestrator + CLI is a v0.13 concern.
381
- process.stderr.write(JSON.stringify({
378
+ // v0.13 envelope harmonization: ok:false body on stdout, exit 1
379
+ // (GENERIC_FAILURE) not 2 (DETECTED_ESCALATE). Pre-v0.13 the
380
+ // body went to stderr and exit was 2; both broke CI consumers
381
+ // that expected the dispatch-error vs verb-finding distinction.
382
+ process.stdout.write(JSON.stringify({
382
383
  ok: false,
383
- error: `report: format "${format}" not in accepted set ${JSON.stringify(VALID_REPORT_FORMATS)}.`,
384
384
  verb: 'report',
385
+ error: `report: format "${format}" not in accepted set ${JSON.stringify(VALID_REPORT_FORMATS)}.`,
385
386
  accepted_formats: VALID_REPORT_FORMATS,
386
387
  }) + '\n');
387
- process.exitCode = 2;
388
+ safeExit(EXIT_CODES.GENERIC_FAILURE);
388
389
  return;
389
390
  }
390
391
 
@@ -705,7 +706,8 @@ async function runValidateCves(rawArgs = []) {
705
706
  catalog = JSON.parse(fs.readFileSync(catalogPath, 'utf8'));
706
707
  } catch (err) {
707
708
  console.error(`[validate-cves] cannot read ${catalogPath}: ${err.message}`);
708
- process.exit(2);
709
+ safeExit(EXIT_CODES.GENERIC_FAILURE);
710
+ return;
709
711
  }
710
712
 
711
713
  // --since <ISO|YYYY-MM-DD>: scope-limit validation to CVEs whose
@@ -875,7 +877,7 @@ async function runValidateCves(rawArgs = []) {
875
877
  }
876
878
  if (driftFound > 0) {
877
879
  console.log(`\n[validate-cves] DRIFT DETECTED on ${driftFound} CVE(s). Update data/cve-catalog.json and bump source_verified.`);
878
- if (!noFail) process.exit(1);
880
+ if (!noFail) { safeExit(EXIT_CODES.GENERIC_FAILURE); return; }
879
881
  } else {
880
882
  console.log('[validate-cves] No drift detected against reachable sources.');
881
883
  }
@@ -926,7 +928,8 @@ async function runValidateRfcs(rawArgs = []) {
926
928
  refs = JSON.parse(fs.readFileSync(refsPath, 'utf8'));
927
929
  } catch (err) {
928
930
  console.error(`[validate-rfcs] cannot read ${refsPath}: ${err.message}`);
929
- process.exit(2);
931
+ safeExit(EXIT_CODES.GENERIC_FAILURE);
932
+ return;
930
933
  }
931
934
 
932
935
  // --since <ISO|YYYY-MM-DD>: scope-limit (parity with validate-cves).
@@ -1055,7 +1058,7 @@ async function runValidateRfcs(rawArgs = []) {
1055
1058
  console.log();
1056
1059
  if (driftFound > 0) {
1057
1060
  console.log(`[validate-rfcs] DRIFT DETECTED on ${driftFound} entry(ies). Update data/rfc-references.json and bump last_verified.`);
1058
- if (!noFail) process.exit(1);
1061
+ if (!noFail) { safeExit(EXIT_CODES.GENERIC_FAILURE); return; }
1059
1062
  } else if (unreachable > 0) {
1060
1063
  console.log(`[validate-rfcs] ${unreachable} entry(ies) unreachable. Network/IETF Datatracker is intermittent — re-run later.`);
1061
1064
  } else if (!offline && validator) {
@@ -1082,15 +1085,26 @@ function runWatchlist(rawArgs = []) {
1082
1085
  const { parseFrontmatter, extractFrontmatterBlock } = require('../lib/lint-skills.js');
1083
1086
 
1084
1087
  const byskill = rawArgs.includes('--by-skill');
1088
+ const alertsMode = rawArgs.includes('--alerts');
1085
1089
  const manifestPath = path.join(__dirname, '..', 'manifest.json');
1086
1090
  const repoRoot = path.join(__dirname, '..');
1087
1091
 
1092
+ // v0.13.1: --alerts re-scopes watchlist from "skills forward_watch" to
1093
+ // "CVE-class alert patterns" — surfaces catalog entries matching
1094
+ // high-priority shape rules (kernel-LPE-with-PoC, supply-chain-family,
1095
+ // AI-discovered-KEV, recently-disclosed-with-active-exploitation).
1096
+ // The two modes are mutually exclusive; --alerts short-circuits the
1097
+ // forward-watch aggregation.
1098
+ if (alertsMode) {
1099
+ return runWatchlistAlerts(rawArgs);
1100
+ }
1101
+
1088
1102
  let manifest;
1089
1103
  try {
1090
1104
  manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
1091
1105
  } catch (err) {
1092
1106
  console.error(`[watchlist] cannot read ${manifestPath}: ${err.message}`);
1093
- process.exitCode = 2;
1107
+ safeExit(EXIT_CODES.GENERIC_FAILURE);
1094
1108
  return;
1095
1109
  }
1096
1110
 
@@ -1138,7 +1152,12 @@ function runWatchlist(rawArgs = []) {
1138
1152
 
1139
1153
  const jsonOut = rawArgs.includes('--json');
1140
1154
  if (jsonOut) {
1155
+ // v0.13.0 envelope harmonization: top-level `ok: true` so every
1156
+ // verb's JSON body shares the contract whether emitted by
1157
+ // bin/exceptd.js emit() (which auto-defaults `ok`) or by the
1158
+ // orchestrator dispatch (which writes stdout directly).
1141
1159
  const out = {
1160
+ ok: true,
1142
1161
  generated_at: new Date().toISOString(),
1143
1162
  skills_scanned: skills.length,
1144
1163
  parse_errors: parseErrors,
@@ -1192,6 +1211,170 @@ function runWatchlist(rawArgs = []) {
1192
1211
  console.log(`Run with --by-skill to invert the view.`);
1193
1212
  }
1194
1213
 
1214
+ /**
1215
+ * v0.13.1 — runWatchlistAlerts surfaces CVE catalog entries matching
1216
+ * high-priority pattern rules. Closes the post-mortem gap from
1217
+ * CVE-2026-46333 (ssh-keysign-pwn) where the toolkit shipped a CVE
1218
+ * matching the kernel-LPE-with-public-PoC shape but had no programmatic
1219
+ * way for an operator to ask "what just landed that needs attention?".
1220
+ *
1221
+ * Patterns are evaluated against every catalog entry; multiple patterns
1222
+ * may fire on the same entry (the report carries the list). The age
1223
+ * filter — pattern.fresh_days — bounds the "recently disclosed" patterns;
1224
+ * older entries already had attention.
1225
+ *
1226
+ * Output (JSON mode):
1227
+ * {
1228
+ * ok: true,
1229
+ * verb: "watchlist",
1230
+ * mode: "alerts",
1231
+ * generated_at: "...",
1232
+ * patterns_evaluated: 5,
1233
+ * entries_scanned: 39,
1234
+ * alerts: [
1235
+ * { cve_id, name, rwep_score, patterns: ["kernel_lpe_class", ...],
1236
+ * disclosed: "...", source_verified: "...", links: [...] }
1237
+ * ]
1238
+ * }
1239
+ */
1240
+ function runWatchlistAlerts(rawArgs = []) {
1241
+ const fs = require('fs');
1242
+ const path = require('path');
1243
+ const jsonOut = rawArgs.includes('--json');
1244
+ const cvePath = path.join(__dirname, '..', 'data', 'cve-catalog.json');
1245
+ let catalog;
1246
+ try {
1247
+ catalog = JSON.parse(fs.readFileSync(cvePath, 'utf8'));
1248
+ } catch (err) {
1249
+ console.error(`[watchlist --alerts] cannot read ${cvePath}: ${err.message}`);
1250
+ safeExit(EXIT_CODES.GENERIC_FAILURE);
1251
+ return;
1252
+ }
1253
+
1254
+ // Pattern definitions. Each pattern is a predicate against a single
1255
+ // catalog entry plus a label + severity. Patterns are intentionally
1256
+ // narrow — false-positive flood would defeat the alert purpose.
1257
+ const today = new Date();
1258
+ function daysSince(iso) {
1259
+ if (typeof iso !== 'string') return Infinity;
1260
+ const t = Date.parse(iso + 'T00:00:00Z');
1261
+ if (Number.isNaN(t)) return Infinity;
1262
+ return Math.floor((today - t) / (24 * 60 * 60 * 1000));
1263
+ }
1264
+ const PATTERNS = [
1265
+ {
1266
+ id: 'kernel_lpe_with_poc',
1267
+ severity: 'high',
1268
+ description: 'Linux kernel LPE class with public PoC. The CVE-2026-46333 (ssh-keysign-pwn) / CVE-2026-46300 (Fragnesia) / CVE-2026-31431 (Copy Fail) shape.',
1269
+ match: (e) =>
1270
+ e && typeof e.vector === 'string' &&
1271
+ /kernel|linux|ptrace|pidfd/i.test(`${e.name || ''} ${e.vector}`) &&
1272
+ e.poc_available === true &&
1273
+ (e.rwep_factors?.blast_radius || 0) >= 25,
1274
+ },
1275
+ {
1276
+ id: 'supply_chain_family',
1277
+ severity: 'high',
1278
+ description: 'Malicious package or framework family (npm / PyPI registry-pivot). The MAL- entries + Shai-Hulud class.',
1279
+ match: (e, id) =>
1280
+ id.startsWith('MAL-') ||
1281
+ (typeof e?.type === 'string' && /malicious|supply.chain|registry-pivot/i.test(e.type)),
1282
+ },
1283
+ {
1284
+ id: 'ai_discovered_kev',
1285
+ severity: 'high',
1286
+ description: 'AI-discovered CVE that also appears on CISA KEV — the operational-reality intersection Hard Rule #7 calls out.',
1287
+ match: (e) => e?.ai_discovered === true && e?.cisa_kev === true,
1288
+ },
1289
+ {
1290
+ id: 'active_exploitation_unpatched',
1291
+ severity: 'critical',
1292
+ description: 'Confirmed in-the-wild exploitation AND no patch available. Defensive-posture-only window.',
1293
+ match: (e) => e?.active_exploitation === 'confirmed' && e?.patch_available !== true,
1294
+ },
1295
+ {
1296
+ id: 'recent_poc_no_kev_yet',
1297
+ severity: 'medium',
1298
+ description: 'CVE with public PoC verified within the last 14 days but not yet on KEV. The "exploitation expected; KEV catch-up pending" window.',
1299
+ match: (e) =>
1300
+ e?.poc_available === true &&
1301
+ e?.cisa_kev !== true &&
1302
+ daysSince(e?.source_verified) <= 14,
1303
+ fresh_only: true,
1304
+ },
1305
+ ];
1306
+
1307
+ const alerts = [];
1308
+ let scanned = 0;
1309
+ for (const [id, entry] of Object.entries(catalog)) {
1310
+ if (id === '_meta') continue;
1311
+ if (!entry || typeof entry !== 'object') continue;
1312
+ scanned++;
1313
+ const matched = [];
1314
+ for (const p of PATTERNS) {
1315
+ try {
1316
+ if (p.match(entry, id)) matched.push({ id: p.id, severity: p.severity });
1317
+ } catch { /* defensive: pattern matcher must not throw on malformed entries */ }
1318
+ }
1319
+ if (matched.length === 0) continue;
1320
+ alerts.push({
1321
+ cve_id: id,
1322
+ name: entry.name || null,
1323
+ rwep_score: entry.rwep_score ?? null,
1324
+ cisa_kev: entry.cisa_kev ?? null,
1325
+ poc_available: entry.poc_available ?? null,
1326
+ active_exploitation: entry.active_exploitation ?? null,
1327
+ patch_available: entry.patch_available ?? null,
1328
+ source_verified: entry.source_verified || null,
1329
+ patterns: matched,
1330
+ links: Array.isArray(entry.verification_sources) ? entry.verification_sources.slice(0, 3) : [],
1331
+ });
1332
+ }
1333
+
1334
+ // Sort: critical-severity matches first, then high, then medium; within
1335
+ // each band, highest RWEP first; finally CVE-id ascending for stability.
1336
+ const severityWeight = { critical: 0, high: 1, medium: 2, low: 3 };
1337
+ alerts.sort((a, b) => {
1338
+ const sa = Math.min(...a.patterns.map((p) => severityWeight[p.severity] ?? 9));
1339
+ const sb = Math.min(...b.patterns.map((p) => severityWeight[p.severity] ?? 9));
1340
+ if (sa !== sb) return sa - sb;
1341
+ const ra = a.rwep_score ?? -1;
1342
+ const rb = b.rwep_score ?? -1;
1343
+ if (ra !== rb) return rb - ra;
1344
+ return a.cve_id.localeCompare(b.cve_id);
1345
+ });
1346
+
1347
+ if (jsonOut) {
1348
+ process.stdout.write(JSON.stringify({
1349
+ ok: true,
1350
+ verb: 'watchlist',
1351
+ mode: 'alerts',
1352
+ generated_at: today.toISOString(),
1353
+ patterns_evaluated: PATTERNS.length,
1354
+ entries_scanned: scanned,
1355
+ alert_count: alerts.length,
1356
+ alerts,
1357
+ }) + '\n');
1358
+ return;
1359
+ }
1360
+
1361
+ console.log(`\nCVE-class Alerts — ${today.toISOString()}`);
1362
+ console.log(`Entries scanned: ${scanned} patterns evaluated: ${PATTERNS.length} alerts: ${alerts.length}\n`);
1363
+ if (alerts.length === 0) {
1364
+ console.log('No alert-pattern matches in current catalog.');
1365
+ return;
1366
+ }
1367
+ for (const a of alerts) {
1368
+ const labels = a.patterns.map((p) => `[${p.severity}] ${p.id}`).join(', ');
1369
+ console.log(`${a.cve_id} RWEP=${a.rwep_score ?? '?'} KEV=${a.cisa_kev ? 'Y' : 'N'} PoC=${a.poc_available ? 'Y' : 'N'} patch=${a.patch_available ? 'Y' : 'N'} active=${a.active_exploitation ?? '?'}`);
1370
+ console.log(` ${a.name || '(no name)'}`);
1371
+ console.log(` patterns: ${labels}`);
1372
+ if (a.links.length > 0) console.log(` ${a.links[0]}`);
1373
+ console.log('');
1374
+ }
1375
+ console.log('Run with --json to consume programmatically.');
1376
+ }
1377
+
1195
1378
  /**
1196
1379
  * Cache-first variant of validateAllCves. For each catalog CVE, reads the
1197
1380
  * NVD + EPSS payload from the prefetch cache (cacheDir/nvd/<id>.json +
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blamejs/exceptd-skills",
3
- "version": "0.12.41",
3
+ "version": "0.13.1",
4
4
  "description": "AI security skills grounded in mid-2026 threat reality, not stale framework documentation. 42 skills, 10 catalogs, 34 jurisdictions, pre-computed indexes, Ed25519-signed.",
5
5
  "keywords": [
6
6
  "ai-security",