@blamejs/exceptd-skills 0.13.41 → 0.13.43

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
@@ -372,7 +372,7 @@ This split costs every consumer the same translation work on every invocation. C
372
372
  exceptd collect secrets | exceptd run secrets --evidence -
373
373
  ```
374
374
 
375
- The collector library is small and grows as playbooks are touched. Ten reference collectors ship today (`lib/collectors/secrets.js`, `lib/collectors/kernel.js`, `lib/collectors/sbom.js`, `lib/collectors/containers.js`, `lib/collectors/library-author.js`, `lib/collectors/crypto-codebase.js`, `lib/collectors/cred-stores.js`, `lib/collectors/hardening.js`, `lib/collectors/runtime.js`, `lib/collectors/ai-api.js`); the rest are written when each playbook needs them. Until a playbook has a collector, the AI/operator owns evidence collection as before.
375
+ The collector library is small and grows as playbooks are touched. Twelve reference collectors ship today (`lib/collectors/secrets.js`, `lib/collectors/kernel.js`, `lib/collectors/sbom.js`, `lib/collectors/containers.js`, `lib/collectors/library-author.js`, `lib/collectors/crypto-codebase.js`, `lib/collectors/cred-stores.js`, `lib/collectors/hardening.js`, `lib/collectors/runtime.js`, `lib/collectors/ai-api.js`, `lib/collectors/mcp.js`, `lib/collectors/crypto.js`); the rest are written when each playbook needs them. Until a playbook has a collector, the AI/operator owns evidence collection as before.
376
376
 
377
377
  ### Precision target for new `look.artifacts[].source` strings
378
378
 
package/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.13.43 — 2026-05-21
4
+
5
+ Twelfth reference collector.
6
+
7
+ ### Features
8
+
9
+ - **`lib/collectors/crypto.js`** — host crypto-posture collector. Linux-only. Reads `openssl version -a` / `openssl list -kem-algorithms` / `openssl list -signature-algorithms` (execFile-shape spawning, never shell-interpolated), parses `/etc/ssh/sshd_config` + `sshd_config.d/*.conf` with Include expansion (same logic the hardening collector uses), and lists the `/etc/ssl/certs` trust-anchor count. Flips five deterministic indicators: `openssl-pre-3-5` (banner is `OpenSSL < 3.5.0` — pre-native-ML-KEM), `ml-kem-absent` (no `mlkem512` / `mlkem768` / `mlkem1024` in `openssl list -kem-algorithms`), `ml-dsa-slh-dsa-absent` (no `ML-DSA` / `SLH-DSA` / `SPHINCS+` / `Falcon` in `openssl list -signature-algorithms`), `sshd-no-pqc-kex` (`KexAlgorithms` line absent OR present-without `sntrup761x25519` / `mlkem768x25519` / `mlkem1024`), `weak-mac-or-cipher` (`MACs` containing `hmac-md5` / `hmac-sha1` without `-etm`, or `Ciphers` containing `arcfour` / `3des-cbc` / `des-cbc` / `blowfish-cbc`). When openssl exec fails AND `/etc/ssh/sshd_config` is unreadable, the indicators stay out of `signal_overrides` so the runner returns inconclusive — same unflipped-when-unreadable semantics as `hardening` / `runtime`. `tls-no-hybrid-group` (needs a live TLS handshake), `rsa-2048-cert-long-life` (cert content + chain walk; sensitivity-horizon comparison is operator review), and `no-crypto-inventory` (governance) remain AI-driven. Path overrides via `args.paths` for synthetic-tempdir tests.
10
+
11
+ ## 0.13.42 — 2026-05-20
12
+
13
+ Eleventh reference collector.
14
+
15
+ ### Features
16
+
17
+ - **`lib/collectors/mcp.js`** — inspects MCP client configurations across Cursor (`~/.cursor/mcp.json`), Claude Code (`~/.config/claude/config.json`, `~/.claude/settings.json`), Windsurf (`~/.codeium/windsurf/mcp_config.json`), VS Code Copilot (`~/.config/Code/User/settings.json`, macOS / Windows variants, project-level `.vscode/settings.json`), and Gemini CLI (`~/.gemini/settings.json`). Flips four deterministic indicators: `mcp-version-without-integrity` (any `mcpServers.<name>.command` / `args` containing `@scope/pkg@X.Y.Z` or `pkg==X.Y.Z` without an `integrity` / `sha256` / `sri` sibling), `copilot-yolo-mode-flag` (`chat.tools.autoApprove: true` at user-global / workspace scope OR any per-server `autoApprove: true|"all"` in `chat.mcp.servers`), `mcp-response-ansi-escape` (any 0x1B byte in MCP tool-response logs under `~/.claude/logs/mcp/*.jsonl`, `~/.cursor/logs/mcp*.{jsonl,log}`, `~/.codeium/windsurf/logs/mcp*`), `mcp-response-unicode-tag-smuggling` (any codepoint in U+E0000..U+E007F in the same log scope). Defers `unsigned-mcp-manifest` (sigstore lookup), `vulnerable-windsurf-version` (install detection), `mcp-server-running-as-root` (ps + capabilities), `mcp-server-invoked-from-ci-pipeline` (process-tree env-var correlation) — out of stdlib scope.
18
+
3
19
  ## 0.13.41 — 2026-05-20
4
20
 
5
21
  Tenth reference collector.
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "schema_version": "1.1.0",
3
- "generated_at": "2026-05-21T05:59:31.155Z",
3
+ "generated_at": "2026-05-21T13:39:43.700Z",
4
4
  "generator": "scripts/build-indexes.js",
5
5
  "source_count": 54,
6
6
  "source_hashes": {
7
- "manifest.json": "baf6b821336ba74ccafa21f09c510e7f581d3157a1aab19ed282f8d975ed8eec",
7
+ "manifest.json": "b56fa964197403bfaeba55f1ae97c663bf447852a6fc0e418965a8faf6eb3387",
8
8
  "data/atlas-ttps.json": "d296c1d3e71807c9279b731f047e57796e85137f186586743a8cdad214b408f9",
9
9
  "data/attack-techniques.json": "49b6010b317edd219def135171ea8f3b1bbf1e00e9c5a08bf7237215ff54e2c3",
10
10
  "data/cve-catalog.json": "a09c83af3f9679a7ea73935726a1ff9de2cab94b4ab6321fc017fc147747d7c3",
@@ -0,0 +1,270 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * lib/collectors/crypto.js
5
+ *
6
+ * Companion collector for the `crypto` playbook. Linux-only. Reads
7
+ * the host's TLS library state (openssl version + KEM / signature
8
+ * algorithm catalogues) and sshd_config (effective directives after
9
+ * Include expansion) to flip post-quantum-readiness indicators.
10
+ *
11
+ * Skipped indicators (require operator judgement or live behavioural
12
+ * data, left unflipped so the runner returns inconclusive rather
13
+ * than a forced miss):
14
+ *
15
+ * tls-no-hybrid-group needs a real TLS handshake against
16
+ * a target server
17
+ * rsa-2048-cert-long-life cert content + chain walk; sensitivity-
18
+ * horizon comparison is operator review
19
+ * no-crypto-inventory governance / process indicator
20
+ *
21
+ * Interface: see lib/collectors/README.md
22
+ */
23
+
24
+ const fs = require("node:fs");
25
+ const path = require("node:path");
26
+ const { spawnSync } = require("node:child_process");
27
+
28
+ const COLLECTOR_ID = "crypto";
29
+
30
+ function readFileSafe(p, max = 256 * 1024) {
31
+ try {
32
+ const st = fs.statSync(p);
33
+ if (st.size > max) return null;
34
+ return fs.readFileSync(p, "utf8");
35
+ } catch { return null; }
36
+ }
37
+
38
+ // Same sshd_config Include-expansion logic as the hardening
39
+ // collector: first-match-wins, inline drop-in files in lexical
40
+ // order at the textual position of the `Include` directive.
41
+ function expandSshdConfig(baseContent, configDPath) {
42
+ if (!baseContent) return "";
43
+ const out = [];
44
+ for (const raw of baseContent.split(/\r?\n/)) {
45
+ const stripped = raw.replace(/#.*$/, "").trim();
46
+ const m = stripped.match(/^Include\s+(\S+)/i);
47
+ if (!m) { out.push(raw); continue; }
48
+ const glob = m[1];
49
+ let dir = null;
50
+ if (glob.endsWith("/sshd_config.d/*.conf")) {
51
+ dir = configDPath;
52
+ } else {
53
+ const dirMatch = glob.match(/^(.*)\/\*\.conf$/);
54
+ if (dirMatch) dir = dirMatch[1];
55
+ }
56
+ if (!dir) { out.push(raw); continue; }
57
+ let entries;
58
+ try { entries = fs.readdirSync(dir).filter(e => /\.conf$/.test(e)).sort(); }
59
+ catch { out.push(raw); continue; }
60
+ for (const e of entries) {
61
+ const c = readFileSafe(path.join(dir, e));
62
+ if (c == null) continue;
63
+ out.push(`# === drop-in: ${e} ===`);
64
+ out.push(c);
65
+ }
66
+ }
67
+ return out.join("\n");
68
+ }
69
+
70
+ // First-match-wins parse of KexAlgorithms / MACs / Ciphers /
71
+ // PermitRootLogin from the effective sshd_config content.
72
+ function parseSshdEffective(content) {
73
+ if (content == null) return { kex: null, macs: null, ciphers: null };
74
+ const out = { kex: null, macs: null, ciphers: null };
75
+ for (const raw of content.split(/\r?\n/)) {
76
+ const line = raw.replace(/#.*$/, "").trim();
77
+ if (!line) continue;
78
+ const m1 = line.match(/^KexAlgorithms\s+(\S+)/i);
79
+ if (m1 && out.kex == null) out.kex = m1[1].toLowerCase();
80
+ const m2 = line.match(/^MACs\s+(\S+)/i);
81
+ if (m2 && out.macs == null) out.macs = m2[1].toLowerCase();
82
+ const m3 = line.match(/^Ciphers\s+(\S+)/i);
83
+ if (m3 && out.ciphers == null) out.ciphers = m3[1].toLowerCase();
84
+ }
85
+ return out;
86
+ }
87
+
88
+ // Compare OpenSSL banner string against the 3.5.0 native-ML-KEM cutoff.
89
+ // Returns "hit" (< 3.5.0), "miss" (>= 3.5.0), or undefined (banner
90
+ // could not be parsed — collector returns inconclusive).
91
+ function compareOpensslVersion(verStr) {
92
+ if (!verStr) return undefined;
93
+ const m = verStr.match(/OpenSSL\s+(\d+)\.(\d+)\.(\d+)/);
94
+ if (!m) return undefined;
95
+ const maj = Number(m[1]);
96
+ const min = Number(m[2]);
97
+ if (maj < 3) return "hit";
98
+ if (maj === 3 && min < 5) return "hit";
99
+ return "miss";
100
+ }
101
+
102
+ // PQC kex: hit when KexAlgorithms is absent (no PQC by default) OR
103
+ // present-without sntrup761x25519 / mlkem768x25519 / mlkem1024.
104
+ function parsePqcKex(content, hasSshdContent) {
105
+ if (!hasSshdContent) return undefined;
106
+ if (content == null) return "hit";
107
+ return /sntrup761x25519|mlkem768x25519|mlkem1024/.test(content) ? "miss" : "hit";
108
+ }
109
+
110
+ // Weak mac or cipher: hit when MACs contains hmac-md5 / hmac-sha1
111
+ // (without -etm suffix) OR Ciphers contains arcfour / 3des-cbc /
112
+ // des-cbc / blowfish-cbc / aes-cbc. The playbook treats every CBC
113
+ // mode as weak under modern SSH cipher policy (BEAST / padding
114
+ // oracle / chosen-plaintext considerations) — aes128-cbc /
115
+ // aes192-cbc / aes256-cbc still appear in legacy sshd configs and
116
+ // must be flagged. Both fields absent → undefined (inconclusive).
117
+ function parseWeakMacOrCipher(macs, ciphers) {
118
+ if (macs == null && ciphers == null) return undefined;
119
+ const macsWeak = macs && /(?:^|,)(?:hmac-md5(?!-etm)|hmac-sha1(?!-etm))(?:,|$)/.test(macs);
120
+ const cipherWeak = ciphers && /(?:^|,)(?:aes(?:128|192|256)-cbc|arcfour(?:128|256)?|3des-cbc|des-cbc|blowfish-cbc)(?:,|$)/.test(ciphers);
121
+ return (macsWeak || cipherWeak) ? "hit" : "miss";
122
+ }
123
+
124
+ // Either read a path-override fixture (synthetic-tempdir tests) or
125
+ // invoke the named binary via execFile-shape spawning. Never
126
+ // shell-interpolated. ENOENT / EACCES → null; caller surfaces that
127
+ // via collector_errors / unflipped indicators. Some binaries write
128
+ // their banner to stderr (e.g. `ssh -V`); others to stdout
129
+ // (`openssl version`). Prefer stdout, fall back to stderr when
130
+ // stdout is empty so the banner is not lost.
131
+ function readOrSpawn(pathOverride, cmd, args, errors) {
132
+ if (pathOverride != null) return readFileSafe(pathOverride);
133
+ const r = spawnSync(cmd, args, {
134
+ encoding: "utf8",
135
+ timeout: 5000,
136
+ stdio: ["ignore", "pipe", "pipe"],
137
+ });
138
+ if (r.error) {
139
+ if (errors) errors.push({ artifact_id: cmd, kind: "spawn_failed", reason: `${cmd}: ${r.error.code || r.error.message}` });
140
+ return null;
141
+ }
142
+ const out = r.stdout || "";
143
+ const err = r.stderr || "";
144
+ if (out.length === 0 && err.length === 0) return null;
145
+ return out.length > 0 ? out : err;
146
+ }
147
+
148
+ function collect({ cwd = process.cwd(), env = process.env, args = {} } = {}) {
149
+ const errors = [];
150
+ const startTime = Date.now();
151
+ const root = path.resolve(cwd);
152
+ const paths = args.paths || {};
153
+ const isLinux = args.forceLinux === true || process.platform === "linux";
154
+
155
+ if (!isLinux) {
156
+ return {
157
+ precondition_checks: { "linux-platform": false },
158
+ artifacts: {
159
+ "openssl-version": { value: "skipped — non-Linux platform", captured: false, reason: `process.platform=${process.platform} (linux required)` },
160
+ "sshd-config-effective": { value: "skipped — non-Linux platform", captured: false, reason: `process.platform=${process.platform} (linux required)` },
161
+ },
162
+ signal_overrides: {},
163
+ collector_meta: {
164
+ collector_id: COLLECTOR_ID,
165
+ collector_version: "2026-05-21",
166
+ platform: process.platform,
167
+ captured_at: new Date().toISOString(),
168
+ cwd: root,
169
+ duration_ms: Date.now() - startTime,
170
+ },
171
+ collector_errors: errors,
172
+ };
173
+ }
174
+
175
+ // TLS library version + algorithm catalogues.
176
+ const opensslVer = readOrSpawn(paths.opensslVersionOutput, "openssl", ["version", "-a"], errors);
177
+ const opensslKem = readOrSpawn(paths.opensslKemOutput, "openssl", ["list", "-kem-algorithms"], errors);
178
+ const opensslSig = readOrSpawn(paths.opensslSignatureOutput, "openssl", ["list", "-signature-algorithms"], errors);
179
+ const sshVer = readOrSpawn(paths.sshVersionOutput, "ssh", ["-V"], errors);
180
+
181
+ // sshd_config: read base + expand Include directives. The base
182
+ // can come from a path override (tests stage a synthetic file
183
+ // tree); otherwise read /etc/ssh/sshd_config directly.
184
+ const sshdConfigPath = paths.sshdConfig || "/etc/ssh/sshd_config";
185
+ const sshdConfigDPath = paths.sshdConfigD || "/etc/ssh/sshd_config.d";
186
+ const sshdBase = readFileSafe(sshdConfigPath);
187
+ const sshdEffective = sshdBase ? expandSshdConfig(sshdBase, sshdConfigDPath) : null;
188
+ const sshdParsed = parseSshdEffective(sshdEffective);
189
+
190
+ // Flip indicators only when the underlying source was readable.
191
+ // Unreadable openssl / sshd_config → indicator stays out of
192
+ // signal_overrides so the runner returns inconclusive rather than
193
+ // asserting a clean posture without evidence.
194
+ const signal_overrides = {};
195
+
196
+ const verSig = compareOpensslVersion(opensslVer);
197
+ if (verSig !== undefined) signal_overrides["openssl-pre-3-5"] = verSig;
198
+
199
+ if (opensslKem !== null) {
200
+ const hasMLKEM = /mlkem(?:512|768|1024)/i.test(opensslKem);
201
+ signal_overrides["ml-kem-absent"] = hasMLKEM ? "miss" : "hit";
202
+ }
203
+ if (opensslSig !== null) {
204
+ const hasPQCsig = /ml-?dsa|slh-?dsa|sphincs|falcon/i.test(opensslSig);
205
+ signal_overrides["ml-dsa-slh-dsa-absent"] = hasPQCsig ? "miss" : "hit";
206
+ }
207
+
208
+ const kexSig = parsePqcKex(sshdParsed.kex, sshdEffective !== null);
209
+ if (kexSig !== undefined) signal_overrides["sshd-no-pqc-kex"] = kexSig;
210
+
211
+ const weakSig = sshdEffective !== null
212
+ ? parseWeakMacOrCipher(sshdParsed.macs, sshdParsed.ciphers)
213
+ : undefined;
214
+ if (weakSig !== undefined) signal_overrides["weak-mac-or-cipher"] = weakSig;
215
+
216
+ // certificate-store: list count of *.pem / *.crt under the
217
+ // standard trust roots. Path overridable for tests.
218
+ let certStoreSummary;
219
+ const certStoreRoot = paths.certStore || "/etc/ssl/certs";
220
+ try {
221
+ const certEntries = fs.readdirSync(certStoreRoot).filter(e => /\.(pem|crt)$/i.test(e));
222
+ certStoreSummary = { value: `${certEntries.length} cert file(s) under ${certStoreRoot}`, captured: true };
223
+ } catch {
224
+ certStoreSummary = { value: `${certStoreRoot} unreadable`, captured: false, reason: "trust-anchor directory not readable" };
225
+ }
226
+
227
+ const artifacts = {
228
+ "openssl-version": opensslVer
229
+ ? { value: (opensslVer.split(/\r?\n/, 1)[0] || "").trim(), captured: true }
230
+ : { value: "openssl version banner unavailable", captured: false, reason: "openssl binary missing or non-readable" },
231
+ "openssl-kem-algorithms": opensslKem !== null
232
+ ? { value: opensslKem.slice(0, 2048), captured: true }
233
+ : { value: "openssl list -kem-algorithms unavailable", captured: false, reason: "openssl list exec failed or fixture absent" },
234
+ "openssl-signature-algorithms": opensslSig !== null
235
+ ? { value: opensslSig.slice(0, 2048), captured: true }
236
+ : { value: "openssl list -signature-algorithms unavailable", captured: false, reason: "openssl list exec failed or fixture absent" },
237
+ "openssl-providers": { value: "not captured by this collector — see openssl-version banner for provider line", captured: false, reason: "openssl list -providers depends on runtime config; deferred to operator evidence" },
238
+ "ssh-version": sshVer
239
+ ? { value: sshVer.trim().split(/\r?\n/, 1)[0], captured: true }
240
+ : { value: "ssh -V unavailable", captured: false, reason: "ssh binary missing or non-readable" },
241
+ "sshd-config-effective": sshdEffective !== null
242
+ ? {
243
+ value: [
244
+ `KexAlgorithms=${sshdParsed.kex ?? "(unset)"}`,
245
+ `MACs=${sshdParsed.macs ?? "(unset)"}`,
246
+ `Ciphers=${sshdParsed.ciphers ?? "(unset)"}`,
247
+ ].join("; "),
248
+ captured: true,
249
+ }
250
+ : { value: `${sshdConfigPath} unreadable or absent`, captured: false, reason: "no sshd_config — host may not run sshd" },
251
+ "certificate-store": certStoreSummary,
252
+ };
253
+
254
+ return {
255
+ precondition_checks: { "linux-platform": true },
256
+ artifacts,
257
+ signal_overrides,
258
+ collector_meta: {
259
+ collector_id: COLLECTOR_ID,
260
+ collector_version: "2026-05-21",
261
+ platform: process.platform,
262
+ captured_at: new Date().toISOString(),
263
+ cwd: root,
264
+ duration_ms: Date.now() - startTime,
265
+ },
266
+ collector_errors: errors,
267
+ };
268
+ }
269
+
270
+ module.exports = { playbook_id: COLLECTOR_ID, collect };
@@ -0,0 +1,312 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * lib/collectors/mcp.js
5
+ *
6
+ * Companion collector for the `mcp` playbook. Reads MCP client
7
+ * config files (Cursor, Claude Code, Windsurf, VS Code Copilot,
8
+ * Gemini CLI) and tool-response logs, flipping deterministic
9
+ * indicators related to:
10
+ * - mcp-version-without-integrity (pinned version without
11
+ * sha256 / sri-integrity sibling)
12
+ * - copilot-yolo-mode-flag (chat.tools.autoApprove true)
13
+ * - mcp-response-ansi-escape (0x1B in tool response)
14
+ * - mcp-response-unicode-tag-smuggling (U+E0000..U+E007F)
15
+ *
16
+ * Defers:
17
+ * - unsigned-mcp-manifest (needs npm/pip package directory walk
18
+ * + sigstore lookup; out of stdlib scope)
19
+ * - vulnerable-windsurf-version (needs Windsurf install detection)
20
+ * - mcp-server-running-as-root (needs ps + capabilities)
21
+ * - mcp-server-invoked-from-ci-pipeline (needs process-tree
22
+ * env-var inspection)
23
+ *
24
+ * Interface: see lib/collectors/README.md
25
+ */
26
+
27
+ const fs = require("node:fs");
28
+ const path = require("node:path");
29
+ const os = require("node:os");
30
+
31
+ const COLLECTOR_ID = "mcp";
32
+
33
+ function readSafe(full, max = 1024 * 1024) {
34
+ try {
35
+ const s = fs.statSync(full);
36
+ if (s.size > max) return null;
37
+ return fs.readFileSync(full, "utf8");
38
+ } catch { return null; }
39
+ }
40
+
41
+ function readJson(full) {
42
+ const c = readSafe(full);
43
+ if (c == null) return null;
44
+ try { return JSON.parse(c); } catch { return null; }
45
+ }
46
+
47
+ function fileExists(full) {
48
+ try { return fs.statSync(full).isFile(); } catch { return false; }
49
+ }
50
+
51
+ // Per-vendor config locations. We try each and capture which were
52
+ // present so the artifact field tells the operator what scope the
53
+ // collector actually exercised.
54
+ function vendorConfigPaths(home) {
55
+ return {
56
+ cursor: [path.join(home, ".cursor", "mcp.json")],
57
+ "claude-code": [
58
+ path.join(home, ".config", "claude", "config.json"),
59
+ path.join(home, ".claude", "settings.json"),
60
+ ],
61
+ windsurf: [path.join(home, ".codeium", "windsurf", "mcp_config.json")],
62
+ "vscode-copilot": [
63
+ // user-global locations
64
+ path.join(home, ".config", "Code", "User", "settings.json"),
65
+ path.join(home, "Library", "Application Support", "Code", "User", "settings.json"),
66
+ path.join(home, "AppData", "Roaming", "Code", "User", "settings.json"),
67
+ path.join(home, ".vscode", "settings.json"),
68
+ ],
69
+ gemini: [path.join(home, ".gemini", "settings.json")],
70
+ };
71
+ }
72
+
73
+ // Walk mcpServers entries across vendor config shapes. Returns
74
+ // [{ vendor, file, name, command, args }].
75
+ function collectMcpServers(configsByVendor) {
76
+ const out = [];
77
+ for (const [vendor, files] of Object.entries(configsByVendor)) {
78
+ for (const f of files) {
79
+ const j = readJson(f);
80
+ if (!j) continue;
81
+ // Cursor / Claude Code / Windsurf: { mcpServers: { <name>: {...} } }
82
+ // VS Code Copilot: { chat: { mcp: { servers: { <name>: {...} } } } }
83
+ // Gemini: { mcpServers: { <name>: {...} } }
84
+ const containers = [
85
+ j.mcpServers,
86
+ j["chat.mcp.servers"],
87
+ j?.chat?.mcp?.servers,
88
+ ].filter(c => c && typeof c === "object");
89
+ for (const c of containers) {
90
+ for (const [name, entry] of Object.entries(c)) {
91
+ if (!entry || typeof entry !== "object") continue;
92
+ out.push({
93
+ vendor, file: f, name,
94
+ command: entry.command,
95
+ args: Array.isArray(entry.args) ? entry.args : [],
96
+ integrity: entry.integrity || entry.sha256 || entry.sri || null,
97
+ });
98
+ }
99
+ }
100
+ }
101
+ }
102
+ return out;
103
+ }
104
+
105
+ // A pinned version is the @1.2.3 / @v1.2.3 / =1.2.3 trailer on a
106
+ // package spec in args[] or command. We look for shapes that
107
+ // downstream operators care about: `npx -y @vendor/pkg@1.2.3`,
108
+ // `uvx pkg==1.2.3`, `pip install pkg==1.2.3`, etc.
109
+ function isPinnedNoIntegrity(server) {
110
+ if (server.integrity) return false;
111
+ const tokens = [server.command, ...server.args].filter(Boolean);
112
+ for (const tok of tokens) {
113
+ if (typeof tok !== "string") continue;
114
+ // npm package@version (excluding scope name@version)
115
+ if (/@[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+@\d+\.\d+\.\d+\b/.test(tok)) return true;
116
+ if (/\bpip\s+install\b/.test(tok)) continue;
117
+ if (/==\d+\.\d+\.\d+/.test(tok)) return true;
118
+ }
119
+ return false;
120
+ }
121
+
122
+ // VS Code yolo-mode flag: chat.tools.autoApprove === true OR any
123
+ // per-tool autoApprove === true.
124
+ function hasYoloMode(j) {
125
+ if (!j || typeof j !== "object") return false;
126
+ if (j["chat.tools.autoApprove"] === true) return true;
127
+ if (j?.chat?.tools?.autoApprove === true) return true;
128
+ // Per-server autoApprove flags in chat.mcp.servers.<name>.autoApprove
129
+ const servers = j["chat.mcp.servers"] || j?.chat?.mcp?.servers;
130
+ if (servers && typeof servers === "object") {
131
+ for (const e of Object.values(servers)) {
132
+ if (e && (e.autoApprove === true || e.autoApprove === "all")) return true;
133
+ }
134
+ }
135
+ return false;
136
+ }
137
+
138
+ // ANSI escape: any 0x1B byte in tool response content.
139
+ function hasAnsiEscape(content) {
140
+ return content.indexOf("\x1b") !== -1;
141
+ }
142
+
143
+ // Unicode tag smuggling: codepoints in U+E0000..U+E007F. These are
144
+ // invisible in normal renders but carry instruction-coercion
145
+ // payloads through MCP responses.
146
+ function hasUnicodeTagSmuggling(content) {
147
+ for (let i = 0; i < content.length; i++) {
148
+ const code = content.codePointAt(i);
149
+ if (code >= 0xE0000 && code <= 0xE007F) return true;
150
+ // Surrogate pair — skip the low surrogate slot.
151
+ if (code > 0xFFFF) i++;
152
+ }
153
+ return false;
154
+ }
155
+
156
+ function findMcpLogs(home) {
157
+ // Candidate log directories per vendor. Caller scans each for
158
+ // *.jsonl / *.log files. Directories like ~/.claude/logs/mcp/ are
159
+ // already MCP-scoped by path; we accept every log file inside.
160
+ // Generic log directories (~/.cursor/logs/) are filtered to
161
+ // filenames containing "mcp".
162
+ const dirs = [
163
+ { path: path.join(home, ".claude", "logs", "mcp"), filterByName: false },
164
+ { path: path.join(home, ".cursor", "logs"), filterByName: true },
165
+ { path: path.join(home, ".codeium", "windsurf", "logs"), filterByName: true },
166
+ ];
167
+ const out = [];
168
+ for (const { path: d, filterByName } of dirs) {
169
+ let entries;
170
+ try { entries = fs.readdirSync(d, { withFileTypes: true }); } catch { continue; }
171
+ for (const e of entries) {
172
+ if (!e.isFile()) continue;
173
+ if (!/\.(jsonl|log)$/i.test(e.name)) continue;
174
+ if (filterByName && !/mcp/i.test(e.name)) continue;
175
+ out.push(path.join(d, e.name));
176
+ }
177
+ }
178
+ return out;
179
+ }
180
+
181
+ function collect({ cwd = process.cwd(), env = process.env, args = {} } = {}) {
182
+ const errors = [];
183
+ const startTime = Date.now();
184
+ const root = path.resolve(cwd);
185
+ const home = (env && env.HOME) || (env && env.USERPROFILE) || os.homedir();
186
+
187
+ // Vendor MCP configs.
188
+ const configsByVendor = vendorConfigPaths(home);
189
+ const servers = collectMcpServers(configsByVendor);
190
+ const pinnedNoIntegrity = servers.filter(isPinnedNoIntegrity);
191
+
192
+ // VS Code yolo-mode across the candidate VS Code settings files.
193
+ let yoloHit = false;
194
+ let yoloFile = null;
195
+ for (const f of configsByVendor["vscode-copilot"]) {
196
+ const j = readJson(f);
197
+ if (j && hasYoloMode(j)) { yoloHit = true; yoloFile = f; break; }
198
+ }
199
+ // Project-level .vscode/settings.json under cwd
200
+ if (!yoloHit) {
201
+ const projVsc = path.join(root, ".vscode", "settings.json");
202
+ const j = readJson(projVsc);
203
+ if (j && hasYoloMode(j)) { yoloHit = true; yoloFile = projVsc; }
204
+ }
205
+
206
+ // MCP response log scan for ANSI / unicode-tag smuggling.
207
+ const logFiles = findMcpLogs(home);
208
+ let ansiHit = false;
209
+ let unicodeTagHit = false;
210
+ let ansiSourceFile = null;
211
+ let unicodeTagSourceFile = null;
212
+ let logBytesScanned = 0;
213
+ for (const f of logFiles) {
214
+ const c = readSafe(f, 4 * 1024 * 1024);
215
+ if (c == null) continue;
216
+ logBytesScanned += c.length;
217
+ if (!ansiHit && hasAnsiEscape(c)) { ansiHit = true; ansiSourceFile = f; }
218
+ if (!unicodeTagHit && hasUnicodeTagSmuggling(c)) {
219
+ unicodeTagHit = true; unicodeTagSourceFile = f;
220
+ }
221
+ if (ansiHit && unicodeTagHit) break;
222
+ }
223
+ const logsScanned = logFiles.length > 0;
224
+
225
+ const signal_overrides = {
226
+ "mcp-version-without-integrity": pinnedNoIntegrity.length > 0 ? "hit" : "miss",
227
+ "copilot-yolo-mode-flag": yoloHit ? "hit" : "miss",
228
+ };
229
+ // ANSI / unicode-tag indicators: only emit when we actually
230
+ // scanned at least one log file. If no logs exist, leave the
231
+ // indicators unflipped (the operator may not have enabled MCP
232
+ // logging — the runner returns inconclusive).
233
+ if (logsScanned) {
234
+ signal_overrides["mcp-response-ansi-escape"] = ansiHit ? "hit" : "miss";
235
+ signal_overrides["mcp-response-unicode-tag-smuggling"] = unicodeTagHit ? "hit" : "miss";
236
+ }
237
+
238
+ const artifacts = {
239
+ "cursor-mcp-config": {
240
+ value: fileExists(configsByVendor.cursor[0])
241
+ ? `${configsByVendor.cursor[0]} present`
242
+ : "absent",
243
+ captured: true,
244
+ },
245
+ "claude-code-mcp-config": {
246
+ value: configsByVendor["claude-code"].filter(fileExists).join(", ") || "absent",
247
+ captured: true,
248
+ },
249
+ "windsurf-mcp-config": {
250
+ value: fileExists(configsByVendor.windsurf[0]) ? "present" : "absent",
251
+ captured: true,
252
+ },
253
+ "vscode-copilot-mcp-config": {
254
+ value: configsByVendor["vscode-copilot"].filter(fileExists).map(f => path.relative(home, f)).join(", ") || "absent",
255
+ captured: true,
256
+ },
257
+ "gemini-cli-mcp-config": {
258
+ value: fileExists(configsByVendor.gemini[0]) ? "present" : "absent",
259
+ captured: true,
260
+ },
261
+ "mcp-server-inventory": {
262
+ value: `${servers.length} server(s) across ${new Set(servers.map(s => s.vendor)).size} vendor(s); ${pinnedNoIntegrity.length} pinned without integrity`,
263
+ captured: true,
264
+ },
265
+ "vscode-copilot-yolo-mode": {
266
+ value: yoloHit ? `chat.tools.autoApprove flag set in ${path.relative(home, yoloFile || "")}` : "scanned VS Code settings; auto-approve flag not set",
267
+ captured: true,
268
+ },
269
+ "mcp-tool-response-log": {
270
+ value: logsScanned
271
+ ? `${logFiles.length} log file(s), ${logBytesScanned} byte(s) scanned; ansi_hit=${ansiHit} (${ansiSourceFile ? path.relative(home, ansiSourceFile) : ""}); unicode_tag_hit=${unicodeTagHit} (${unicodeTagSourceFile ? path.relative(home, unicodeTagSourceFile) : ""})`
272
+ : "no MCP log files found at the canonical paths",
273
+ captured: logsScanned,
274
+ reason: logsScanned ? undefined : "MCP client logging may be disabled; ansi-escape / unicode-tag indicators left unflipped (inconclusive)",
275
+ },
276
+ "mcp-process-list": {
277
+ value: "skipped — process-list capture deferred to operator/AI evidence",
278
+ captured: false,
279
+ reason: "mcp-server-running-as-root / mcp-server-invoked-from-ci-pipeline need ps + env-var inspection that's out of stdlib scope",
280
+ },
281
+ "mcp-manifest-signatures": {
282
+ value: "skipped — sigstore lookup deferred to operator/AI evidence",
283
+ captured: false,
284
+ reason: "unsigned-mcp-manifest needs package-directory + sigstore-rekor / in-toto attestation lookup that's out of stdlib scope",
285
+ },
286
+ };
287
+
288
+ return {
289
+ precondition_checks: {
290
+ "home-dir-readable": fs.existsSync(home),
291
+ },
292
+ artifacts,
293
+ signal_overrides,
294
+ collector_meta: {
295
+ collector_id: COLLECTOR_ID,
296
+ collector_version: "2026-05-20",
297
+ platform: process.platform,
298
+ captured_at: new Date().toISOString(),
299
+ cwd: root,
300
+ home,
301
+ duration_ms: Date.now() - startTime,
302
+ vendors_with_config: Object.entries(configsByVendor)
303
+ .filter(([_, files]) => files.some(fileExists))
304
+ .map(([v]) => v),
305
+ servers_found: servers.length,
306
+ logs_scanned: logFiles.length,
307
+ },
308
+ collector_errors: errors,
309
+ };
310
+ }
311
+
312
+ module.exports = { playbook_id: COLLECTOR_ID, collect };
package/manifest.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "exceptd-security",
3
- "version": "0.13.41",
3
+ "version": "0.13.43",
4
4
  "description": "AI security skills grounded in mid-2026 threat reality, not stale framework documentation",
5
5
  "homepage": "https://exceptd.com",
6
6
  "license": "Apache-2.0",
@@ -53,7 +53,7 @@
53
53
  ],
54
54
  "last_threat_review": "2026-05-01",
55
55
  "signature": "lXhZgoIrrVloO3XaTvo/43AxZn4mwErstd7DR0O/oVhD3AOGODM4HqrageYEou9WKOdMEGP5mJNTjJsXdP5NDA==",
56
- "signed_at": "2026-05-21T05:58:40.487Z",
56
+ "signed_at": "2026-05-21T13:36:49.117Z",
57
57
  "cwe_refs": [
58
58
  "CWE-125",
59
59
  "CWE-362",
@@ -117,7 +117,7 @@
117
117
  ],
118
118
  "last_threat_review": "2026-05-01",
119
119
  "signature": "vSVqu4wBm+d68ujZmM6Rto/HzViCkE0gPUcv/MYE/bjFiqamf/s0On4kTOo1KIveV9cOwYNxiItaGEWlVkRFDg==",
120
- "signed_at": "2026-05-21T05:58:40.488Z",
120
+ "signed_at": "2026-05-21T13:36:49.119Z",
121
121
  "cwe_refs": [
122
122
  "CWE-1039",
123
123
  "CWE-1426",
@@ -180,7 +180,7 @@
180
180
  ],
181
181
  "last_threat_review": "2026-05-01",
182
182
  "signature": "RIgXKvolQjgJdnlrDnVOd90IOY1B7VHHZD/YJQRzouL+wUeOLclPrdK/EgEuFyiu7lR4bi+Pl6aGB9G9tOxYCQ==",
183
- "signed_at": "2026-05-21T05:58:40.489Z",
183
+ "signed_at": "2026-05-21T13:36:49.119Z",
184
184
  "cwe_refs": [
185
185
  "CWE-22",
186
186
  "CWE-345",
@@ -226,7 +226,7 @@
226
226
  "framework_gaps": [],
227
227
  "last_threat_review": "2026-05-01",
228
228
  "signature": "RYOxeq/o3uTwTWq4H7RcdH2Aclg9UyCERfUH9Frwkzncsowg7LgxpaEDc3swTCv73HMEGbU8wVbXguZ4JxHUCQ==",
229
- "signed_at": "2026-05-21T05:58:40.489Z"
229
+ "signed_at": "2026-05-21T13:36:49.120Z"
230
230
  },
231
231
  {
232
232
  "name": "compliance-theater",
@@ -257,7 +257,7 @@
257
257
  ],
258
258
  "last_threat_review": "2026-05-01",
259
259
  "signature": "DneJCPKCPcoe6nQ82XptqSqNfSRdt1orKaO+o7K36YCciDrzwJb+1BuBLusPDtpcdDaGY0y0e+AqiTYJklhBAQ==",
260
- "signed_at": "2026-05-21T05:58:40.489Z"
260
+ "signed_at": "2026-05-21T13:36:49.120Z"
261
261
  },
262
262
  {
263
263
  "name": "exploit-scoring",
@@ -286,7 +286,7 @@
286
286
  ],
287
287
  "last_threat_review": "2026-05-01",
288
288
  "signature": "NA1hoQycvQhSUoG5rwlXX0mOVmGxoXRVezkELGEA2nZOdGis4gXkHT3O6Sfw7zxE4JuMrsCb65TEeOWk9WEPDg==",
289
- "signed_at": "2026-05-21T05:58:40.490Z"
289
+ "signed_at": "2026-05-21T13:36:49.121Z"
290
290
  },
