praxis-sec 1.0.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.
Files changed (183) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +170 -0
  3. package/ai-defense/cost-protection.md +292 -0
  4. package/ai-defense/llm-security-checklist.md +324 -0
  5. package/ai-defense/prompt-injection-patterns.js +283 -0
  6. package/ai-defense/system-prompt-armor.md +327 -0
  7. package/checklists/launch-day.md +168 -0
  8. package/cli/agents/abom-generator.js +225 -0
  9. package/cli/agents/agent-attestation-agent.js +318 -0
  10. package/cli/agents/agent-config-scanner.js +787 -0
  11. package/cli/agents/agent-telemetry-agent.js +415 -0
  12. package/cli/agents/agentic-security-agent.js +296 -0
  13. package/cli/agents/agentic-supply-chain-agent.js +463 -0
  14. package/cli/agents/ai-infra-inventory-agent.js +449 -0
  15. package/cli/agents/api-fuzzer.js +345 -0
  16. package/cli/agents/auth-bypass-agent.js +348 -0
  17. package/cli/agents/base-agent.js +280 -0
  18. package/cli/agents/cicd-scanner.js +300 -0
  19. package/cli/agents/config-auditor.js +757 -0
  20. package/cli/agents/deep-analyzer.js +776 -0
  21. package/cli/agents/endpoint-agent-abuse-agent.js +404 -0
  22. package/cli/agents/exception-handler-agent.js +187 -0
  23. package/cli/agents/git-history-scanner.js +169 -0
  24. package/cli/agents/governance-audits.js +138 -0
  25. package/cli/agents/hermes-security-agent.js +536 -0
  26. package/cli/agents/html-reporter.js +1125 -0
  27. package/cli/agents/index.js +147 -0
  28. package/cli/agents/injection-tester.js +502 -0
  29. package/cli/agents/legal-risk-agent.js +328 -0
  30. package/cli/agents/llm-redteam.js +199 -0
  31. package/cli/agents/managed-agent-scanner.js +333 -0
  32. package/cli/agents/mcp-security-agent.js +588 -0
  33. package/cli/agents/memory-poisoning-agent.js +305 -0
  34. package/cli/agents/mobile-scanner.js +231 -0
  35. package/cli/agents/model-file-scanner.js +259 -0
  36. package/cli/agents/orchestrator.js +355 -0
  37. package/cli/agents/pii-compliance-agent.js +301 -0
  38. package/cli/agents/policy-engine.js +229 -0
  39. package/cli/agents/prompt-injection-prober.js +224 -0
  40. package/cli/agents/rag-security-agent.js +204 -0
  41. package/cli/agents/recon-agent.js +207 -0
  42. package/cli/agents/sbom-generator.js +265 -0
  43. package/cli/agents/scoring-engine.js +273 -0
  44. package/cli/agents/ssrf-prober.js +130 -0
  45. package/cli/agents/stateful-watcher.js +238 -0
  46. package/cli/agents/supabase-rls-agent.js +154 -0
  47. package/cli/agents/supply-chain-agent.js +857 -0
  48. package/cli/agents/swarm-orchestrator.js +200 -0
  49. package/cli/agents/verifier-agent.js +303 -0
  50. package/cli/agents/vibe-coding-agent.js +250 -0
  51. package/cli/bin/praxis.js +866 -0
  52. package/cli/commands/abom.js +73 -0
  53. package/cli/commands/agent-fix.js +1245 -0
  54. package/cli/commands/audit.js +1180 -0
  55. package/cli/commands/autofix.js +383 -0
  56. package/cli/commands/baseline.js +193 -0
  57. package/cli/commands/benchmark.js +327 -0
  58. package/cli/commands/checklist.js +223 -0
  59. package/cli/commands/ci.js +403 -0
  60. package/cli/commands/deps.js +516 -0
  61. package/cli/commands/diff.js +200 -0
  62. package/cli/commands/doctor.js +195 -0
  63. package/cli/commands/env-audit.js +349 -0
  64. package/cli/commands/fix.js +218 -0
  65. package/cli/commands/guard.js +396 -0
  66. package/cli/commands/hooks.js +278 -0
  67. package/cli/commands/init.js +514 -0
  68. package/cli/commands/legal.js +158 -0
  69. package/cli/commands/live-advisories.js +241 -0
  70. package/cli/commands/mcp.js +660 -0
  71. package/cli/commands/openclaw.js +386 -0
  72. package/cli/commands/red-team.js +350 -0
  73. package/cli/commands/redteam.js +78 -0
  74. package/cli/commands/remediate.js +797 -0
  75. package/cli/commands/rotate.js +768 -0
  76. package/cli/commands/rules.js +196 -0
  77. package/cli/commands/scan-mcp.js +534 -0
  78. package/cli/commands/scan-skill.js +588 -0
  79. package/cli/commands/scan-standard.js +251 -0
  80. package/cli/commands/scan.js +524 -0
  81. package/cli/commands/score.js +449 -0
  82. package/cli/commands/shell.js +514 -0
  83. package/cli/commands/team-report.js +398 -0
  84. package/cli/commands/undo.js +161 -0
  85. package/cli/commands/update-intel.js +126 -0
  86. package/cli/commands/vibe-check.js +276 -0
  87. package/cli/commands/watch.js +757 -0
  88. package/cli/commands/web.js +63 -0
  89. package/cli/core/ast/guardrail-detector.js +141 -0
  90. package/cli/core/ast/index.js +22 -0
  91. package/cli/core/ast/parser.js +676 -0
  92. package/cli/core/ast/scope-tree.js +287 -0
  93. package/cli/core/ast/taint-tracker.js +158 -0
  94. package/cli/core/branding.js +37 -0
  95. package/cli/core/env.js +38 -0
  96. package/cli/core/errors.js +61 -0
  97. package/cli/core/fs.js +62 -0
  98. package/cli/core/output/compliance.js +90 -0
  99. package/cli/core/output/html-theme.js +158 -0
  100. package/cli/core/output/index.js +57 -0
  101. package/cli/core/output/json.js +48 -0
  102. package/cli/core/output/sarif.js +240 -0
  103. package/cli/core/version.js +67 -0
  104. package/cli/core/web/jobs.js +183 -0
  105. package/cli/core/web/projects.js +146 -0
  106. package/cli/core/web/server.js +439 -0
  107. package/cli/data/atlas-knowledge.json +5640 -0
  108. package/cli/data/eaa-catalog.json +39 -0
  109. package/cli/data/known-mcps.json +26 -0
  110. package/cli/data/probes/prompt-injection-corpus.json +271 -0
  111. package/cli/data/threat-intel.json +85 -0
  112. package/cli/data/threatpacks/latest.json +41 -0
  113. package/cli/hooks/patterns.js +313 -0
  114. package/cli/hooks/post-tool-use.js +140 -0
  115. package/cli/hooks/pre-tool-use.js +186 -0
  116. package/cli/index.js +90 -0
  117. package/cli/providers/llm-provider.js +766 -0
  118. package/cli/utils/autofix-rules.js +74 -0
  119. package/cli/utils/cache-manager.js +310 -0
  120. package/cli/utils/compliance-map.js +191 -0
  121. package/cli/utils/entropy.js +132 -0
  122. package/cli/utils/fix-ledger.js +127 -0
  123. package/cli/utils/hermes-tool-registry.js +252 -0
  124. package/cli/utils/intel/cache.js +61 -0
  125. package/cli/utils/intel/http.js +88 -0
  126. package/cli/utils/intel/index.js +235 -0
  127. package/cli/utils/intel/merge.js +229 -0
  128. package/cli/utils/intel/sources/epss.js +54 -0
  129. package/cli/utils/intel/sources/ghsa.js +81 -0
  130. package/cli/utils/intel/sources/gitguardian.js +40 -0
  131. package/cli/utils/intel/sources/gitleaks.js +101 -0
  132. package/cli/utils/intel/sources/kev.js +38 -0
  133. package/cli/utils/intel/sources/nvd.js +84 -0
  134. package/cli/utils/intel/sources/osv.js +132 -0
  135. package/cli/utils/intel/sources/phylum.js +44 -0
  136. package/cli/utils/intel/sources/snyk.js +46 -0
  137. package/cli/utils/intel/sources/socket.js +69 -0
  138. package/cli/utils/intel/sources/sonatype.js +84 -0
  139. package/cli/utils/intel/sources/threatpack.js +69 -0
  140. package/cli/utils/mcp-trust.js +60 -0
  141. package/cli/utils/output.js +251 -0
  142. package/cli/utils/patterns.js +1130 -0
  143. package/cli/utils/pdf-generator.js +94 -0
  144. package/cli/utils/plugin-loader.js +364 -0
  145. package/cli/utils/rule-import.js +228 -0
  146. package/cli/utils/rule-registry.js +426 -0
  147. package/cli/utils/scan-fingerprint.js +109 -0
  148. package/cli/utils/scan-playbook.js +312 -0
  149. package/cli/utils/score-history.js +119 -0
  150. package/cli/utils/secrets-verifier.js +247 -0
  151. package/cli/utils/security-memory.js +296 -0
  152. package/cli/utils/standards/atlas-knowledge.js +87 -0
  153. package/cli/utils/standards/index.js +127 -0
  154. package/cli/utils/standards/sources/avid.js +45 -0
  155. package/cli/utils/standards/sources/eu-ai-act.js +89 -0
  156. package/cli/utils/standards/sources/google-saif.js +39 -0
  157. package/cli/utils/standards/sources/iso-42001.js +94 -0
  158. package/cli/utils/standards/sources/mitre-atlas.js +54 -0
  159. package/cli/utils/standards/sources/nist-ai-600-1.js +45 -0
  160. package/cli/utils/standards/sources/owasp-llm.js +45 -0
  161. package/cli/utils/standards/sources/owasp-ml.js +45 -0
  162. package/cli/utils/threat-intel.js +265 -0
  163. package/configs/firebase/firestore-rules.txt +215 -0
  164. package/configs/firebase/security-checklist.md +236 -0
  165. package/configs/firebase/storage-rules.txt +206 -0
  166. package/configs/gitignore-template +258 -0
  167. package/configs/nextjs-security-headers.js +220 -0
  168. package/configs/praxisignore-template +50 -0
  169. package/configs/supabase/secure-client.ts +225 -0
  170. package/configs/supabase/security-checklist.md +278 -0
  171. package/docs/THIRD_PARTY_NOTICES.md +26 -0
  172. package/docs/THREAT_INTEL.md +292 -0
  173. package/docs/USAGE.md +1205 -0
  174. package/docs/design/WEB-UI.md +82 -0
  175. package/package.json +71 -0
  176. package/scripts/check-determinism.mjs +119 -0
  177. package/snippets/README.md +122 -0
  178. package/snippets/api-security/api-security-checklist.md +412 -0
  179. package/snippets/api-security/cors-config.ts +322 -0
  180. package/snippets/api-security/input-validation.ts +430 -0
  181. package/snippets/auth/jwt-checklist.md +322 -0
  182. package/snippets/rate-limiting/nextjs-middleware.ts +211 -0
  183. package/snippets/rate-limiting/upstash-ratelimit.ts +229 -0
