@mcp-shark/mcp-shark 1.5.13 → 1.7.2

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 (158) hide show
  1. package/README.md +482 -56
  2. package/bin/mcp-shark.js +146 -52
  3. package/core/cli/AutoFixEngine.js +93 -0
  4. package/core/cli/ConfigScanner.js +193 -0
  5. package/core/cli/DataLoader.js +200 -0
  6. package/core/cli/DeclarativeRuleEngine.js +363 -0
  7. package/core/cli/DoctorCommand.js +218 -0
  8. package/core/cli/FixHandlers.js +222 -0
  9. package/core/cli/HtmlReportGenerator.js +203 -0
  10. package/core/cli/IdeConfigPaths.js +175 -0
  11. package/core/cli/ListCommand.js +255 -0
  12. package/core/cli/LockCommand.js +164 -0
  13. package/core/cli/LockDiffEngine.js +152 -0
  14. package/core/cli/RuleRegistryConfig.js +131 -0
  15. package/core/cli/ScanCommand.js +244 -0
  16. package/core/cli/ScanService.js +200 -0
  17. package/core/cli/SecretDetector.js +92 -0
  18. package/core/cli/SharkScoreCalculator.js +109 -0
  19. package/core/cli/ToolClassifications.js +51 -0
  20. package/core/cli/ToxicFlowAnalyzer.js +212 -0
  21. package/core/cli/UpdateCommand.js +188 -0
  22. package/core/cli/WalkthroughGenerator.js +195 -0
  23. package/core/cli/WatchCommand.js +129 -0
  24. package/core/cli/YamlRuleEngine.js +197 -0
  25. package/core/cli/data/rule-packs/aauth-visibility.json +117 -0
  26. package/core/cli/data/rule-packs/agentic-security-2026.json +180 -0
  27. package/core/cli/data/rule-packs/general-security.json +173 -0
  28. package/core/cli/data/rule-packs/owasp-mcp-2026.json +244 -0
  29. package/core/cli/data/rule-packs/toxic-flow-heuristics.json +21 -0
  30. package/core/cli/data/rule-sources.json +5 -0
  31. package/core/cli/data/secret-patterns.json +18 -0
  32. package/core/cli/data/tool-classifications.json +111 -0
  33. package/core/cli/data/toxic-flow-rules.json +47 -0
  34. package/core/cli/index.js +23 -0
  35. package/core/cli/output/Banner.js +52 -0
  36. package/core/cli/output/Formatter.js +183 -0
  37. package/core/cli/output/JsonFormatter.js +106 -0
  38. package/core/cli/output/index.js +16 -0
  39. package/core/cli/secureRegistryFetch.js +157 -0
  40. package/core/cli/symbols.js +16 -0
  41. package/core/configs/environment.js +3 -1
  42. package/core/configs/index.js +3 -64
  43. package/core/container/DependencyContainer.js +4 -1
  44. package/core/mcp-server/index.js +4 -1
  45. package/core/mcp-server/server/external/all.js +10 -3
  46. package/core/mcp-server/server/external/config.js +62 -5
  47. package/core/models/RequestFilters.js +3 -0
  48. package/core/repositories/PacketRepository.js +16 -0
  49. package/core/services/AuditService.js +2 -0
  50. package/core/services/ConfigService.js +9 -1
  51. package/core/services/ConfigTransformService.js +34 -2
  52. package/core/services/RequestService.js +58 -5
  53. package/core/services/ServerManagementService.js +59 -4
  54. package/core/services/security/StaticRulesService.js +69 -13
  55. package/core/services/security/TrafficAnalysisService.js +19 -1
  56. package/core/services/security/TrafficToxicFlowService.js +154 -0
  57. package/core/services/security/aauthGraph.js +199 -0
  58. package/core/services/security/aauthParser.js +274 -0
  59. package/core/services/security/aauthSelfTest.js +346 -0
  60. package/core/services/security/index.js +2 -1
  61. package/core/services/security/rules/index.js +25 -59
  62. package/core/services/security/rules/scans/configPermissions.js +91 -0
  63. package/core/services/security/rules/scans/duplicateToolNames.js +85 -0
  64. package/core/services/security/rules/scans/insecureTransport.js +148 -0
  65. package/core/services/security/rules/scans/missingContainment.js +123 -0
  66. package/core/services/security/rules/scans/shellEnvInjection.js +101 -0
  67. package/core/services/security/rules/scans/unsafeDefaults.js +99 -0
  68. package/core/services/security/toolsListFromTrafficParser.js +70 -0
  69. package/core/tui/App.js +144 -0
  70. package/core/tui/FindingsPanel.js +115 -0
  71. package/core/tui/FixPanel.js +132 -0
  72. package/core/tui/Header.js +51 -0
  73. package/core/tui/HelpBar.js +42 -0
  74. package/core/tui/ServersPanel.js +109 -0
  75. package/core/tui/ToxicFlowsPanel.js +100 -0
  76. package/core/tui/h.js +8 -0
  77. package/core/tui/index.js +11 -0
  78. package/core/tui/render.js +22 -0
  79. package/package.json +24 -16
  80. package/ui/dist/assets/index-D6zDrtMV.js +81 -0
  81. package/ui/dist/index.html +1 -1
  82. package/ui/server/controllers/AauthController.js +279 -0
  83. package/ui/server/controllers/RequestController.js +12 -1
  84. package/ui/server/controllers/SecurityFindingsController.js +46 -1
  85. package/ui/server/routes/aauth.js +18 -0
  86. package/ui/server/routes/requests.js +8 -1
  87. package/ui/server/routes/security.js +5 -1
  88. package/ui/server/setup.js +224 -6
  89. package/ui/server/swagger/paths/components.js +55 -0
  90. package/ui/server/swagger/paths/securityTrafficFlows.js +59 -0
  91. package/ui/server/swagger/paths.js +2 -2
  92. package/ui/server/swagger/swagger.js +5 -2
  93. package/ui/server.js +1 -1
  94. package/ui/src/App.jsx +26 -52
  95. package/ui/src/PacketFilters.jsx +31 -1
  96. package/ui/src/PacketList.jsx +2 -2
  97. package/ui/src/Security.jsx +10 -0
  98. package/ui/src/TabNavigation.jsx +8 -0
  99. package/ui/src/components/AAuthBadge.jsx +92 -0
  100. package/ui/src/components/AauthExplorer/AauthExplorerGraph.jsx +231 -0
  101. package/ui/src/components/AauthExplorer/AauthExplorerView.jsx +387 -0
  102. package/ui/src/components/AauthExplorer/NodeDetailPanel.jsx +272 -0
  103. package/ui/src/components/App/ActionMenu.jsx +4 -31
  104. package/ui/src/components/App/ApiDocsButton.jsx +0 -1
  105. package/ui/src/components/App/ShutdownButton.jsx +0 -1
  106. package/ui/src/components/App/useAppState.js +19 -26
  107. package/ui/src/components/DetailsTab/AAuthIdentitySection.jsx +119 -0
  108. package/ui/src/components/DetailsTab/RequestDetailsSection.jsx +2 -0
  109. package/ui/src/components/DetailsTab/ResponseDetailsSection.jsx +2 -0
  110. package/ui/src/components/DetectedPathsList.jsx +1 -5
  111. package/ui/src/components/FileInput.jsx +0 -1
  112. package/ui/src/components/PacketFilters/AAuthPostureFilter.jsx +81 -0
  113. package/ui/src/components/RequestRow/RequestRowMain.jsx +7 -1
  114. package/ui/src/components/Security/AAuthPosturePanel.jsx +360 -0
  115. package/ui/src/components/Security/ScannerContent.jsx +33 -1
  116. package/ui/src/components/Security/TrafficToxicFlowsPanel.jsx +253 -0
  117. package/ui/src/components/Security/securityApi.js +15 -0
  118. package/ui/src/components/Security/useSecurity.js +60 -3
  119. package/ui/src/components/ServerControl.jsx +0 -1
  120. package/ui/src/components/TabNavigation/DesktopTabs.jsx +0 -11
  121. package/ui/src/components/TabNavigationIcons.jsx +5 -0
  122. package/ui/src/components/ViewModeTabs.jsx +0 -1
  123. package/ui/src/utils/animations.js +26 -9
  124. package/core/services/security/rules/scans/agentic01GoalHijack.js +0 -130
  125. package/core/services/security/rules/scans/agentic02ToolMisuse.js +0 -129
  126. package/core/services/security/rules/scans/agentic03IdentityAbuse.js +0 -130
  127. package/core/services/security/rules/scans/agentic04SupplyChain.js +0 -130
  128. package/core/services/security/rules/scans/agentic06MemoryPoisoning.js +0 -130
  129. package/core/services/security/rules/scans/agentic07InsecureCommunication.js +0 -135
  130. package/core/services/security/rules/scans/agentic08CascadingFailures.js +0 -135
  131. package/core/services/security/rules/scans/agentic09TrustExploitation.js +0 -135
  132. package/core/services/security/rules/scans/agentic10RogueAgent.js +0 -130
  133. package/core/services/security/rules/scans/hardcodedSecrets.js +0 -130
  134. package/core/services/security/rules/scans/mcp01TokenMismanagement.js +0 -127
  135. package/core/services/security/rules/scans/mcp02ScopeCreep.js +0 -130
  136. package/core/services/security/rules/scans/mcp03ToolPoisoning.js +0 -132
  137. package/core/services/security/rules/scans/mcp04SupplyChain.js +0 -131
  138. package/core/services/security/rules/scans/mcp06PromptInjection.js +0 -200
  139. package/core/services/security/rules/scans/mcp07InsufficientAuth.js +0 -130
  140. package/core/services/security/rules/scans/mcp08LackAudit.js +0 -129
  141. package/core/services/security/rules/scans/mcp09ShadowServers.js +0 -129
  142. package/core/services/security/rules/scans/mcp10ContextInjection.js +0 -130
  143. package/ui/dist/assets/index-CiCSDYf-.js +0 -97
  144. package/ui/server/routes/help.js +0 -44
  145. package/ui/server/swagger/paths/help.js +0 -82
  146. package/ui/src/HelpGuide/HelpGuideContent.jsx +0 -118
  147. package/ui/src/HelpGuide/HelpGuideFooter.jsx +0 -59
  148. package/ui/src/HelpGuide/HelpGuideHeader.jsx +0 -57
  149. package/ui/src/HelpGuide.jsx +0 -78
  150. package/ui/src/IntroTour.jsx +0 -154
  151. package/ui/src/components/App/HelpButton.jsx +0 -90
  152. package/ui/src/components/TourOverlay.jsx +0 -117
  153. package/ui/src/components/TourTooltip/TourTooltipButtons.jsx +0 -120
  154. package/ui/src/components/TourTooltip/TourTooltipHeader.jsx +0 -71
  155. package/ui/src/components/TourTooltip/TourTooltipIcons.jsx +0 -54
  156. package/ui/src/components/TourTooltip/useTooltipPosition.js +0 -135
  157. package/ui/src/components/TourTooltip.jsx +0 -91
  158. package/ui/src/config/tourSteps.jsx +0 -140