291
291
  {
292
292
  "name": "rag-pipeline-security",
@@ -323,7 +323,7 @@
323
323
  ],
324
324
  "last_threat_review": "2026-05-01",
325
325
  "signature": "XgrzcA2brPhXrSTxrcLnJec0OpgGYJBoSTUlJ10UdePHffxqb9LTVGnfbmEk1ykQifXREZexui2bG7X/+eFfCQ==",
326
- "signed_at": "2026-05-21T05:58:40.490Z",
326
+ "signed_at": "2026-05-21T13:36:49.121Z",
327
327
  "cwe_refs": [
328
328
  "CWE-1395",
329
329
  "CWE-1426"
@@ -380,7 +380,7 @@
380
380
  ],
381
381
  "last_threat_review": "2026-05-01",
382
382
  "signature": "9+hZlZOqZdeACUmamQk66L5levZhhwnFXuYRhdT6Mce99eQaKT7wNfWq12hXQztkRcVRKaFH+a01zwJQwsRQCA==",
383
- "signed_at": "2026-05-21T05:58:40.491Z",
383
+ "signed_at": "2026-05-21T13:36:49.121Z",
384
384
  "d3fend_refs": [
385
385
  "D3-CA",
386
386
  "D3-CSPP",
@@ -415,7 +415,7 @@
415
415
  "framework_gaps": [],
416
416
  "last_threat_review": "2026-05-01",
417
417
  "signature": "ciqhVloMWWXEigPZvvwoV2c54tEqsDqsoc+sS/mNTFFJk2H+tz2+XUrgfEPRuYw0FeyNB6/+27pL2NpKHzUqAg==",
418
- "signed_at": "2026-05-21T05:58:40.491Z",
418
+ "signed_at": "2026-05-21T13:36:49.122Z",
419
419
  "cwe_refs": [
420
420
  "CWE-1188"
421
421
  ]
@@ -443,7 +443,7 @@
443
443
  "framework_gaps": [],
444
444
  "last_threat_review": "2026-05-01",
445
445
  "signature": "xiHAhhdufm9hCKU8PLiPE0MX65ej2F4OZwtlWLGLCiie9/km+Kiqbt192LcMvr94v83C98pb9wIaqFsFWft6AQ==",
446
- "signed_at": "2026-05-21T05:58:40.491Z"
446
+ "signed_at": "2026-05-21T13:36:49.122Z"
447
447
  },
