@sigildev/sigil 0.1.3 → 0.2.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/README.md +2 -2
- package/dist/index.js +1 -1
- package/dist/rules/auth.d.ts.map +1 -1
- package/dist/rules/auth.js +12 -6
- package/dist/rules/auth.js.map +1 -1
- package/dist/rules/config.d.ts.map +1 -1
- package/dist/rules/config.js +10 -9
- package/dist/rules/config.js.map +1 -1
- package/dist/rules/data.d.ts.map +1 -1
- package/dist/rules/data.js +9 -5
- package/dist/rules/data.js.map +1 -1
- package/dist/rules/description.d.ts.map +1 -1
- package/dist/rules/description.js +3 -0
- package/dist/rules/description.js.map +1 -1
- package/dist/rules/injection.d.ts.map +1 -1
- package/dist/rules/injection.js +18 -44
- package/dist/rules/injection.js.map +1 -1
- package/dist/rules/permissions.d.ts.map +1 -1
- package/dist/rules/permissions.js +20 -13
- package/dist/rules/permissions.js.map +1 -1
- package/dist/rules/utils.d.ts +20 -0
- package/dist/rules/utils.d.ts.map +1 -0
- package/dist/rules/utils.js +109 -0
- package/dist/rules/utils.js.map +1 -0
- package/dist/rules/validation.d.ts.map +1 -1
- package/dist/rules/validation.js +3 -3
- package/dist/rules/validation.js.map +1 -1
- package/dist/scanner.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -24,7 +24,7 @@ npx @sigildev/sigil .
|
|
|
24
24
|
```
|
|
25
25
|
|
|
26
26
|
```
|
|
27
|
-
Sigil v0.
|
|
27
|
+
Sigil v0.2.0
|
|
28
28
|
|
|
29
29
|
Scanning: ./my-mcp-server
|
|
30
30
|
Language: TypeScript
|
|
@@ -199,7 +199,7 @@ Three-layer analysis pipeline. No dynamic analysis — the scanner never runs yo
|
|
|
199
199
|
|
|
200
200
|
1. **Discovery** — Finds MCP server entry points, parses config files (`claude_desktop_config.json`, `.mcp.json`), reads `package.json`/`pyproject.toml`, discovers source files.
|
|
201
201
|
2. **Analysis** — 16 rules run against source code:
|
|
202
|
-
- **Pattern Analyzer** —
|
|
202
|
+
- **Pattern Analyzer** — Tool-handler-scoped regex detection for injection sinks, dangerous permissions, credential leaks, prompt injection, and configuration issues. Only flags patterns found within MCP tool handler contexts (`.tool()`, `@mcp.tool()`), skips build scripts, CLI utilities, and test fixtures.
|
|
203
203
|
- **Dependency Checker** — Parses dependency manifests, queries OSV.dev for known CVEs.
|
|
204
204
|
3. **Reporting** — Aggregates findings, computes trust score, formats output.
|
|
205
205
|
|
package/dist/index.js
CHANGED
|
@@ -8,7 +8,7 @@ const program = new Command();
|
|
|
8
8
|
program
|
|
9
9
|
.name("sigil")
|
|
10
10
|
.description("Security scanner for MCP (Model Context Protocol) servers")
|
|
11
|
-
.version("0.
|
|
11
|
+
.version("0.2.0")
|
|
12
12
|
.argument("<target>", "Path to MCP server directory, file, or config")
|
|
13
13
|
.option("-o, --output <format>", "Output format: text, json, sarif", "text")
|
|
14
14
|
.option("-s, --severity <level>", "Minimum severity to report: low, medium, high, critical", "low")
|
package/dist/rules/auth.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/rules/auth.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/rules/auth.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AA6BtE,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,EAAE,CAwC9E;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,EAAE,CAqCzE"}
|
package/dist/rules/auth.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { findLineNumber, shouldSkipFile, isPlaceholderCredential, isComment, } from "./utils.js";
|
|
1
2
|
const CREDENTIAL_PATTERNS = [
|
|
2
3
|
{ pattern: /["'`](sk-[a-zA-Z0-9_-]{20,})["'`]/g, label: "OpenAI API key" },
|
|
3
4
|
{ pattern: /["'`](sk-proj-[a-zA-Z0-9_-]{20,})["'`]/g, label: "OpenAI project key" },
|
|
@@ -18,12 +19,12 @@ const CREDENTIAL_PATTERNS = [
|
|
|
18
19
|
// Private keys
|
|
19
20
|
{ pattern: /-----BEGIN (?:RSA |EC |DSA )?PRIVATE KEY-----/g, label: "Private key" },
|
|
20
21
|
];
|
|
21
|
-
function findLineNumber(content, index) {
|
|
22
|
-
return content.slice(0, index).split("\n").length;
|
|
23
|
-
}
|
|
24
22
|
export function detectHardcodedCredentials(context) {
|
|
25
23
|
const findings = [];
|
|
26
24
|
for (const [file, content] of context.sources) {
|
|
25
|
+
// Skip test fixtures — they intentionally contain fake credentials
|
|
26
|
+
if (shouldSkipFile(file))
|
|
27
|
+
continue;
|
|
27
28
|
const lines = content.split("\n");
|
|
28
29
|
for (const { pattern, label } of CREDENTIAL_PATTERNS) {
|
|
29
30
|
pattern.lastIndex = 0;
|
|
@@ -31,13 +32,15 @@ export function detectHardcodedCredentials(context) {
|
|
|
31
32
|
while ((match = pattern.exec(content)) !== null) {
|
|
32
33
|
const lineNumber = findLineNumber(content, match.index);
|
|
33
34
|
const line = lines[lineNumber - 1] || "";
|
|
34
|
-
|
|
35
|
-
// Skip comments
|
|
36
|
-
if (trimmed.startsWith("//") || trimmed.startsWith("#") || trimmed.startsWith("*"))
|
|
35
|
+
if (isComment(line))
|
|
37
36
|
continue;
|
|
38
37
|
// Skip env var references
|
|
39
38
|
if (/process\.env|os\.environ|getenv|ENV\[/.test(line))
|
|
40
39
|
continue;
|
|
40
|
+
// Skip test/placeholder values
|
|
41
|
+
const matchedValue = match[1] || match[0];
|
|
42
|
+
if (isPlaceholderCredential(matchedValue))
|
|
43
|
+
continue;
|
|
41
44
|
findings.push({
|
|
42
45
|
ruleId: "MCS-AUTH-001",
|
|
43
46
|
severity: "critical",
|
|
@@ -61,6 +64,9 @@ export function detectSecretsInConfig(context) {
|
|
|
61
64
|
if (!entry.env)
|
|
62
65
|
continue;
|
|
63
66
|
for (const [key, value] of Object.entries(entry.env)) {
|
|
67
|
+
// Skip placeholder values
|
|
68
|
+
if (isPlaceholderCredential(value))
|
|
69
|
+
continue;
|
|
64
70
|
const looksLikeSecret = /key|token|secret|password|credential|auth/i.test(key) &&
|
|
65
71
|
!value.startsWith("$") &&
|
|
66
72
|
!value.startsWith("${") &&
|
package/dist/rules/auth.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/rules/auth.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/rules/auth.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/rules/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/rules/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAGtE,wBAAgB,eAAe,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,EAAE,CA0CnE;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,EAAE,CA2DvE;AAED,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,EAAE,CAoC3E"}
|
package/dist/rules/config.js
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
return content.slice(0, index).split("\n").length;
|
|
3
|
-
}
|
|
1
|
+
import { findLineNumber, shouldSkipFile, isComment } from "./utils.js";
|
|
4
2
|
export function detectDebugMode(context) {
|
|
5
3
|
const findings = [];
|
|
6
4
|
const DEBUG_PATTERNS = [
|
|
@@ -12,14 +10,15 @@ export function detectDebugMode(context) {
|
|
|
12
10
|
/log_level\s*=\s*["']debug["']/gi,
|
|
13
11
|
];
|
|
14
12
|
for (const [file, content] of context.sources) {
|
|
13
|
+
if (shouldSkipFile(file))
|
|
14
|
+
continue;
|
|
15
15
|
for (const pattern of DEBUG_PATTERNS) {
|
|
16
16
|
pattern.lastIndex = 0;
|
|
17
17
|
let match;
|
|
18
18
|
while ((match = pattern.exec(content)) !== null) {
|
|
19
19
|
const line = findLineNumber(content, match.index);
|
|
20
20
|
const lineContent = content.split("\n")[line - 1] || "";
|
|
21
|
-
|
|
22
|
-
if (trimmed.startsWith("//") || trimmed.startsWith("#") || trimmed.startsWith("*"))
|
|
21
|
+
if (isComment(lineContent))
|
|
23
22
|
continue;
|
|
24
23
|
// Skip if it's in a conditional check (e.g., if (NODE_ENV === 'development'))
|
|
25
24
|
if (/if\s*\(/.test(lineContent) || /if\s+/.test(lineContent))
|
|
@@ -56,14 +55,15 @@ export function detectVerboseErrors(context) {
|
|
|
56
55
|
context.language === "typescript" ? VERBOSE_PATTERNS_TS :
|
|
57
56
|
[...VERBOSE_PATTERNS_TS, ...VERBOSE_PATTERNS_PY];
|
|
58
57
|
for (const [file, content] of context.sources) {
|
|
58
|
+
if (shouldSkipFile(file))
|
|
59
|
+
continue;
|
|
59
60
|
for (const pattern of patterns) {
|
|
60
61
|
pattern.lastIndex = 0;
|
|
61
62
|
let match;
|
|
62
63
|
while ((match = pattern.exec(content)) !== null) {
|
|
63
64
|
const line = findLineNumber(content, match.index);
|
|
64
65
|
const lineContent = content.split("\n")[line - 1] || "";
|
|
65
|
-
|
|
66
|
-
if (trimmed.startsWith("//") || trimmed.startsWith("#") || trimmed.startsWith("*"))
|
|
66
|
+
if (isComment(lineContent))
|
|
67
67
|
continue;
|
|
68
68
|
// Only flag if it's in a catch block or error handler that returns to client
|
|
69
69
|
const before = content.slice(Math.max(0, match.index - 500), match.index);
|
|
@@ -96,14 +96,15 @@ export function detectInsecureTransport(context) {
|
|
|
96
96
|
{ pattern: /Access-Control-Allow-Origin['":\s]*\*/g, label: "CORS allows all origins" },
|
|
97
97
|
];
|
|
98
98
|
for (const [file, content] of context.sources) {
|
|
99
|
+
if (shouldSkipFile(file))
|
|
100
|
+
continue;
|
|
99
101
|
for (const { pattern, label } of TRANSPORT_PATTERNS) {
|
|
100
102
|
pattern.lastIndex = 0;
|
|
101
103
|
let match;
|
|
102
104
|
while ((match = pattern.exec(content)) !== null) {
|
|
103
105
|
const line = findLineNumber(content, match.index);
|
|
104
106
|
const lineContent = content.split("\n")[line - 1] || "";
|
|
105
|
-
|
|
106
|
-
if (trimmed.startsWith("//") || trimmed.startsWith("#") || trimmed.startsWith("*"))
|
|
107
|
+
if (isComment(lineContent))
|
|
107
108
|
continue;
|
|
108
109
|
findings.push({
|
|
109
110
|
ruleId: "MCS-CFG-003",
|
package/dist/rules/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/rules/config.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/rules/config.ts"],"names":[],"mappings":"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"}
|
package/dist/rules/data.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"data.d.ts","sourceRoot":"","sources":["../../src/rules/data.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"data.d.ts","sourceRoot":"","sources":["../../src/rules/data.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAqBtE,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,EAAE,CAsCxE;AAED,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,EAAE,CAoC3E"}
|
package/dist/rules/data.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { findLineNumber, isInToolHandler, shouldSkipFile, isComment, } from "./utils.js";
|
|
1
2
|
// Patterns that expose entire environment
|
|
2
3
|
const ENV_EXPOSURE_TS = [
|
|
3
4
|
/\bprocess\.env\b(?!\s*\.)(?!\s*\[)/g, // process.env without any key access (bare reference)
|
|
@@ -9,23 +10,24 @@ const ENV_EXPOSURE_PY = [
|
|
|
9
10
|
/dict\s*\(\s*os\.environ\b/g,
|
|
10
11
|
/json\.dumps\s*\(\s*(?:dict\s*\(\s*)?os\.environ/g,
|
|
11
12
|
];
|
|
12
|
-
function findLineNumber(content, index) {
|
|
13
|
-
return content.slice(0, index).split("\n").length;
|
|
14
|
-
}
|
|
15
13
|
export function detectEnvVarExposure(context) {
|
|
16
14
|
const findings = [];
|
|
17
15
|
const patterns = context.language === "python" ? ENV_EXPOSURE_PY :
|
|
18
16
|
context.language === "typescript" ? ENV_EXPOSURE_TS :
|
|
19
17
|
[...ENV_EXPOSURE_TS, ...ENV_EXPOSURE_PY];
|
|
20
18
|
for (const [file, content] of context.sources) {
|
|
19
|
+
if (shouldSkipFile(file))
|
|
20
|
+
continue;
|
|
21
21
|
for (const pattern of patterns) {
|
|
22
22
|
pattern.lastIndex = 0;
|
|
23
23
|
let match;
|
|
24
24
|
while ((match = pattern.exec(content)) !== null) {
|
|
25
25
|
const line = findLineNumber(content, match.index);
|
|
26
26
|
const lineContent = content.split("\n")[line - 1] || "";
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
if (isComment(lineContent))
|
|
28
|
+
continue;
|
|
29
|
+
// Only flag if in a tool handler context
|
|
30
|
+
if (!isInToolHandler(content, match.index, context.language))
|
|
29
31
|
continue;
|
|
30
32
|
findings.push({
|
|
31
33
|
ruleId: "MCS-DATA-001",
|
|
@@ -53,6 +55,8 @@ export function detectCredentialLeakage(context) {
|
|
|
53
55
|
// This rule is hard to detect well with regex alone — keep it conservative
|
|
54
56
|
// Flag cases where raw HTTP response objects are returned in tool responses
|
|
55
57
|
for (const [file, content] of context.sources) {
|
|
58
|
+
if (shouldSkipFile(file))
|
|
59
|
+
continue;
|
|
56
60
|
// Look for tool handlers that return raw response data
|
|
57
61
|
const toolHandlerRegex = /\.tool\s*\([^)]*\)\s*[^{]*\{[\s\S]*?return\s*\{[\s\S]*?content/g;
|
|
58
62
|
let handlerMatch;
|
package/dist/rules/data.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"data.js","sourceRoot":"","sources":["../../src/rules/data.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"data.js","sourceRoot":"","sources":["../../src/rules/data.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"description.d.ts","sourceRoot":"","sources":["../../src/rules/description.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"description.d.ts","sourceRoot":"","sources":["../../src/rules/description.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AA+EtE,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,EAAE,CA2BhF"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { shouldSkipFile } from "./utils.js";
|
|
1
2
|
// Prompt injection patterns in tool descriptions
|
|
2
3
|
const INJECTION_PATTERNS = [
|
|
3
4
|
// Override/ignore instructions
|
|
@@ -67,6 +68,8 @@ function extractDescriptionStrings(content, language) {
|
|
|
67
68
|
export function detectSuspiciousDescriptions(context) {
|
|
68
69
|
const findings = [];
|
|
69
70
|
for (const [file, content] of context.sources) {
|
|
71
|
+
if (shouldSkipFile(file))
|
|
72
|
+
continue;
|
|
70
73
|
const descriptions = extractDescriptionStrings(content, context.language);
|
|
71
74
|
for (const { text, line } of descriptions) {
|
|
72
75
|
for (const { pattern, label } of INJECTION_PATTERNS) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"description.js","sourceRoot":"","sources":["../../src/rules/description.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"description.js","sourceRoot":"","sources":["../../src/rules/description.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"injection.d.ts","sourceRoot":"","sources":["../../src/rules/injection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"injection.d.ts","sourceRoot":"","sources":["../../src/rules/injection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAkFtE,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,EAAE,CAiD1E;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,EAAE,CAsCtE;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,EAAE,CAyCvE"}
|
package/dist/rules/injection.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { findLineNumber, isInToolHandler, shouldSkipFile, isComment, } from "./utils.js";
|
|
1
2
|
// Dangerous shell execution sinks (TS/JS)
|
|
2
3
|
const EXEC_PATTERNS_TS = [
|
|
3
4
|
/(?<!\.)exec\s*\(/g,
|
|
@@ -18,12 +19,9 @@ const EXEC_PATTERNS_PY = [
|
|
|
18
19
|
];
|
|
19
20
|
// SQL injection patterns
|
|
20
21
|
const SQL_CONCAT_TS = [
|
|
21
|
-
// Template literal with variable in SQL context
|
|
22
22
|
/(?:query|execute|prepare|raw)\s*\(\s*`[^`]*\$\{/g,
|
|
23
|
-
// String concatenation in SQL context
|
|
24
23
|
/(?:query|execute|prepare|raw)\s*\(\s*["'][^"']*["']\s*\+/g,
|
|
25
24
|
/(?:query|execute|prepare|raw)\s*\([^)]*\+\s*["']/g,
|
|
26
|
-
// f-string SQL (Python)
|
|
27
25
|
/(?:execute|cursor\.execute|\.query)\s*\(\s*f["']/g,
|
|
28
26
|
];
|
|
29
27
|
const SQL_CONCAT_PY = [
|
|
@@ -60,40 +58,7 @@ const PATH_SAFE_PATTERNS = [
|
|
|
60
58
|
/root_dir/i,
|
|
61
59
|
/prefix.*check/i,
|
|
62
60
|
];
|
|
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
61
|
function hasPathValidation(content, matchIndex) {
|
|
96
|
-
// Check surrounding context (200 chars before and after) for validation patterns
|
|
97
62
|
const start = Math.max(0, matchIndex - 500);
|
|
98
63
|
const end = Math.min(content.length, matchIndex + 500);
|
|
99
64
|
const context = content.slice(start, end);
|
|
@@ -105,15 +70,16 @@ export function detectCommandInjection(context) {
|
|
|
105
70
|
context.language === "typescript" ? EXEC_PATTERNS_TS :
|
|
106
71
|
[...EXEC_PATTERNS_TS, ...EXEC_PATTERNS_PY];
|
|
107
72
|
for (const [file, content] of context.sources) {
|
|
73
|
+
// Skip non-server files (scripts, CLI, tests, etc.)
|
|
74
|
+
if (shouldSkipFile(file))
|
|
75
|
+
continue;
|
|
108
76
|
for (const pattern of patterns) {
|
|
109
77
|
pattern.lastIndex = 0;
|
|
110
78
|
let match;
|
|
111
79
|
while ((match = pattern.exec(content)) !== null) {
|
|
112
80
|
const line = findLineNumber(content, match.index);
|
|
113
81
|
const lineContent = content.split("\n")[line - 1] || "";
|
|
114
|
-
|
|
115
|
-
const trimmed = lineContent.trimStart();
|
|
116
|
-
if (trimmed.startsWith("//") || trimmed.startsWith("#") || trimmed.startsWith("*"))
|
|
82
|
+
if (isComment(lineContent))
|
|
117
83
|
continue;
|
|
118
84
|
// Skip if using execFile (safe — no shell)
|
|
119
85
|
if (/execFile/.test(lineContent))
|
|
@@ -126,11 +92,14 @@ export function detectCommandInjection(context) {
|
|
|
126
92
|
continue;
|
|
127
93
|
if (/\bpromisify\s*\(\s*exec\s*\)/.test(lineContent))
|
|
128
94
|
continue;
|
|
95
|
+
// Only flag if in a tool handler context
|
|
96
|
+
if (!isInToolHandler(content, match.index, context.language))
|
|
97
|
+
continue;
|
|
129
98
|
findings.push({
|
|
130
99
|
ruleId: "MCS-INJ-001",
|
|
131
100
|
severity: "critical",
|
|
132
101
|
title: "Command Injection via Tool Input",
|
|
133
|
-
message: `
|
|
102
|
+
message: `Shell command execution pattern detected in tool handler context. If tool input reaches this call, it enables arbitrary command execution.`,
|
|
134
103
|
location: { file, startLine: line, endLine: line },
|
|
135
104
|
fix: {
|
|
136
105
|
description: "Use execFile() with an argument array instead of exec(). Allowlist permitted commands.",
|
|
@@ -148,14 +117,18 @@ export function detectSqlInjection(context) {
|
|
|
148
117
|
context.language === "typescript" ? SQL_CONCAT_TS :
|
|
149
118
|
[...SQL_CONCAT_TS, ...SQL_CONCAT_PY];
|
|
150
119
|
for (const [file, content] of context.sources) {
|
|
120
|
+
if (shouldSkipFile(file))
|
|
121
|
+
continue;
|
|
151
122
|
for (const pattern of patterns) {
|
|
152
123
|
pattern.lastIndex = 0;
|
|
153
124
|
let match;
|
|
154
125
|
while ((match = pattern.exec(content)) !== null) {
|
|
155
126
|
const line = findLineNumber(content, match.index);
|
|
156
127
|
const lineContent = content.split("\n")[line - 1] || "";
|
|
157
|
-
|
|
158
|
-
|
|
128
|
+
if (isComment(lineContent))
|
|
129
|
+
continue;
|
|
130
|
+
// Only flag if in a tool handler context
|
|
131
|
+
if (!isInToolHandler(content, match.index, context.language))
|
|
159
132
|
continue;
|
|
160
133
|
findings.push({
|
|
161
134
|
ruleId: "MCS-INJ-002",
|
|
@@ -179,14 +152,15 @@ export function detectPathTraversal(context) {
|
|
|
179
152
|
context.language === "typescript" ? PATH_TRAVERSAL_TS :
|
|
180
153
|
[...PATH_TRAVERSAL_TS, ...PATH_TRAVERSAL_PY];
|
|
181
154
|
for (const [file, content] of context.sources) {
|
|
155
|
+
if (shouldSkipFile(file))
|
|
156
|
+
continue;
|
|
182
157
|
for (const pattern of patterns) {
|
|
183
158
|
pattern.lastIndex = 0;
|
|
184
159
|
let match;
|
|
185
160
|
while ((match = pattern.exec(content)) !== null) {
|
|
186
161
|
const line = findLineNumber(content, match.index);
|
|
187
162
|
const lineContent = content.split("\n")[line - 1] || "";
|
|
188
|
-
|
|
189
|
-
if (trimmed.startsWith("//") || trimmed.startsWith("#") || trimmed.startsWith("*"))
|
|
163
|
+
if (isComment(lineContent))
|
|
190
164
|
continue;
|
|
191
165
|
// Skip if there's path validation nearby
|
|
192
166
|
if (hasPathValidation(content, match.index))
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"injection.js","sourceRoot":"","sources":["../../src/rules/injection.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"injection.js","sourceRoot":"","sources":["../../src/rules/injection.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +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;
|
|
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;AAuDtE,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,EAAE,CAoC3E;AAED,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,EAAE,CAoEhF;AAED,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,EAAE,CAwChF"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { findLineNumber, isInToolHandler, shouldSkipFile, isComment, } from "./utils.js";
|
|
1
2
|
// Unrestricted outbound HTTP patterns
|
|
2
3
|
const BROAD_HTTP_PATTERNS = [
|
|
3
4
|
// fetch with a variable URL (not a string literal)
|
|
@@ -34,9 +35,6 @@ const CODE_EXEC_PATTERNS_PY = [
|
|
|
34
35
|
/(?<!\.)\bcompile\s*\(\s*(?!["'])/g,
|
|
35
36
|
/\b__import__\s*\(/g,
|
|
36
37
|
];
|
|
37
|
-
function findLineNumber(content, index) {
|
|
38
|
-
return content.slice(0, index).split("\n").length;
|
|
39
|
-
}
|
|
40
38
|
function hasHostValidation(content, matchIndex) {
|
|
41
39
|
const start = Math.max(0, matchIndex - 800);
|
|
42
40
|
const end = Math.min(content.length, matchIndex + 300);
|
|
@@ -46,17 +44,21 @@ function hasHostValidation(content, matchIndex) {
|
|
|
46
44
|
export function detectBroadCapabilities(context) {
|
|
47
45
|
const findings = [];
|
|
48
46
|
for (const [file, content] of context.sources) {
|
|
47
|
+
if (shouldSkipFile(file))
|
|
48
|
+
continue;
|
|
49
49
|
for (const pattern of BROAD_HTTP_PATTERNS) {
|
|
50
50
|
pattern.lastIndex = 0;
|
|
51
51
|
let match;
|
|
52
52
|
while ((match = pattern.exec(content)) !== null) {
|
|
53
53
|
const line = findLineNumber(content, match.index);
|
|
54
54
|
const lineContent = content.split("\n")[line - 1] || "";
|
|
55
|
-
|
|
56
|
-
if (trimmed.startsWith("//") || trimmed.startsWith("#") || trimmed.startsWith("*"))
|
|
55
|
+
if (isComment(lineContent))
|
|
57
56
|
continue;
|
|
58
57
|
if (hasHostValidation(content, match.index))
|
|
59
58
|
continue;
|
|
59
|
+
// Only flag if in a tool handler context
|
|
60
|
+
if (!isInToolHandler(content, match.index, context.language))
|
|
61
|
+
continue;
|
|
60
62
|
findings.push({
|
|
61
63
|
ruleId: "MCS-PERM-001",
|
|
62
64
|
severity: "high",
|
|
@@ -75,9 +77,6 @@ export function detectBroadCapabilities(context) {
|
|
|
75
77
|
}
|
|
76
78
|
export function detectUnrestrictedFilesystem(context) {
|
|
77
79
|
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
80
|
const FS_PATTERNS_TS = [
|
|
82
81
|
/\bfs\.readFileSync\s*\(\s*(?!["'`])\w+/g,
|
|
83
82
|
/\bfs\.readFile\s*\(\s*(?!["'`])\w+/g,
|
|
@@ -104,14 +103,15 @@ export function detectUnrestrictedFilesystem(context) {
|
|
|
104
103
|
/allowlist/i,
|
|
105
104
|
];
|
|
106
105
|
for (const [file, content] of context.sources) {
|
|
106
|
+
if (shouldSkipFile(file))
|
|
107
|
+
continue;
|
|
107
108
|
for (const pattern of patterns) {
|
|
108
109
|
pattern.lastIndex = 0;
|
|
109
110
|
let match;
|
|
110
111
|
while ((match = pattern.exec(content)) !== null) {
|
|
111
112
|
const line = findLineNumber(content, match.index);
|
|
112
113
|
const lineContent = content.split("\n")[line - 1] || "";
|
|
113
|
-
|
|
114
|
-
if (trimmed.startsWith("//") || trimmed.startsWith("#") || trimmed.startsWith("*"))
|
|
114
|
+
if (isComment(lineContent))
|
|
115
115
|
continue;
|
|
116
116
|
// Check for restrictions in surrounding context
|
|
117
117
|
const start = Math.max(0, match.index - 800);
|
|
@@ -119,6 +119,9 @@ export function detectUnrestrictedFilesystem(context) {
|
|
|
119
119
|
const ctx = content.slice(start, end);
|
|
120
120
|
if (RESTRICT_PATTERNS.some((p) => p.test(ctx)))
|
|
121
121
|
continue;
|
|
122
|
+
// Only flag if in a tool handler context
|
|
123
|
+
if (!isInToolHandler(content, match.index, context.language))
|
|
124
|
+
continue;
|
|
122
125
|
findings.push({
|
|
123
126
|
ruleId: "MCS-PERM-002",
|
|
124
127
|
severity: "high",
|
|
@@ -140,23 +143,27 @@ export function detectArbitraryCodeExecution(context) {
|
|
|
140
143
|
context.language === "typescript" ? CODE_EXEC_PATTERNS_TS :
|
|
141
144
|
[...CODE_EXEC_PATTERNS_TS, ...CODE_EXEC_PATTERNS_PY];
|
|
142
145
|
for (const [file, content] of context.sources) {
|
|
146
|
+
if (shouldSkipFile(file))
|
|
147
|
+
continue;
|
|
143
148
|
for (const pattern of patterns) {
|
|
144
149
|
pattern.lastIndex = 0;
|
|
145
150
|
let match;
|
|
146
151
|
while ((match = pattern.exec(content)) !== null) {
|
|
147
152
|
const line = findLineNumber(content, match.index);
|
|
148
153
|
const lineContent = content.split("\n")[line - 1] || "";
|
|
149
|
-
|
|
150
|
-
if (trimmed.startsWith("//") || trimmed.startsWith("#") || trimmed.startsWith("*"))
|
|
154
|
+
if (isComment(lineContent))
|
|
151
155
|
continue;
|
|
152
156
|
// In Python, exec() with a string literal is less dangerous — skip those
|
|
153
157
|
if (context.language === "python" && /\bexec\s*\(\s*["']/.test(lineContent))
|
|
154
158
|
continue;
|
|
159
|
+
// Only flag if in a tool handler context
|
|
160
|
+
if (!isInToolHandler(content, match.index, context.language))
|
|
161
|
+
continue;
|
|
155
162
|
findings.push({
|
|
156
163
|
ruleId: "MCS-PERM-003",
|
|
157
164
|
severity: "critical",
|
|
158
165
|
title: "Tool Can Execute Arbitrary Code",
|
|
159
|
-
message: `Code evaluation function (eval/exec/Function)
|
|
166
|
+
message: `Code evaluation function (eval/exec/Function) detected in tool handler context. If tool input reaches this, it enables arbitrary code execution.`,
|
|
160
167
|
location: { file, startLine: line, endLine: line },
|
|
161
168
|
fix: {
|
|
162
169
|
description: "Remove eval/exec usage. Use an allowlist of permitted operations instead of evaluating user input as code.",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"permissions.js","sourceRoot":"","sources":["../../src/rules/permissions.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"permissions.js","sourceRoot":"","sources":["../../src/rules/permissions.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared utilities for security rules.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Returns true if the file is unlikely to contain MCP tool handler code.
|
|
6
|
+
* Used to skip build scripts, CLI utilities, test fixtures, etc.
|
|
7
|
+
*/
|
|
8
|
+
export declare function shouldSkipFile(filePath: string): boolean;
|
|
9
|
+
export declare function findLineNumber(content: string, index: number): number;
|
|
10
|
+
/**
|
|
11
|
+
* Checks if a match is within an MCP tool handler context.
|
|
12
|
+
* Returns false (safe) by default — only flags code we're confident is in a handler.
|
|
13
|
+
*/
|
|
14
|
+
export declare function isInToolHandler(content: string, matchIndex: number, language: string): boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Returns true if the matched credential value looks like a test/placeholder.
|
|
17
|
+
*/
|
|
18
|
+
export declare function isPlaceholderCredential(value: string): boolean;
|
|
19
|
+
export declare function isComment(line: string): boolean;
|
|
20
|
+
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/rules/utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AA0BH;;;GAGG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAExD;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAErE;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,GACf,OAAO,CAoCT;AAqBD;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAE9D;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAQ/C"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared utilities for security rules.
|
|
3
|
+
*/
|
|
4
|
+
/** Non-server files that should not produce findings. */
|
|
5
|
+
const SKIP_FILE_PATTERNS = [
|
|
6
|
+
/\bscripts?\//i,
|
|
7
|
+
/\brelease\.\w+$/i,
|
|
8
|
+
/\bbuild\.\w+$/i,
|
|
9
|
+
/\bgulpfile\./i,
|
|
10
|
+
/\bwebpack\.\w+/i,
|
|
11
|
+
/\brollup\.\w+/i,
|
|
12
|
+
/\bvite\.config\./i,
|
|
13
|
+
/\bvitest\.config\./i,
|
|
14
|
+
/\best\.config\./i,
|
|
15
|
+
/\bjest\.config\./i,
|
|
16
|
+
/\btsconfig\./i,
|
|
17
|
+
/\b\.smithery\//,
|
|
18
|
+
/\b__tests__\//,
|
|
19
|
+
/\b__mocks__\//,
|
|
20
|
+
/\bfixtures?\//i,
|
|
21
|
+
/\bexamples?\//i,
|
|
22
|
+
/\bdocs?\//i,
|
|
23
|
+
/\b\.github\//,
|
|
24
|
+
/\bcli\//i,
|
|
25
|
+
/\bbin\//i,
|
|
26
|
+
];
|
|
27
|
+
/**
|
|
28
|
+
* Returns true if the file is unlikely to contain MCP tool handler code.
|
|
29
|
+
* Used to skip build scripts, CLI utilities, test fixtures, etc.
|
|
30
|
+
*/
|
|
31
|
+
export function shouldSkipFile(filePath) {
|
|
32
|
+
return SKIP_FILE_PATTERNS.some((p) => p.test(filePath));
|
|
33
|
+
}
|
|
34
|
+
export function findLineNumber(content, index) {
|
|
35
|
+
return content.slice(0, index).split("\n").length;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Checks if a match is within an MCP tool handler context.
|
|
39
|
+
* Returns false (safe) by default — only flags code we're confident is in a handler.
|
|
40
|
+
*/
|
|
41
|
+
export function isInToolHandler(content, matchIndex, language) {
|
|
42
|
+
const before = content.slice(Math.max(0, matchIndex - 3000), matchIndex);
|
|
43
|
+
if (language === "typescript" || language === "unknown") {
|
|
44
|
+
// Check for .tool( pattern — MCP SDK tool registration
|
|
45
|
+
if (/\.tool\s*\(/g.test(before)) {
|
|
46
|
+
const lastToolIndex = before.lastIndexOf(".tool(");
|
|
47
|
+
if (lastToolIndex !== -1) {
|
|
48
|
+
const afterTool = before.slice(lastToolIndex);
|
|
49
|
+
const opens = (afterTool.match(/\{/g) || []).length;
|
|
50
|
+
const closes = (afterTool.match(/\}/g) || []).length;
|
|
51
|
+
if (opens > closes)
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// Check for server.setRequestHandler — another MCP SDK pattern
|
|
56
|
+
if (/setRequestHandler\s*\(/g.test(before)) {
|
|
57
|
+
const lastHandler = before.lastIndexOf("setRequestHandler(");
|
|
58
|
+
if (lastHandler !== -1) {
|
|
59
|
+
const afterHandler = before.slice(lastHandler);
|
|
60
|
+
const opens = (afterHandler.match(/\{/g) || []).length;
|
|
61
|
+
const closes = (afterHandler.match(/\}/g) || []).length;
|
|
62
|
+
if (opens > closes)
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (language === "python" || language === "unknown") {
|
|
68
|
+
// Check for @mcp.tool() or @server.tool() decorator
|
|
69
|
+
if (/@\w+\.tool\s*\(/g.test(before))
|
|
70
|
+
return true;
|
|
71
|
+
// Check for FastMCP tool function patterns
|
|
72
|
+
if (/@\w+\.resource\s*\(/g.test(before))
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
// Not in a tool handler — don't flag
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
/** Common test/placeholder credential values that shouldn't trigger alerts */
|
|
79
|
+
const PLACEHOLDER_PATTERNS = [
|
|
80
|
+
/^test[-_]?/i,
|
|
81
|
+
/^placeholder/i,
|
|
82
|
+
/^example/i,
|
|
83
|
+
/^dummy/i,
|
|
84
|
+
/^fake/i,
|
|
85
|
+
/^sample/i,
|
|
86
|
+
/^your[-_]/i,
|
|
87
|
+
/^xxx/i,
|
|
88
|
+
/^changeme/i,
|
|
89
|
+
/^TODO/i,
|
|
90
|
+
/^REPLACE/i,
|
|
91
|
+
/^insert[-_]/i,
|
|
92
|
+
/^my[-_]?(?:api[-_]?)?key/i,
|
|
93
|
+
/^<.*>$/,
|
|
94
|
+
/^\$\{/,
|
|
95
|
+
];
|
|
96
|
+
/**
|
|
97
|
+
* Returns true if the matched credential value looks like a test/placeholder.
|
|
98
|
+
*/
|
|
99
|
+
export function isPlaceholderCredential(value) {
|
|
100
|
+
return PLACEHOLDER_PATTERNS.some((p) => p.test(value));
|
|
101
|
+
}
|
|
102
|
+
export function isComment(line) {
|
|
103
|
+
const trimmed = line.trimStart();
|
|
104
|
+
return (trimmed.startsWith("//") ||
|
|
105
|
+
trimmed.startsWith("#") ||
|
|
106
|
+
trimmed.startsWith("*") ||
|
|
107
|
+
trimmed.startsWith("/*"));
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/rules/utils.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +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;
|
|
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;AAGtE,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,EAAE,CAoE5E"}
|
package/dist/rules/validation.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
return content.slice(0, index).split("\n").length;
|
|
3
|
-
}
|
|
1
|
+
import { findLineNumber, shouldSkipFile } from "./utils.js";
|
|
4
2
|
export function detectMissingInputSchema(context) {
|
|
5
3
|
const findings = [];
|
|
6
4
|
for (const [file, content] of context.sources) {
|
|
5
|
+
if (shouldSkipFile(file))
|
|
6
|
+
continue;
|
|
7
7
|
if (context.language === "typescript" || context.language === "unknown") {
|
|
8
8
|
// Detect z.any() or z.unknown() in tool schemas — these accept anything without validation
|
|
9
9
|
const weakTypeRegex = /z\.(?:any|unknown)\s*\(\)/g;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validation.js","sourceRoot":"","sources":["../../src/rules/validation.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"validation.js","sourceRoot":"","sources":["../../src/rules/validation.ts"],"names":[],"mappings":"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"}
|
package/dist/scanner.js
CHANGED
|
@@ -6,7 +6,7 @@ import { parseManifest } from "./discovery/manifest.js";
|
|
|
6
6
|
import { parseConfig } from "./discovery/config-parser.js";
|
|
7
7
|
import { rules } from "./rules/index.js";
|
|
8
8
|
import { detectVulnerableDeps } from "./rules/deps.js";
|
|
9
|
-
const PKG_VERSION = "0.
|
|
9
|
+
const PKG_VERSION = "0.2.0";
|
|
10
10
|
const SEVERITY_ORDER = {
|
|
11
11
|
critical: 0,
|
|
12
12
|
high: 1,
|
package/package.json
CHANGED