@mcp-shark/mcp-shark 1.5.13 → 1.6.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 (100) hide show
  1. package/README.md +396 -59
  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 +224 -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/agentic-security-2026.json +180 -0
  26. package/core/cli/data/rule-packs/general-security.json +173 -0
  27. package/core/cli/data/rule-packs/owasp-mcp-2026.json +244 -0
  28. package/core/cli/data/rule-packs/toxic-flow-heuristics.json +21 -0
  29. package/core/cli/data/rule-sources.json +5 -0
  30. package/core/cli/data/secret-patterns.json +18 -0
  31. package/core/cli/data/tool-classifications.json +111 -0
  32. package/core/cli/data/toxic-flow-rules.json +47 -0
  33. package/core/cli/index.js +23 -0
  34. package/core/cli/output/Banner.js +52 -0
  35. package/core/cli/output/Formatter.js +183 -0
  36. package/core/cli/output/JsonFormatter.js +106 -0
  37. package/core/cli/output/index.js +16 -0
  38. package/core/cli/secureRegistryFetch.js +157 -0
  39. package/core/cli/symbols.js +16 -0
  40. package/core/container/DependencyContainer.js +4 -1
  41. package/core/repositories/PacketRepository.js +16 -0
  42. package/core/services/AuditService.js +2 -0
  43. package/core/services/security/StaticRulesService.js +69 -13
  44. package/core/services/security/TrafficAnalysisService.js +19 -1
  45. package/core/services/security/TrafficToxicFlowService.js +154 -0
  46. package/core/services/security/index.js +2 -1
  47. package/core/services/security/rules/index.js +25 -59
  48. package/core/services/security/rules/scans/configPermissions.js +91 -0
  49. package/core/services/security/rules/scans/duplicateToolNames.js +85 -0
  50. package/core/services/security/rules/scans/insecureTransport.js +148 -0
  51. package/core/services/security/rules/scans/missingContainment.js +123 -0
  52. package/core/services/security/rules/scans/shellEnvInjection.js +101 -0
  53. package/core/services/security/rules/scans/unsafeDefaults.js +99 -0
  54. package/core/services/security/toolsListFromTrafficParser.js +70 -0
  55. package/core/tui/App.js +144 -0
  56. package/core/tui/FindingsPanel.js +115 -0
  57. package/core/tui/FixPanel.js +132 -0
  58. package/core/tui/Header.js +51 -0
  59. package/core/tui/HelpBar.js +42 -0
  60. package/core/tui/ServersPanel.js +109 -0
  61. package/core/tui/ToxicFlowsPanel.js +100 -0
  62. package/core/tui/h.js +8 -0
  63. package/core/tui/index.js +11 -0
  64. package/core/tui/render.js +22 -0
  65. package/package.json +24 -16
  66. package/ui/dist/assets/{index-CiCSDYf-.js → index-Buah4fNS.js} +32 -32
  67. package/ui/dist/index.html +1 -1
  68. package/ui/server/controllers/RequestController.js +9 -1
  69. package/ui/server/controllers/SecurityFindingsController.js +46 -1
  70. package/ui/server/routes/requests.js +8 -1
  71. package/ui/server/routes/security.js +5 -1
  72. package/ui/server/setup.js +2 -0
  73. package/ui/server/swagger/paths/components.js +55 -0
  74. package/ui/server/swagger/paths/securityTrafficFlows.js +59 -0
  75. package/ui/server/swagger/paths.js +2 -0
  76. package/ui/server/swagger/swagger.js +5 -1
  77. package/ui/src/Security.jsx +10 -0
  78. package/ui/src/components/Security/ScannerContent.jsx +30 -1
  79. package/ui/src/components/Security/TrafficToxicFlowsPanel.jsx +253 -0
  80. package/ui/src/components/Security/securityApi.js +15 -0
  81. package/ui/src/components/Security/useSecurity.js +60 -3
  82. package/core/services/security/rules/scans/agentic01GoalHijack.js +0 -130
  83. package/core/services/security/rules/scans/agentic02ToolMisuse.js +0 -129
  84. package/core/services/security/rules/scans/agentic03IdentityAbuse.js +0 -130
  85. package/core/services/security/rules/scans/agentic04SupplyChain.js +0 -130
  86. package/core/services/security/rules/scans/agentic06MemoryPoisoning.js +0 -130
  87. package/core/services/security/rules/scans/agentic07InsecureCommunication.js +0 -135
  88. package/core/services/security/rules/scans/agentic08CascadingFailures.js +0 -135
  89. package/core/services/security/rules/scans/agentic09TrustExploitation.js +0 -135
  90. package/core/services/security/rules/scans/agentic10RogueAgent.js +0 -130
  91. package/core/services/security/rules/scans/hardcodedSecrets.js +0 -130
  92. package/core/services/security/rules/scans/mcp01TokenMismanagement.js +0 -127
  93. package/core/services/security/rules/scans/mcp02ScopeCreep.js +0 -130
  94. package/core/services/security/rules/scans/mcp03ToolPoisoning.js +0 -132
  95. package/core/services/security/rules/scans/mcp04SupplyChain.js +0 -131
  96. package/core/services/security/rules/scans/mcp06PromptInjection.js +0 -200
  97. package/core/services/security/rules/scans/mcp07InsufficientAuth.js +0 -130
  98. package/core/services/security/rules/scans/mcp08LackAudit.js +0 -129
  99. package/core/services/security/rules/scans/mcp09ShadowServers.js +0 -129
  100. package/core/services/security/rules/scans/mcp10ContextInjection.js +0 -130