448
448
  {
449
449
  "name": "global-grc",
@@ -475,7 +475,7 @@
475
475
  "framework_gaps": [],
476
476
  "last_threat_review": "2026-05-01",
477
477
  "signature": "oYsSk35N2Uzq7MRofACykylcVwkgPhI4luWZ14vmQT+gUKLyZiKVOUJbe1+7lGl6BYPRN0sUDQ0f7S5Eu5w2Ag==",
478
- "signed_at": "2026-05-21T05:58:40.492Z"
478
+ "signed_at": "2026-05-21T13:36:49.122Z"
479
479
  },
480
480
  {
481
481
  "name": "zeroday-gap-learn",
@@ -502,7 +502,7 @@
502
502
  "framework_gaps": [],
503
503
  "last_threat_review": "2026-05-01",
504
504
  "signature": "igRqYyU1unRFH40BsPyAR62SPrk8QZv8dPGb8S9O9EvLCNOZAzm3t+HdT/NKqzWHwrpomOzkkkyLfYI/0qTUDA==",
505
- "signed_at": "2026-05-21T05:58:40.492Z"
505
+ "signed_at": "2026-05-21T13:36:49.123Z"
506
506
  },
507
507
  {
508
508
  "name": "pqc-first",
@@ -554,7 +554,7 @@
554
554
  ],