@@ -0,0 +1,346 @@
1
+ /**
2
+ * AAuth self-test traffic synthesizer.
3
+ *
4
+ * Generates realistic AAuth-shaped HTTP packets and writes them through
5
+ * mcp-shark's normal audit path (AuditService.logRequestPacket /
6
+ * logResponsePacket) so they look identical to live captured traffic.
7
+ *
8
+ * Strict scope:
9
+ * - Pure observability fixture. No real outbound network calls.
10
+ * - No cryptographic signing. The Signature/Signature-Input bytes are
11
+ * placeholders meant to be visually inspected, not validated.
12
+ * - The point is to populate the Traffic, Posture, and Explorer views so
13
+ * a developer can see how AAuth signals are surfaced even before any
14
+ * production agent integration is in place.
15
+ */
16
+
17
+ import { readFileSync } from 'node:fs';
18
+ import { homedir } from 'node:os';
19
+ import { join } from 'node:path';
20
+
21
+ const KNOWN_AGENTS = [
22
+ { id: 'aauth:demo-agent@example.dev', alg: 'ed25519', keyid: 'demo-key-1' },
23
+ { id: 'aauth:read-only-agent@example.dev', alg: 'ecdsa-p256-sha256', keyid: 'read-key-1' },
24
+ { id: 'aauth:writer-agent@example.dev', alg: 'ed25519', keyid: 'writer-key-1' },
25
+ ];
26
+
27
+ const KNOWN_MISSIONS = [
28
+ 'mission:lookup-customer-record',
29
+ 'mission:summarize-incident-2026-04',
30
+ 'mission:reset-staging-database',
31
+ ];
32
+
33
+ const KNOWN_TOOLS = ['lookup_record', 'summarize_incident', 'protected_query', 'echo'];
34
+
35
+ const ACCESS_MODES = [
36
+ { mode: 'ps-managed', resource: 'https://api.example.com/records' },
37
+ { mode: 'identity-based', resource: 'https://api.example.com/profiles' },
38
+ { mode: 'federated', resource: 'https://api.partner.example.com/exchange' },
39
+ ];
40
+
41
+ const PLACEHOLDER_SIG =
42
+ 'sig1=:MEUCIQDxPLACEHOLDER0000000000000000000000000000000000000000AAAAAAA=:';
43
+ const PLACEHOLDER_KEY_THUMBPRINT = 'sha-256=:AbCdEfGhIjKlMnOpQrStUvWxYz0123456789AAAAAAAA=:';
44
+
45
+ function pick(list, i) {
46
+ return list[i % list.length];
47
+ }
48
+
49
+ function buildSignatureInput(agent) {
50
+ const created = Math.floor(Date.now() / 1000);
51
+ return `sig1=("@method" "@target-uri" "host" "content-digest");keyid="${agent.keyid}";alg="${agent.alg}";created=${created}`;
52
+ }
53
+
54
+ function buildJsonRpcRequest(id, toolName, missionId) {
55
+ return {
56
+ jsonrpc: '2.0',
57
+ id,
58
+ method: 'tools/call',
59
+ params: {
60
+ name: toolName,
61
+ arguments: { mission: missionId, ts: new Date().toISOString() },
62
+ },
63
+ };
64
+ }
65
+
66
+ function buildJsonRpcResponse(id, toolName) {
67
+ return {
68
+ jsonrpc: '2.0',
69
+ id,
70
+ result: {
71
+ content: [{ type: 'text', text: `synthetic ${toolName} response` }],
72
+ },
73
+ };
74
+ }
75
+
76
+ function readMcpsConfigSafely() {
77
+ try {
78
+ const p = join(homedir(), '.mcp-shark', 'mcps.json');
79
+ const raw = readFileSync(p, 'utf8');
80
+ const parsed = JSON.parse(raw);
81
+ return parsed && typeof parsed.servers === 'object' ? parsed.servers : {};
82
+ } catch {
83
+ return {};
84
+ }
85
+ }
86
+
87
+ /**
88
+ * Auto-detect HTTP MCP upstreams from ~/.mcp-shark/mcps.json. We don't actually
89
+ * call them — we use the names/URLs to make synthetic packets feel grounded
90
+ * to the developer ("oh, that's the upstream I configured").
91
+ */
92
+ export function detectHttpUpstreams() {
93
+ const servers = readMcpsConfigSafely();
94
+ const detected = [];
95
+ for (const [name, def] of Object.entries(servers)) {
96
+ if (!def || typeof def !== 'object') {
97
+ continue;
98
+ }
99
+ if (def.type === 'http' && typeof def.url === 'string') {
100
+ detected.push({ name, url: def.url });
101
+ }
102
+ }
103
+ return detected;
104
+ }
105
+
106
+ const SCENARIOS = [
107
+ {
108
+ id: 'signed',
109
+ label: 'Signed AAuth call',
110
+ posture: 'signed',
111
+ buildHeaders(ctx) {
112
+ return {
113
+ host: ctx.host,
114
+ 'content-type': 'application/json',
115
+ 'user-agent': 'mcp-shark-self-test/1.0',
116
+ signature: PLACEHOLDER_SIG,
117
+ 'signature-input': buildSignatureInput(ctx.agent),
118
+ 'signature-key': PLACEHOLDER_KEY_THUMBPRINT,
119
+ 'aauth-agent': ctx.agent.id,
120
+ 'aauth-mission': ctx.mission,
121
+ };
122
+ },
123
+ buildResponseHeaders(ctx) {
124
+ return {
125
+ 'content-type': 'application/json',
126
+ signature: PLACEHOLDER_SIG,
127
+ 'signature-input': buildSignatureInput(ctx.agent),
128
+ 'signature-key': PLACEHOLDER_KEY_THUMBPRINT,
129
+ 'aauth-agent': ctx.agent.id,
130
+ 'aauth-mission': ctx.mission,
131
+ };
132
+ },
133
+ statusCode: 200,
134
+ },
135
+ {
136
+ id: 'challenge',
137
+ label: 'AAuth challenge (401)',
138
+ posture: 'none-then-aware',
139
+ buildHeaders(ctx) {
140
+ return {
141
+ host: ctx.host,
142
+ 'content-type': 'application/json',
143
+ 'user-agent': 'mcp-shark-self-test/1.0',
144
+ };
145
+ },
146
+ buildResponseHeaders(ctx) {
147
+ const access = ctx.access;
148
+ return {
149
+ 'content-type': 'application/json',
150
+ 'aauth-requirement': `mode="${access.mode}"; resource="${access.resource}"; jwks_uri="https://example.com/.well-known/aauth/jwks.json"`,
151
+ 'www-authenticate': `AAuth realm="example", mode="${access.mode}", resource="${access.resource}"`,
152
+ };
153
+ },
154
+ statusCode: 401,
155
+ },
156
+ {
157
+ id: 'bearer',
158
+ label: 'Bearer token call',
159
+ posture: 'bearer',
160
+ buildHeaders(ctx) {
161
+ return {
162
+ host: ctx.host,
163
+ 'content-type': 'application/json',
164
+ 'user-agent': 'mcp-shark-self-test/1.0',
165
+ authorization: 'Bearer demo-token-must-be-rotated-9c4f2a',
166
+ };
167
+ },
168
+ buildResponseHeaders() {
169
+ return {
170
+ 'content-type': 'application/json',
171
+ 'www-authenticate': 'Bearer realm="example"',
172
+ };
173
+ },
174
+ statusCode: 200,
175
+ },
176
+ {
177
+ id: 'bearer-coexist',
178
+ label: 'Bearer + AAuth (drift smell)',
179
+ posture: 'bearer',
180
+ buildHeaders(ctx) {
181
+ return {
182
+ host: ctx.host,
183
+ 'content-type': 'application/json',
184
+ 'user-agent': 'mcp-shark-self-test/1.0',
185
+ authorization: 'Bearer legacy-token-rotation-required',
186
+ 'signature-input': buildSignatureInput(ctx.agent),
187
+ signature: PLACEHOLDER_SIG,
188
+ 'aauth-agent': ctx.agent.id,
189
+ };
190
+ },
191
+ buildResponseHeaders() {
192
+ return { 'content-type': 'application/json' };
193
+ },
194
+ statusCode: 200,
195
+ },
196
+ ];
197
+
198
+ /**
199
+ * Run a single self-test pass. Synthesizes scenario-driven JSON-RPC traffic
200
+ * for each detected upstream (or a default placeholder triple if none are
201
+ * configured) and writes both request and response packets via the audit
202
+ * service so they appear as normal captured traffic.
203
+ *
204
+ * @param {object} deps
205
+ * @param {object} deps.auditService - mcp-shark AuditService
206
+ * @param {object} deps.logger - pino-style logger
207
+ * @param {object} [opts]
208
+ * @param {number} [opts.rounds=2] - how many rounds (each round runs every scenario once per upstream)
209
+ * @returns {{
210
+ * rounds: number,
211
+ * targets: Array<{name:string,url:string}>,
212
+ * inserted: number,
213
+ * by_scenario: Record<string, number>,
214
+ * by_posture: Record<string, number>,
215
+ * started_at_iso: string,
216
+ * finished_at_iso: string,
217
+ * }}
218
+ */
219
+ export function runAauthSelfTest({ auditService, logger }, opts = {}) {
220
+ const rounds = Math.max(1, Math.min(10, Number(opts.rounds) || 2));
221
+ const startedAt = new Date();
222
+
223
+ let upstreams = detectHttpUpstreams();
224
+ if (upstreams.length === 0) {
225
+ upstreams = [
226
+ { name: 'aauth-signed', url: 'http://127.0.0.1:9701/mcp' },
227
+ { name: 'aauth-challenge', url: 'http://127.0.0.1:9702/mcp' },
228
+ { name: 'aauth-bearer', url: 'http://127.0.0.1:9703/mcp' },
229
+ ];
230
+ }
231
+
232
+ let inserted = 0;
233
+ const byScenario = {};
234
+ const byPosture = {};
235
+ let nextJsonRpcId = Date.now();
236
+
237
+ for (let round = 0; round < rounds; round++) {
238
+ for (let u = 0; u < upstreams.length; u++) {
239
+ const upstream = upstreams[u];
240
+ const url = upstream.url;
241
+ let host;
242
+ try {
243
+ host = new URL(url).host;
244
+ } catch {
245
+ host = `${upstream.name}.local`;
246
+ }
247
+
248
+ const upstreamScenarios = chooseScenariosForUpstream(upstream.name);
249
+
250
+ for (const scenario of upstreamScenarios) {
251
+ const agent = pick(KNOWN_AGENTS, round + u);
252
+ const mission = pick(KNOWN_MISSIONS, round + u + scenario.id.length);
253
+ const access = pick(ACCESS_MODES, round + u);
254
+ const tool = pick(KNOWN_TOOLS, round + u);
255
+ const ctx = { agent, mission, access, host, upstream };
256
+
257
+ const jsonrpcId = `self-test-${nextJsonRpcId++}`;
258
+ const reqBody = buildJsonRpcRequest(jsonrpcId, tool, mission);
259
+ const reqHeaders = scenario.buildHeaders(ctx);
260
+
261
+ try {
262
+ auditService.logRequestPacket({
263
+ method: 'POST',
264
+ url,
265
+ headers: reqHeaders,
266
+ body: reqBody,
267
+ userAgent: reqHeaders['user-agent'] || 'mcp-shark-self-test/1.0',
268
+ remoteAddress: '127.0.0.1',
269
+ });
270
+ } catch (err) {
271
+ logger?.warn?.(
272
+ { error: err.message, scenario: scenario.id },
273
+ 'self-test request log failed'
274
+ );
275
+ continue;
276
+ }
277
+
278
+ const respBody =
279
+ scenario.statusCode === 401
280
+ ? {
281
+ jsonrpc: '2.0',
282
+ id: jsonrpcId,
283
+ error: { code: -32001, message: 'AAuth signature required' },
284
+ }
285
+ : buildJsonRpcResponse(jsonrpcId, tool);
286
+ const respHeaders = scenario.buildResponseHeaders(ctx);
287
+ respHeaders.host = host;
288
+
289
+ try {
290
+ auditService.logResponsePacket({
291
+ statusCode: scenario.statusCode,
292
+ headers: respHeaders,
293
+ body: respBody,
294
+ jsonrpcId,
295
+ userAgent: reqHeaders['user-agent'] || 'mcp-shark-self-test/1.0',
296
+ remoteAddress: '127.0.0.1',
297
+ });
298
+ } catch (err) {
299
+ logger?.warn?.(
300
+ { error: err.message, scenario: scenario.id },
301
+ 'self-test response log failed'
302
+ );
303
+ continue;
304
+ }
305
+
306
+ inserted += 2;
307
+ byScenario[scenario.id] = (byScenario[scenario.id] || 0) + 1;
308
+ byPosture[scenario.posture] = (byPosture[scenario.posture] || 0) + 1;
309
+ }
310
+ }
311
+ }
312
+
313
+ const finishedAt = new Date();
314
+ return {
315
+ rounds,
316
+ targets: upstreams,
317
+ inserted,
318
+ by_scenario: byScenario,
319
+ by_posture: byPosture,
320
+ started_at_iso: startedAt.toISOString(),
321
+ finished_at_iso: finishedAt.toISOString(),
322
+ };
323
+ }
324
+
325
+ /**
326
+ * Pick the scenarios that match an upstream's apparent role. Server names
327
+ * containing well-known substrings get tailored scenarios so the resulting
328
+ * graph is intuitive (signed-server → signed scenario). Anything else gets
329
+ * the full mix.
330
+ */
331
+ function chooseScenariosForUpstream(name) {
332
+ const lc = (name || '').toLowerCase();
333
+ if (lc.includes('signed')) {
334
+ return SCENARIOS.filter((s) => s.id === 'signed');
335
+ }
336
+ if (lc.includes('challenge')) {
337
+ return SCENARIOS.filter((s) => s.id === 'challenge');
338
+ }
339
+ if (lc.includes('bearer') && lc.includes('coexist')) {
340
+ return SCENARIOS.filter((s) => s.id === 'bearer-coexist');
341
+ }
342
+ if (lc.includes('bearer')) {
343
+ return SCENARIOS.filter((s) => s.id === 'bearer');
344
+ }
345
+ return SCENARIOS;
346
+ }
@@ -2,9 +2,10 @@
2
2
  * Security services exports