@@ -0,0 +1,16 @@
1
+ /**
2
+ * CLI Symbol Vocabulary
3
+ * Geometric symbols — consistent across all output.
4
+ * Inspired by Biome/Astro/@clack aesthetic.
5
+ */
6
+
7
+ export const S = {
8
+ pass: '◇',
9
+ fail: '◆',
10
+ warn: '▲',
11
+ info: '│',
12
+ dot: '·',
13
+ bar: '─',
14
+ arrow: '→',
15
+ pointer: '▸',
16
+ };
@@ -33,6 +33,7 @@ import {
33
33
  StatisticsService,
34
34
  TokenService,
35
35
  TrafficAnalysisService,
36
+ TrafficToxicFlowService,
36
37
  YaraEngineService,
37
38
  } from '#core/services/index.js';
38
39
  import { ConfigParserFactory } from '#core/services/parsers/ConfigParserFactory.js';
@@ -160,10 +161,12 @@ export class DependencyContainer {
160
161
  this._services.yaraEngine,
161
162
  libs.logger
162
163
  );
164
+ this._services.trafficToxicFlow = new TrafficToxicFlowService(repos.packet, libs.logger);
163
165
  this._services.trafficAnalysis = new TrafficAnalysisService(
164
166
  this._services.staticRules,
165
167
  repos.securityFindings,
166
- libs.logger
168
+ libs.logger,
169
+ this._services.trafficToxicFlow
167
170
  );
168
171
  this._services.rulesManager = new RulesManagerService(
169
172
  repos.securityRules,
@@ -234,4 +234,20 @@ export class PacketRepository {
234
234
  const stmt = this.db.prepare('SELECT MAX(timestamp_ns) as max_ts FROM packets');
235
235
  return stmt.get();
236
236
  }
237
+
238
+ /**
239
+ * Response packets whose JSON-RPC result likely includes a tools array (tools/list success).
240
+ * Used to replay cross-server toxic-flow analysis from captured proxy traffic.
241
+ */
242
+ listResponsesWithToolsList() {
243
+ const stmt = this.db.prepare(`
244
+ SELECT frame_number, session_id, remote_address, jsonrpc_result, body_json, body_raw, timestamp_ns
245
+ FROM packets
246
+ WHERE direction = 'response'
247
+ AND jsonrpc_result IS NOT NULL
248
+ AND jsonrpc_result LIKE '%"tools"%'
249
+ ORDER BY timestamp_ns ASC
250
+ `);
251
+ return stmt.all();
252
+ }
237
253
  }
@@ -97,6 +97,7 @@ export class AuditService {
97
97
  frameNumber: result.frameNumber,
98
98
  body: options.body,
99
99
  sessionId: result.sessionId,
100
+ mcpServerName: options.remoteAddress || null,
100
101
  });
101
102
  } catch (error) {
102
103
  // Log but don't fail the request
@@ -172,6 +173,7 @@ export class AuditService {
172
173
  frameNumber: result.frameNumber,
173
174
  body: options.body,
174
175
  sessionId: result.sessionId,
176
+ mcpServerName: options.remoteAddress || null,
175
177
  });