@@ -0,0 +1,588 @@
1
+ /**
2
+ * MCP Security Agent
3
+ * ===================
4
+ *
5
+ * Detects security vulnerabilities in MCP (Model Context Protocol)
6
+ * server implementations. MCP servers are the new attack surface
7
+ * for AI-powered applications.
8
+ *
9
+ * In 2026, 30+ CVEs were filed against MCP servers in 60 days.
10
+ * 82% of implementations are prone to path traversal.
11
+ *
12
+ * Checks: tool poisoning, unauthenticated endpoints, overprivileged
13
+ * tools, input injection, missing rate limiting, credential exposure,
14
+ * unsafe transport.
15
+ *
16
+ * Maps to: OWASP Agentic AI ASI02 (Tool Misuse), ASI03 (Privilege Abuse)
17
+ */
18
+
19
+ import fs from 'fs';
20
+ import path from 'path';
21
+ import os from 'os';
22
+ import { BaseAgent } from './base-agent.js';
23
+ import { lookupTrust, listKnown } from '../utils/mcp-trust.js';
24
+
25
+ // =============================================================================
26
+ // MCP SECURITY PATTERNS
27
+ // =============================================================================
28
+
29
+ export const PATTERNS = [
30
+ // ── Tool Poisoning & Validation ──────────────────────────────────────────
31
+ {
32
+ rule: 'MCP_NO_TOOL_VALIDATION',
33
+ title: 'MCP: Tool Call Without Validation',
34
+ regex: /(?:tools\/call|tool_call|callTool|executeTool)\s*\(/g,
35
+ severity: 'high',
36
+ cwe: 'CWE-20',
37
+ owasp: 'A03:2021',
38
+ confidence: 'medium',
39
+ description: 'MCP tool invocation without visible tool-name validation or allowlisting. Attackers can invoke arbitrary tools via tool poisoning.',
40
+ fix: 'Validate tool names against an explicit allowlist before execution',
41
+ },
42
+ {
43
+ rule: 'MCP_DYNAMIC_TOOL_REGISTRATION',
44
+ title: 'MCP: Dynamic Tool Registration from External Source',
45
+ regex: /(?:registerTool|addTool|server\.tool)\s*\(\s*(?:req\.|request\.|body\.|data\.|input\.|params\.)/g,
46
+ severity: 'critical',
47
+ cwe: 'CWE-94',
48
+ owasp: 'A03:2021',
49
+ description: 'Tool registration from external/user input allows attackers to inject malicious tool definitions (tool poisoning attack).',
50
+ fix: 'Only register tools from trusted, hardcoded definitions. Never accept tool definitions from user input.',
51
+ },
52
+
53
+ // ── Authentication & Access Control ──────────────────────────────────────
54
+ {
55
+ rule: 'MCP_NO_AUTH_TRANSPORT',
56
+ title: 'MCP: Server Without Authentication',
57
+ regex: /(?:McpServer|Server|createServer)\s*\(\s*\{(?:(?!auth|token|apiKey|bearer|jwt|session|credential).)*\}\s*\)/gs,
58
+ severity: 'critical',
59
+ cwe: 'CWE-306',
60
+ owasp: 'A07:2021',
61
+ confidence: 'medium',
62
+ description: 'MCP server created without any authentication configuration. Any client can connect and invoke tools.',
63
+ fix: 'Add authentication to MCP server transport: API key validation, JWT verification, or OAuth',
64
+ },
65
+ {
66
+ rule: 'MCP_STDIO_NO_SANDBOX',
67
+ title: 'MCP: stdio Transport Without Sandbox',
68
+ regex: /(?:StdioServerTransport|stdio|transport.*stdio)/g,
69
+ severity: 'medium',
70
+ cwe: 'CWE-269',
71
+ owasp: 'A04:2021',
72
+ confidence: 'medium',
73
+ description: 'MCP server using stdio transport runs in the same process context. Consider sandboxing for untrusted tools.',
74
+ fix: 'Run MCP servers in sandboxed containers or separate processes with limited permissions',
75
+ },
76
+
77
+ // ── Overprivileged Tools ─────────────────────────────────────────────────
78
+ {
79
+ rule: 'MCP_TOOL_SHELL_EXEC',
80
+ title: 'MCP: Tool Executes Shell Commands',
81
+ regex: /(?:server\.tool|registerTool|addTool)[\s\S]{0,500}(?:exec|execSync|spawn|spawnSync|execFile|child_process|subprocess|os\.system|os\.popen)/g, // praxis-ignore MCP_TOOL_SHELL_EXEC — detector definition
82
+ severity: 'critical',
83
+ cwe: 'CWE-78',
84
+ owasp: 'A03:2021',
85
+ description: 'MCP tool handler executes shell commands. If tool arguments are user-influenced via prompt injection, this enables RCE.',
86
+ fix: 'Avoid shell execution in MCP tools. If necessary, use strict allowlists for commands and validate all arguments.',
87
+ },
88
+ {
89
+ rule: 'MCP_TOOL_FS_WRITE',
90
+ title: 'MCP: Tool Has File System Write Access',
91
+ regex: /(?:server\.tool|registerTool|addTool)[\s\S]{0,500}(?:writeFile|writeFileSync|appendFile|createWriteStream|fs\.write|unlink|rmdir|mkdir)/g,
92
+ severity: 'high',
93
+ cwe: 'CWE-732',
94
+ owasp: 'A01:2021',
95
+ description: 'MCP tool can write to the file system. Prompt injection could lead to arbitrary file writes or deletions.',
96
+ fix: 'Restrict file operations to a sandboxed directory. Validate all paths against an allowlist.',
97
+ },
98
+ {
99
+ rule: 'MCP_TOOL_DB_MUTATION',
100
+ title: 'MCP: Tool Has Database Write Access',
101
+ regex: /(?:server\.tool|registerTool|addTool)[\s\S]{0,500}(?:INSERT|UPDATE|DELETE|DROP|CREATE|ALTER|\.create\(|\.update\(|\.delete\(|\.destroy\(|\.remove\()/g,
102
+ severity: 'high',
103
+ cwe: 'CWE-284',
104
+ owasp: 'A01:2021',
105
+ description: 'MCP tool can mutate database records. Without confirmation gates, prompt injection can modify or delete data.',
106
+ fix: 'Add human-in-the-loop confirmation for destructive database operations in MCP tools.',
107
+ },
108
+ {
109
+ rule: 'MCP_TOOL_NETWORK_REQUEST',
110
+ title: 'MCP: Tool Makes External Network Requests',
111
+ regex: /(?:server\.tool|registerTool|addTool)[\s\S]{0,500}(?:fetch\(|axios\.|got\(|http\.get|https\.get|request\(|urllib|requests\.)/g,
112
+ severity: 'medium',
113
+ cwe: 'CWE-918',
114
+ owasp: 'A10:2021',
115
+ confidence: 'medium',
116
+ description: 'MCP tool makes external HTTP requests. Prompt injection could trigger SSRF via tool arguments.',
117
+ fix: 'Validate URLs against allowlist. Block internal/private IP ranges.',
118
+ },
119
+
120
+ // ── Input Injection ──────────────────────────────────────────────────────
121
+ {
122
+ rule: 'MCP_TOOL_ARGS_TO_SQL',
123
+ title: 'MCP: Tool Arguments in SQL Query',
124
+ regex: /(?:server\.tool|registerTool)[\s\S]{0,500}(?:`SELECT|`INSERT|`UPDATE|`DELETE|\.query\s*\(\s*`|\.raw\s*\()/g, // praxis-ignore MCP_TOOL_ARGS_TO_SQL — detector definition
125
+ severity: 'critical',
126
+ cwe: 'CWE-89',
127
+ owasp: 'A03:2021',
128
+ description: 'MCP tool constructs SQL queries that may include tool arguments from LLM output. This enables SQL injection via prompt injection.',
129
+ fix: 'Use parameterized queries in all MCP tool handlers. Never interpolate tool arguments into SQL.',
130
+ },
131
+ {
132
+ rule: 'MCP_TOOL_ARGS_TO_EVAL',
133
+ title: 'MCP: Tool Arguments Passed to eval()',
134
+ regex: /(?:server\.tool|registerTool)[\s\S]{0,500}eval\s*\(/g,
135
+ severity: 'critical',
136
+ cwe: 'CWE-94',
137
+ owasp: 'A03:2021',
138
+ description: 'MCP tool passes arguments to eval(). Prompt injection can achieve arbitrary code execution.',
139
+ fix: 'Never use eval() in MCP tool handlers. Use structured data parsing instead.',
140
+ },
141
+ {
142
+ rule: 'MCP_TOOL_PATH_TRAVERSAL',
143
+ title: 'MCP: Tool Arguments in File Path',
144
+ regex: /(?:server\.tool|registerTool)[\s\S]{0,500}(?:path\.join|path\.resolve|readFile|readFileSync)\s*\(\s*(?!__dirname)/g,
145
+ severity: 'high',
146
+ cwe: 'CWE-22',
147
+ owasp: 'A01:2021',
148
+ confidence: 'medium',
149
+ description: 'MCP tool constructs file paths from arguments. Path traversal via prompt injection can read arbitrary files.',
150
+ fix: 'Validate file paths against an allowed directory. Use path.resolve() and check the result starts with the allowed base.',
151
+ },
152
+
153
+ // ── Credential Exposure ──────────────────────────────────────────────────
154
+ {
155
+ rule: 'MCP_HARDCODED_CREDENTIALS',
156
+ title: 'MCP: Credentials in Server Config',
157
+ regex: /(?:mcpServers|mcp_server|server\.json)[\s\S]{0,300}(?:password|secret|token|apiKey|api_key|credential)\s*[:=]\s*["'][^"']+["']/gi,
158
+ severity: 'critical',
159
+ cwe: 'CWE-798',
160
+ owasp: 'A07:2021',
161
+ description: 'Hardcoded credentials in MCP server configuration. These are exposed to anyone with access to the config.',
162
+ fix: 'Use environment variables or a secrets manager for MCP server credentials.',
163
+ },
164
+ {
165
+ rule: 'MCP_ENV_IN_TOOL_RESPONSE',
166
+ title: 'MCP: Environment Variables Exposed in Tool Response',
167
+ regex: /(?:server\.tool|registerTool)[\s\S]{0,500}(?:process\.env|os\.environ)/g,
168
+ severity: 'high',
169
+ cwe: 'CWE-200',
170
+ owasp: 'A01:2021',
171
+ confidence: 'medium',
172
+ description: 'MCP tool accesses environment variables. If returned in tool responses, secrets may leak to the LLM and user.',
173
+ fix: 'Never return raw environment variables in tool responses. Filter sensitive values.',
174
+ },
175
+
176
+ // ── Remote/Untrusted Connections ─────────────────────────────────────────
177
+ {
178
+ rule: 'MCP_REMOTE_UNPINNED',
179
+ title: 'MCP: Remote Server Without Version Pinning',
180
+ regex: /(?:mcpServers|mcp_servers)[\s\S]{0,200}(?:url|command)\s*[:=]\s*["'][^"']*["'](?![\s\S]{0,100}(?:hash|integrity|version|sha|pin|digest))/g,
181
+ severity: 'medium',
182
+ cwe: 'CWE-494',
183
+ owasp: 'A08:2021',
184
+ confidence: 'medium',
185
+ description: 'MCP server reference without version pinning or integrity hash. Vulnerable to rug-pull attacks.',
186
+ fix: 'Pin MCP server versions and validate integrity hashes to prevent supply chain attacks.',
187
+ },
188
+ {
189
+ rule: 'MCP_HTTP_NO_TLS',
190
+ title: 'MCP: HTTP Transport Without TLS',
191
+ regex: /(?:SSEServerTransport|StreamableHTTPServerTransport|mcpServers)[\s\S]{0,200}http:\/\/(?!localhost|127\.0\.0\.1)/g,
192
+ severity: 'high',
193
+ cwe: 'CWE-319',
194
+ owasp: 'A02:2021',
195
+ description: 'MCP server using HTTP (not HTTPS) for non-localhost connections. Tool calls and responses are sent in plaintext.',
196
+ fix: 'Use HTTPS for all remote MCP server connections.',
197
+ },
198
+
199
+ // ── Missing Rate Limiting ────────────────────────────────────────────────
200
+ {
201
+ rule: 'MCP_NO_RATE_LIMIT',
202
+ title: 'MCP: No Rate Limiting on Tool Calls',
203
+ regex: /(?:McpServer|Server|createServer)\s*\(\s*\{(?:(?!rateLimit|rateLimiter|throttle|limit|maxRequests).)*\}\s*\)/gs,
204
+ severity: 'medium',
205
+ cwe: 'CWE-770',
206
+ owasp: 'A04:2021',
207
+ confidence: 'low',
208
+ description: 'MCP server without rate limiting. Enables unbounded consumption attacks (denial of wallet).',
209
+ fix: 'Add rate limiting to MCP server: limit tool calls per client per time window.',
210
+ },
211
+
212
+ // ── Tool Result Injection ────────────────────────────────────────────────
213
+ {
214
+ rule: 'MCP_TOOL_RESULT_UNESCAPED',
215
+ title: 'MCP: Tool Result Injected Into Prompt Without Escaping',
216
+ regex: /(?:tool_result|toolResult|function_result)[\s\S]{0,200}(?:content|messages|prompt)\s*(?:\.push|\.append|\+=|\.concat)/g,
217
+ severity: 'high',
218
+ cwe: 'CWE-74',
219
+ owasp: 'A03:2021',
220
+ confidence: 'medium',
221
+ description: 'Raw tool results injected back into LLM prompt context. Enables tool-to-prompt injection attacks.',
222
+ fix: 'Sanitize and escape tool results before including them in LLM context. Strip any instruction-like content.',
223
+ },
224
+ ];
225
+
226
+ // =============================================================================
227
+ // STRUCTURAL CHECKS (beyond line-by-line regex)
228
+ // =============================================================================
229
+
230
+ const MCP_CONFIG_FILES = [
231
+ 'mcp.json',
232
+ '.mcp.json',
233
+ 'mcp-config.json',
234
+ 'claude_desktop_config.json',
235
+ '.cursor/mcp.json',
236
+ '.vscode/mcp.json',
237
+ ];
238
+
239
+ // Well-known official MCP server packages
240
+ const OFFICIAL_MCP_SERVERS = new Set([
241
+ '@modelcontextprotocol/server-filesystem',
242
+ '@modelcontextprotocol/server-github',
243
+ '@modelcontextprotocol/server-gitlab',
244
+ '@modelcontextprotocol/server-google-maps',
245
+ '@modelcontextprotocol/server-memory',
246
+ '@modelcontextprotocol/server-postgres',
247
+ '@modelcontextprotocol/server-puppeteer',
248
+ '@modelcontextprotocol/server-slack',
249
+ '@modelcontextprotocol/server-sqlite',
250
+ '@modelcontextprotocol/server-brave-search',
251
+ '@modelcontextprotocol/server-fetch',
252
+ '@modelcontextprotocol/server-everything',
253
+ '@modelcontextprotocol/server-sequential-thinking',
254
+ ]);
255
+
256
+ // =============================================================================
257
+ // MCP SECURITY AGENT
258
+ // =============================================================================
259
+
260
+ export class MCPSecurityAgent extends BaseAgent {
261
+ constructor() {
262
+ super(
263
+ 'MCPSecurityAgent',
264
+ 'Detect MCP server security vulnerabilities — tool poisoning, auth gaps, privilege escalation',
265
+ 'llm'
266
+ );
267
+ }
268
+
269
+ async analyze(context) {
270
+ const { files, rootPath } = context;
271
+ let findings = [];
272
+
273
+ // ── 1. Scan code files for MCP patterns ──────────────────────────────
274
+ const codeFiles = files.filter(f => {
275
+ const ext = path.extname(f).toLowerCase();
276
+ return ['.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs', '.py', '.rb', '.go'].includes(ext);
277
+ });
278
+
279
+ for (const file of codeFiles) {
280
+ findings = findings.concat(this.scanFileWithPatterns(file, PATTERNS));
281
+ }
282
+
283
+ // ── 2. Scan MCP config files ─────────────────────────────────────────
284
+ const configFiles = files.filter(f => {
285
+ const basename = path.basename(f);
286
+ const rel = path.relative(rootPath, f).replace(/\\/g, '/');
287
+ return MCP_CONFIG_FILES.some(cfg => rel.endsWith(cfg) || basename === cfg);
288
+ });
289
+
290
+ for (const file of configFiles) {
291
+ findings = findings.concat(this.scanFileWithPatterns(file, PATTERNS));
292
+ findings = findings.concat(this._checkConfigFile(file));
293
+ }
294
+
295
+ // ── 3. Check for MCP server files without auth patterns ──────────────
296
+ const mcpServerFiles = codeFiles.filter(f => {
297
+ const content = this.readFile(f);
298
+ return content && /(?:McpServer|@modelcontextprotocol|mcp-server|from\s+mcp)/i.test(content);
299
+ });
300
+
301
+ for (const file of mcpServerFiles) {
302
+ findings = findings.concat(this._checkServerAuth(file));
303
+ }
304
+
305
+ // ── 4. Check MCP configs for typosquatting & over-permissioned servers ─
306
+ for (const file of configFiles) {
307
+ findings = findings.concat(this._checkMcpTyposquatting(file));
308
+ findings = findings.concat(this._checkOverPermissioned(file));
309
+ findings = findings.concat(this._checkTrustRegistry(file));
310
+ }
311
+
312
+ // ── 5. Detect shadow MCP configs (not in version control) ───────────
313
+ findings = findings.concat(this._detectShadowMcpConfigs(rootPath));
314
+
315
+ return findings;
316
+ }
317
+
318
+ /**
319
+ * Check MCP config files for misconfigurations.
320
+ */
321
+ _checkConfigFile(filePath) {
322
+ const content = this.readFile(filePath);
323
+ if (!content) return [];
324
+
325
+ const findings = [];
326
+
327
+ // Check for hardcoded secrets in config
328
+ const secretPatterns = /(?:password|secret|token|apiKey|api_key)\s*[:=]\s*["'][^"']{8,}["']/gi;
329
+ const lines = content.split('\n');
330
+ for (let i = 0; i < lines.length; i++) {
331
+ if (this.isSuppressed(lines[i])) continue;
332
+ secretPatterns.lastIndex = 0;
333
+ if (secretPatterns.test(lines[i])) {
334
+ findings.push({
335
+ file: filePath,
336
+ line: i + 1,
337
+ column: 0,
338
+ severity: 'critical',
339
+ category: this.category,
340
+ rule: 'MCP_CONFIG_HARDCODED_SECRET',
341
+ title: 'MCP: Hardcoded Secret in Config',
342
+ description: 'MCP configuration contains a hardcoded secret. Use environment variables instead.',
343
+ matched: lines[i].trim().substring(0, 100),
344
+ confidence: 'high',
345
+ cwe: 'CWE-798',
346
+ owasp: 'A07:2021',
347
+ fix: 'Replace hardcoded values with environment variable references: {"env": "MY_SECRET"}',
348
+ });
349
+ }
350
+ }
351
+
352
+ return findings;
353
+ }
354
+
355
+ /**
356
+ * Check if MCP server files have authentication.
357
+ */
358
+ _checkServerAuth(filePath) {
359
+ const content = this.readFile(filePath);
360
+ if (!content) return [];
361
+
362
+ const findings = [];
363
+
364
+ // Check if server file has any auth patterns
365
+ const hasAuth = /(?:auth|authenticate|authorization|bearer|jwt|token|apiKey|session|passport|middleware)/i.test(content);
366
+
367
+ if (!hasAuth) {
368
+ findings.push({
369
+ file: filePath,
370
+ line: 1,
371
+ column: 0,
372
+ severity: 'high',
373
+ category: this.category,
374
+ rule: 'MCP_SERVER_NO_AUTH',
375
+ title: 'MCP: Server Implementation Without Authentication',
376
+ description: 'MCP server implementation has no visible authentication mechanism. Any client can connect and invoke tools.',
377
+ matched: 'No auth pattern found in MCP server file',
378
+ confidence: 'medium',
379
+ cwe: 'CWE-306',
380
+ owasp: 'A07:2021',
381
+ fix: 'Add authentication middleware to your MCP server. Validate client identity before allowing tool invocations.',
382
+ });
383
+ }
384
+
385
+ return findings;
386
+ }
387
+
388
+ /**
389
+ * Trust-registry check: flag npx packages that aren't in the curated
390
+ * known-MCP registry (unknown trust), and surface community-tier scores.
391
+ */
392
+ _checkTrustRegistry(filePath) {
393
+ const content = this.readFile(filePath);
394
+ if (!content) return [];
395
+ const findings = [];
396
+
397
+ let config;
398
+ try {
399
+ config = JSON.parse(content);
400
+ } catch {
401
+ return findings;
402
+ }
403
+
404
+ const servers = config.mcpServers || config.servers || {};
405
+ for (const [name, server] of Object.entries(servers)) {
406
+ const fullCmd = `${server.command || ''} ${(server.args || []).join(' ')}`;
407
+ const npxMatch = fullCmd.match(/npx\s+(?:-[^\s]{1,50}\s+){0,50}([^\s]{1,200})/);
408
+ if (!npxMatch) continue;
409
+ const pkg = npxMatch[1];
410
+
411
+ const trust = lookupTrust(pkg);
412
+ if (trust.tier === 'verified' || trust.tier === 'community') continue;
413
+
414
+ findings.push({
415
+ file: filePath, line: 1, column: 0,
416
+ severity: 'medium',
417
+ category: this.category,
418
+ rule: 'MCP_UNVERIFIED_SOURCE',
419
+ title: `MCP: Unverified Server Source "${pkg}"`,
420
+ description: `MCP server "${name}" runs package "${pkg}", which is not in the curated known-MCP trust registry. An unverified MCP server gains the agent's tools, credentials, and filesystem context — treat it as untrusted until reviewed.${trust.tier === 'unverified-registry' ? ' (Trust registry failed its integrity check.)' : ''}`,
421
+ matched: pkg,
422
+ confidence: 'medium',
423
+ cwe: 'CWE-1357',
424
+ owasp: 'ASI04',
425
+ eaa: 'EAA-006',
426
+ fix: 'Verify the package publisher, pin a reviewed version with a checksum, and prefer servers from the trust registry (praxis scan standard mitre-atlas for context).',
427
+ });
428
+ }
429
+ return findings;
430
+ }
431
+
432
+ /**
433
+ * Detect possible typosquatted MCP server names in config.
434
+ */
435
+ _checkMcpTyposquatting(filePath) {
436
+ const content = this.readFile(filePath);
437
+ if (!content) return [];
438
+ const findings = [];
439
+
440
+ try {
441
+ const config = JSON.parse(content);
442
+ const servers = config.mcpServers || config.servers || {};
443
+
444
+ for (const [name, server] of Object.entries(servers)) {
445
+ const cmd = server.command || '';
446
+ const args = (server.args || []).join(' ');
447
+ const fullCmd = `${cmd} ${args}`;
448
+
449
+ // Check if server uses an npx package that looks like a typosquat of
450
+ // official/community known servers (trust registry + built-in list).
451
+ const npxMatch = fullCmd.match(/npx\s+(?:-[^\s]{1,50}\s+){0,50}([^\s]{1,200})/);
452
+ if (npxMatch) {
453
+ const pkg = npxMatch[1];
454
+ const knownSet = new Set([...OFFICIAL_MCP_SERVERS, ...listKnown()]);
455
+ for (const official of knownSet) {
456
+ const distance = this._levenshtein(pkg, official);
457
+ if (distance > 0 && distance <= 3 && pkg !== official) {
458
+ findings.push({
459
+ file: filePath, line: 1, column: 0,
460
+ severity: 'critical',
461
+ category: this.category,
462
+ rule: 'MCP_TYPOSQUAT_SERVER',
463
+ title: `MCP: Possible Typosquatted Server "${pkg}"`,
464
+ description: `MCP server package "${pkg}" is ${distance} char(s) from official "${official}". Could be a supply chain attack.`,
465
+ matched: pkg,
466
+ confidence: 'medium',
467
+ cwe: 'CWE-494',
468
+ owasp: 'A08:2021',
469
+ fix: `Verify this is the correct package. Did you mean "${official}"?`,
470
+ });
471
+ }
472
+ }
473
+ }
474
+ }
475
+ } catch { /* not valid JSON */ }
476
+
477
+ return findings;
478
+ }
479
+
480
+ /**
481
+ * Check for over-permissioned MCP servers (filesystem access to / or ~).
482
+ */
483
+ _checkOverPermissioned(filePath) {
484
+ const content = this.readFile(filePath);
485
+ if (!content) return [];
486
+ const findings = [];
487
+
488
+ try {
489
+ const config = JSON.parse(content);
490
+ const servers = config.mcpServers || config.servers || {};
491
+
492
+ for (const [name, server] of Object.entries(servers)) {
493
+ const args = server.args || [];
494
+ const argsStr = args.join(' ');
495
+
496
+ // Check for root/home filesystem access
497
+ if (/(?:^\/\s|['" ]\/['"]?\s|\/Users\/|\/home\/|\\Users\\|C:\\)/.test(argsStr)) {
498
+ const hasWideAccess = args.some(a =>
499
+ a === '/' || a === '~' || a === '%USERPROFILE%' ||
500
+ /^\/(?:Users|home)\/[^/]+$/.test(a) ||
501
+ /^[A-Z]:\\(?:Users\\)?[^\\]*$/.test(a)
502
+ );
503
+ if (hasWideAccess) {
504
+ findings.push({
505
+ file: filePath, line: 1, column: 0,
506
+ severity: 'high',
507
+ category: this.category,
508
+ rule: 'MCP_OVER_PERMISSIONED',
509
+ title: `MCP: Server "${name}" Has Broad Filesystem Access`,
510
+ description: `MCP server "${name}" has access to a wide directory scope. A prompt injection attack could read or modify sensitive files.`,
511
+ matched: argsStr.slice(0, 200),
512
+ confidence: 'high',
513
+ cwe: 'CWE-269',
514
+ owasp: 'A01:2021',
515
+ fix: 'Restrict filesystem access to the minimum required directory: e.g., the project folder only.',
516
+ });
517
+ }
518
+ }
519
+ }
520
+ } catch { /* not valid JSON */ }
521
+
522
+ return findings;
523
+ }
524
+
525
+ /**
526
+ * Detect shadow MCP configs that exist but aren't in .gitignore or git.
527
+ */
528
+ _detectShadowMcpConfigs(rootPath) {
529
+ const findings = [];
530
+ const home = os.homedir();
531
+
532
+ // Check common locations for MCP configs outside version control
533
+ const homeConfigs = [
534
+ path.join(home, '.cursor', 'mcp.json'),
535
+ path.join(home, 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json'),
536
+ path.join(home, 'AppData', 'Roaming', 'Claude', 'claude_desktop_config.json'),
537
+ ];
538
+
539
+ for (const configPath of homeConfigs) {
540
+ try {
541
+ if (fs.existsSync(configPath)) {
542
+ const content = fs.readFileSync(configPath, 'utf-8');
543
+ const config = JSON.parse(content);
544
+ const servers = config.mcpServers || config.servers || {};
545
+ const serverCount = Object.keys(servers).length;
546
+
547
+ if (serverCount > 0) {
548
+ findings.push({
549
+ file: configPath, line: 1, column: 0,
550
+ severity: 'medium',
551
+ category: this.category,
552
+ rule: 'MCP_SHADOW_CONFIG',
553
+ title: `MCP: ${serverCount} Shadow Server(s) in User Config`,
554
+ description: `Found ${serverCount} MCP server(s) configured outside the project in ${configPath}. These operate outside your project's security controls.`,
555
+ matched: Object.keys(servers).join(', '),
556
+ confidence: 'medium',
557
+ cwe: 'CWE-269',
558
+ owasp: 'A05:2021',
559
+ fix: 'Review shadow MCP servers. Move project-specific servers to the project mcp.json and track in version control.',
560
+ });
561
+ }
562
+ }
563
+ } catch { /* skip */ }
564
+ }
565
+
566
+ return findings;
567
+ }
568
+
569
+ /**
570
+ * Simple Levenshtein distance for typosquatting detection.
571
+ */
572
+ _levenshtein(a, b) {
573
+ const m = a.length, n = b.length;
574
+ const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));
575
+ for (let i = 0; i <= m; i++) dp[i][0] = i;
576
+ for (let j = 0; j <= n; j++) dp[0][j] = j;
577
+ for (let i = 1; i <= m; i++) {
578
+ for (let j = 1; j <= n; j++) {
579
+ dp[i][j] = a[i-1] === b[j-1]
580
+ ? dp[i-1][j-1]
581
+ : 1 + Math.min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1]);
582
+ }
583
+ }
584
+ return dp[m][n];
585
+ }
586
+ }
587
+
588
+ export default MCPSecurityAgent;