3
3
  * All security-related services for OWASP vulnerability detection
4
4
  */
5
- export { StaticRulesService } from './StaticRulesService.js';
5
+ export { StaticRulesService, resetStaticRulesCache } from './StaticRulesService.js';
6
6
  export { SecurityDetectionService } from './SecurityDetectionService.js';
7
7
  export { TrafficAnalysisService } from './TrafficAnalysisService.js';
8
+ export { TrafficToxicFlowService } from './TrafficToxicFlowService.js';
8
9
  export { YaraEngineService } from './YaraEngineService.js';
9
10
  export { RulesManagerService } from './RulesManagerService.js';
10
11
  export * from './YaraMatchConverter.js';
@@ -1,71 +1,37 @@
1
1
  /**
2
- * Static security rules index
2
+ * Static security rules index — AUTO-GENERATED
3
+ * Do not edit manually. Run: npm run generate:rules
4
+ *
3
5
  * Exports all OWASP MCP Top 10 + Agentic Security + General security rules
4
6
  */
5
7
 
6
- // MCP OWASP Top 10 Rules
7
- import * as mcp01 from './scans/mcp01TokenMismanagement.js';
8
- import * as mcp02 from './scans/mcp02ScopeCreep.js';
9
- import * as mcp03 from './scans/mcp03ToolPoisoning.js';
10
- import * as mcp04 from './scans/mcp04SupplyChain.js';
11
- import * as mcp05 from './scans/mcp05CommandInjection.js';
12
- import * as mcp06 from './scans/mcp06PromptInjection.js';
13
- import * as mcp07 from './scans/mcp07InsufficientAuth.js';
14
- import * as mcp08 from './scans/mcp08LackAudit.js';
15
- import * as mcp09 from './scans/mcp09ShadowServers.js';
16
- import * as mcp10 from './scans/mcp10ContextInjection.js';
17
-
18
- // Agentic Security Initiative (ASI) Rules
19
- import * as asi01 from './scans/agentic01GoalHijack.js';
20
- import * as asi02 from './scans/agentic02ToolMisuse.js';
21
- import * as asi03 from './scans/agentic03IdentityAbuse.js';
22
- import * as asi04 from './scans/agentic04SupplyChain.js';
23
- import * as asi05 from './scans/agentic05RCE.js';
24
- import * as asi06 from './scans/agentic06MemoryPoisoning.js';
25
- import * as asi07 from './scans/agentic07InsecureCommunication.js';
26
- import * as asi08 from './scans/agentic08CascadingFailures.js';
27
- import * as asi09 from './scans/agentic09TrustExploitation.js';
28
- import * as asi10 from './scans/agentic10RogueAgent.js';
29
-
30
- // General Security Rules
31
- import * as cmdInj from './scans/commandInjection.js';
32
- import * as crossServer from './scans/crossServerShadowing.js';
33
- import * as secrets from './scans/hardcodedSecrets.js';
34
- import * as nameAmbig from './scans/toolNameAmbiguity.js';
8
+ import * as agentic05RCE from './scans/agentic05RCE.js';
9
+ import * as commandInjection from './scans/commandInjection.js';
10
+ import * as configPermissions from './scans/configPermissions.js';
11
+ import * as crossServerShadowing from './scans/crossServerShadowing.js';
12
+ import * as duplicateToolNames from './scans/duplicateToolNames.js';
13
+ import * as insecureTransport from './scans/insecureTransport.js';
14
+ import * as mcp05CommandInjection from './scans/mcp05CommandInjection.js';
15
+ import * as missingContainment from './scans/missingContainment.js';
16
+ import * as shellEnvInjection from './scans/shellEnvInjection.js';
17
+ import * as toolNameAmbiguity from './scans/toolNameAmbiguity.js';
18
+ import * as unsafeDefaults from './scans/unsafeDefaults.js';
35
19
 