176
178
  } catch (error) {
177
179
  // Log but don't fail the response
@@ -1,9 +1,72 @@
1
1
  /**
2
2
  * Static Rules Service
3
- * Executes pattern-based security rules against MCP server definitions and traffic
3
+ * Executes pattern-based security rules against MCP server definitions and traffic.
4
+ *
5
+ * Combines two rule sources:
6
+ * 1. JS plugin rules (structural/scored — from rules/index.js)
7
+ * 2. Declarative JSON rule packs (pattern-based — from DeclarativeRuleEngine)
4
8
  */
9
+ import { loadDeclarativeRules } from '#core/cli/DeclarativeRuleEngine.js';
5
10
  import { getAllRuleMetadata, getEnabledRules } from './rules/index.js';
6
11
 
12
+ let cachedCombinedRules = null;
13
+
14
+ /**
15
+ * Clear cached combined rules (e.g. after downloading new declarative packs, or before watch re-scan).
16
+ */
17
+ export function resetStaticRulesCache() {
18
+ cachedCombinedRules = null;
19
+ }
20
+
21
+ /**
22
+ * Load and cache the combined set of JS plugin + declarative rules.
23
+ */
24
+ function getCombinedRules() {
25
+ if (cachedCombinedRules) {
26
+ return cachedCombinedRules;
27
+ }
28
+
29
+ const jsRules = getEnabledRules();
30
+ const declarativeRules = loadDeclarativeRules().map((rule) => ({
31
+ id: rule.ruleMetadata.id,
32
+ ...rule.ruleMetadata,
33
+ analyzeTool: rule.analyzeTool,
34
+ analyzePrompt: rule.analyzePrompt,
35
+ analyzeResource: rule.analyzeResource,
36
+ analyzePacket: rule.analyzePacket,
37
+ }));
38
+
39
+ const ruleMap = new Map();
40
+ for (const rule of jsRules) {
41
+ ruleMap.set(rule.id, rule);
42
+ }
43
+ for (const rule of declarativeRules) {
44
+ ruleMap.set(rule.id, rule);
45
+ }
46
+
47
+ cachedCombinedRules = [...ruleMap.values()];
48
+ return cachedCombinedRules;
49
+ }
50
+
51
+ /**
52
+ * Get combined metadata from JS plugins + declarative packs.
53
+ */
54
+ function getCombinedMetadata() {
55
+ const jsMetadata = getAllRuleMetadata();
56
+ const declarativeRules = loadDeclarativeRules();
57
+ const declarativeMetadata = declarativeRules.map((r) => r.ruleMetadata);
58
+
59
+ const metaMap = new Map();
60
+ for (const m of jsMetadata) {
61
+ metaMap.set(m.id, m);
62
+ }
63
+ for (const m of declarativeMetadata) {
64
+ metaMap.set(m.id, m);
65
+ }
66
+
67
+ return [...metaMap.values()];
68
+ }
69
+
7
70
  export class StaticRulesService {
8
71
  constructor(logger) {
9
72
  this.logger = logger;
@@ -13,7 +76,7 @@ export class StaticRulesService {
13
76
  * Get all available rule metadata
14
77
  */
15
78
  getRuleMetadata() {
16
- return getAllRuleMetadata();
79
+ return getCombinedMetadata();
17
80
  }
18
81
 
19
82
  /**
@@ -21,7 +84,7 @@ export class StaticRulesService {
21
84
  */
22
85
  analyzeTool(tool, serverName = null) {
23
86
  const findings = [];
24
- const rules = getEnabledRules();
87
+ const rules = getCombinedRules();
25
88
 
26
89
  for (const rule of rules) {
27
90
  try {
@@ -49,7 +112,7 @@ export class StaticRulesService {
49
112
  */
50
113
  analyzePrompt(prompt, serverName = null) {
51
114
  const findings = [];
52
- const rules = getEnabledRules();
115
+ const rules = getCombinedRules();
53
116
 
54
117
  for (const rule of rules) {
55
118
  try {
@@ -77,7 +140,7 @@ export class StaticRulesService {
77
140
  */
78
141
  analyzeResource(resource, serverName = null) {
79
142
  const findings = [];
80
- const rules = getEnabledRules();
143
+ const rules = getCombinedRules();
81
144
 
82
145
  for (const rule of rules) {
83
146
  try {
@@ -105,7 +168,7 @@ export class StaticRulesService {
105
168
  */
106
169
  analyzePacket(packet, sessionId = null) {
107
170
  const findings = [];
108
- const rules = getEnabledRules();
171
+ const rules = getCombinedRules();
109
172
 
110
173
  for (const rule of rules) {
111
174
  try {
@@ -136,21 +199,18 @@ export class StaticRulesService {
136
199
  const serverName = serverConfig.name || 'unknown';
137
200
  const findings = [];
138
201
 
139
- // Analyze tools
140
202
  if (serverConfig.tools && Array.isArray(serverConfig.tools)) {
141
203
  for (const tool of serverConfig.tools) {
142
204
  findings.push(...this.analyzeTool(tool, serverName));
143
205
  }
144
206
  }
145
207
 
146
- // Analyze prompts
147
208
  if (serverConfig.prompts && Array.isArray(serverConfig.prompts)) {
148
209
  for (const prompt of serverConfig.prompts) {
149
210
  findings.push(...this.analyzePrompt(prompt, serverName));
150
211
  }
151
212
  }
152
213
 
153
- // Analyze resources
154
214
  if (serverConfig.resources && Array.isArray(serverConfig.resources)) {
155
215
  for (const resource of serverConfig.resources) {
156
216
  findings.push(...this.analyzeResource(resource, serverName));
@@ -196,22 +256,18 @@ export class StaticRulesService {
196
256
  };
197
257
 
198
258
  for (const finding of findings) {
199
- // By severity
200
259
  if (summary.bySeverity[finding.severity] !== undefined) {
201
260
  summary.bySeverity[finding.severity]++;
202
261
  }
203
262
 
204
- // By OWASP ID
205
263
  if (finding.owasp_id) {
206
264
  summary.byOwasp[finding.owasp_id] = (summary.byOwasp[finding.owasp_id] || 0) + 1;
207
265
  }
208
266
 
209
- // By server
210
267
  if (finding.server_name) {
211
268
  summary.byServer[finding.server_name] = (summary.byServer[finding.server_name] || 0) + 1;
212
269
  }
213
270
 
214
- // By type
215
271
  if (summary.byType[finding.finding_type] !== undefined) {
216
272
  summary.byType[finding.finding_type]++;
217
273
  }
@@ -4,10 +4,16 @@
4
4
  * Hooks into the audit logging pipeline
5
5
  */
6
6
  export class TrafficAnalysisService {
7
- constructor(staticRulesService, securityFindingsRepository, logger) {
7
+ constructor(
8
+ staticRulesService,
9
+ securityFindingsRepository,
10
+ logger,
11
+ trafficToxicFlowService = null
12
+ ) {
8
13
  this.staticRulesService = staticRulesService;
9
14
  this.findingsRepository = securityFindingsRepository;
10
15
  this.logger = logger;
16
+ this.trafficToxicFlowService = trafficToxicFlowService;
11
17
  this.enabled = true;
12
18
  }
13
19
 
@@ -96,6 +102,18 @@ export class TrafficAnalysisService {
96
102
  );
97
103
  }
98
104
 
105
+ if (this.trafficToxicFlowService) {
106
+ try {
107
+ this.trafficToxicFlowService.ingestFromTrafficResponse({
108
+ mcpServerName: packetData.mcpServerName ?? null,
109
+ sessionId: packetData.sessionId ?? null,
110
+ body: packetData.body,
111
+ });
112
+ } catch (err) {
113
+ this.logger?.error({ error: err.message }, 'Traffic toxic flow ingest failed');
114
+ }
115
+ }
116
+
99
117
  return findings;
100
118
  } catch (error) {
101
119
  this.logger?.error({ error: error.message }, 'Error analyzing response packet');
@@ -0,0 +1,154 @@
1
+ /**
2
+ * Cross-server toxic-flow analysis from observed MCP traffic (tools/list responses).
3
+ * Complements CLI static scan: same analyzeToxicFlows heuristics, fed by proxy captures.
4
+ */
5
+ import { analyzeToxicFlows } from '#core/cli/ToxicFlowAnalyzer.js';
6
+ import {
7
+ toolsFromJsonrpcResultString,
8
+ toolsFromTrafficResponseBody,
9
+ } from './toolsListFromTrafficParser.js';
10
+
11
+ const TRAFFIC_IDE = 'Traffic';
12
+ const DEBOUNCE_MS = 400;
13
+
14
+ function serverKey(mcpServerName, sessionId) {
15
+ if (mcpServerName && String(mcpServerName).trim()) {
16
+ return String(mcpServerName).trim();
17
+ }
18
+ if (sessionId && String(sessionId).trim()) {
19
+ return `session:${String(sessionId).trim()}`;
20
+ }
21
+ return null;
22
+ }
23
+
24
+ export class TrafficToxicFlowService {
25
+ constructor(packetRepository, logger) {
26
+ this.packetRepository = packetRepository;
27
+ this.logger = logger;
28
+ /** @type {Map<string, { tools: object[], updatedAt: number }>} */
29
+ this._byServer = new Map();
30
+ this._debounceTimer = null;
31
+ this._lastFlows = [];
32
+ this._lastComputedAt = null;
33
+ this._lastReplayPacketCount = 0;
34
+ }
35
+
36
+ /**
37
+ * Ingest a proxied JSON-RPC response (live path).
38
+ * @param {{ mcpServerName?: string|null, sessionId?: string|null, body?: unknown }} packetData
39
+ */
40
+ ingestFromTrafficResponse(packetData) {
41
+ const tools = toolsFromTrafficResponseBody(packetData?.body);
42
+ if (!tools?.length) {
43
+ return;
44
+ }
45
+ const key = serverKey(packetData?.mcpServerName, packetData?.sessionId);
46
+ if (!key) {
47
+ this.logger?.debug?.('Traffic toxic flow: skip tools/list — no server name or session');
48
+ return;
49
+ }
50
+ this._byServer.set(key, { tools, updatedAt: Date.now() });
51
+ this._scheduleRecompute();
52
+ }
53
+
54
+ _scheduleRecompute() {
55
+ if (this._debounceTimer) {
56
+ clearTimeout(this._debounceTimer);
57
+ }
58
+ this._debounceTimer = setTimeout(() => {
59
+ this._debounceTimer = null;
60
+ this._recomputeNow();
61
+ }, DEBOUNCE_MS);
62
+ }
63
+
64
+ _serversForAnalyzer() {
65
+ const servers = [];
66
+ for (const [name, entry] of this._byServer.entries()) {
67
+ servers.push({
68
+ name,
69
+ ide: TRAFFIC_IDE,
70
+ config: {},
71
+ tools: entry.tools,
72
+ });
73
+ }
74
+ return servers;
75
+ }
76
+
77
+ _recomputeNow() {
78
+ const servers = this._serversForAnalyzer();
79
+ this._lastFlows = servers.length >= 2 ? analyzeToxicFlows(servers) : [];
80
+ this._lastComputedAt = Date.now();
81
+ this.logger?.debug?.(
82
+ { serverCount: servers.length, flowCount: this._lastFlows.length },
83
+ 'Traffic toxic flows recomputed'
84
+ );
85
+ }
86
+
87
+ /**
88
+ * Batch replay: rebuild registry from sqlite packets, then recompute.
89
+ * @returns {{ packetRows: number, serverCount: number, flowCount: number }}
90
+ */
91
+ rebuildFromDatabase() {
92
+ if (!this.packetRepository?.listResponsesWithToolsList) {
93
+ this.logger?.warn?.('Traffic toxic flow replay: packet repository unavailable');
94
+ return { packetRows: 0, serverCount: 0, flowCount: 0 };
95
+ }
96
+ const rows = this.packetRepository.listResponsesWithToolsList();
97
+ this._byServer.clear();
98
+ this._lastReplayPacketCount = rows.length;
99
+
100
+ for (const row of rows) {
101
+ let tools = toolsFromJsonrpcResultString(row.jsonrpc_result);
102
+ if (!tools?.length) {
103
+ tools = toolsFromTrafficResponseBody(row.body_json || row.body_raw);
104
+ }
105
+ if (!tools?.length) {
106
+ continue;
107
+ }
108
+ const key = serverKey(row.remote_address, row.session_id);
109
+ if (!key) {
110
+ continue;
111
+ }
112
+ this._byServer.set(key, { tools, updatedAt: Date.now() });
113
+ }
114
+
115
+ this._recomputeNow();
116
+ return {
117
+ packetRows: rows.length,
118
+ serverCount: this._byServer.size,
119
+ flowCount: this._lastFlows.length,
120
+ };
121
+ }
122
+
123
+ getSnapshot() {
124
+ const servers = [];
125
+ for (const [name, entry] of this._byServer.entries()) {
126
+ servers.push({
127
+ name,
128
+ toolCount: entry.tools.length,
129
+ updatedAt: entry.updatedAt,
130
+ });
131
+ }
132
+ return {
133
+ success: true,
134
+ toxicFlows: this._lastFlows,
135
+ servers,
136
+ computedAt: this._lastComputedAt,
137
+ lastReplayPacketCount: this._lastReplayPacketCount,
138
+ note:
139
+ 'Heuristic cross-server pairs from tools seen in tools/list traffic (HTTP proxy). ' +
140
+ 'Not runtime taint tracking. Requires at least two distinct server keys and overlapping capability rules.',
141
+ };
142
+ }
143
+
144
+ clear() {
145
+ this._byServer.clear();
146
+ this._lastFlows = [];
147
+ this._lastComputedAt = null;
148
+ this._lastReplayPacketCount = 0;
149
+ if (this._debounceTimer) {
150
+ clearTimeout(this._debounceTimer);
151
+ this._debounceTimer = null;
152
+ }
153
+ }
154
+ }
@@ -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
+ };