@blamejs/exceptd-skills 0.13.4 → 0.13.6

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 CHANGED
@@ -228,6 +228,22 @@ Recently added (use the IDs in skill prose and operator briefings; full text in
228
228
  | `NEW-CTRL-053` | MCP-SERVER-CONFIG-ALLOWLIST | `CVE-2026-30623` (Anthropic MCP SDK stdio injection) | NIST AI RMF MEASURE 2.7, OWASP LLM Top 10 2025 LLM05 |
229
229
  | `NEW-CTRL-054` | BACKUP-TIER-NETWORK-ISOLATION | `CVE-2025-59389` (QNAP Hyper Data Protector preauth RCE) | ISO-27001-2022 A.8.13, NIS2 Art.21 business-continuity |
230
230
  | `NEW-CTRL-055` | SECURITY-TOOL-INTEGRITY-VERIFICATION | `CVE-2025-11837` (QNAP Malware Remover code-injection) | NIST-800-53 SI-3, ISO-27001-2022 A.8.7, PCI-DSS 4.0 §5.1 |
231
+ | `NEW-CTRL-056` | MOBILE-ENDPOINT-MDM-ENFORCED-KEV-SLA | `CVE-2025-14174` / `CVE-2025-43529` / `CVE-2025-24201` / `CVE-2025-43300` | NIST-800-53 SI-2, ISO-27001-2022 A.8.8, CIS Benchmarks mobile-OS profiles |
232
+ | `NEW-CTRL-057` | BROWSER-MANAGED-UPDATE-NO-DEFERRAL | `CVE-2025-10585` / `CVE-2025-14174` / `CVE-2025-43529` / `CVE-2025-4919` | NIST-800-53 SI-2, CISA KEV SLA, vendor-channel security-release contracts |
233
+ | `NEW-CTRL-058` | CLOUD-CONTROL-PLANE-CROSS-TENANT-CLAIM-VALIDATION | `CVE-2025-55241` (Entra ID Actor-token impersonation) | NIST-800-53 AC-2/AU-2/AC-16, ISO-27001-2022 A.5.16, CIS Cloud Foundations |
234
+ | `NEW-CTRL-059` | SENSITIVE-DATA-IN-LOGS-LINT | `CVE-2025-21085` (Cisco Duo credential leakage) | NIST-800-53 AU-9/SI-12, ISO-27001-2022 A.8.15, PCI-DSS 4.0 §10.5 |
235
+ | `NEW-CTRL-060` | DATABASE-SERVER-SIDE-SCRIPTING-DEFAULT-DENY | `CVE-2025-49844` (Redis RediShell Lua UAF) | NIST-800-53 CM-6/CM-7, ISO-27001-2022 A.8.9, vendor secure-baseline profiles |
236
+ | `NEW-CTRL-061` | IN-MEMORY-DATASTORE-MEMORY-DISCLOSURE-NETWORK-EXPOSURE-AUDIT | `CVE-2025-14847` (MongoBleed) | NIST-800-53 SC-7/SC-28, PCI-DSS 4.0 §1.4, ISO-27001-2022 A.8.20 |
237
+ | `NEW-CTRL-062` | HTTP2-STREAM-RESET-ACCOUNTING | `CVE-2025-8671` (MadeYouReset) | NIST-800-53 SC-5, ISO-27001-2022 A.8.6, vendor HTTP/2 secure-default profiles |
238
+ | `NEW-CTRL-063` | MULTIMODAL-INFERENCE-INPUT-DECODER-ISOLATION | `CVE-2026-22778` (vLLM heap-overflow RCE) | NIST AI RMF MANAGE 4.1, NIST-800-53 SC-39, OWASP LLM Top 10 2025 LLM10 |
239
+ | `NEW-CTRL-064` | LLM-OUTPUT-DESERIALIZATION-TRUST-ZONE | `CVE-2025-68664` (LangChain LangGrinch) | NIST AI RMF MEASURE 2.7, OWASP LLM Top 10 2025 LLM03/LLM08, NIST-800-53 SI-10 |
240
+ | `NEW-CTRL-065` | AI-MODEL-SERVER-DEFAULT-AUTHENTICATION | `CVE-2026-7482` (Ollama Bleeding Llama) | NIST-800-53 IA-2/AC-3, ISO-27001-2022 A.8.5, NIST AI RMF GOVERN 5.1 |
241
+ | `NEW-CTRL-066` | AGENTIC-IDE-HOST-EXECUTION-SANDBOX | `CVE-2025-55319` (VSCode agentic-AI command-injection) | NIST AI RMF MANAGE 4.1, ISO/IEC 42001 §6.1.4, NIST-800-53 SC-39 |
242
+ | `NEW-CTRL-067` | AI-PLATFORM-CONTROL-PLANE-RBAC-OVERLAY-AUDIT | `CVE-2025-10725` (OpenShift AI privilege escalation) | NIST-800-53 AC-2/AC-6, CIS Kubernetes Benchmark §5, NIST AI RMF GOVERN 1.5 |
243
+ | `NEW-CTRL-068` | HYPERVISOR-VM-ESCAPE-TENANCY-ASSUMPTION | `CVE-2025-22224` / `CVE-2025-22225` / `CVE-2025-22226` (VMSA-2025-0004 ESXi chain) | NIST-800-53 SC-7/SI-2, ISO-27001-2022 A.8.20, CIS VMware ESXi Benchmark |
244
+ | `NEW-CTRL-069` | ECOSYSTEM-PACKAGE-TEMPORAL-TRUST-DRIFT-DETECTION | `MAL-2026-RUBYGEMS-BUFFERZONECORP-SLEEPER` | NIST-800-218 SSDF PW.4, EU CRA Annex I §1(2)(b), SLSA Build L3 |
245
+ | `NEW-CTRL-070` | TYPOSQUAT-INSTALL-TIME-NAME-CONFUSION-GUARD | `MAL-2025-PYPI-COLORAMA-SOLANA-STEALER` | NIST-800-218 SSDF PW.4, NIST-800-53 SI-7, EU CRA Annex I §1(2)(c) |
246
+ | `NEW-CTRL-071` | AI-DISCOVERY-CREDIT-IN-COMPLIANCE-EVIDENCE | `MAL-2025-AI-FOUND-FFMPEG-BIGSLEEP` + `CVE-2025-6965` + `CVE-2025-0133` + ZeroPath quartet | NIST AI RMF MEASURE 2.7, ISO/IEC 42001 §6.1.4 (records of AI use), EU AI Act Art.12 (record-keeping) |
231
247
 
232
248
  When you cite a `NEW-CTRL-*` ID in a skill body, the lint reads the upstream `zeroday-lessons.json` entry as the authoritative source for the requirement text — do not paraphrase the description in the skill body, link to the ID instead.
233
249
 
package/CHANGELOG.md CHANGED
@@ -1,5 +1,63 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.13.6 — 2026-05-18
4
+
5
+ CVE catalog expansion (38 → 67 entries) covering threat classes the catalog previously did not address, plus a `doctor` undercount fix.
6
+
7
+ ### Features
8
+
9
+ **29 new catalog entries** across the under-represented classes:
10
+
11
+ - **Browsers (4)** — Chrome V8 TAG-disclosed zero-day `CVE-2025-10585`, WebKit DarkSword chain `CVE-2025-14174` + `CVE-2025-43529`, Firefox SpiderMonkey Pwn2Own `CVE-2025-4919`.
12
+ - **Mobile OS (3)** — WebKit Glass Cage iOS chain `CVE-2025-24201`, ImageIO zero-click root `CVE-2025-43300`, Android POSIX-CPU-timer race `CVE-2025-38352`.
13
+ - **Identity providers (2)** — Entra ID cross-tenant Actor-token impersonation `CVE-2025-55241` (CVSS 10.0), Cisco Duo log credential disclosure `CVE-2025-21085`.
14
+ - **Database engines (3)** — PostgreSQL psql ACE `CVE-2025-1094` (BeyondTrust / Treasury breaches), Redis RediShell Lua UAF `CVE-2025-49844` (CVSS 10.0), MongoBleed memory disclosure `CVE-2025-14847`.
15
+ - **HTTP/2 (1)** — MadeYouReset stream-reset DoS `CVE-2025-8671` (Rapid Reset successor, 2.8M+ vulnerable instances).
16
+ - **AI model serving (4)** — vLLM heap-overflow RCE `CVE-2026-22778`, Ollama Bleeding Llama `CVE-2026-7482`, LangChain LangGrinch `CVE-2025-68664`, Big Sleep SQLite zero-day `CVE-2025-6965`.
17
+ - **VMware ESXi (3)** — `CVE-2025-22224` / `CVE-2025-22225` / `CVE-2025-22226` (VMSA-2025-0004, ransomware-active VM-escape chain).
18
+ - **Malicious packages (3)** — ultralytics XMRig `MAL-2024-PYPI-ULTRALYTICS-XMRIG` (60M-download AI library), RubyGems + Go sleeper `MAL-2026-RUBYGEMS-BUFFERZONECORP-SLEEPER`, PyPI colorama Solana stealer `MAL-2025-PYPI-COLORAMA-SOLANA-STEALER`.
19
+ - **AI-discovery anchors (6)** — XBOW Palo Alto GlobalProtect `CVE-2025-0133` (HackerOne #1 Q2 2025), ZeroPath cluster (`CVE-2025-59529` / `CVE-2025-55319` / `CVE-2025-53767` / `CVE-2025-10725`), Big Sleep FFmpeg + ImageMagick tranche `MAL-2025-AI-FOUND-FFMPEG-BIGSLEEP`.
20
+
21
+ Every entry carries the full RWEP factor set, named verification sources, vendor advisory references, and a matching `data/zeroday-lessons.json` lesson. AI-discovered rate climbs 5/38 (0.132) → 12/67 (0.179), clearing the next ladder rung toward the Hard Rule #7 target of 0.40.
22
+
23
+ **16 new control requirements** mint `NEW-CTRL-056` through `NEW-CTRL-071`, named in `AGENTS.md` with the surfacing zero-day and gap-closed framework controls. Coverage spans mobile MDM SLA enforcement, browser managed-update no-deferral, cloud-control-plane cross-tenant claim validation, sensitive-data-in-logs lint, database server-side scripting default-deny, in-memory datastore memory-disclosure exposure audit, HTTP/2 stream-reset accounting, multimodal inference decoder isolation, LLM-output deserialization trust zone, AI-model-server default auth, agentic-IDE host-execution sandbox, AI-platform control-plane RBAC overlay, hypervisor tenancy assumption, ecosystem-package temporal trust drift, typosquat install-time guard, and AI-discovery credit in compliance evidence.
24
+
25
+ **ATT&CK + ATLAS catalogs extended** to back the new entries: 8 new ATT&CK techniques (T1005, T1189, T1496, T1498, T1499.001, T1499.002, T1539, T1657) and 3 new ATLAS TTPs (AML.T0007 Discover ML Artifacts, AML.T0011 User Execution, AML.T0047 LLM Meta Prompt Extraction).
26
+
27
+ ### Bugs
28
+
29
+ **`exceptd doctor` no longer undercounts the catalog.** The prior implementation parsed `validate-cves` text output, which only counts `CVE-*` prefixes — `MAL-*` (malicious-package) entries were silently dropped from the total. An operator reading `CVE catalog: 34 entries` on a 38-entry catalog would conclude that the Shai-Hulud / TanStack worm intelligence had been removed when it was present all along. The check now reads `data/cve-catalog.json` directly and reports the combined total with the per-prefix breakdown: `CVE catalog: 67 entries (60 CVE + 7 MAL), drift 0`. The `validate-cves` text output gains a clarifying suffix noting that the count is CVE-IDs queued for NVD validation and that the combined catalog total lives under `exceptd doctor`.
30
+
31
+ ## 0.13.5 — 2026-05-18
32
+
33
+ Three new playbooks, two cross-cutting CLI behaviours, and a deterministic schema gate on `active_exploitation` vocabulary.
34
+
35
+ ### Features
36
+
37
+ **Three new playbooks bring the canonical set to 23.**
38
+
39
+ - **`post-quantum-migration`** — the operational migration programme (distinct from `crypto`, which is handshake-level). Covers per-asset cryptographic register, vendor-SLA tracking, regulator-deadline orchestration (CNSA 2.0, OMB M-23-02, NIS2 Art.21(2)(h), DORA Art.9, EU CRA Annex I, BSI TR-02102, ACSC ISM-1546), and HNDL exposure-window analysis. Nine indicators including `no-cryptographic-asset-register`, `hsm-firmware-no-pqc` (Thales/Entrust/CloudHSM migration blocker), `long-retention-classical-only-asset`, and `embedded-tls-stack-classical-only`. 13 framework-gap mappings (NIST SC-12/SC-13, ISO A.8.24/A.8.25, PCI 3.6/4.2.1, NIS2/DORA/EU CRA, UK CAF, AU ISM, MAS TRM, JP NISC, HIPAA). Feeds into `crypto` + `framework` + `sbom`.
40
+
41
+ - **`ai-discovered-cve-triage`** — operator-side response to AI-discovered CVE arrival. Anchors on CVE-2026-31431 (Copy Fail, Theori+Xint), CVE-2026-46300 (Fragnesia, Zellic AI-agentic), CVE-2026-42945 (NGINX Rift, depthfirst — first publicly-attributed AI-discovered nginx CVE), the GTIG 41% AI-zero-day statistic, and Hard Rule #7. Seven indicators including `ai-discovery-attribution-band-c-unverified` (don't apply +15 ai_factor on unverified claims), `ai-discovery-feed-coverage-incomplete` (operator pipeline misses Theori/depthfirst/Zellic/GTIG/Project Zero AI sources), and `asset-unpatched-past-rwep-sla` (RWEP-derived SLA: 4h ≥ 90, 24h 75–89, 72h 60–74). Feeds into `framework` + `kernel` + `sbom` + `runtime`.
42
+
43
+ - **`supply-chain-recovery`** — post-compromise recovery workflow (distinct from `sbom`, which is pre-incident hygiene). Anchors on Shai-Hulud (Sep 2025 / Nov 2025 / May 2026 waves), MAL-2026-SHAI-HULUD-OSS (TeamPCP open-sourced 2026-05-12), MAL-2026-TANSTACK-MINI (42 `@tanstack/*` packages), MAL-2026-NODE-IPC-STEALER, and CVE-2026-45321. Encodes NEW-CTRL-050 (exhaustive maintainer-credential rotation), NEW-CTRL-051 (install-window audit), NEW-CTRL-052 (AI-assistant config exfil as first-class — `~/.cursor`, `~/.codeium`, `~/.claude`). Eight indicators including `ai-assistant-config-mutated` (Shai-Hulud startup-hook persistence), `outbound-exfil-during-window`, `operator-published-package-republish` (downstream notification mandatory), `long-lived-token-in-compromised-ci-log`. Feeds into `cred-stores` + `idp-incident-response` + `sbom` + `mcp` + `framework`.
44
+
45
+ **`exceptd watchlist --org-scan --output-format markdown`.** Adds GitHub-flavored markdown table output for PR / issue / advisory body consumption. Accepted values: `json` | `markdown` | `human` (default). The legacy `--json` shorthand remains accepted (equivalent to `--output-format json`). Invalid values exit non-zero with the accepted-set in the error envelope.
46
+
47
+ **`exceptd doctor --ai-config --fix` now repairs Windows ACLs.** The POSIX path applied `chmod 0600`; on Windows the audit reported the gap as "manual review." The Windows path now invokes `icacls /inheritance:r /grant:r` to restrict to the current user + SYSTEM + Administrators. The audit check itself (without `--fix`) parses `icacls <path>` and reports any extra principals.
48
+
49
+ **Skill chain: MCP findings inside a CI runner escalate to `cicd-pipeline-compromise`.** New deterministic indicator `mcp-server-invoked-from-ci-pipeline` keys on `GITHUB_ACTIONS` / `GITLAB_CI` / `BUILDKITE` / `JENKINS_URL` / `CIRCLECI` / `RUNNER_OS` env vars and known runner workdirs (`/_work/`, `/builds/`, `/var/jenkins_home/workspace/`, `/var/lib/buildkite-agent/builds/`). When paired with any other high-confidence MCP signal, the finding feeds into `cicd-pipeline-compromise` for OIDC / signing-key / publish-channel scope handling. Without this arc, MCP findings in CI received local-dev close-out only, under-counting publish-channel blast radius.
50
+
51
+ ### Bugs
52
+
53
+ **`cve-catalog.schema.json` `active_exploitation` enum now matches `_meta.active_exploitation_vocabulary`.** The schema enumerated four values (`confirmed` / `suspected` / `none` / `unknown`); the meta vocabulary listed five (adding `theoretical`). Catalog entries written against the meta vocabulary that used `theoretical` were silently rejected at validation time. Schema now lists five values; `validate-cve-catalog.js` adds a cross-check that fails the gate if the two surfaces ever drift again.
54
+
55
+ ### Internal
56
+
57
+ - Test count baseline updated for the +3 playbook delta and the new `--output-format` test cases.
58
+ - `validate-cve-catalog.js` schema-vs-meta enum cross-check is a hard predeploy gate.
59
+ - All 23 playbooks pass `validate-playbooks` and `lint-skills` warning-free.
60
+
3
61
  ## 0.13.4 — 2026-05-18
4
62
 
5
63
  Warning-cleanup pass + catalog hygiene + docs surfacing. The post-v0.13.3 state had ~43 skill lint warnings and 20 cosmetic playbook warnings that operators saw on every predeploy run; this release drives both to zero. README and AGENTS catch up with the v0.13.0 → v0.13.3 operator surface.
package/bin/exceptd.js CHANGED
@@ -251,6 +251,63 @@ const RENAMED_VERBS_HINT = {
251
251
  "build-indexes": "refresh --indexes-only",
252
252
  };
253
253
 
254
+ /**
255
+ * v0.13.5: Windows ACL audit helper for `doctor --ai-config`. Replaces
256
+ * the v0.13.3 "manual review" placeholder with a real check.
257
+ *
258
+ * Runs `icacls <path>` and parses the output for any principal beyond
259
+ * the current user. Anything other than the running USERNAME, NT
260
+ * AUTHORITY\SYSTEM, and BUILTIN\Administrators on the ACL counts as
261
+ * "broader than user-only" — typical offenders are inherited entries
262
+ * for BUILTIN\Users, Authenticated Users, or Everyone.
263
+ *
264
+ * Returns { ok: boolean, extraPrincipals: string[], error?: string }.
265
+ * On non-Windows hosts (defensive — only invoked from the win32 branch
266
+ * in cmdDoctor anyway), returns { ok: true, extraPrincipals: [] }.
267
+ */
268
+ function checkWindowsAcl(targetPath) {
269
+ if (process.platform !== 'win32') return { ok: true, extraPrincipals: [] };
270
+ const childProc = require('child_process');
271
+ const user = (process.env.USERNAME || '').toLowerCase();
272
+ // Principals that are EXPECTED on every Windows ACL and don't count
273
+ // as "broader than user-only" — admins legitimately need access for
274
+ // system maintenance; SYSTEM is required for backup/restore.
275
+ const ALLOWED_PRINCIPAL_SUFFIXES = [
276
+ `\\${user}`,
277
+ 'nt authority\\system',
278
+ 'builtin\\administrators',
279
+ 'administrators',
280
+ ];
281
+ let stdout;
282
+ try {
283
+ stdout = childProc.execFileSync('icacls', [targetPath], {
284
+ encoding: 'utf8',
285
+ stdio: ['ignore', 'pipe', 'pipe'],
286
+ timeout: 5000,
287
+ });
288
+ } catch (e) {
289
+ return { ok: false, extraPrincipals: [], error: (e && e.message) || String(e) };
290
+ }
291
+ const extraPrincipals = [];
292
+ // icacls output format: each principal on its own line, prefixed by
293
+ // whitespace and ending with permission bits in parens. Lines for
294
+ // the file itself (target path) and the "Successfully processed"
295
+ // footer are skipped.
296
+ for (const rawLine of stdout.split(/\r?\n/)) {
297
+ const line = rawLine.trim();
298
+ if (!line) continue;
299
+ if (line.toLowerCase().startsWith('successfully processed')) continue;
300
+ // The first line is the path; principal lines start with NT AUTHORITY,
301
+ // BUILTIN, or a domain\user. Match `name:(perms)` shape.
302
+ const m = line.match(/^([^:()]+?):\(/);
303
+ if (!m) continue;
304
+ const principal = m[1].trim().toLowerCase();
305
+ const isAllowed = ALLOWED_PRINCIPAL_SUFFIXES.some((suffix) => principal.endsWith(suffix));
306
+ if (!isAllowed) extraPrincipals.push(m[1].trim());
307
+ }
308
+ return { ok: extraPrincipals.length === 0, extraPrincipals };
309
+ }
310
+
254
311
  function readPkgVersion() {
255
312
  try {
256
313
  return JSON.parse(fs.readFileSync(path.join(PKG_ROOT, "package.json"), "utf8")).version;
@@ -5338,19 +5395,36 @@ function cmdDoctor(runner, args, runOpts, pretty) {
5338
5395
  if (runCves) {
5339
5396
  try {
5340
5397
  const orchPath = path.join(PKG_ROOT, "orchestrator", "index.js");
5341
- // validate-cves doesn't emit JSON; parse text for row count + drift.
5398
+ // validate-cves doesn't emit JSON; parse text for drift signal.
5342
5399
  const res = spawnSync(process.execPath, [orchPath, "validate-cves", "--offline"], {
5343
5400
  encoding: "utf8",
5344
5401
  cwd: PKG_ROOT,
5345
5402
  timeout: 30000,
5346
5403
  });
5347
5404
  const text = (res.stdout || "") + (res.stderr || "");
5348
- const totalMatch = text.match(/(\d+)\s+CVEs?\s+in\s+catalog/i);
5349
5405
  const driftMatch = text.match(/drift[:\s]+(\d+)/i);
5350
5406
  const ok = res.status === 0;
5407
+ // v0.13.6: total comes from the catalog file directly. The
5408
+ // validate-cves text-scrape only ever counted CVE-* prefixes, so
5409
+ // MAL-* (malicious package) entries silently dropped from the
5410
+ // doctor report — operators reading "34 entries" assumed the
5411
+ // Shai-Hulud / TanStack worm intel had been removed when it was
5412
+ // present all along. Read the catalog and report both totals.
5413
+ let total = null;
5414
+ let cve_count = null;
5415
+ let mal_count = null;
5416
+ try {
5417
+ const catalog = require(path.join(PKG_ROOT, "data", "cve-catalog.json"));
5418
+ const keys = Object.keys(catalog).filter((k) => !k.startsWith("_"));
5419
+ cve_count = keys.filter((k) => k.startsWith("CVE-")).length;
5420
+ mal_count = keys.filter((k) => k.startsWith("MAL-")).length;
5421
+ total = keys.length;
5422
+ } catch { /* fall through with nulls */ }
5351
5423
  checks.cves = {
5352
5424
  ok,
5353
- total: totalMatch ? Number(totalMatch[1]) : null,
5425
+ total,
5426
+ cve_count,
5427
+ mal_count,
5354
5428
  drift: driftMatch ? Number(driftMatch[1]) : 0,
5355
5429
  ...(ok ? {} : { exit_code: res.status, raw: text.slice(0, 500) }),
5356
5430
  };
@@ -5510,15 +5584,23 @@ function cmdDoctor(runner, args, runOpts, pretty) {
5510
5584
  let st;
5511
5585
  try { st = fs.statSync(childAbs); } catch { continue; }
5512
5586
  if (process.platform === 'win32') {
5513
- // Windows POSIX mode bits don't carry meaningful ACL info.
5514
- // Flag every sensitive file with a manual-review note rather
5515
- // than emit a noisy permission claim that's likely wrong.
5587
+ // v0.13.5: real ACL check via icacls. Replaces the v0.13.3
5588
+ // "manual review" info-level placeholder. Confirms only the
5589
+ // current-user SID has any read entry. Anything else (Users,
5590
+ // Everyone, Authenticated Users, BUILTIN\Users, etc.) on the
5591
+ // ACL counts as "broader than user-only" and surfaces as a
5592
+ // warn finding. The `--fix` path applies `icacls /inheritance:r
5593
+ // /grant:r <USER>:F` to strip inherited entries.
5594
+ const aclCheck = checkWindowsAcl(childAbs);
5595
+ if (aclCheck.ok) continue;
5516
5596
  findings.push({
5517
5597
  path: `${displayRoot}/${childRel}`,
5518
5598
  mode: null,
5519
- severity: 'info',
5520
- issue: 'win32_acl_check_not_implemented',
5521
- hint: 'On Windows the POSIX mode bits are not load-bearing. Use icacls to confirm only the current user has read access. Tracked for v0.14+.',
5599
+ severity: 'warn',
5600
+ issue: 'broader_than_user_only_acl',
5601
+ acl_extra_principals: aclCheck.extraPrincipals,
5602
+ hint: `icacls "${childAbs}" /inheritance:r /grant:r %USERNAME%:F # NEW-CTRL-050: AI-assistant configs holding MCP tokens / API keys must restrict ACL to the workstation user`,
5603
+ fix_command: ['icacls', childAbs, '/inheritance:r', '/grant:r', `${process.env.USERNAME}:F`],
5522
5604
  });
5523
5605
  continue;
5524
5606
  }
@@ -5530,6 +5612,8 @@ function cmdDoctor(runner, args, runOpts, pretty) {
5530
5612
  severity: 'warn',
5531
5613
  issue: 'group_or_other_readable',
5532
5614
  hint: `chmod 600 '${childAbs}' # NEW-CTRL-050: AI-assistant configs holding MCP tokens / API keys must be 0600 to defeat unprivileged exfil`,
5615
+ fix_chmod: 0o600,
5616
+ fix_abs_path: childAbs,
5533
5617
  });
5534
5618
  }
5535
5619
  }
@@ -5543,9 +5627,49 @@ function cmdDoctor(runner, args, runOpts, pretty) {
5543
5627
  }
5544
5628
  }
5545
5629
  const errorFindings = findings.filter((f) => f.severity === 'warn');
5630
+
5631
+ // v0.13.5: --fix path. When `doctor --ai-config --fix` is invoked
5632
+ // AND warn-severity findings exist, apply the per-finding fix
5633
+ // command (chmod 600 on POSIX; icacls /inheritance:r /grant:r on
5634
+ // Windows). The fix attempt is recorded per-finding so the report
5635
+ // surfaces which fixes landed vs which failed.
5636
+ let fixesApplied = 0;
5637
+ let fixesFailed = 0;
5638
+ if (args.fix && errorFindings.length > 0) {
5639
+ const childProc = require('child_process');
5640
+ for (const f of errorFindings) {
5641
+ if (f.fix_chmod && f.fix_abs_path) {
5642
+ try {
5643
+ fs.chmodSync(f.fix_abs_path, f.fix_chmod);
5644
+ f.fix_status = 'chmod_applied';
5645
+ fixesApplied++;
5646
+ } catch (e) {
5647
+ f.fix_status = 'chmod_failed';
5648
+ f.fix_error = e.message;
5649
+ fixesFailed++;
5650
+ }
5651
+ continue;
5652
+ }
5653
+ if (f.fix_command) {
5654
+ try {
5655
+ childProc.execFileSync(f.fix_command[0], f.fix_command.slice(1), {
5656
+ stdio: ['ignore', 'ignore', 'pipe'],
5657
+ timeout: 5000,
5658
+ });
5659
+ f.fix_status = 'icacls_applied';
5660
+ fixesApplied++;
5661
+ } catch (e) {
5662
+ f.fix_status = 'icacls_failed';
5663
+ f.fix_error = (e && e.message) || String(e);
5664
+ fixesFailed++;
5665
+ }
5666
+ }
5667
+ }
5668
+ }
5669
+
5546
5670
  checks.ai_config = {
5547
- ok: errorFindings.length === 0,
5548
- severity: errorFindings.length > 0 ? 'warn' : 'info',
5671
+ ok: errorFindings.length === 0 || (args.fix && fixesFailed === 0),
5672
+ severity: errorFindings.length > 0 && fixesFailed > 0 ? 'warn' : (errorFindings.length > 0 && !args.fix ? 'warn' : 'info'),
5549
5673
  scanned_dirs: scannedDirs,
5550
5674
  scanned_files: scannedFiles,
5551
5675
  directories_inspected: AI_CONFIG_DIRS.map((d) => d.display),
@@ -5553,8 +5677,9 @@ function cmdDoctor(runner, args, runOpts, pretty) {
5553
5677
  findings,
5554
5678
  platform: process.platform,
5555
5679
  control_reference: 'NEW-CTRL-050 (MAL-2026-SHAI-HULUD-OSS lesson)',
5680
+ ...(args.fix ? { fix_applied: fixesApplied, fix_failed: fixesFailed } : {}),
5556
5681
  };
5557
- if (errorFindings.length > 0) issues.push('ai_config');
5682
+ if (errorFindings.length > 0 && (!args.fix || fixesFailed > 0)) issues.push('ai_config');
5558
5683
  }
5559
5684
 
5560
5685
  // Walk every check and split: errors (severity error/missing/fail) vs warnings
@@ -5685,11 +5810,14 @@ function cmdDoctor(runner, args, runOpts, pretty) {
5685
5810
  ? `skill currency: all green (${c.total_skills ?? "?"} skills)`
5686
5811
  : `skill currency: ${c.stale_skills?.length || "?"} stale, ${c.critical_count ?? 0} critical`
5687
5812
  );
5688
- mark(checks.cves, c =>
5689
- c.ok
5690
- ? `CVE catalog: ${c.total ?? "?"} entries, drift ${c.drift ?? 0}`
5691
- : `CVE catalog FAILED (exit=${c.exit_code ?? "?"})`
5692
- );
5813
+ mark(checks.cves, c => {
5814
+ if (!c.ok) return `CVE catalog FAILED (exit=${c.exit_code ?? "?"})`;
5815
+ const total = c.total ?? "?";
5816
+ const breakdown = (c.cve_count != null && c.mal_count != null)
5817
+ ? ` (${c.cve_count} CVE + ${c.mal_count} MAL)`
5818
+ : "";
5819
+ return `CVE catalog: ${total} entries${breakdown}, drift ${c.drift ?? 0}`;
5820
+ });
5693
5821
  mark(checks.rfcs, c =>
5694
5822
  c.ok
5695
5823
  ? `RFC catalog: ${c.total ?? "?"} entries, drift ${c.drift ?? 0}`
@@ -1,21 +1,21 @@
1
1
  {
2
2
  "schema_version": "1.1.0",
3
- "generated_at": "2026-05-18T04:13:12.063Z",
3
+ "generated_at": "2026-05-18T07:02:32.618Z",
4
4
  "generator": "scripts/build-indexes.js",
5
5
  "source_count": 54,
6
6
  "source_hashes": {
7
- "manifest.json": "0d7cc1e5a718515519e81b973126f0fe316ad8252e4c8e04f54934ea575a9b80",
8
- "data/atlas-ttps.json": "2b021f47355365d1ba59078dfa582397c7a64c2b4ebea4657ea260a66b76daf6",
9
- "data/attack-techniques.json": "76461dbec048c5e072435d57e3a04b780e3992dab9f316b1b52608e0a997e355",
10
- "data/cve-catalog.json": "4b8c05074744f9e099c776e0f9c3afd2b978fc52d702bc8805c3b5bfecdbafcb",
7
+ "manifest.json": "fac154a4d63a13c1289d3498abab8d28433c30b79393e63f49c6a2ce21f7922e",
8
+ "data/atlas-ttps.json": "c2aee9c70ec24cf48f1ea4daf170aa6e7b93292888239c46a8ec9e522ee32119",
9
+ "data/attack-techniques.json": "29cd5690040c7153dbf293b7e3a99b72fc897b0495478e369f7ce7004b8d64f4",
10
+ "data/cve-catalog.json": "b3731361d298483648264215fd8dbfca36d0f4e2ead4aebf7c49718e12038e1f",
11
11
  "data/cwe-catalog.json": "4a0036f9ec17af29e0df111ac77b94f8be6a52742bfd89ff3583096d23b75e35",
12
12
  "data/d3fend-catalog.json": "a1fc2827ceb344669e148d55197dbf1b0e5b20bcc618e90517639c17d67ee82d",
13
13
  "data/dlp-controls.json": "d2406c482dddd30e49203879999dc4b3a7fd4d0494d6a61d86b91ee76415df19",
14
14
  "data/exploit-availability.json": "ec2656f0d9a893610e27b43eb6035fe9b18e057c9f6dfaac7e7d4959bbcbb795",
15
- "data/framework-control-gaps.json": "994bf3203f3a2c80fe21194d00f67ecffa77b80193ba3f4b046e9d38e7b09f0f",
15
+ "data/framework-control-gaps.json": "d49c75de55e6e1dabec46e6e975619489a2093b0be53c2a0b654e5c1826fbe46",
16
16
  "data/global-frameworks.json": "9ba563a85f7f8d6c3c957de64945e20925a89d0ed6ea6fc561cf093811acf558",
17
17
  "data/rfc-references.json": "e253a548c8a829d178d5aea601e268724b85c936ccbfa51c2e5d80c5f8efe2b0",
18
- "data/zeroday-lessons.json": "3d4c18977f2100f200e209dc55331931a5d0adc54af35879fc58f1b43deac56f",
18
+ "data/zeroday-lessons.json": "bb3fec080f649a5968f8d0c6d69ca4d32fb120de0f7d07b1f5058184d4d3ff3a",
19
19
  "skills/kernel-lpe-triage/skill.md": "08b3e9815ba481c57c80f5fc0ccbf5bb7cbb41f570c235ba6ff9596b8c07354d",
20
20
  "skills/ai-attack-surface/skill.md": "d1361c53c8360999e1ec6a403bcbfaa53d0afc11689e8781d26081196dd079d4",
21
21
  "skills/mcp-agent-trust/skill.md": "19a6b54375808e59143070011328d8c936836845bca4a484108738bbef290694",
@@ -72,7 +72,7 @@
72
72
  "dlp_refs": 0
73
73
  },
74
74
  "trigger_table_entries": 538,
75
- "chains_cve_entries": 34,
75
+ "chains_cve_entries": 59,
76
76
  "chains_cwe_entries": 55,
77
77
  "jurisdictions_indexed": 29,
78
78
  "handoff_dag_nodes": 42,
@@ -5,6 +5,22 @@
5
5
  "event_count": 54
6
6
  },
7
7
  "events": [
8
+ {
9
+ "date": "2026-05-18",
10
+ "type": "catalog_update",
11
+ "artifact": "data/cve-catalog.json",
12
+ "path": "data/cve-catalog.json",
13
+ "schema_version": "1.0.0",
14
+ "entry_count": 67
15
+ },
16
+ {
17
+ "date": "2026-05-18",
18
+ "type": "catalog_update",
19
+ "artifact": "data/zeroday-lessons.json",
20
+ "path": "data/zeroday-lessons.json",
21
+ "schema_version": "1.1.0",
22
+ "entry_count": 67
23
+ },
8
24
  {
9
25
  "date": "2026-05-15",
10
26
  "type": "skill_review",
@@ -39,7 +55,7 @@
39
55
  "artifact": "data/atlas-ttps.json",
40
56
  "path": "data/atlas-ttps.json",
41
57
  "schema_version": "1.0.0",
42
- "entry_count": 30
58
+ "entry_count": 33
43
59
  },
44
60
  {
45
61
  "date": "2026-05-15",
@@ -47,7 +63,7 @@
47
63
  "artifact": "data/attack-techniques.json",
48
64
  "path": "data/attack-techniques.json",
49
65
  "schema_version": "1.0.0",
50
- "entry_count": 98
66
+ "entry_count": 106
51
67
  },
52
68
  {
53
69
  "date": "2026-05-15",
@@ -81,14 +97,6 @@
81
97
  "schema_version": "1.0.0",
82
98
  "entry_count": 41
83
99
  },
84
- {
85
- "date": "2026-05-15",
86
- "type": "catalog_update",
87
- "artifact": "data/zeroday-lessons.json",
88
- "path": "data/zeroday-lessons.json",
89
- "schema_version": "1.1.0",
90
- "entry_count": 38
91
- },
92
100
  {
93
101
  "date": "2026-05-15",
94
102
  "type": "manifest_review",
@@ -96,14 +104,6 @@
96
104
  "path": "manifest.json",
97
105
  "note": "manifest threat_review_date — 42 skills, 11 catalogs"
98
106
  },
99
- {
100
- "date": "2026-05-13",
101
- "type": "catalog_update",
102
- "artifact": "data/cve-catalog.json",
103
- "path": "data/cve-catalog.json",
104
- "schema_version": "1.0.0",
105
- "entry_count": 38
106
- },
107
107
  {
108
108
  "date": "2026-05-13",
109
109
  "type": "catalog_update",
@@ -18,7 +18,7 @@
18
18
  "rebuild_after_days": 365,
19
19
  "note": "Per-entry last_verified governs decay. Skills depending on this catalog must check entry freshness before high-stakes use."
20
20
  },
21
- "entry_count": 30,
21
+ "entry_count": 33,
22
22
  "sample_keys": [
23
23
  "AML.T0001",
24
24
  "AML.T0040",
@@ -40,7 +40,7 @@
40
40
  "rebuild_after_days": 365,
41
41
  "note": "Catalog must be rebuilt against the upstream ATT&CK release whenever MITRE publishes a new version. AGENTS.md external-data version-pinning rule requires the bump to be intentional, not silent. ATT&CK ships semi-annually (April + October); audit on each release for tactic moves, technique splits, and new Detection Strategies."
42
42
  },
43
- "entry_count": 98,
43
+ "entry_count": 106,
44
44
  "sample_keys": [
45
45
  "T0001",
46
46
  "T0017",
@@ -53,7 +53,7 @@
53
53
  "path": "data/cve-catalog.json",
54
54
  "purpose": "Per-CVE record (CVSS, EPSS, CISA KEV, RWEP, AI-discovery, vendor advisories, framework gaps, ATLAS/ATT&CK mappings). Cross-validated against NVD + CISA KEV + FIRST EPSS via validate-cves.",
55
55
  "schema_version": "1.0.0",
56
- "last_updated": "2026-05-13",
56
+ "last_updated": "2026-05-18",
57
57
  "tlp": "CLEAR",
58
58
  "source_confidence_default": "A1",
59
59
  "freshness_policy": {
@@ -62,7 +62,7 @@
62
62
  "rebuild_after_days": 365,
63
63
  "note": "Per-entry last_verified governs decay. Skills depending on this catalog must check entry freshness before high-stakes use."
64
64
  },
65
- "entry_count": 38,
65
+ "entry_count": 67,
66
66
  "sample_keys": [
67
67
  "CVE-2025-53773",
68
68
  "CVE-2026-30615",
@@ -229,7 +229,7 @@
229
229
  "path": "data/zeroday-lessons.json",
230
230
  "purpose": "Distilled lessons from notable zero-days and campaigns (SesameOp, Copy Fail, Dirty Frag, Copilot RCE, Windsurf MCP). Each entry: technique, distinguishing characteristic, what it means for the framework lag.",
231
231
  "schema_version": "1.1.0",
232
- "last_updated": "2026-05-15",
232
+ "last_updated": "2026-05-18",
233
233
  "tlp": "CLEAR",
234
234
  "source_confidence_default": "B2",
235
235
  "freshness_policy": {
@@ -238,7 +238,7 @@
238
238
  "rebuild_after_days": 365,
239
239
  "note": "Per-entry last_verified governs decay. Skills depending on this catalog must check entry freshness before high-stakes use."
240
240
  },
241
- "entry_count": 38,
241
+ "entry_count": 67,
242
242
  "sample_keys": [
243
243
  "CVE-2026-31431",
244
244
  "CVE-2025-53773",