36
20
  /**
37
21
  * All available static rules with their analysis functions
38
22
  */
39
23
  export const staticRules = {
40
- // MCP OWASP Top 10
41
- 'mcp01-token-mismanagement': mcp01,
42
- 'mcp02-scope-creep': mcp02,
43
- 'mcp03-tool-poisoning': mcp03,
44
- 'mcp04-supply-chain': mcp04,
45
- 'mcp05-command-injection': mcp05,
46
- 'mcp06-prompt-injection': mcp06,
47
- 'mcp07-insufficient-auth': mcp07,
48
- 'mcp08-lack-audit': mcp08,
49
- 'mcp09-shadow-servers': mcp09,
50
- 'mcp10-context-injection': mcp10,
51
-
52
- // Agentic Security Initiative
53
- 'asi01-goal-hijack': asi01,
54
- 'asi02-tool-misuse': asi02,
55
- 'asi03-identity-abuse': asi03,
56
- 'asi04-supply-chain': asi04,
57
- 'asi05-rce': asi05,
58
- 'asi06-memory-poisoning': asi06,
59
- 'asi07-insecure-communication': asi07,
60
- 'asi08-cascading-failures': asi08,
61
- 'asi09-trust-exploitation': asi09,
62
- 'asi10-rogue-agent': asi10,
63
-
64
- // General Security
65
- 'command-injection': cmdInj,
66
- 'cross-server-shadowing': crossServer,
67
- 'hardcoded-secrets': secrets,
68
- 'tool-name-ambiguity': nameAmbig,
24
+ [agentic05RCE.ruleMetadata.id]: agentic05RCE,
25
+ [commandInjection.ruleMetadata.id]: commandInjection,
26
+ [configPermissions.ruleMetadata.id]: configPermissions,
27
+ [crossServerShadowing.ruleMetadata.id]: crossServerShadowing,
28
+ [duplicateToolNames.ruleMetadata.id]: duplicateToolNames,
29
+ [insecureTransport.ruleMetadata.id]: insecureTransport,
30
+ [mcp05CommandInjection.ruleMetadata.id]: mcp05CommandInjection,
31
+ [missingContainment.ruleMetadata.id]: missingContainment,
32
+ [shellEnvInjection.ruleMetadata.id]: shellEnvInjection,
33
+ [toolNameAmbiguity.ruleMetadata.id]: toolNameAmbiguity,
34
+ [unsafeDefaults.ruleMetadata.id]: unsafeDefaults,
69
35
  };