555
555
  "last_threat_review": "2026-05-01",
556
556
  "signature": "vhc3wuQEro/86s1ro2b/KakUXg8QVnySYTBqA7ebzv9oeR2HYO5bvGEJp3oOHWtL37JDqcCAHYadSN/qxIyCCA==",
557
- "signed_at": "2026-05-21T05:58:40.492Z",
557
+ "signed_at": "2026-05-21T13:36:49.123Z",
558
558
  "cwe_refs": [
559
559
  "CWE-327"
560
560
  ],
@@ -601,7 +601,7 @@
601
601
  ],
602
602
  "last_threat_review": "2026-05-01",
603
603
  "signature": "MS35nWm8djfJGn4OOoT0JKJ2aO+Dkbb6wOOWJYvNZlRKT3UGA59o2gxg1JOnD20hb/RwxtkmCujhl2tuYSR+AQ==",
604
- "signed_at": "2026-05-21T05:58:40.493Z"
604
+ "signed_at": "2026-05-21T13:36:49.123Z"
605
605
  },
606
606
  {
607
607
  "name": "security-maturity-tiers",
@@ -638,7 +638,7 @@
638
638
  ],
639
639
  "last_threat_review": "2026-05-01",
640
640
  "signature": "8Px1s2lDj10/Q6erwEQlXgUHM1+OTruUR8qAHPX7Oo3k/l69N6P9sm0PsafS9wDFtj9l5C/OiLiFgzMlMt6vBw==",
