@sigildev/sigil 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +246 -0
- package/dist/analyzers/ast/python.d.ts +14 -0
- package/dist/analyzers/ast/python.d.ts.map +1 -0
- package/dist/analyzers/ast/python.js +15 -0
- package/dist/analyzers/ast/python.js.map +1 -0
- package/dist/analyzers/ast/taint.d.ts +45 -0
- package/dist/analyzers/ast/taint.d.ts.map +1 -0
- package/dist/analyzers/ast/taint.js +32 -0
- package/dist/analyzers/ast/taint.js.map +1 -0
- package/dist/analyzers/ast/typescript.d.ts +15 -0
- package/dist/analyzers/ast/typescript.d.ts.map +1 -0
- package/dist/analyzers/ast/typescript.js +16 -0
- package/dist/analyzers/ast/typescript.js.map +1 -0
- package/dist/analyzers/deps.d.ts +13 -0
- package/dist/analyzers/deps.d.ts.map +1 -0
- package/dist/analyzers/deps.js +14 -0
- package/dist/analyzers/deps.js.map +1 -0
- package/dist/analyzers/pattern.d.ts +12 -0
- package/dist/analyzers/pattern.d.ts.map +1 -0
- package/dist/analyzers/pattern.js +13 -0
- package/dist/analyzers/pattern.js.map +1 -0
- package/dist/analyzers/types.d.ts +111 -0
- package/dist/analyzers/types.d.ts.map +1 -0
- package/dist/analyzers/types.js +3 -0
- package/dist/analyzers/types.js.map +1 -0
- package/dist/discovery/config-parser.d.ts +7 -0
- package/dist/discovery/config-parser.d.ts.map +1 -0
- package/dist/discovery/config-parser.js +23 -0
- package/dist/discovery/config-parser.js.map +1 -0
- package/dist/discovery/files.d.ts +6 -0
- package/dist/discovery/files.d.ts.map +1 -0
- package/dist/discovery/files.js +43 -0
- package/dist/discovery/files.js.map +1 -0
- package/dist/discovery/manifest.d.ts +6 -0
- package/dist/discovery/manifest.d.ts.map +1 -0
- package/dist/discovery/manifest.js +82 -0
- package/dist/discovery/manifest.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +60 -0
- package/dist/index.js.map +1 -0
- package/dist/reporters/json.d.ts +3 -0
- package/dist/reporters/json.d.ts.map +1 -0
- package/dist/reporters/json.js +4 -0
- package/dist/reporters/json.js.map +1 -0
- package/dist/reporters/sarif.d.ts +3 -0
- package/dist/reporters/sarif.d.ts.map +1 -0
- package/dist/reporters/sarif.js +57 -0
- package/dist/reporters/sarif.js.map +1 -0
- package/dist/reporters/text.d.ts +7 -0
- package/dist/reporters/text.d.ts.map +1 -0
- package/dist/reporters/text.js +89 -0
- package/dist/reporters/text.js.map +1 -0
- package/dist/rules/auth.d.ts +4 -0
- package/dist/rules/auth.d.ts.map +1 -0
- package/dist/rules/auth.js +88 -0
- package/dist/rules/auth.js.map +1 -0
- package/dist/rules/config.d.ts +5 -0
- package/dist/rules/config.d.ts.map +1 -0
- package/dist/rules/config.js +123 -0
- package/dist/rules/config.js.map +1 -0
- package/dist/rules/data.d.ts +4 -0
- package/dist/rules/data.d.ts.map +1 -0
- package/dist/rules/data.js +79 -0
- package/dist/rules/data.js.map +1 -0
- package/dist/rules/deps.d.ts +3 -0
- package/dist/rules/deps.d.ts.map +1 -0
- package/dist/rules/deps.js +68 -0
- package/dist/rules/deps.js.map +1 -0
- package/dist/rules/description.d.ts +3 -0
- package/dist/rules/description.d.ts.map +1 -0
- package/dist/rules/description.js +91 -0
- package/dist/rules/description.js.map +1 -0
- package/dist/rules/index.d.ts +3 -0
- package/dist/rules/index.d.ts.map +1 -0
- package/dist/rules/index.js +154 -0
- package/dist/rules/index.js.map +1 -0
- package/dist/rules/injection.d.ts +5 -0
- package/dist/rules/injection.d.ts.map +1 -0
- package/dist/rules/injection.js +213 -0
- package/dist/rules/injection.js.map +1 -0
- package/dist/rules/permissions.d.ts +5 -0
- package/dist/rules/permissions.d.ts.map +1 -0
- package/dist/rules/permissions.js +170 -0
- package/dist/rules/permissions.js.map +1 -0
- package/dist/rules/validation.d.ts +3 -0
- package/dist/rules/validation.d.ts.map +1 -0
- package/dist/rules/validation.js +67 -0
- package/dist/rules/validation.js.map +1 -0
- package/dist/scanner.d.ts +9 -0
- package/dist/scanner.d.ts.map +1 -0
- package/dist/scanner.js +149 -0
- package/dist/scanner.js.map +1 -0
- package/dist/scoring.d.ts +3 -0
- package/dist/scoring.d.ts.map +1 -0
- package/dist/scoring.js +35 -0
- package/dist/scoring.js.map +1 -0
- package/package.json +57 -0
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
// Dangerous shell execution sinks (TS/JS)
|
|
2
|
+
const EXEC_PATTERNS_TS = [
|
|
3
|
+
/(?<!\.)exec\s*\(/g,
|
|
4
|
+
/(?<!\.)execSync\s*\(/g,
|
|
5
|
+
/(?<!\.)execAsync\s*\(/g,
|
|
6
|
+
/\bspawn\s*\([^)]*shell\s*:\s*true/g,
|
|
7
|
+
/\bspawnSync\s*\([^)]*shell\s*:\s*true/g,
|
|
8
|
+
/child_process\.\s*exec\s*\(/g,
|
|
9
|
+
];
|
|
10
|
+
// Dangerous shell execution sinks (Python)
|
|
11
|
+
const EXEC_PATTERNS_PY = [
|
|
12
|
+
/\bos\.system\s*\(/g,
|
|
13
|
+
/\bos\.popen\s*\(/g,
|
|
14
|
+
/\bsubprocess\.run\s*\([^)]*shell\s*=\s*True/g,
|
|
15
|
+
/\bsubprocess\.call\s*\([^)]*shell\s*=\s*True/g,
|
|
16
|
+
/\bsubprocess\.Popen\s*\([^)]*shell\s*=\s*True/g,
|
|
17
|
+
/\bsubprocess\.check_output\s*\([^)]*shell\s*=\s*True/g,
|
|
18
|
+
];
|
|
19
|
+
// SQL injection patterns
|
|
20
|
+
const SQL_CONCAT_TS = [
|
|
21
|
+
// Template literal with variable in SQL context
|
|
22
|
+
/(?:query|execute|prepare|raw)\s*\(\s*`[^`]*\$\{/g,
|
|
23
|
+
// String concatenation in SQL context
|
|
24
|
+
/(?:query|execute|prepare|raw)\s*\(\s*["'][^"']*["']\s*\+/g,
|
|
25
|
+
/(?:query|execute|prepare|raw)\s*\([^)]*\+\s*["']/g,
|
|
26
|
+
// f-string SQL (Python)
|
|
27
|
+
/(?:execute|cursor\.execute|\.query)\s*\(\s*f["']/g,
|
|
28
|
+
];
|
|
29
|
+
const SQL_CONCAT_PY = [
|
|
30
|
+
/(?:execute|cursor\.execute)\s*\(\s*f["']/g,
|
|
31
|
+
/(?:execute|cursor\.execute)\s*\(\s*["'][^"']*["']\s*%/g,
|
|
32
|
+
/(?:execute|cursor\.execute)\s*\(\s*["'][^"']*["']\s*\.\s*format/g,
|
|
33
|
+
/(?:execute|cursor\.execute)\s*\(\s*["'][^"']*["']\s*\+/g,
|
|
34
|
+
];
|
|
35
|
+
// File operation patterns without validation
|
|
36
|
+
const PATH_TRAVERSAL_TS = [
|
|
37
|
+
/\bfs\.readFile(?:Sync)?\s*\(/g,
|
|
38
|
+
/\bfs\.writeFile(?:Sync)?\s*\(/g,
|
|
39
|
+
/\bfs\.readdir(?:Sync)?\s*\(/g,
|
|
40
|
+
/\bfs\.unlink(?:Sync)?\s*\(/g,
|
|
41
|
+
/\bfs\.mkdir(?:Sync)?\s*\(/g,
|
|
42
|
+
/\bfs\.access(?:Sync)?\s*\(/g,
|
|
43
|
+
];
|
|
44
|
+
const PATH_TRAVERSAL_PY = [
|
|
45
|
+
/\bopen\s*\(/g,
|
|
46
|
+
/\bos\.path\.\w+\s*\(/g,
|
|
47
|
+
/\bshutil\.\w+\s*\(/g,
|
|
48
|
+
/\bpathlib\.Path\s*\(/g,
|
|
49
|
+
];
|
|
50
|
+
// Path validation patterns (if present, the file op is probably safe)
|
|
51
|
+
const PATH_SAFE_PATTERNS = [
|
|
52
|
+
/realpath/,
|
|
53
|
+
/resolve\s*\(/,
|
|
54
|
+
/startsWith\s*\(/,
|
|
55
|
+
/startswith\s*\(/,
|
|
56
|
+
/ALLOWED/i,
|
|
57
|
+
/allowlist/i,
|
|
58
|
+
/whitelist/i,
|
|
59
|
+
/base_dir/i,
|
|
60
|
+
/root_dir/i,
|
|
61
|
+
/prefix.*check/i,
|
|
62
|
+
];
|
|
63
|
+
function findLineNumber(content, index) {
|
|
64
|
+
return content.slice(0, index).split("\n").length;
|
|
65
|
+
}
|
|
66
|
+
function isInToolHandler(content, matchIndex, language) {
|
|
67
|
+
// Look backwards from the match for a tool registration pattern
|
|
68
|
+
const before = content.slice(Math.max(0, matchIndex - 2000), matchIndex);
|
|
69
|
+
if (language === "typescript" || language === "unknown") {
|
|
70
|
+
// Check for .tool( pattern
|
|
71
|
+
if (/\.tool\s*\(/g.test(before)) {
|
|
72
|
+
// Make sure we're not past the end of that handler
|
|
73
|
+
// Simple heuristic: count braces
|
|
74
|
+
const lastToolIndex = before.lastIndexOf(".tool(");
|
|
75
|
+
if (lastToolIndex !== -1) {
|
|
76
|
+
const afterTool = before.slice(lastToolIndex);
|
|
77
|
+
const opens = (afterTool.match(/\{/g) || []).length;
|
|
78
|
+
const closes = (afterTool.match(/\}/g) || []).length;
|
|
79
|
+
// If we haven't closed all the braces, we're still in the handler
|
|
80
|
+
if (opens > closes)
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (language === "python" || language === "unknown") {
|
|
86
|
+
// Check for @mcp.tool() or @server.tool() decorator
|
|
87
|
+
if (/@\w+\.tool\s*\(/g.test(before))
|
|
88
|
+
return true;
|
|
89
|
+
if (/def\s+\w+.*->/.test(before))
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
// Fallback: if it's in a server file, consider it relevant
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
function hasPathValidation(content, matchIndex) {
|
|
96
|
+
// Check surrounding context (200 chars before and after) for validation patterns
|
|
97
|
+
const start = Math.max(0, matchIndex - 500);
|
|
98
|
+
const end = Math.min(content.length, matchIndex + 500);
|
|
99
|
+
const context = content.slice(start, end);
|
|
100
|
+
return PATH_SAFE_PATTERNS.some((p) => p.test(context));
|
|
101
|
+
}
|
|
102
|
+
export function detectCommandInjection(context) {
|
|
103
|
+
const findings = [];
|
|
104
|
+
const patterns = context.language === "python" ? EXEC_PATTERNS_PY :
|
|
105
|
+
context.language === "typescript" ? EXEC_PATTERNS_TS :
|
|
106
|
+
[...EXEC_PATTERNS_TS, ...EXEC_PATTERNS_PY];
|
|
107
|
+
for (const [file, content] of context.sources) {
|
|
108
|
+
for (const pattern of patterns) {
|
|
109
|
+
pattern.lastIndex = 0;
|
|
110
|
+
let match;
|
|
111
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
112
|
+
const line = findLineNumber(content, match.index);
|
|
113
|
+
const lineContent = content.split("\n")[line - 1] || "";
|
|
114
|
+
// Skip if it's a comment
|
|
115
|
+
const trimmed = lineContent.trimStart();
|
|
116
|
+
if (trimmed.startsWith("//") || trimmed.startsWith("#") || trimmed.startsWith("*"))
|
|
117
|
+
continue;
|
|
118
|
+
// Skip if using execFile (safe — no shell)
|
|
119
|
+
if (/execFile/.test(lineContent))
|
|
120
|
+
continue;
|
|
121
|
+
// Skip if using shell: false explicitly
|
|
122
|
+
if (/shell\s*:\s*false/.test(lineContent))
|
|
123
|
+
continue;
|
|
124
|
+
// Skip if it's just an import/require or promisify setup line
|
|
125
|
+
if (/\bimport\b/.test(lineContent) || /\brequire\b/.test(lineContent))
|
|
126
|
+
continue;
|
|
127
|
+
if (/\bpromisify\s*\(\s*exec\s*\)/.test(lineContent))
|
|
128
|
+
continue;
|
|
129
|
+
findings.push({
|
|
130
|
+
ruleId: "MCS-INJ-001",
|
|
131
|
+
severity: "critical",
|
|
132
|
+
title: "Command Injection via Tool Input",
|
|
133
|
+
message: `Potential shell command execution found. If tool input reaches this call, it enables arbitrary command execution.`,
|
|
134
|
+
location: { file, startLine: line, endLine: line },
|
|
135
|
+
fix: {
|
|
136
|
+
description: "Use execFile() with an argument array instead of exec(). Allowlist permitted commands.",
|
|
137
|
+
suggestion: "execFile('/usr/bin/cmd', [arg1, arg2])",
|
|
138
|
+
},
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return findings;
|
|
144
|
+
}
|
|
145
|
+
export function detectSqlInjection(context) {
|
|
146
|
+
const findings = [];
|
|
147
|
+
const patterns = context.language === "python" ? SQL_CONCAT_PY :
|
|
148
|
+
context.language === "typescript" ? SQL_CONCAT_TS :
|
|
149
|
+
[...SQL_CONCAT_TS, ...SQL_CONCAT_PY];
|
|
150
|
+
for (const [file, content] of context.sources) {
|
|
151
|
+
for (const pattern of patterns) {
|
|
152
|
+
pattern.lastIndex = 0;
|
|
153
|
+
let match;
|
|
154
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
155
|
+
const line = findLineNumber(content, match.index);
|
|
156
|
+
const lineContent = content.split("\n")[line - 1] || "";
|
|
157
|
+
const trimmed = lineContent.trimStart();
|
|
158
|
+
if (trimmed.startsWith("//") || trimmed.startsWith("#") || trimmed.startsWith("*"))
|
|
159
|
+
continue;
|
|
160
|
+
findings.push({
|
|
161
|
+
ruleId: "MCS-INJ-002",
|
|
162
|
+
severity: "critical",
|
|
163
|
+
title: "SQL Injection via Tool Input",
|
|
164
|
+
message: `SQL query constructed using string interpolation or concatenation. Use parameterized queries ($1, ?, :param) instead.`,
|
|
165
|
+
location: { file, startLine: line, endLine: line },
|
|
166
|
+
fix: {
|
|
167
|
+
description: "Use parameterized queries instead of string concatenation.",
|
|
168
|
+
suggestion: "db.query('SELECT * FROM users WHERE name = $1', [name])",
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return findings;
|
|
175
|
+
}
|
|
176
|
+
export function detectPathTraversal(context) {
|
|
177
|
+
const findings = [];
|
|
178
|
+
const patterns = context.language === "python" ? PATH_TRAVERSAL_PY :
|
|
179
|
+
context.language === "typescript" ? PATH_TRAVERSAL_TS :
|
|
180
|
+
[...PATH_TRAVERSAL_TS, ...PATH_TRAVERSAL_PY];
|
|
181
|
+
for (const [file, content] of context.sources) {
|
|
182
|
+
for (const pattern of patterns) {
|
|
183
|
+
pattern.lastIndex = 0;
|
|
184
|
+
let match;
|
|
185
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
186
|
+
const line = findLineNumber(content, match.index);
|
|
187
|
+
const lineContent = content.split("\n")[line - 1] || "";
|
|
188
|
+
const trimmed = lineContent.trimStart();
|
|
189
|
+
if (trimmed.startsWith("//") || trimmed.startsWith("#") || trimmed.startsWith("*"))
|
|
190
|
+
continue;
|
|
191
|
+
// Skip if there's path validation nearby
|
|
192
|
+
if (hasPathValidation(content, match.index))
|
|
193
|
+
continue;
|
|
194
|
+
// Only flag if we're in a tool handler context
|
|
195
|
+
if (!isInToolHandler(content, match.index, context.language))
|
|
196
|
+
continue;
|
|
197
|
+
findings.push({
|
|
198
|
+
ruleId: "MCS-INJ-003",
|
|
199
|
+
severity: "high",
|
|
200
|
+
title: "Path Traversal in File Operations",
|
|
201
|
+
message: `File operation without path validation. Tool input could access files outside the intended directory.`,
|
|
202
|
+
location: { file, startLine: line, endLine: line },
|
|
203
|
+
fix: {
|
|
204
|
+
description: "Resolve the path with realpath() and verify it starts with an allowed directory prefix.",
|
|
205
|
+
suggestion: "const resolved = await fs.realpath(path.join(ALLOWED_DIR, input)); if (!resolved.startsWith(ALLOWED_DIR)) throw new Error('Access denied');",
|
|
206
|
+
},
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return findings;
|
|
212
|
+
}
|
|
213
|
+
//# sourceMappingURL=injection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"injection.js","sourceRoot":"","sources":["../../src/rules/injection.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { AnalysisContext, Finding } from "../analyzers/types.js";
|
|
2
|
+
export declare function detectBroadCapabilities(context: AnalysisContext): Finding[];
|
|
3
|
+
export declare function detectUnrestrictedFilesystem(context: AnalysisContext): Finding[];
|
|
4
|
+
export declare function detectArbitraryCodeExecution(context: AnalysisContext): Finding[];
|
|
5
|
+
//# sourceMappingURL=permissions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"permissions.d.ts","sourceRoot":"","sources":["../../src/rules/permissions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAqDtE,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,EAAE,CAgC3E;AAED,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,EAAE,CAoEhF;AAED,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,EAAE,CAoChF"}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
// Unrestricted outbound HTTP patterns
|
|
2
|
+
const BROAD_HTTP_PATTERNS = [
|
|
3
|
+
// fetch with a variable URL (not a string literal)
|
|
4
|
+
/\bfetch\s*\(\s*(?!["'`])\w+/g,
|
|
5
|
+
// axios/http with variable URL
|
|
6
|
+
/\baxios\.\w+\s*\(\s*(?!["'`])\w+/g,
|
|
7
|
+
/\bhttp\.request\s*\(/g,
|
|
8
|
+
/\bhttps\.request\s*\(/g,
|
|
9
|
+
// Python requests
|
|
10
|
+
/\brequests\.\w+\s*\(\s*(?!["'`])\w+/g,
|
|
11
|
+
/\bhttpx\.\w+\s*\(\s*(?!["'`])\w+/g,
|
|
12
|
+
];
|
|
13
|
+
const HOST_CHECK_PATTERNS = [
|
|
14
|
+
/ALLOWED_HOSTS/i,
|
|
15
|
+
/allowedHosts/i,
|
|
16
|
+
/hostname.*includes/,
|
|
17
|
+
/host.*===?\s*["']/,
|
|
18
|
+
/url.*startsWith/,
|
|
19
|
+
/whitelist/i,
|
|
20
|
+
/allowlist/i,
|
|
21
|
+
];
|
|
22
|
+
// Arbitrary code execution patterns
|
|
23
|
+
const CODE_EXEC_PATTERNS_TS = [
|
|
24
|
+
/\beval\s*\(/g,
|
|
25
|
+
/\bFunction\s*\(\s*["'`]/g,
|
|
26
|
+
/new\s+Function\s*\(/g,
|
|
27
|
+
/\bvm\.runInNewContext\s*\(/g,
|
|
28
|
+
/\bvm\.runInThisContext\s*\(/g,
|
|
29
|
+
/\bvm\.createScript\s*\(/g,
|
|
30
|
+
];
|
|
31
|
+
const CODE_EXEC_PATTERNS_PY = [
|
|
32
|
+
/(?<!\.)eval\s*\(/g,
|
|
33
|
+
/(?<!\.)exec\s*\(/g,
|
|
34
|
+
/(?<!\.)\bcompile\s*\(\s*(?!["'])/g,
|
|
35
|
+
/\b__import__\s*\(/g,
|
|
36
|
+
];
|
|
37
|
+
function findLineNumber(content, index) {
|
|
38
|
+
return content.slice(0, index).split("\n").length;
|
|
39
|
+
}
|
|
40
|
+
function hasHostValidation(content, matchIndex) {
|
|
41
|
+
const start = Math.max(0, matchIndex - 800);
|
|
42
|
+
const end = Math.min(content.length, matchIndex + 300);
|
|
43
|
+
const context = content.slice(start, end);
|
|
44
|
+
return HOST_CHECK_PATTERNS.some((p) => p.test(context));
|
|
45
|
+
}
|
|
46
|
+
export function detectBroadCapabilities(context) {
|
|
47
|
+
const findings = [];
|
|
48
|
+
for (const [file, content] of context.sources) {
|
|
49
|
+
for (const pattern of BROAD_HTTP_PATTERNS) {
|
|
50
|
+
pattern.lastIndex = 0;
|
|
51
|
+
let match;
|
|
52
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
53
|
+
const line = findLineNumber(content, match.index);
|
|
54
|
+
const lineContent = content.split("\n")[line - 1] || "";
|
|
55
|
+
const trimmed = lineContent.trimStart();
|
|
56
|
+
if (trimmed.startsWith("//") || trimmed.startsWith("#") || trimmed.startsWith("*"))
|
|
57
|
+
continue;
|
|
58
|
+
if (hasHostValidation(content, match.index))
|
|
59
|
+
continue;
|
|
60
|
+
findings.push({
|
|
61
|
+
ruleId: "MCS-PERM-001",
|
|
62
|
+
severity: "high",
|
|
63
|
+
title: "Overly Broad Tool Capabilities",
|
|
64
|
+
message: `Outbound HTTP request with no apparent host restriction. A compromised tool input could exfiltrate data to any URL.`,
|
|
65
|
+
location: { file, startLine: line, endLine: line },
|
|
66
|
+
fix: {
|
|
67
|
+
description: "Restrict outbound requests to an allowlist of permitted hosts.",
|
|
68
|
+
suggestion: "if (!ALLOWED_HOSTS.includes(new URL(url).hostname)) throw new Error('Host not allowed');",
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return findings;
|
|
75
|
+
}
|
|
76
|
+
export function detectUnrestrictedFilesystem(context) {
|
|
77
|
+
const findings = [];
|
|
78
|
+
// This is handled by MCS-INJ-003 (path traversal) — they overlap.
|
|
79
|
+
// MCS-PERM-002 specifically flags file tools with no allowlist at all.
|
|
80
|
+
// We look for file read/write in tool handlers without any path restriction.
|
|
81
|
+
const FS_PATTERNS_TS = [
|
|
82
|
+
/\bfs\.readFileSync\s*\(\s*(?!["'`])\w+/g,
|
|
83
|
+
/\bfs\.readFile\s*\(\s*(?!["'`])\w+/g,
|
|
84
|
+
/\bfs\.writeFileSync\s*\(\s*(?!["'`])\w+/g,
|
|
85
|
+
/\bfs\.writeFile\s*\(\s*(?!["'`])\w+/g,
|
|
86
|
+
];
|
|
87
|
+
const FS_PATTERNS_PY = [
|
|
88
|
+
/\bopen\s*\(\s*(?!["'`])\w+/g,
|
|
89
|
+
];
|
|
90
|
+
const patterns = context.language === "python" ? FS_PATTERNS_PY :
|
|
91
|
+
context.language === "typescript" ? FS_PATTERNS_TS :
|
|
92
|
+
[...FS_PATTERNS_TS, ...FS_PATTERNS_PY];
|
|
93
|
+
const RESTRICT_PATTERNS = [
|
|
94
|
+
/ALLOWED_DIR/i,
|
|
95
|
+
/allowedDir/i,
|
|
96
|
+
/base_?dir/i,
|
|
97
|
+
/root_?dir/i,
|
|
98
|
+
/startsWith/,
|
|
99
|
+
/startswith/,
|
|
100
|
+
/realpath/,
|
|
101
|
+
/resolve.*startsWith/,
|
|
102
|
+
/prefix/i,
|
|
103
|
+
/whitelist/i,
|
|
104
|
+
/allowlist/i,
|
|
105
|
+
];
|
|
106
|
+
for (const [file, content] of context.sources) {
|
|
107
|
+
for (const pattern of patterns) {
|
|
108
|
+
pattern.lastIndex = 0;
|
|
109
|
+
let match;
|
|
110
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
111
|
+
const line = findLineNumber(content, match.index);
|
|
112
|
+
const lineContent = content.split("\n")[line - 1] || "";
|
|
113
|
+
const trimmed = lineContent.trimStart();
|
|
114
|
+
if (trimmed.startsWith("//") || trimmed.startsWith("#") || trimmed.startsWith("*"))
|
|
115
|
+
continue;
|
|
116
|
+
// Check for restrictions in surrounding context
|
|
117
|
+
const start = Math.max(0, match.index - 800);
|
|
118
|
+
const end = Math.min(content.length, match.index + 300);
|
|
119
|
+
const ctx = content.slice(start, end);
|
|
120
|
+
if (RESTRICT_PATTERNS.some((p) => p.test(ctx)))
|
|
121
|
+
continue;
|
|
122
|
+
findings.push({
|
|
123
|
+
ruleId: "MCS-PERM-002",
|
|
124
|
+
severity: "high",
|
|
125
|
+
title: "Unrestricted Filesystem Access",
|
|
126
|
+
message: `File operation accepts a variable path with no directory restriction. Can access any file on the system.`,
|
|
127
|
+
location: { file, startLine: line, endLine: line },
|
|
128
|
+
fix: {
|
|
129
|
+
description: "Restrict file operations to an allowed directory. Use realpath() + prefix check.",
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return findings;
|
|
136
|
+
}
|
|
137
|
+
export function detectArbitraryCodeExecution(context) {
|
|
138
|
+
const findings = [];
|
|
139
|
+
const patterns = context.language === "python" ? CODE_EXEC_PATTERNS_PY :
|
|
140
|
+
context.language === "typescript" ? CODE_EXEC_PATTERNS_TS :
|
|
141
|
+
[...CODE_EXEC_PATTERNS_TS, ...CODE_EXEC_PATTERNS_PY];
|
|
142
|
+
for (const [file, content] of context.sources) {
|
|
143
|
+
for (const pattern of patterns) {
|
|
144
|
+
pattern.lastIndex = 0;
|
|
145
|
+
let match;
|
|
146
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
147
|
+
const line = findLineNumber(content, match.index);
|
|
148
|
+
const lineContent = content.split("\n")[line - 1] || "";
|
|
149
|
+
const trimmed = lineContent.trimStart();
|
|
150
|
+
if (trimmed.startsWith("//") || trimmed.startsWith("#") || trimmed.startsWith("*"))
|
|
151
|
+
continue;
|
|
152
|
+
// In Python, exec() with a string literal is less dangerous — skip those
|
|
153
|
+
if (context.language === "python" && /\bexec\s*\(\s*["']/.test(lineContent))
|
|
154
|
+
continue;
|
|
155
|
+
findings.push({
|
|
156
|
+
ruleId: "MCS-PERM-003",
|
|
157
|
+
severity: "critical",
|
|
158
|
+
title: "Tool Can Execute Arbitrary Code",
|
|
159
|
+
message: `Code evaluation function (eval/exec/Function) found. If tool input reaches this, it enables arbitrary code execution.`,
|
|
160
|
+
location: { file, startLine: line, endLine: line },
|
|
161
|
+
fix: {
|
|
162
|
+
description: "Remove eval/exec usage. Use an allowlist of permitted operations instead of evaluating user input as code.",
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return findings;
|
|
169
|
+
}
|
|
170
|
+
//# sourceMappingURL=permissions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"permissions.js","sourceRoot":"","sources":["../../src/rules/permissions.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../src/rules/validation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAMtE,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,EAAE,CAmE5E"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
function findLineNumber(content, index) {
|
|
2
|
+
return content.slice(0, index).split("\n").length;
|
|
3
|
+
}
|
|
4
|
+
export function detectMissingInputSchema(context) {
|
|
5
|
+
const findings = [];
|
|
6
|
+
for (const [file, content] of context.sources) {
|
|
7
|
+
if (context.language === "typescript" || context.language === "unknown") {
|
|
8
|
+
// Detect z.any() or z.unknown() in tool schemas — these accept anything without validation
|
|
9
|
+
const weakTypeRegex = /z\.(?:any|unknown)\s*\(\)/g;
|
|
10
|
+
let match;
|
|
11
|
+
while ((match = weakTypeRegex.exec(content)) !== null) {
|
|
12
|
+
const line = findLineNumber(content, match.index);
|
|
13
|
+
const lineContent = content.split("\n")[line - 1] || "";
|
|
14
|
+
const trimmed = lineContent.trimStart();
|
|
15
|
+
if (trimmed.startsWith("//") || trimmed.startsWith("#"))
|
|
16
|
+
continue;
|
|
17
|
+
// Only flag if this is within a .tool() registration context
|
|
18
|
+
const before = content.slice(Math.max(0, match.index - 500), match.index);
|
|
19
|
+
if (!/\.tool\s*\(/.test(before))
|
|
20
|
+
continue;
|
|
21
|
+
findings.push({
|
|
22
|
+
ruleId: "MCS-VALID-001",
|
|
23
|
+
severity: "medium",
|
|
24
|
+
title: "Missing Input Schema",
|
|
25
|
+
message: `Tool registered with z.any() or z.unknown() — accepts any input without type or constraint checking.`,
|
|
26
|
+
location: { file, startLine: line, endLine: line },
|
|
27
|
+
fix: {
|
|
28
|
+
description: "Define input validation using specific Zod schemas to constrain what the LLM can pass to this tool.",
|
|
29
|
+
suggestion: "{ query: z.string().max(100), limit: z.number().int().min(1).max(50) }",
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
if (context.language === "python" || context.language === "unknown") {
|
|
35
|
+
// Detect tool functions with untyped parameters (no type hint)
|
|
36
|
+
// Pattern: @mcp.tool() decorated function with at least one param missing a type hint
|
|
37
|
+
const pyToolFnRegex = /@\w+\.tool\s*\(\s*\)\s*\n\s*(?:async\s+)?def\s+\w+\s*\(([^)]*)\)/g;
|
|
38
|
+
let match;
|
|
39
|
+
while ((match = pyToolFnRegex.exec(content)) !== null) {
|
|
40
|
+
const params = match[1];
|
|
41
|
+
const paramList = params.split(",").map((p) => p.trim()).filter((p) => p.length > 0);
|
|
42
|
+
if (paramList.length === 0)
|
|
43
|
+
continue; // no-arg functions are fine
|
|
44
|
+
// Skip self parameter (for class methods)
|
|
45
|
+
const realParams = paramList.filter((p) => p !== "self");
|
|
46
|
+
if (realParams.length === 0)
|
|
47
|
+
continue;
|
|
48
|
+
const allTyped = realParams.every((p) => p.includes(":"));
|
|
49
|
+
if (allTyped)
|
|
50
|
+
continue;
|
|
51
|
+
const line = findLineNumber(content, match.index);
|
|
52
|
+
findings.push({
|
|
53
|
+
ruleId: "MCS-VALID-001",
|
|
54
|
+
severity: "medium",
|
|
55
|
+
title: "Missing Input Schema",
|
|
56
|
+
message: `Tool function has untyped parameters. FastMCP uses type hints to generate input schemas — untyped params bypass validation.`,
|
|
57
|
+
location: { file, startLine: line, endLine: line },
|
|
58
|
+
fix: {
|
|
59
|
+
description: "Add type hints to all tool function parameters.",
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return findings;
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=validation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validation.js","sourceRoot":"","sources":["../../src/rules/validation.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ScanResult, Severity } from "./analyzers/types.js";
|
|
2
|
+
export interface ScanOptions {
|
|
3
|
+
minSeverity?: Severity;
|
|
4
|
+
ignoreRules?: string[];
|
|
5
|
+
configPath?: string;
|
|
6
|
+
verbose?: boolean;
|
|
7
|
+
}
|
|
8
|
+
export declare function scan(target: string, options?: ScanOptions): Promise<ScanResult>;
|
|
9
|
+
//# sourceMappingURL=scanner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scanner.d.ts","sourceRoot":"","sources":["../src/scanner.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,UAAU,EAGV,QAAQ,EAET,MAAM,sBAAsB,CAAC;AAU9B,MAAM,WAAW,WAAW;IAC1B,WAAW,CAAC,EAAE,QAAQ,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AASD,wBAAsB,IAAI,CACxB,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,WAAgB,GACxB,OAAO,CAAC,UAAU,CAAC,CAuGrB"}
|