70
36
 
71
37
  /**
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Config File Permissions Check
3
+ * Detects world-readable MCP config files that may expose secrets.
4
+ * Only applicable on Unix-like systems.
5
+ */
6
+ import { createRuleAdapter } from '../utils/adapter.js';
7
+
8
+ const RULE_ID = 'config-permissions';
9
+ const OWASP_ID = 'MCP01';
10
+ const RECOMMENDATION =
11
+ 'Set config file permissions to 600 (owner read/write only). Run: npx mcp-shark scan --fix';
12
+
13
+ export function scanConfigPermissions(_mcpData = {}) {
14
+ return {
15
+ toolFindings: [],
16
+ resourceFindings: [],
17
+ promptFindings: [],
18
+ notablePatterns: [],
19
+ recommendations: [RECOMMENDATION],
20
+ };
21
+ }
22
+
23
+ /**
24
+ * Analyze a config file path for permission issues
25
+ * Called directly by ScanService, not through the adapter
26
+ * @param {string} configPath - Path to the config file
27
+ * @param {string} permissions - Octal permission string (e.g., '644')
28
+ * @returns {Array} Findings
29
+ */
30
+ export function analyzeConfigPermissions(configPath, permissions) {
31
+ if (process.platform === 'win32') {
32
+ return [];
33
+ }
34
+ if (!permissions) {
35
+ return [];
36
+ }
37
+
38
+ const perms = Number.parseInt(permissions, 8);
39
+ const worldReadable = (perms & 0o004) !== 0;
40
+ const groupReadable = (perms & 0o040) !== 0;
41
+
42
+ const findings = [];
43
+
44
+ if (worldReadable) {
45
+ findings.push({
46
+ rule_id: RULE_ID,
47
+ severity: 'high',
48
+ owasp_id: OWASP_ID,
49
+ title: `Config file is world-readable (${permissions})`,
50
+ description: `${configPath} has permissions ${permissions} — any user on this system can read your MCP secrets.`,
51
+ recommendation: RECOMMENDATION,
52
+ config_path: configPath,
53
+ confidence: 'definite',
54
+ fixable: true,
55
+ fix_type: 'chmod',
56
+ fix_data: { oldPerms: permissions },
57
+ });
58
+ } else if (groupReadable) {
59
+ findings.push({
60
+ rule_id: RULE_ID,
61
+ severity: 'medium',
62
+ owasp_id: OWASP_ID,
63
+ title: `Config file is group-readable (${permissions})`,
64
+ description: `${configPath} has permissions ${permissions} — group members can read your MCP secrets.`,
65
+ recommendation: RECOMMENDATION,
66
+ config_path: configPath,
67
+ confidence: 'probable',
68
+ fixable: true,
69
+ fix_type: 'chmod',
70
+ fix_data: { oldPerms: permissions },
71
+ });
72
+ }
73
+
74
+ return findings;
75
+ }
76
+
77
+ const adapter = createRuleAdapter(scanConfigPermissions, RULE_ID, OWASP_ID, RECOMMENDATION);
78
+ export const analyzeTool = adapter.analyzeTool;
79
+ export const analyzePrompt = adapter.analyzePrompt;
80
+ export const analyzeResource = adapter.analyzeResource;
81
+ export const analyzePacket = adapter.analyzePacket;
82
+
83
+ export const ruleMetadata = {
84
+ id: RULE_ID,
85
+ name: 'Config File Permissions',
86
+ owasp_id: OWASP_ID,
87
+ severity: 'high',
88
+ description: 'Detects world-readable or group-readable MCP configuration files.',
89
+ source: 'static',
90
+ type: 'general-security',
91
+ };
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Duplicate Tool Names Detection (Config-Level)
3
+ * Detects tools with identical names across different servers,
4
+ * which enables cross-server shadowing attacks.
5
+ * Catalog reference: §1.4 (tool name collision)
6
+ */
7
+ import { createRuleAdapter } from '../utils/adapter.js';
8
+
9
+ const RULE_ID = 'duplicate-tool-names';
10
+ const OWASP_ID = 'MCP09';
11
+ const RECOMMENDATION =
12
+ 'Rename tools to be unique across all servers, or isolate conflicting servers into separate agent sessions.';
13
+
14
+ export function scanDuplicateToolNames(_mcpData = {}) {
15
+ return {
16
+ toolFindings: [],
17
+ resourceFindings: [],
18
+ promptFindings: [],
19
+ notablePatterns: [],
20
+ recommendations: [RECOMMENDATION],
21
+ };
22
+ }
23
+
24
+ /**
25
+ * Analyze all servers for duplicate tool names
26
+ * Called directly by ScanService with the full server list
27
+ * @param {Array} servers - Flat array of server objects
28
+ * @returns {Array} Findings for duplicate tool names
29
+ */
30
+ export function analyzeAllServerToolNames(servers) {
31
+ const toolMap = new Map();
32
+
33
+ for (const server of servers) {
34
+ const tools = Array.isArray(server.tools) ? server.tools : [];
35
+ for (const tool of tools) {
36
+ const name = typeof tool === 'string' ? tool : tool?.name;
37
+ if (!name) {
38
+ continue;
39
+ }
40
+ if (!toolMap.has(name)) {
41
+ toolMap.set(name, []);
42
+ }
43
+ toolMap.get(name).push(server.name);
44
+ }
45
+ }
46
+
47
+ const findings = [];
48
+ for (const [toolName, serverNames] of toolMap) {
49
+ if (serverNames.length <= 1) {
50
+ continue;
51
+ }
52
+ const uniqueServers = [...new Set(serverNames)];
53
+ if (uniqueServers.length <= 1) {
54
+ continue;
55
+ }
56
+
57
+ findings.push({
58
+ rule_id: RULE_ID,
59
+ severity: 'high',
60
+ owasp_id: OWASP_ID,
61
+ title: `Duplicate tool "${toolName}" across ${uniqueServers.length} servers`,
62
+ description: `Tool "${toolName}" exists in ${uniqueServers.join(', ')}. An attacker could shadow one server's tool with another.`,
63
+ recommendation: RECOMMENDATION,
64
+ confidence: 'definite',
65
+ });
66
+ }
67
+
68
+ return findings;
69
+ }
70
+
71
+ const adapter = createRuleAdapter(scanDuplicateToolNames, RULE_ID, OWASP_ID, RECOMMENDATION);
72
+ export const analyzeTool = adapter.analyzeTool;
73
+ export const analyzePrompt = adapter.analyzePrompt;
74
+ export const analyzeResource = adapter.analyzeResource;
75
+ export const analyzePacket = adapter.analyzePacket;
76
+
77
+ export const ruleMetadata = {
78
+ id: RULE_ID,
79
+ name: 'Duplicate Tool Names',
80
+ owasp_id: OWASP_ID,
81
+ severity: 'high',
82
+ description: 'Detects identical tool names across servers enabling shadowing attacks.',
83
+ source: 'static',
84
+ type: 'general-security',
85
+ };