ship-safe 6.0.0 → 6.1.0

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.
@@ -0,0 +1,547 @@
1
+ /**
2
+ * Agent Config Scanner
3
+ * =====================
4
+ *
5
+ * Detects security vulnerabilities in AI agent configuration and
6
+ * instruction files — the new control plane for AI-powered development.
7
+ *
8
+ * OpenClaw had 7 CVEs in 60 days (ClawJacked, CVE-2026-25253).
9
+ * ClawHavoc campaign injected 1,184 malicious skills into ClawHub.
10
+ * Check Point disclosed RCE via malicious Claude Code hooks.
11
+ * OWASP Agentic Top 10 (ASI01–ASI10) treats agent configs as code.
12
+ *
13
+ * Scans: .cursorrules, CLAUDE.md, AGENTS.md, .windsurfrules,
14
+ * copilot-instructions.md, OpenClaw configs, Claude Code hooks,
15
+ * agent memory files. Detects prompt injection, data exfiltration,
16
+ * encoded payloads, excessive permissions, unsafe OpenClaw settings.
17
+ *
18
+ * Maps to: OWASP Agentic AI ASI01 (Goal Hijacking), ASI02 (Tool Misuse),
19
+ * ASI03 (Privilege Abuse), ASI04 (Supply Chain)
20
+ */
21
+
22
+ import fs from 'fs';
23
+ import path from 'path';
24
+ import os from 'os';
25
+ import fg from 'fast-glob';
26
+ import { BaseAgent, createFinding } from './base-agent.js';
27
+
28
+ // =============================================================================
29
+ // TARGET FILES
30
+ // =============================================================================
31
+
32
+ const AGENT_RULES_FILES = [
33
+ '.cursorrules',
34
+ '.windsurfrules',
35
+ 'CLAUDE.md',
36
+ 'AGENTS.md',
37
+ '.github/copilot-instructions.md',
38
+ '.aider.conf.yml',
39
+ '.continue/config.json',
40
+ ];
41
+
42
+ const AGENT_RULES_GLOBS = [
43
+ '.cursor/rules/*.mdc',
44
+ '.claude/commands/*.md',
45
+ ];
46
+
47
+ const OPENCLAW_FILES = [
48
+ 'openclaw.json',
49
+ 'openclaw.config.json',
50
+ 'clawhub.json',
51
+ ];
52
+
53
+ const OPENCLAW_GLOBS = [
54
+ '.openclaw/**/*.json',
55
+ ];
56
+
57
+ const MEMORY_GLOBS = [
58
+ '.claude/memory/**',
59
+ '.cursor/memory/**',
60
+ '.continue/memory/**',
61
+ ];
62
+
63
+ // =============================================================================
64
+ // PATTERNS — Prompt Injection & Malicious Instructions in Agent Config Files
65
+ // =============================================================================
66
+
67
+ const PATTERNS = [
68
+ // ── Prompt Override Injection (ASI01) ────────────────────────────────────
69
+ {
70
+ rule: 'AGENT_CFG_PROMPT_OVERRIDE',
71
+ title: 'Agent Config: Prompt Injection — Override Instructions',
72
+ regex: /(?:ignore\s+(?:all\s+)?(?:previous|prior|above)\s+instructions|disregard\s+(?:all\s+)?(?:above|prior|previous)|you\s+are\s+now\s+(?:a|an)\s|new\s+(?:instructions|role|persona)\s*:|override\s+(?:system|previous|all)\s+(?:instructions|prompt|rules)|forget\s+(?:everything|all\s+(?:previous|prior)))/gi,
73
+ severity: 'critical',
74
+ cwe: 'CWE-74',
75
+ owasp: 'ASI01',
76
+ description: 'Agent config file contains prompt injection phrasing that attempts to override system instructions. This is a goal hijacking attack.',
77
+ fix: 'Remove the injected instruction. If this file is from a third party or untrusted source, treat the entire file as compromised.',
78
+ },
79
+ {
80
+ rule: 'AGENT_CFG_ROLE_HIJACK',
81
+ title: 'Agent Config: Role/Identity Hijacking',
82
+ regex: /(?:act\s+as\s+(?:a\s+)?(?:hacker|attacker|malicious|evil|unfiltered)|pretend\s+(?:you\s+are|to\s+be)\s+(?:a\s+)?(?:different|new|unrestricted)|your\s+(?:new|real|true|actual)\s+(?:role|purpose|goal|identity)\s+is)/gi,
83
+ severity: 'critical',
84
+ cwe: 'CWE-74',
85
+ owasp: 'ASI01',
86
+ description: 'Agent config attempts to hijack the AI agent\'s role or identity. Attacker can repurpose the agent for malicious actions.',
87
+ fix: 'Remove the role hijacking instruction. Review the file\'s origin and git blame for the change.',
88
+ },
89
+ {
90
+ rule: 'AGENT_CFG_HIDDEN_INSTRUCTION',
91
+ title: 'Agent Config: Hidden Instructions in Comments',
92
+ regex: /<!--[\s\S]{0,500}?(?:ignore|override|execute|fetch|curl|wget|send\s+to|exfiltrate|upload)[\s\S]{0,500}?-->/gi,
93
+ severity: 'critical',
94
+ cwe: 'CWE-74',
95
+ owasp: 'ASI01',
96
+ description: 'Malicious instructions hidden inside HTML comments in agent config file. These are invisible to casual review but processed by some AI agents.',
97
+ fix: 'Remove the HTML comment containing suspicious instructions. Audit all comments in agent config files.',
98
+ },
99
+
100
+ // ── Data Exfiltration (ASI02) ───────────────────────────────────────────
101
+ {
102
+ rule: 'AGENT_CFG_EXFIL_URL',
103
+ title: 'Agent Config: Data Exfiltration Instructions',
104
+ regex: /(?:send\s+(?:all\s+)?(?:data|code|content|files|source|secrets|tokens|keys)\s+to|POST\s+(?:all\s+)?(?:data|code|content)\s+to|exfiltrate\s+to|upload\s+(?:all\s+)?(?:data|code|files)\s+to|forward\s+(?:all\s+)?(?:data|code|output)\s+to)\s+https?:\/\//gi,
105
+ severity: 'critical',
106
+ cwe: 'CWE-200',
107
+ owasp: 'ASI02',
108
+ description: 'Agent config instructs the AI to send data to an external URL. This is a data exfiltration attack.',
109
+ fix: 'Remove the exfiltration instruction immediately. Investigate who added it and what data may have been exposed.',
110
+ },
111
+ {
112
+ rule: 'AGENT_CFG_WEBHOOK',
113
+ title: 'Agent Config: Known Exfiltration Service Domain',
114
+ regex: /(?:webhook\.site|requestbin\.com|hookbin\.com|pipedream\.net|ngrok\.io|ngrok\.app|burpcollaborator\.net|interact\.sh|oastify\.com|canarytokens\.com)/gi,
115
+ severity: 'critical',
116
+ cwe: 'CWE-200',
117
+ owasp: 'ASI02',
118
+ description: 'Agent config references a known data interception/exfiltration service. These are commonly used in prompt injection attacks to steal data.',
119
+ fix: 'Remove the reference to the exfiltration service. These domains have no legitimate use in agent configuration files.',
120
+ },
121
+ {
122
+ rule: 'AGENT_CFG_CURL_FETCH',
123
+ title: 'Agent Config: Outbound HTTP Request Instructions',
124
+ regex: /(?:(?:curl|wget|fetch|http\.get|requests\.get|requests\.post|axios\.(?:get|post))\s+https?:\/\/(?!(?:localhost|127\.0\.0\.1|::1)))/gi,
125
+ severity: 'high',
126
+ cwe: 'CWE-918',
127
+ owasp: 'ASI02',
128
+ confidence: 'medium',
129
+ description: 'Agent config instructs outbound HTTP requests to an external host. Could be used for data exfiltration or command-and-control.',
130
+ fix: 'Verify this URL is legitimate and necessary. Agent config files should not contain outbound request instructions.',
131
+ },
132
+
133
+ // ── Code Execution Injection (ASI02) ────────────────────────────────────
134
+ {
135
+ rule: 'AGENT_CFG_SHELL_EXEC',
136
+ title: 'Agent Config: Shell Command Execution',
137
+ regex: /(?:run\s+(?:this\s+)?(?:command|shell|bash|terminal)|execute\s+(?:this\s+)?(?:command|script|code|shell)|(?:^|\s)eval\s*\(|(?:^|\s)exec\s*\(|(?:^|\s)system\s*\(|subprocess\.(?:run|call|Popen))/gim,
138
+ severity: 'high',
139
+ cwe: 'CWE-78',
140
+ owasp: 'ASI02',
141
+ confidence: 'medium',
142
+ description: 'Agent config contains instructions to execute shell commands. If injected, this enables remote code execution via the AI agent.',
143
+ fix: 'Remove command execution instructions from agent config. If needed, use explicit allowlisted commands in a dedicated hook system.',
144
+ },
145
+ {
146
+ rule: 'AGENT_CFG_DOWNLOAD_EXEC',
147
+ title: 'Agent Config: Download-and-Execute Pattern',
148
+ regex: /(?:download\s+and\s+(?:run|execute)|curl.*\|\s*(?:bash|sh|zsh|node|python)|wget.*\|\s*(?:bash|sh|zsh|node|python)|pipe\s+to\s+(?:bash|sh|interpreter))/gi,
149
+ severity: 'critical',
150
+ cwe: 'CWE-78',
151
+ owasp: 'ASI02',
152
+ description: 'Agent config contains a download-and-execute pattern (e.g., curl | bash). This is a classic remote code execution vector.',
153
+ fix: 'Remove the download-and-execute instruction. Never pipe untrusted content to an interpreter.',
154
+ },
155
+
156
+ // ── Encoded / Obfuscated Payloads ───────────────────────────────────────
157
+ {
158
+ rule: 'AGENT_CFG_UNICODE_TAGS',
159
+ title: 'Agent Config: Invisible Unicode Tag Characters',
160
+ regex: /[\u{E0001}-\u{E007F}]/gu,
161
+ severity: 'critical',
162
+ cwe: 'CWE-116',
163
+ owasp: 'ASI01',
164
+ description: 'Agent config contains Unicode Tag characters (U+E0001–U+E007F) used for invisible prompt injection. These are invisible to humans but processed by LLMs.',
165
+ fix: 'Strip all Unicode Tag characters. This is almost certainly a prompt injection attack.',
166
+ },
167
+ {
168
+ rule: 'AGENT_CFG_ZERO_WIDTH',
169
+ title: 'Agent Config: Zero-Width Character Cluster',
170
+ regex: /[\u200B\u200C\u200D\uFEFF\u2060]{4,}/g,
171
+ severity: 'high',
172
+ cwe: 'CWE-116',
173
+ owasp: 'ASI01',
174
+ description: 'Agent config contains a cluster of zero-width characters that may hide encoded instructions.',
175
+ fix: 'Remove zero-width character clusters. Inspect the content for hidden payloads.',
176
+ },
177
+
178
+ // ── Excessive Permissions (ASI03) ───────────────────────────────────────
179
+ {
180
+ rule: 'AGENT_CFG_ALLOW_ALL',
181
+ title: 'Agent Config: Overly Permissive Instructions',
182
+ regex: /(?:allow\s+all\s+(?:tools|commands|actions|operations|requests)|no\s+(?:restrictions|limits|boundaries|safeguards)\s+(?:on|for|when)|bypass\s+(?:all\s+)?(?:security|safety|confirmation|approval)|auto[_-]?approve\s+(?:all|everything|any)|skip\s+(?:all\s+)?(?:confirmation|approval|verification|validation))/gi,
183
+ severity: 'high',
184
+ cwe: 'CWE-269',
185
+ owasp: 'ASI03',
186
+ description: 'Agent config grants overly broad permissions or disables safety checks. A prompt injection attack inherits these elevated privileges.',
187
+ fix: 'Apply principle of least privilege. Remove blanket permission grants and configure specific, scoped allowlists.',
188
+ },
189
+ {
190
+ rule: 'AGENT_CFG_DISABLE_SAFETY',
191
+ title: 'Agent Config: Safety Mechanism Disabled',
192
+ regex: /(?:disable\s+(?:all\s+)?(?:safety|security|guardrails|filters|protections)|turn\s+off\s+(?:all\s+)?(?:safety|security|protection|filtering)|remove\s+(?:all\s+)?(?:restrictions|limits|guards|safeguards|filters))/gi,
193
+ severity: 'high',
194
+ cwe: 'CWE-269',
195
+ owasp: 'ASI03',
196
+ description: 'Agent config explicitly disables safety mechanisms. This removes guardrails that protect against prompt injection and misuse.',
197
+ fix: 'Re-enable safety mechanisms. If specific overrides are needed, configure them granularly rather than disabling all protections.',
198
+ },
199
+ ];
200
+
201
+ // =============================================================================
202
+ // AGENT CONFIG SCANNER
203
+ // =============================================================================
204
+
205
+ export class AgentConfigScanner extends BaseAgent {
206
+ constructor() {
207
+ super(
208
+ 'AgentConfigScanner',
209
+ 'Detect security risks in AI agent config files — prompt injection in .cursorrules/CLAUDE.md, malicious hooks, OpenClaw misconfigs, encoded payloads',
210
+ 'llm'
211
+ );
212
+ }
213
+
214
+ async analyze(context) {
215
+ const { rootPath } = context;
216
+ let findings = [];
217
+
218
+ // ── 1. Discover all agent config files ─────────────────────────────────
219
+ const discovered = await this._findAgentConfigFiles(rootPath);
220
+
221
+ // ── 2. Scan rules/instruction files with prompt injection patterns ─────
222
+ for (const file of discovered.rulesFiles) {
223
+ findings = findings.concat(this.scanFileWithPatterns(file, PATTERNS));
224
+ findings = findings.concat(this._checkEncodedPayloads(file));
225
+ }
226
+
227
+ // ── 3. Scan OpenClaw configs (structural + patterns) ───────────────────
228
+ for (const file of discovered.openclawFiles) {
229
+ findings = findings.concat(this.scanFileWithPatterns(file, PATTERNS));
230
+ findings = findings.concat(this._scanOpenClawConfig(file));
231
+ }
232
+
233
+ // ── 4. Scan Claude Code hooks ──────────────────────────────────────────
234
+ for (const file of discovered.claudeSettingsFiles) {
235
+ findings = findings.concat(this._scanClaudeHooks(file));
236
+ }
237
+
238
+ // ── 5. Scan agent memory directories for poisoning ─────────────────────
239
+ for (const file of discovered.memoryFiles) {
240
+ findings = findings.concat(this.scanFileWithPatterns(file, PATTERNS));
241
+ }
242
+
243
+ return findings;
244
+ }
245
+
246
+ // ===========================================================================
247
+ // FILE DISCOVERY
248
+ // ===========================================================================
249
+
250
+ async _findAgentConfigFiles(rootPath) {
251
+ const rulesFiles = [];
252
+ const openclawFiles = [];
253
+ const claudeSettingsFiles = [];
254
+ const memoryFiles = [];
255
+
256
+ // ── Static rules files ──────────────────────────────────────────────────
257
+ for (const rel of AGENT_RULES_FILES) {
258
+ const full = path.join(rootPath, rel);
259
+ if (fs.existsSync(full)) rulesFiles.push(full);
260
+ }
261
+
262
+ // ── Glob-based rules files ──────────────────────────────────────────────
263
+ try {
264
+ const globbed = await fg(AGENT_RULES_GLOBS, {
265
+ cwd: rootPath, absolute: true, dot: true,
266
+ });
267
+ rulesFiles.push(...globbed);
268
+ } catch { /* skip */ }
269
+
270
+ // ── OpenClaw files ──────────────────────────────────────────────────────
271
+ for (const rel of OPENCLAW_FILES) {
272
+ const full = path.join(rootPath, rel);
273
+ if (fs.existsSync(full)) openclawFiles.push(full);
274
+ }
275
+ try {
276
+ const globbed = await fg(OPENCLAW_GLOBS, {
277
+ cwd: rootPath, absolute: true, dot: true,
278
+ });
279
+ openclawFiles.push(...globbed);
280
+ } catch { /* skip */ }
281
+
282
+ // ── Claude settings (project + shadow) ──────────────────────────────────
283
+ const projectClaudeSettings = path.join(rootPath, '.claude', 'settings.json');
284
+ if (fs.existsSync(projectClaudeSettings)) claudeSettingsFiles.push(projectClaudeSettings);
285
+
286
+ const home = os.homedir();
287
+ const shadowClaudeSettings = path.join(home, '.claude', 'settings.json');
288
+ if (fs.existsSync(shadowClaudeSettings) && shadowClaudeSettings !== projectClaudeSettings) {
289
+ claudeSettingsFiles.push(shadowClaudeSettings);
290
+ }
291
+
292
+ // ── Memory files ────────────────────────────────────────────────────────
293
+ try {
294
+ const globbed = await fg(MEMORY_GLOBS, {
295
+ cwd: rootPath, absolute: true, dot: true,
296
+ });
297
+ memoryFiles.push(...globbed);
298
+ } catch { /* skip */ }
299
+
300
+ return { rulesFiles, openclawFiles, claudeSettingsFiles, memoryFiles };
301
+ }
302
+
303
+ // ===========================================================================
304
+ // STRUCTURAL CHECKS
305
+ // ===========================================================================
306
+
307
+ /**
308
+ * Scan OpenClaw configuration files for unsafe settings.
309
+ * Covers CVE-2026-25253 (0.0.0.0 binding), missing auth, untrusted skills.
310
+ */
311
+ _scanOpenClawConfig(filePath) {
312
+ const content = this.readFile(filePath);
313
+ if (!content) return [];
314
+ const findings = [];
315
+
316
+ let config;
317
+ try { config = JSON.parse(content); } catch { return []; }
318
+
319
+ // ── Public bind (CVE-2026-25253) ──────────────────────────────────────
320
+ const host = config.host || config.bind || config.gateway?.host || config.gateway?.bind || '';
321
+ if (host === '0.0.0.0') {
322
+ findings.push({
323
+ file: filePath, line: 1, column: 0,
324
+ severity: 'critical',
325
+ category: this.category,
326
+ rule: 'OPENCLAW_PUBLIC_BIND',
327
+ title: 'OpenClaw: Gateway Bound to 0.0.0.0 (Public)',
328
+ description: 'OpenClaw gateway is bound to all network interfaces (0.0.0.0), exposing the agent to the public internet. This is the CVE-2026-25253 pattern — the default that exposed 135,000+ instances.',
329
+ matched: `host: "${host}"`,
330
+ confidence: 'high',
331
+ cwe: 'CWE-668',
332
+ owasp: 'A05:2021',
333
+ fix: 'Bind to 127.0.0.1 (localhost) unless you explicitly need remote access with proper authentication.',
334
+ });
335
+ }
336
+
337
+ // ── No authentication ─────────────────────────────────────────────────
338
+ const hasAuth = config.auth || config.authentication || config.apiKey ||
339
+ config.gateway?.auth || config.gateway?.apiKey || config.gateway?.password;
340
+ if (!hasAuth) {
341
+ findings.push({
342
+ file: filePath, line: 1, column: 0,
343
+ severity: 'critical',
344
+ category: this.category,
345
+ rule: 'OPENCLAW_NO_AUTH',
346
+ title: 'OpenClaw: No Authentication Configured',
347
+ description: 'OpenClaw gateway has no authentication. Any client can connect and control the agent — execute commands, read files, send messages.',
348
+ matched: 'No auth/apiKey/password field found',
349
+ confidence: 'high',
350
+ cwe: 'CWE-306',
351
+ owasp: 'A07:2021',
352
+ fix: 'Configure authentication: set an API key, password, or OAuth. Never run OpenClaw unauthenticated.',
353
+ });
354
+ }
355
+
356
+ // ── No TLS (ws:// instead of wss://) ──────────────────────────────────
357
+ const url = config.url || config.gateway?.url || config.websocket || '';
358
+ if (/^ws:\/\/(?!localhost|127\.0\.0\.1|::1)/i.test(url)) {
359
+ findings.push({
360
+ file: filePath, line: 1, column: 0,
361
+ severity: 'high',
362
+ category: this.category,
363
+ rule: 'OPENCLAW_NO_TLS',
364
+ title: 'OpenClaw: WebSocket Without TLS',
365
+ description: 'OpenClaw uses ws:// (unencrypted WebSocket) for a non-localhost connection. Agent commands and data are sent in plaintext.',
366
+ matched: url,
367
+ confidence: 'high',
368
+ cwe: 'CWE-319',
369
+ owasp: 'A02:2021',
370
+ fix: 'Use wss:// (WebSocket Secure) for all non-localhost connections.',
371
+ });
372
+ }
373
+
374
+ // ── safeBins disabled ─────────────────────────────────────────────────
375
+ if (config.safeBins === false || (config.safeBins && Array.isArray(config.safeBins) && config.safeBins.length === 0)) {
376
+ findings.push({
377
+ file: filePath, line: 1, column: 0,
378
+ severity: 'high',
379
+ category: this.category,
380
+ rule: 'OPENCLAW_SAFEBINS_BYPASS',
381
+ title: 'OpenClaw: safeBins Protection Disabled',
382
+ description: 'OpenClaw safeBins is disabled or empty. The agent can execute any binary on the system without restriction (CVE-2026-28363 pattern).',
383
+ matched: `safeBins: ${JSON.stringify(config.safeBins)}`,
384
+ confidence: 'high',
385
+ cwe: 'CWE-269',
386
+ owasp: 'ASI03',
387
+ fix: 'Enable safeBins with an explicit allowlist of permitted binaries.',
388
+ });
389
+ }
390
+
391
+ // ── Skills with shell/exec capabilities ───────────────────────────────
392
+ const skills = config.skills || config.agentSkills || [];
393
+ if (Array.isArray(skills)) {
394
+ for (const skill of skills) {
395
+ const name = typeof skill === 'string' ? skill : (skill.name || skill.id || '');
396
+ const caps = typeof skill === 'object' ? JSON.stringify(skill) : '';
397
+ if (/(?:shell|exec|command|terminal|bash|subprocess|system)/i.test(caps)) {
398
+ findings.push({
399
+ file: filePath, line: 1, column: 0,
400
+ severity: 'high',
401
+ category: this.category,
402
+ rule: 'OPENCLAW_SKILL_SHELL',
403
+ title: `OpenClaw: Skill "${name}" Has Shell Access`,
404
+ description: `OpenClaw skill "${name}" has shell/command execution capabilities. Prompt injection can achieve RCE through this skill.`,
405
+ matched: name,
406
+ confidence: 'medium',
407
+ cwe: 'CWE-78',
408
+ owasp: 'ASI02',
409
+ fix: 'Remove shell execution skills unless absolutely necessary. Use scoped, validated alternatives.',
410
+ });
411
+ }
412
+ }
413
+ }
414
+
415
+ return findings;
416
+ }
417
+
418
+ /**
419
+ * Scan .claude/settings.json for malicious hooks.
420
+ * Based on Check Point Research disclosure: hooks in settings.json
421
+ * execute arbitrary commands when developers open a project.
422
+ */
423
+ _scanClaudeHooks(filePath) {
424
+ const content = this.readFile(filePath);
425
+ if (!content) return [];
426
+ const findings = [];
427
+
428
+ let config;
429
+ try { config = JSON.parse(content); } catch { return []; }
430
+
431
+ const hooks = config.hooks || {};
432
+ const hookEntries = Object.entries(hooks);
433
+
434
+ for (const [event, hookList] of hookEntries) {
435
+ const items = Array.isArray(hookList) ? hookList : [hookList];
436
+ for (const hook of items) {
437
+ const cmd = typeof hook === 'string' ? hook : (hook.command || hook.cmd || hook.run || '');
438
+ if (!cmd) continue;
439
+
440
+ // ── Shell command execution ─────────────────────────────────────
441
+ if (/(?:bash\s+-c|sh\s+-c|cmd\s+\/c|powershell\s+-|pwsh\s+-)/i.test(cmd)) {
442
+ findings.push({
443
+ file: filePath, line: 1, column: 0,
444
+ severity: 'critical',
445
+ category: this.category,
446
+ rule: 'CLAUDE_HOOK_SHELL_CMD',
447
+ title: `Claude Hook: Shell Execution on "${event}"`,
448
+ description: `Claude Code hook on "${event}" event executes a shell command. A malicious .claude/settings.json in a repo can achieve RCE when a developer opens the project.`,
449
+ matched: cmd.substring(0, 200),
450
+ confidence: 'high',
451
+ cwe: 'CWE-94',
452
+ owasp: 'ASI04',
453
+ fix: 'Remove the shell execution hook. If automation is needed, use Claude Code\'s built-in hook system with explicit user approval.',
454
+ });
455
+ }
456
+
457
+ // ── Download from external URL ──────────────────────────────────
458
+ if (/(?:curl|wget|fetch|http\.get|Invoke-WebRequest)\s+https?:\/\/(?!localhost|127\.0\.0\.1)/i.test(cmd)) {
459
+ findings.push({
460
+ file: filePath, line: 1, column: 0,
461
+ severity: 'critical',
462
+ category: this.category,
463
+ rule: 'CLAUDE_HOOK_DOWNLOAD',
464
+ title: `Claude Hook: External Download on "${event}"`,
465
+ description: `Claude Code hook on "${event}" event downloads content from an external URL. This could fetch and execute malicious payloads.`,
466
+ matched: cmd.substring(0, 200),
467
+ confidence: 'high',
468
+ cwe: 'CWE-494',
469
+ owasp: 'ASI04',
470
+ fix: 'Remove the download hook. Agent hooks should not fetch content from external URLs.',
471
+ });
472
+ }
473
+
474
+ // ── Pipe to interpreter ─────────────────────────────────────────
475
+ if (/\|\s*(?:bash|sh|zsh|node|python|ruby|perl|php)/i.test(cmd)) {
476
+ findings.push({
477
+ file: filePath, line: 1, column: 0,
478
+ severity: 'critical',
479
+ category: this.category,
480
+ rule: 'CLAUDE_HOOK_PIPE_EXEC',
481
+ title: `Claude Hook: Pipe-to-Interpreter on "${event}"`,
482
+ description: `Claude Code hook on "${event}" pipes output to an interpreter (bash, node, python). Classic download-and-execute RCE vector.`,
483
+ matched: cmd.substring(0, 200),
484
+ confidence: 'high',
485
+ cwe: 'CWE-78',
486
+ owasp: 'ASI04',
487
+ fix: 'Remove the pipe-to-interpreter pattern. Never pipe untrusted content to a language interpreter.',
488
+ });
489
+ }
490
+ }
491
+ }
492
+
493
+ return findings;
494
+ }
495
+
496
+ /**
497
+ * Check for encoded/obfuscated payloads in agent config files.
498
+ * Detects large base64 blocks that may hide prompt injection.
499
+ */
500
+ _checkEncodedPayloads(filePath) {
501
+ const content = this.readFile(filePath);
502
+ if (!content) return [];
503
+ const findings = [];
504
+
505
+ // Check for large base64 blocks (60+ chars, not in code/URLs)
506
+ const lines = content.split('\n');
507
+ for (let i = 0; i < lines.length; i++) {
508
+ if (this.isSuppressed(lines[i])) continue;
509
+
510
+ const b64Match = lines[i].match(/(?<![a-zA-Z0-9+/=])([A-Za-z0-9+/]{60,}={0,2})(?![a-zA-Z0-9+/=])/);
511
+ if (b64Match) {
512
+ // Try to decode and check for injection
513
+ let decoded = '';
514
+ let suspicious = false;
515
+ try {
516
+ decoded = Buffer.from(b64Match[1], 'base64').toString('utf-8');
517
+ // Check if decoded content contains readable injection text
518
+ suspicious = /(?:ignore|override|execute|fetch|curl|send\s+to|system\(|eval\()/i.test(decoded) &&
519
+ /[a-zA-Z\s]{10,}/.test(decoded); // at least some readable text
520
+ } catch { /* not valid base64 */ }
521
+
522
+ const severity = suspicious ? 'critical' : 'high';
523
+ const desc = suspicious
524
+ ? `Large base64-encoded block that decodes to suspicious content: "${decoded.substring(0, 80)}..."`
525
+ : 'Large base64-encoded block in agent config file. May hide obfuscated instructions.';
526
+
527
+ findings.push({
528
+ file: filePath, line: i + 1, column: 0,
529
+ severity,
530
+ category: this.category,
531
+ rule: 'AGENT_CFG_BASE64_BLOCK',
532
+ title: 'Agent Config: Base64-Encoded Payload',
533
+ description: desc,
534
+ matched: b64Match[1].substring(0, 80) + (b64Match[1].length > 80 ? '...' : ''),
535
+ confidence: suspicious ? 'high' : 'medium',
536
+ cwe: 'CWE-116',
537
+ owasp: 'ASI01',
538
+ fix: 'Decode and inspect the base64 content. Remove if it contains hidden instructions.',
539
+ });
540
+ }
541
+ }
542
+
543
+ return findings;
544
+ }
545
+ }
546
+
547
+ export default AgentConfigScanner;
@@ -16,6 +16,7 @@
16
16
 
17
17
  import fs from 'fs';
18
18
  import path from 'path';
19
+ import { getComplianceSummary } from '../utils/compliance-map.js';
19
20
 
20
21
  export class HTMLReporter {
21
22
  /**
@@ -120,6 +121,26 @@ small{color:#64748b}
120
121
  <tbody>${findingRows || '<tr><td colspan="5" style="text-align:center;color:#22c55e">No findings — clean!</td></tr>'}</tbody>
121
122
  </table>
122
123
 
124
+ <h2>Compliance Mapping</h2>
125
+ ${(() => {
126
+ const compliance = getComplianceSummary(findings);
127
+ const s = compliance.summary;
128
+ return `<div class="stats">
129
+ <div class="stat"><div class="stat-number" style="color:#38bdf8">${s.soc2Controls}</div><div class="stat-label">SOC 2 Controls</div></div>
130
+ <div class="stat"><div class="stat-number" style="color:#38bdf8">${s.iso27001Controls}</div><div class="stat-label">ISO 27001 Controls</div></div>
131
+ <div class="stat"><div class="stat-number" style="color:#38bdf8">${s.nistAiRmfControls}</div><div class="stat-label">NIST AI RMF Controls</div></div>
132
+ <div class="stat"><div class="stat-number" style="color:#94a3b8">${s.totalFindings}</div><div class="stat-label">Mapped Findings</div></div>
133
+ </div>
134
+ <table>
135
+ <thead><tr><th>Framework</th><th>Controls Impacted</th><th>Details</th></tr></thead>
136
+ <tbody>
137
+ <tr><td>SOC 2 Type II</td><td>${s.soc2Controls}</td><td>${Object.entries(compliance.soc2).map(([k,v]) => k + ' (' + v + ')').join(', ') || 'None'}</td></tr>
138
+ <tr><td>ISO 27001:2022</td><td>${s.iso27001Controls}</td><td>${Object.entries(compliance.iso27001).map(([k,v]) => k + ' (' + v + ')').join(', ') || 'None'}</td></tr>
139
+ <tr><td>NIST AI RMF</td><td>${s.nistAiRmfControls}</td><td>${Object.entries(compliance.nistAiRmf).map(([k,v]) => k + ' (' + v + ')').join(', ') || 'None'}</td></tr>
140
+ </tbody>
141
+ </table>`;
142
+ })()}
143
+
123
144
  ${recon ? `<h2>Attack Surface</h2>
124
145
  <table>
125
146
  <tbody>
@@ -25,6 +25,8 @@ export { RAGSecurityAgent } from './rag-security-agent.js';
25
25
  export { PIIComplianceAgent } from './pii-compliance-agent.js';
26
26
  export { VibeCodingAgent } from './vibe-coding-agent.js';
27
27
  export { ExceptionHandlerAgent } from './exception-handler-agent.js';
28
+ export { AgentConfigScanner } from './agent-config-scanner.js';
29
+ export { ABOMGenerator } from './abom-generator.js';
28
30
  export { VerifierAgent } from './verifier-agent.js';
29
31
  export { DeepAnalyzer } from './deep-analyzer.js';
30
32
  export { ScoringEngine, GRADES, CATEGORIES } from './scoring-engine.js';
@@ -33,7 +35,7 @@ export { PolicyEngine } from './policy-engine.js';
33
35
  export { HTMLReporter } from './html-reporter.js';
34
36
 
35
37
  /**
36
- * Create a fully configured orchestrator with all 15 scanning agents.
38
+ * Create a fully configured orchestrator with all 16 scanning agents.
37
39
  * (VerifierAgent and DeepAnalyzer run as post-processors, not in the agent pool.)
38
40
  */
39
41
  import { Orchestrator as OrchestratorClass } from './orchestrator.js';
@@ -54,6 +56,7 @@ import { RAGSecurityAgent as RAGSecurityAgentClass } from './rag-security-agent.
54
56
  import { PIIComplianceAgent as PIIComplianceAgentClass } from './pii-compliance-agent.js';
55
57
  import { VibeCodingAgent as VibeCodingAgentClass } from './vibe-coding-agent.js';
56
58
  import { ExceptionHandlerAgent as ExceptionHandlerAgentClass } from './exception-handler-agent.js';
59
+ import { AgentConfigScanner as AgentConfigScannerClass } from './agent-config-scanner.js';
57
60
 
58
61
  export function buildOrchestrator() {
59
62
  const orchestrator = new OrchestratorClass();
@@ -75,6 +78,7 @@ export function buildOrchestrator() {
75
78
  new PIIComplianceAgentClass(),
76
79
  new VibeCodingAgentClass(),
77
80
  new ExceptionHandlerAgentClass(),
81
+ new AgentConfigScannerClass(),
78
82
  ]);
79
83
  return orchestrator;
80
84
  }
@@ -11,6 +11,7 @@
11
11
 
12
12
  import fs from 'fs';
13
13
  import path from 'path';
14
+ import { getComplianceSummary } from '../utils/compliance-map.js';
14
15
 
15
16
  // =============================================================================
16
17
  // SCORING CONFIGURATION
@@ -47,6 +48,7 @@ const FALLBACK_CATEGORY_MAP = {
47
48
  'rag': 'llm',
48
49
  'vibe': 'injection', // Vibe coding findings → Code Vulnerabilities
49
50
  'exception': 'injection', // OWASP A10:2025 — Mishandling of Exceptional Conditions
51
+ 'agent-config': 'llm', // Agent config security → AI/LLM category
50
52
  'recon': null, // skip recon findings
51
53
  };
52
54
 
@@ -136,12 +138,21 @@ export class ScoringEngine {
136
138
  const score = Math.max(0, 100 - totalDeduction);
137
139
  const grade = GRADES.find(g => score >= g.min);
138
140
 
141
+ // ── Compliance mapping ─────────────────────────────────────────────────
142
+ let compliance;
143
+ try {
144
+ compliance = getComplianceSummary(findings);
145
+ } catch {
146
+ compliance = null;
147
+ }
148
+
139
149
  return {
140
150
  score,
141
151
  grade,
142
152
  categories: categoryResults,
143
153
  totalFindings: findings.length,
144
154
  totalDepVulns: depVulns.length,
155
+ compliance,
145
156
  };
146
157
  }
147
158