@blamejs/exceptd-skills 0.13.4 → 0.13.5
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 +30 -0
- package/bin/exceptd.js +117 -9
- package/data/_indexes/_meta.json +2 -2
- package/data/playbooks/ai-discovered-cve-triage.json +1146 -0
- package/data/playbooks/cicd-pipeline-compromise.json +3 -0
- package/data/playbooks/cred-stores.json +1 -0
- package/data/playbooks/crypto.json +3 -0
- package/data/playbooks/framework.json +3 -0
- package/data/playbooks/idp-incident.json +2 -1
- package/data/playbooks/kernel.json +1 -0
- package/data/playbooks/mcp.json +27 -2
- package/data/playbooks/post-quantum-migration.json +1268 -0
- package/data/playbooks/runtime.json +1 -0
- package/data/playbooks/sbom.json +3 -0
- package/data/playbooks/supply-chain-recovery.json +1332 -0
- package/lib/schemas/cve-catalog.schema.json +2 -1
- package/lib/validate-cve-catalog.js +27 -0
- package/manifest.json +44 -44
- package/orchestrator/index.js +58 -1
- package/package.json +1 -1
- package/sbom.cdx.json +57 -24
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.13.5 — 2026-05-18
|
|
4
|
+
|
|
5
|
+
Three new playbooks, two cross-cutting CLI behaviours, and a deterministic schema gate on `active_exploitation` vocabulary.
|
|
6
|
+
|
|
7
|
+
### Features
|
|
8
|
+
|
|
9
|
+
**Three new playbooks bring the canonical set to 23.**
|
|
10
|
+
|
|
11
|
+
- **`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`.
|
|
12
|
+
|
|
13
|
+
- **`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`.
|
|
14
|
+
|
|
15
|
+
- **`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`.
|
|
16
|
+
|
|
17
|
+
**`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.
|
|
18
|
+
|
|
19
|
+
**`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.
|
|
20
|
+
|
|
21
|
+
**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.
|
|
22
|
+
|
|
23
|
+
### Bugs
|
|
24
|
+
|
|
25
|
+
**`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.
|
|
26
|
+
|
|
27
|
+
### Internal
|
|
28
|
+
|
|
29
|
+
- Test count baseline updated for the +3 playbook delta and the new `--output-format` test cases.
|
|
30
|
+
- `validate-cve-catalog.js` schema-vs-meta enum cross-check is a hard predeploy gate.
|
|
31
|
+
- All 23 playbooks pass `validate-playbooks` and `lint-skills` warning-free.
|
|
32
|
+
|
|
3
33
|
## 0.13.4 — 2026-05-18
|
|
4
34
|
|
|
5
35
|
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;
|
|
@@ -5510,15 +5567,23 @@ function cmdDoctor(runner, args, runOpts, pretty) {
|
|
|
5510
5567
|
let st;
|
|
5511
5568
|
try { st = fs.statSync(childAbs); } catch { continue; }
|
|
5512
5569
|
if (process.platform === 'win32') {
|
|
5513
|
-
//
|
|
5514
|
-
//
|
|
5515
|
-
//
|
|
5570
|
+
// v0.13.5: real ACL check via icacls. Replaces the v0.13.3
|
|
5571
|
+
// "manual review" info-level placeholder. Confirms only the
|
|
5572
|
+
// current-user SID has any read entry. Anything else (Users,
|
|
5573
|
+
// Everyone, Authenticated Users, BUILTIN\Users, etc.) on the
|
|
5574
|
+
// ACL counts as "broader than user-only" and surfaces as a
|
|
5575
|
+
// warn finding. The `--fix` path applies `icacls /inheritance:r
|
|
5576
|
+
// /grant:r <USER>:F` to strip inherited entries.
|
|
5577
|
+
const aclCheck = checkWindowsAcl(childAbs);
|
|
5578
|
+
if (aclCheck.ok) continue;
|
|
5516
5579
|
findings.push({
|
|
5517
5580
|
path: `${displayRoot}/${childRel}`,
|
|
5518
5581
|
mode: null,
|
|
5519
|
-
severity: '
|
|
5520
|
-
issue: '
|
|
5521
|
-
|
|
5582
|
+
severity: 'warn',
|
|
5583
|
+
issue: 'broader_than_user_only_acl',
|
|
5584
|
+
acl_extra_principals: aclCheck.extraPrincipals,
|
|
5585
|
+
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`,
|
|
5586
|
+
fix_command: ['icacls', childAbs, '/inheritance:r', '/grant:r', `${process.env.USERNAME}:F`],
|
|
5522
5587
|
});
|
|
5523
5588
|
continue;
|
|
5524
5589
|
}
|
|
@@ -5530,6 +5595,8 @@ function cmdDoctor(runner, args, runOpts, pretty) {
|
|
|
5530
5595
|
severity: 'warn',
|
|
5531
5596
|
issue: 'group_or_other_readable',
|
|
5532
5597
|
hint: `chmod 600 '${childAbs}' # NEW-CTRL-050: AI-assistant configs holding MCP tokens / API keys must be 0600 to defeat unprivileged exfil`,
|
|
5598
|
+
fix_chmod: 0o600,
|
|
5599
|
+
fix_abs_path: childAbs,
|
|
5533
5600
|
});
|
|
5534
5601
|
}
|
|
5535
5602
|
}
|
|
@@ -5543,9 +5610,49 @@ function cmdDoctor(runner, args, runOpts, pretty) {
|
|
|
5543
5610
|
}
|
|
5544
5611
|
}
|
|
5545
5612
|
const errorFindings = findings.filter((f) => f.severity === 'warn');
|
|
5613
|
+
|
|
5614
|
+
// v0.13.5: --fix path. When `doctor --ai-config --fix` is invoked
|
|
5615
|
+
// AND warn-severity findings exist, apply the per-finding fix
|
|
5616
|
+
// command (chmod 600 on POSIX; icacls /inheritance:r /grant:r on
|
|
5617
|
+
// Windows). The fix attempt is recorded per-finding so the report
|
|
5618
|
+
// surfaces which fixes landed vs which failed.
|
|
5619
|
+
let fixesApplied = 0;
|
|
5620
|
+
let fixesFailed = 0;
|
|
5621
|
+
if (args.fix && errorFindings.length > 0) {
|
|
5622
|
+
const childProc = require('child_process');
|
|
5623
|
+
for (const f of errorFindings) {
|
|
5624
|
+
if (f.fix_chmod && f.fix_abs_path) {
|
|
5625
|
+
try {
|
|
5626
|
+
fs.chmodSync(f.fix_abs_path, f.fix_chmod);
|
|
5627
|
+
f.fix_status = 'chmod_applied';
|
|
5628
|
+
fixesApplied++;
|
|
5629
|
+
} catch (e) {
|
|
5630
|
+
f.fix_status = 'chmod_failed';
|
|
5631
|
+
f.fix_error = e.message;
|
|
5632
|
+
fixesFailed++;
|
|
5633
|
+
}
|
|
5634
|
+
continue;
|
|
5635
|
+
}
|
|
5636
|
+
if (f.fix_command) {
|
|
5637
|
+
try {
|
|
5638
|
+
childProc.execFileSync(f.fix_command[0], f.fix_command.slice(1), {
|
|
5639
|
+
stdio: ['ignore', 'ignore', 'pipe'],
|
|
5640
|
+
timeout: 5000,
|
|
5641
|
+
});
|
|
5642
|
+
f.fix_status = 'icacls_applied';
|
|
5643
|
+
fixesApplied++;
|
|
5644
|
+
} catch (e) {
|
|
5645
|
+
f.fix_status = 'icacls_failed';
|
|
5646
|
+
f.fix_error = (e && e.message) || String(e);
|
|
5647
|
+
fixesFailed++;
|
|
5648
|
+
}
|
|
5649
|
+
}
|
|
5650
|
+
}
|
|
5651
|
+
}
|
|
5652
|
+
|
|
5546
5653
|
checks.ai_config = {
|
|
5547
|
-
ok: errorFindings.length === 0,
|
|
5548
|
-
severity: errorFindings.length > 0 ? 'warn' : 'info',
|
|
5654
|
+
ok: errorFindings.length === 0 || (args.fix && fixesFailed === 0),
|
|
5655
|
+
severity: errorFindings.length > 0 && fixesFailed > 0 ? 'warn' : (errorFindings.length > 0 && !args.fix ? 'warn' : 'info'),
|
|
5549
5656
|
scanned_dirs: scannedDirs,
|
|
5550
5657
|
scanned_files: scannedFiles,
|
|
5551
5658
|
directories_inspected: AI_CONFIG_DIRS.map((d) => d.display),
|
|
@@ -5553,8 +5660,9 @@ function cmdDoctor(runner, args, runOpts, pretty) {
|
|
|
5553
5660
|
findings,
|
|
5554
5661
|
platform: process.platform,
|
|
5555
5662
|
control_reference: 'NEW-CTRL-050 (MAL-2026-SHAI-HULUD-OSS lesson)',
|
|
5663
|
+
...(args.fix ? { fix_applied: fixesApplied, fix_failed: fixesFailed } : {}),
|
|
5556
5664
|
};
|
|
5557
|
-
if (errorFindings.length > 0) issues.push('ai_config');
|
|
5665
|
+
if (errorFindings.length > 0 && (!args.fix || fixesFailed > 0)) issues.push('ai_config');
|
|
5558
5666
|
}
|
|
5559
5667
|
|
|
5560
5668
|
// Walk every check and split: errors (severity error/missing/fail) vs warnings
|
package/data/_indexes/_meta.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema_version": "1.1.0",
|
|
3
|
-
"generated_at": "2026-05-18T04:
|
|
3
|
+
"generated_at": "2026-05-18T04:58:08.316Z",
|
|
4
4
|
"generator": "scripts/build-indexes.js",
|
|
5
5
|
"source_count": 54,
|
|
6
6
|
"source_hashes": {
|
|
7
|
-
"manifest.json": "
|
|
7
|
+
"manifest.json": "6bbf4f4d9540c2539d6f2635b39ba42f963e3a2238e9e8b6569d0cc65719b813",
|
|
8
8
|
"data/atlas-ttps.json": "2b021f47355365d1ba59078dfa582397c7a64c2b4ebea4657ea260a66b76daf6",
|
|
9
9
|
"data/attack-techniques.json": "76461dbec048c5e072435d57e3a04b780e3992dab9f316b1b52608e0a997e355",
|
|
10
10
|
"data/cve-catalog.json": "4b8c05074744f9e099c776e0f9c3afd2b978fc52d702bc8805c3b5bfecdbafcb",
|