641
- "signed_at": "2026-05-21T05:58:40.493Z",
641
+ "signed_at": "2026-05-21T13:36:49.124Z",
642
642
  "cwe_refs": [
643
643
  "CWE-1188"
644
644
  ]
@@ -673,7 +673,7 @@
673
673
  "framework_gaps": [],
674
674
  "last_threat_review": "2026-05-11",
675
675
  "signature": "WAu5fRirzSOcnnZsTx2d/JJZwa/LPpXCi+31qATTGLmoNuhyy81k3ooPe9kCM3E0CLMtvTePg9DagYqBninZDQ==",
676
- "signed_at": "2026-05-21T05:58:40.493Z"
676
+ "signed_at": "2026-05-21T13:36:49.124Z"
677
677
  },
678
678
  {
679
679
  "name": "attack-surface-pentest",
@@ -744,7 +744,7 @@
744
744
  "PTES revision incorporating AI-surface enumeration"
745
745
  ],
746
746
  "signature": "7eEwCXFd9pDKUw7yCUbRJSjfzozE44dwwwemCQUPm8JBPztLltibD9bL/RszSbYyCrYJmVb5Drncz2cGe62gCw==",
747
- "signed_at": "2026-05-21T05:58:40.493Z"
747
+ "signed_at": "2026-05-21T13:36:49.124Z"
748
748
  },
749
749
  {
750
750
  "name": "fuzz-testing-strategy",
@@ -804,7 +804,7 @@
804
804
  "OSS-Fuzz-Gen / AI-assisted harness generation becoming the default expectation for OSS maintainers"
805
805
  ],
806
806
  "signature": "Z7ypCUnXx8JpLtgxxB6RHNi39w74AmrGY1N4ofAGCXhkuM2EaFVm1AU0dvl9UQ1bVLfHKEDGqMO/TwlIY7RABg==",
807
- "signed_at": "2026-05-21T05:58:40.494Z"
807
+ "signed_at": "2026-05-21T13:36:49.125Z"
808
808
  },
809
809
  {
810
810
  "name": "dlp-gap-analysis",
@@ -879,7 +879,7 @@
879
879
  "Quebec Law 25, India DPDPA, KSA PDPL enforcement actions naming AI-tool prompt data as in-scope personal information"
880
880
  ],
881
881
  "signature": "fgxG344JGYBWWWwFXZ1IzGipWKP7EyBhrsvsbsb0CCGXfv/MvNHVNI6G0zQddCsWX1JeQbhZT3Vk8v1uJKDTDA==",
882
- "signed_at": "2026-05-21T05:58:40.494Z"
882
+ "signed_at": "2026-05-21T13:36:49.125Z"
883
883
  },
884
884
  {
885
885
  "name": "supply-chain-integrity",
@@ -956,7 +956,7 @@
956
956
  "OpenSSF model-signing — emerging Sigstore-based signing standard for ML model weights; track for production adoption"
957
957
  ],
958
958
  "signature": "pcLrM98A3vUSZRjwNAk0aZ9umvOwB41XCLLsCOy/IebB2F/06oIrGUKkMHtHwm4pTVPShMMcKdZQQ3jz30FnCg==",
959
- "signed_at": "2026-05-21T05:58:40.494Z"
959
+ "signed_at": "2026-05-21T13:36:49.125Z"
960
960
  },
961
961
  {
962
962
  "name": "defensive-countermeasure-mapping",
@@ -1013,7 +1013,7 @@
1013
1013
  ],
1014
1014
  "last_threat_review": "2026-05-11",
1015
1015
  "signature": "gqF8eU3VBrZhO2WnlcqKa7wm1d2mmWtvpbmx0kNCgHojNV+qEt+Ij84RO6bZvaUqhfYPWizWL79Fa4DL0curAQ==",
1016
- "signed_at": "2026-05-21T05:58:40.495Z"
1016
+ "signed_at": "2026-05-21T13:36:49.126Z"
1017
1017
  },
1018
1018
  {
1019
1019
  "name": "identity-assurance",
@@ -1080,7 +1080,7 @@
1080
1080
  "d3fend_refs": [],
1081
1081
  "last_threat_review": "2026-05-11",
1082
1082
  "signature": "Wv5hGMeHjlaQK1zwicVCA7AvdKgJBgvcjdpGM9Ywahh9tagAKhbkOjybowDQZzu7OZ3bDkbh6pBYc1Sdwr6NAA==",
1083
- "signed_at": "2026-05-21T05:58:40.495Z"
1083
+ "signed_at": "2026-05-21T13:36:49.126Z"
1084
1084
  },
1085
1085
  {
1086
1086
  "name": "ot-ics-security",
@@ -1136,7 +1136,7 @@
1136
1136
  "d3fend_refs": [],
1137
1137
  "last_threat_review": "2026-05-11",
1138
1138
  "signature": "8t5qKHd3yWi57dvG36YQkLN/X9bQWqtEiYjay4IfSmqhJpM/xXPaQVKNGz3wscrO8OLKUZ0OaX7Mj5kzpgBKBQ==",
1139
- "signed_at": "2026-05-21T05:58:40.495Z"
1139
+ "signed_at": "2026-05-21T13:36:49.126Z"
1140
1140
  },
1141
1141
  {
1142
1142
  "name": "coordinated-vuln-disclosure",
@@ -1188,7 +1188,7 @@
1188
1188
  "NYDFS 23 NYCRR 500 amendments potentially adding explicit CVD program requirements"
1189
1189
  ],
1190
1190
  "signature": "GDGt4UPqBa04PjlpSmpyihGzd3OgfBN7jaAK5tfwp+LRSs3ygKOdbeivUCCHNagTY1hE6hG2Ou40ADfBFuXeAg==",
1191
- "signed_at": "2026-05-21T05:58:40.496Z"
1191
+ "signed_at": "2026-05-21T13:36:49.127Z"
1192
1192
  },
1193
1193
  {
1194
1194
  "name": "threat-modeling-methodology",
@@ -1238,7 +1238,7 @@
1238
1238
  "PASTA v2 updates incorporating AI/ML application threats"
1239
1239
  ],
1240
1240
  "signature": "rFBpOQEJUPpl+v88Lw/WqVJRhTl80vy0VbPAbzQj3Q0suJRRrJg368I9uKu5LXIBKFDvKxnGIcIzbGg9NUtaCA==",
1241
- "signed_at": "2026-05-21T05:58:40.496Z"
1241
+ "signed_at": "2026-05-21T13:36:49.127Z"
1242
1242
  },
1243
1243
  {
1244
1244
  "name": "webapp-security",
@@ -1312,7 +1312,7 @@
1312
1312
  "d3fend_refs": [],
1313
1313
  "last_threat_review": "2026-05-11",
1314
1314
  "signature": "ux85YI4t2mVHOyt744Yin1HHy+z11JIFygjKfFfQOBBl5QVV3A267jeIy7utix85irMcpZm/T3yx/ooqiK2tBA==",
1315
- "signed_at": "2026-05-21T05:58:40.496Z"
1315
+ "signed_at": "2026-05-21T13:36:49.127Z"
1316
1316
  },
1317
1317
  {
1318
1318
  "name": "ai-risk-management",
@@ -1362,7 +1362,7 @@
1362
1362
  "d3fend_refs": [],
1363
1363
  "last_threat_review": "2026-05-11",
1364
1364
  "signature": "IIXnkZ5ZNqFwOto5KfytADTLLZLoyXNZACD1ORZ40P1HUAQxe6u2uyXFzzsfuob4Uy06jNkRGr2FFgCphUH1Cw==",
1365
- "signed_at": "2026-05-21T05:58:40.497Z"
1365
+ "signed_at": "2026-05-21T13:36:49.128Z"
1366
1366
  },
1367
1367
  {
1368
1368
  "name": "sector-healthcare",
@@ -1422,7 +1422,7 @@
1422
1422
  "d3fend_refs": [],
1423
1423
  "last_threat_review": "2026-05-11",
1424
1424
  "signature": "AhF9KF8ZBlDteciV+F8IBSmFVYCvQOn44GmD4rZjgLoPxfIv/QE1/vSkK32zyqDKtHWkLSXExbkkPkxA/V6dDw==",
1425
- "signed_at": "2026-05-21T05:58:40.497Z"
1425
+ "signed_at": "2026-05-21T13:36:49.128Z"
1426
1426
  },
1427
1427
  {
1428
1428
  "name": "sector-financial",
@@ -1503,7 +1503,7 @@
1503
1503
  "TIBER-EU framework v2.0 alignment with DORA TLPT RTS (JC 2024/40); cross-recognition with CBEST and iCAST"
1504
1504
  ],
1505
1505
  "signature": "HQgZvb4ReziEz5rNFr8i/O8/rJEZR+iHRROT7m/D2QUqhrcNISPkYXENsUZlG8xapzy/Ik92ehkseyj4hdmhCQ==",
1506
- "signed_at": "2026-05-21T05:58:40.498Z"
1506
+ "signed_at": "2026-05-21T13:36:49.129Z"
1507
1507
  },
1508
1508
  {
1509
1509
  "name": "sector-federal-government",
@@ -1572,7 +1572,7 @@
1572
1572
  "Australia PSPF 2024 revision and ISM quarterly updates — track for Essential Eight Maturity Level requirements for federal entities"
1573
1573
  ],
1574
1574
  "signature": "linxmsXZiOYtcs71sSWgGCrvb8xQfmxmtTY5PRvZJ0/8FgJulo0tQtejzexYG775s7XhjAmGsDP238BQTQ8ADA==",
1575
- "signed_at": "2026-05-21T05:58:40.498Z"
1575
+ "signed_at": "2026-05-21T13:36:49.129Z"
1576
1576
  },
1577
1577
  {
1578
1578
  "name": "sector-energy",
@@ -1637,7 +1637,7 @@
1637
1637
  "ICS-CERT advisory feed (https://www.cisa.gov/news-events/cybersecurity-advisories/ics-advisories) for vendor CVEs in Siemens, Rockwell, Schneider Electric, ABB, GE Vernova, Hitachi Energy, AVEVA / OSIsoft PI"
1638
1638
  ],
1639
1639
  "signature": "JjBfc0ovta560Clk0x3QGRM5osFJDwcvpy3rT7QEGdCIL827jzE8QCow1C8deXq+4JhY2sA/d7/8IsxikdlkCg==",
1640
- "signed_at": "2026-05-21T05:58:40.498Z"
1640
+ "signed_at": "2026-05-21T13:36:49.130Z"
1641
1641
  },
1642
1642
  {
1643
1643
  "name": "sector-telecom",
@@ -1723,7 +1723,7 @@
1723
1723
  "O-RAN SFG / WG11 security specifications"
1724
1724
  ],
1725
1725
  "signature": "JWVxKFoKrbX4d+Tko1d4OBdwyg25MfFFKn4CT6E/CzH+YwnU3T6Y76uBQIKg3+gIGTvPduqyvQwQQ5FxKDuPBw==",
1726
- "signed_at": "2026-05-21T05:58:40.498Z"
1726
+ "signed_at": "2026-05-21T13:36:49.130Z"
1727
1727
  },
1728
1728
  {
1729
1729
  "name": "api-security",
@@ -1792,7 +1792,7 @@
1792
1792
  "d3fend_refs": [],
1793
1793
  "last_threat_review": "2026-05-11",
1794
1794
  "signature": "BmCRCestWqr55+fCynEhtAl5NWLT+xLTkpwS0Icp3SaoZOw/ce3Y6TtqjHRSKn4CBJq7YDiLRWxmhO3MStvOAA==",
1795
- "signed_at": "2026-05-21T05:58:40.499Z"
1795
+ "signed_at": "2026-05-21T13:36:49.130Z"
1796
1796
  },
1797
1797
  {
1798
1798
  "name": "cloud-security",
@@ -1873,7 +1873,7 @@
1873
1873
  "CISA KEV additions for cloud-control-plane CVEs (IMDSv1 abuses, federation token mishandling, cross-tenant boundary failures); CISA Cybersecurity Advisories for cross-cloud advisories"
1874
1874
  ],
1875
1875
  "signature": "/DV3pmZwrRySrk1OCbyI+0BQESacjupJfUX3eC2NGtXuYOBro0vndIP+z27heFxumnjU3a9sfla7/U9X+pqnDw==",
1876
- "signed_at": "2026-05-21T05:58:40.499Z"
1876
+ "signed_at": "2026-05-21T13:36:49.131Z"
1877
1877
  },
1878
1878
  {
1879
1879
  "name": "container-runtime-security",
@@ -1935,7 +1935,7 @@
1935
1935
  "d3fend_refs": [],
1936
1936
  "last_threat_review": "2026-05-11",
1937
1937
  "signature": "E2UGSf9ATyYgzBr8uM/0ubOUmDqo1jVA7f9mVxv6LHfWGCNuQNXDyuNou9VAmUCeeXEeUYIi3AFjXkJqpOkxDA==",
1938
- "signed_at": "2026-05-21T05:58:40.499Z"
1938
+ "signed_at": "2026-05-21T13:36:49.131Z"
1939
1939
  },
1940
1940
  {
1941
1941
  "name": "mlops-security",
@@ -2006,7 +2006,7 @@
2006
2006
  "MITRE ATLAS v5.6.0 (released February 2026) shipped the AML.T0010 sub-technique expansion this forecast tracked plus new techniques (\"Publish Poisoned AI Agent Tool\", \"Escape to Host\"); inventory now 16 tactics, 84 techniques, 56 sub-techniques. Forward watch: ATLAS v5.5 / v6.0 — track next-cadence updates to agentic-AI TTPs and MLOps-pipeline-specific techniques"
2007
2007
  ],
2008
2008
  "signature": "BGNE6ZQWBA1LmsUFe8tU0L67iGDSrFqiuqaZD2f1KqfcyqqzQfMs9PWNHFzxxaJmXeKlm87eU8lgELF0bX+RBA==",
2009
- "signed_at": "2026-05-21T05:58:40.500Z"
2009
+ "signed_at": "2026-05-21T13:36:49.131Z"
2010
2010
  },
2011
2011
  {
2012
2012
  "name": "incident-response-playbook",
@@ -2068,7 +2068,7 @@
2068
2068
  "NYDFS 23 NYCRR 500.17 amendments tightening ransom-payment 24h disclosure operationalization"
2069
2069
  ],
2070
2070
  "signature": "FkZQerh3VHVJAwIcCktDyMRh5KE2+Em/i0ek8zEz7JG/PXtQx8ujHWTh3VjZbOLhPNtdB2qxgXOIAYIofaVOAQ==",
2071
- "signed_at": "2026-05-21T05:58:40.500Z"
2071
+ "signed_at": "2026-05-21T13:36:49.132Z"
2072
2072
  },
2073
2073
  {
2074
2074
  "name": "ransomware-response",
@@ -2148,7 +2148,7 @@
2148
2148
  ],
2149
2149
  "last_threat_review": "2026-05-15",
2150
2150
  "signature": "n3UToNuN3A1HgLvcuqmIx8vrZY71+r/79waK92jG+rSX4uYOzkmxMUpROrE5K9bDwMezNBHdjWv8Uul6zugyDQ==",
2151
- "signed_at": "2026-05-21T05:58:40.500Z"
2151
+ "signed_at": "2026-05-21T13:36:49.132Z"
2152
2152
  },
2153
2153
  {
2154
2154
  "name": "email-security-anti-phishing",
@@ -2201,7 +2201,7 @@
2201
2201
  "d3fend_refs": [],
2202
2202
  "last_threat_review": "2026-05-11",
2203
2203
  "signature": "rK+WnuS+9tqEABmwc0jO/PEmxcLjG1/tmUb897HsClQeKzf+TQOlwBE+OsbtuKxpjYNwur62Xxs3TxObkwm8Cw==",
2204
- "signed_at": "2026-05-21T05:58:40.501Z"
2204
+ "signed_at": "2026-05-21T13:36:49.132Z"
2205
2205
  },
2206
2206
  {
2207
2207
  "name": "age-gates-child-safety",
@@ -2269,7 +2269,7 @@
2269
2269
  "US state adult-site age-verification laws — 19+ states by mid-2026 (TX HB 18 upheld by SCOTUS June 2025 in Free Speech Coalition v. Paxton); track ongoing challenges in remaining states"
2270
2270
  ],
2271
2271
  "signature": "+OO0RhQ303RJV7kaH38IuZpLeQbapep6Ds4Re/WEZu0FHBwKSlwvF7jbtP7KQ57xldJYn/xZm2jaszyOacMfDg==",
2272
- "signed_at": "2026-05-21T05:58:40.501Z"
2272
+ "signed_at": "2026-05-21T13:36:49.133Z"
2273
2273
  },
2274
2274
  {
2275
2275
  "name": "cloud-iam-incident",
@@ -2349,7 +2349,7 @@
2349
2349
  ],
2350
2350
  "last_threat_review": "2026-05-15",
2351
2351
  "signature": "e/kij7GtKaytROyIj7V5RH+FC9WtmVFzrmG2kIlNDNn29ep/CRNlIQKwXLpzo/81AIf634pmdr1qy/+vwIuUDA==",
2352
- "signed_at": "2026-05-21T05:58:40.501Z"
2352
+ "signed_at": "2026-05-21T13:36:49.133Z"
2353
2353
  },
2354
2354
  {
2355
2355
  "name": "idp-incident-response",
@@ -2430,11 +2430,11 @@
2430
2430
  ],
2431
2431
  "last_threat_review": "2026-05-15",
2432
2432
  "signature": "ew9Kglc9fAZzbn0ZIfGP7WSK/j4eV2VhSvpy+s5bEfNEVYIMa2kZjnGBapgUsyGDLes9H9K2ovjQyX17+GKiBw==",
2433
- "signed_at": "2026-05-21T05:58:40.502Z"
2433
+ "signed_at": "2026-05-21T13:36:49.133Z"
2434
2434
  }
2435
2435
  ],
2436
2436
  "manifest_signature": {
2437
2437
  "algorithm": "Ed25519",
2438
- "signature_base64": "hR1NLS5mN1goInEizRpPOjO5xkg3wB4zsmciSo3/UIgmS10Qa0WJNTWX52o032ZLC4Dxn4snJ60zlx1vKuvhBQ=="
2438
+ "signature_base64": "fBH0Bn3i2Zd9wSqpmXxFtFMNP/LLblr6K7GqjP5S9M7bObYHCPe9n/+kkPaKFmD8JeFRe5aupw2v42LEY/jQAg=="
2439
2439
  }
2440
2440
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blamejs/exceptd-skills",
3
- "version": "0.13.41",
3
+ "version": "0.13.43",
4
4
  "description": "AI security skills grounded in mid-2026 threat reality, not stale framework documentation. 42 skills, 10 catalogs (312 CVEs / 171 CWEs / 805 ATT&CK + ICS / 170 ATLAS / 468 D3FEND / 7476 RFCs), 34 jurisdictions, 10-class catalog gap detector + budget gate, real XML parser + canonical-form diff + content-pattern regression detection, Ed25519-signed.",
5
5
  "keywords": [
6
6
  "ai-security",
package/sbom.cdx.json CHANGED
@@ -1,22 +1,22 @@
1
1
  {
2
2
  "bomFormat": "CycloneDX",
3
3
  "specVersion": "1.6",
4
- "serialNumber": "urn:uuid:4fd52afb-0580-4176-b3f6-4f7c125d7e1e",
4
+ "serialNumber": "urn:uuid:84eca508-82ac-48fd-a76a-8da0f63e8cd0",
5
5
  "version": 1,
6
6
  "metadata": {
7
- "timestamp": "2068-06-10T23:17:15.000Z",
7
+ "timestamp": "2096-09-01T08:34:16.000Z",
8
8
  "tools": [
9
9
  {
10
10
  "vendor": "blamejs",
11
11
  "name": "scripts/refresh-sbom.js",
12
- "version": "0.13.41"
12
+ "version": "0.13.43"
13
13
  }
14
14
  ],
15
15
  "component": {
16
- "bom-ref": "pkg:npm/@blamejs/exceptd-skills@0.13.41",
16
+ "bom-ref": "pkg:npm/@blamejs/exceptd-skills@0.13.43",
17
17
  "type": "application",
18
18
  "name": "@blamejs/exceptd-skills",
19
- "version": "0.13.41",
19
+ "version": "0.13.43",
20
20
  "description": "AI security skills grounded in mid-2026 threat reality, not stale framework documentation. 42 skills, 10 catalogs (312 CVEs / 171 CWEs / 805 ATT&CK + ICS / 170 ATLAS / 468 D3FEND / 7476 RFCs), 34 jurisdictions, 10-class catalog gap detector + budget gate, real XML parser + canonical-form diff + content-pattern regression detection, Ed25519-signed.",
21
21
  "licenses": [
22
22
  {
@@ -25,17 +25,17 @@
25
25
  }
26
26
  }
27
27
  ],
28
- "purl": "pkg:npm/%40blamejs/exceptd-skills@0.13.41",
28
+ "purl": "pkg:npm/%40blamejs/exceptd-skills@0.13.43",
29
29
  "hashes": [
30
30
  {
31
31
  "alg": "SHA-256",
32
- "content": "2170da0695dce4c96c204240cfce3133f147737709260c42e828be6778099cf9"
32
+ "content": "ccd1718b7ea2dde128a031519a09bdea00d381b3a47b615f6e48f743ba584d79"
33
33
  }
34
34
  ],
35
35
  "externalReferences": [
36
36
  {
37
37
  "type": "distribution",
38
- "url": "https://www.npmjs.com/package/@blamejs/exceptd-skills/v/0.13.41"
38
+ "url": "https://www.npmjs.com/package/@blamejs/exceptd-skills/v/0.13.43"
39
39
  },
40
40
  {
41
41
  "type": "vcs",
@@ -86,11 +86,11 @@
86
86
  "hashes": [
87
87
  {
88
88
  "alg": "SHA-256",
89
- "content": "f11900411cb9b38926cd06bd0fc3a5f9a976992e7fd10fc256430c86a01c2ff4"
89
+ "content": "746ea19ed2019d0b1b838f4a8f2a58ee2b23c51869d52ff28ef5cfad65352d27"
90
90
  },
91
91
  {
92
92
  "alg": "SHA3-512",
93
- "content": "7aacca7daeb4c211da11f0bc01844d39601fe2e6d4104653b9ef6ff6f0646261e4d9e1681c789154ed3ca454abf3bc8f255c9bfc741ef2498b2898f144fc5b4a"
93
+ "content": "c21665a68dd2c69befeec2abef4e884a1337304c46ac1e9e838428cfbccfb5fc80bde52654ad9b6ccb309af97d86903f0c8a17eb8770038b3af6552b66dd4923"
94
94
  }
95
95
  ]
96
96
  },
@@ -116,11 +116,11 @@
116
116
  "hashes": [
117
117
  {
118
118
  "alg": "SHA-256",
119
- "content": "25679cb7c885f4c94779172be8505d09229eb9a09b279d95c75b710bc30b8e02"
119
+ "content": "1e83723db6d7acb8b6b331125e0beeb40632bde827672734274b504feca480c2"
120
120
  },
121
121
  {
122
122
  "alg": "SHA3-512",
123
- "content": "7aebbb9fb903f3ec6892214943ce47b894f31800e6cfadcd16b748faa687e71146812c2181003aa9f437cd7b5f80943574319ec974d8a686232bbdeb76bb45ee"
123
+ "content": "3ccd524f1dbf0d26089d7b9475a15168a387eb76deafb25dc5b93ceec27a30addd30cf59046f1069a712ff07cd817a223de2b356e070f1625a5c357db0a9ed0b"
124
124
  }
125
125
  ]
126
126
  },
@@ -934,6 +934,21 @@
934
934
  }
935
935
  ]
936
936
  },
937
+ {
938
+ "bom-ref": "file:lib/collectors/crypto.js",
939
+ "type": "file",
940
+ "name": "lib/collectors/crypto.js",
941
+ "hashes": [
942
+ {
943
+ "alg": "SHA-256",
944
+ "content": "0ffbe135ebd17e6c462b9a569a6ece0472c1ccd3f5ee1385bd6583fd5f65f102"
945
+ },
946
+ {
947
+ "alg": "SHA3-512",
948
+ "content": "df63bb0ea76f83fe77f1c6ab559bab5948f0e4bf003192760a3bfe39bbf3764f36fcfe9a22ca029011bcc0d8684530f4133cd942ac942facf1c947b8c5b6cf93"
949
+ }
950
+ ]
951
+ },
937
952
  {
938
953
  "bom-ref": "file:lib/collectors/hardening.js",
939
954
  "type": "file",
@@ -979,6 +994,21 @@
979
994
  }
980
995
  ]
981
996
  },
997
+ {
998
+ "bom-ref": "file:lib/collectors/mcp.js",
999
+ "type": "file",
1000
+ "name": "lib/collectors/mcp.js",
1001
+ "hashes": [
1002
+ {
1003
+ "alg": "SHA-256",
1004
+ "content": "1b6f614c62b528e159eb325f2e4326749d89f96add26c2133a63fb3e7746382b"
1005
+ },
1006
+ {
1007
+ "alg": "SHA3-512",
1008
+ "content": "78816fe42de6d9773b91dfd68abba8c8681f099a8a12e300a3f1b0b4e63f8f43cf4c0260e9404df61bbe18c7ae4960ff61e5dc17be61e065ac576a9605b06413"
1009
+ }
1010
+ ]
1011
+ },
982
1012
  {
983
1013
  "bom-ref": "file:lib/collectors/runtime.js",
984
1014
  "type": "file",
@@ -1616,11 +1646,11 @@
1616
1646
  "hashes": [
1617
1647
  {
1618
1648
  "alg": "SHA-256",
1619
- "content": "baf6b821336ba74ccafa21f09c510e7f581d3157a1aab19ed282f8d975ed8eec"
1649
+ "content": "b56fa964197403bfaeba55f1ae97c663bf447852a6fc0e418965a8faf6eb3387"
1620
1650
  },
1621
1651
  {
1622
1652
  "alg": "SHA3-512",
1623
- "content": "e0a13900f9bd00255a766fa39d8ae79952c6c4c9cea0798bdacfe943f2b52350e5b0f32b261a850b54d67d2fbe50ed3e0608fd4669f95253bc93da7e1518c895"
1653
+ "content": "7bb61363bcfa3d898bef149c9707ac69c4728b4ac2f826c7f21382519b2c9c268633e8e69c5a062f1ef228d8094471d87981043143cc4f1181acc399de6f2992"
1624
1654
  }
1625
1655
  ]
1626
1656
  },