@sigildev/sigil 0.1.3 → 0.2.1
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 +27 -44
- package/dist/rules/injection.js.map +1 -1
- package/dist/rules/permissions.d.ts.map +1 -1
- package/dist/rules/permissions.js +25 -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 +115 -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.1
|
|
11
|
+
.version("0.2.1")
|
|
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;AA2FtE,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,19 +19,20 @@ 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,
|
|
26
|
+
// Direct variable pass-through (.query(sql) without a string literal)
|
|
27
|
+
/\.query\s*\(\s*(?!["'`])[a-zA-Z_]\w*\s*[,)]/g,
|
|
28
28
|
];
|
|
29
29
|
const SQL_CONCAT_PY = [
|
|
30
30
|
/(?:execute|cursor\.execute)\s*\(\s*f["']/g,
|
|
31
31
|
/(?:execute|cursor\.execute)\s*\(\s*["'][^"']*["']\s*%/g,
|
|
32
32
|
/(?:execute|cursor\.execute)\s*\(\s*["'][^"']*["']\s*\.\s*format/g,
|
|
33
33
|
/(?:execute|cursor\.execute)\s*\(\s*["'][^"']*["']\s*\+/g,
|
|
34
|
+
// Direct variable pass-through (cursor.execute(sql) without a string literal)
|
|
35
|
+
/(?:cursor\.execute|\.execute)\s*\(\s*(?!["'f])[a-zA-Z_]\w*\s*[,)]/g,
|
|
34
36
|
];
|
|
35
37
|
// File operation patterns without validation
|
|
36
38
|
const PATH_TRAVERSAL_TS = [
|
|
@@ -59,41 +61,13 @@ const PATH_SAFE_PATTERNS = [
|
|
|
59
61
|
/base_dir/i,
|
|
60
62
|
/root_dir/i,
|
|
61
63
|
/prefix.*check/i,
|
|
64
|
+
/validate[_-]?path/i,
|
|
65
|
+
/check[_-]?path/i,
|
|
66
|
+
/sanitize[_-]?path/i,
|
|
67
|
+
/safe[_-]?path/i,
|
|
68
|
+
/allowed[_-]?paths/i,
|
|
62
69
|
];
|
|
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
70
|
function hasPathValidation(content, matchIndex) {
|
|
96
|
-
// Check surrounding context (200 chars before and after) for validation patterns
|
|
97
71
|
const start = Math.max(0, matchIndex - 500);
|
|
98
72
|
const end = Math.min(content.length, matchIndex + 500);
|
|
99
73
|
const context = content.slice(start, end);
|
|
@@ -105,15 +79,16 @@ export function detectCommandInjection(context) {
|
|
|
105
79
|
context.language === "typescript" ? EXEC_PATTERNS_TS :
|
|
106
80
|
[...EXEC_PATTERNS_TS, ...EXEC_PATTERNS_PY];
|
|
107
81
|
for (const [file, content] of context.sources) {
|
|
82
|
+
// Skip non-server files (scripts, CLI, tests, etc.)
|
|
83
|
+
if (shouldSkipFile(file))
|
|
84
|
+
continue;
|
|
108
85
|
for (const pattern of patterns) {
|
|
109
86
|
pattern.lastIndex = 0;
|
|
110
87
|
let match;
|
|
111
88
|
while ((match = pattern.exec(content)) !== null) {
|
|
112
89
|
const line = findLineNumber(content, match.index);
|
|
113
90
|
const lineContent = content.split("\n")[line - 1] || "";
|
|
114
|
-
|
|
115
|
-
const trimmed = lineContent.trimStart();
|
|
116
|
-
if (trimmed.startsWith("//") || trimmed.startsWith("#") || trimmed.startsWith("*"))
|
|
91
|
+
if (isComment(lineContent))
|
|
117
92
|
continue;
|
|
118
93
|
// Skip if using execFile (safe — no shell)
|
|
119
94
|
if (/execFile/.test(lineContent))
|
|
@@ -126,11 +101,14 @@ export function detectCommandInjection(context) {
|
|
|
126
101
|
continue;
|
|
127
102
|
if (/\bpromisify\s*\(\s*exec\s*\)/.test(lineContent))
|
|
128
103
|
continue;
|
|
104
|
+
// Only flag if in a tool handler context
|
|
105
|
+
if (!isInToolHandler(content, match.index, context.language))
|
|
106
|
+
continue;
|
|
129
107
|
findings.push({
|
|
130
108
|
ruleId: "MCS-INJ-001",
|
|
131
109
|
severity: "critical",
|
|
132
110
|
title: "Command Injection via Tool Input",
|
|
133
|
-
message: `
|
|
111
|
+
message: `Shell command execution pattern detected in tool handler context. If tool input reaches this call, it enables arbitrary command execution.`,
|
|
134
112
|
location: { file, startLine: line, endLine: line },
|
|
135
113
|
fix: {
|
|
136
114
|
description: "Use execFile() with an argument array instead of exec(). Allowlist permitted commands.",
|
|
@@ -148,14 +126,18 @@ export function detectSqlInjection(context) {
|
|
|
148
126
|
context.language === "typescript" ? SQL_CONCAT_TS :
|
|
149
127
|
[...SQL_CONCAT_TS, ...SQL_CONCAT_PY];
|
|
150
128
|
for (const [file, content] of context.sources) {
|
|
129
|
+
if (shouldSkipFile(file))
|
|
130
|
+
continue;
|
|
151
131
|
for (const pattern of patterns) {
|
|
152
132
|
pattern.lastIndex = 0;
|
|
153
133
|
let match;
|
|
154
134
|
while ((match = pattern.exec(content)) !== null) {
|
|
155
135
|
const line = findLineNumber(content, match.index);
|
|
156
136
|
const lineContent = content.split("\n")[line - 1] || "";
|
|
157
|
-
|
|
158
|
-
|
|
137
|
+
if (isComment(lineContent))
|
|
138
|
+
continue;
|
|
139
|
+
// Only flag if in a tool handler context
|
|
140
|
+
if (!isInToolHandler(content, match.index, context.language))
|
|
159
141
|
continue;
|
|
160
142
|
findings.push({
|
|
161
143
|
ruleId: "MCS-INJ-002",
|
|
@@ -179,14 +161,15 @@ export function detectPathTraversal(context) {
|
|
|
179
161
|
context.language === "typescript" ? PATH_TRAVERSAL_TS :
|
|
180
162
|
[...PATH_TRAVERSAL_TS, ...PATH_TRAVERSAL_PY];
|
|
181
163
|
for (const [file, content] of context.sources) {
|
|
164
|
+
if (shouldSkipFile(file))
|
|
165
|
+
continue;
|
|
182
166
|
for (const pattern of patterns) {
|
|
183
167
|
pattern.lastIndex = 0;
|
|
184
168
|
let match;
|
|
185
169
|
while ((match = pattern.exec(content)) !== null) {
|
|
186
170
|
const line = findLineNumber(content, match.index);
|
|
187
171
|
const lineContent = content.split("\n")[line - 1] || "";
|
|
188
|
-
|
|
189
|
-
if (trimmed.startsWith("//") || trimmed.startsWith("#") || trimmed.startsWith("*"))
|
|
172
|
+
if (isComment(lineContent))
|
|
190
173
|
continue;
|
|
191
174
|
// Skip if there's path validation nearby
|
|
192
175
|
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,CAyEhF;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,
|
|
@@ -102,16 +101,22 @@ export function detectUnrestrictedFilesystem(context) {
|
|
|
102
101
|
/prefix/i,
|
|
103
102
|
/whitelist/i,
|
|
104
103
|
/allowlist/i,
|
|
104
|
+
/validate[_-]?path/i,
|
|
105
|
+
/check[_-]?path/i,
|
|
106
|
+
/sanitize[_-]?path/i,
|
|
107
|
+
/safe[_-]?path/i,
|
|
108
|
+
/allowed[_-]?paths/i,
|
|
105
109
|
];
|
|
106
110
|
for (const [file, content] of context.sources) {
|
|
111
|
+
if (shouldSkipFile(file))
|
|
112
|
+
continue;
|
|
107
113
|
for (const pattern of patterns) {
|
|
108
114
|
pattern.lastIndex = 0;
|
|
109
115
|
let match;
|
|
110
116
|
while ((match = pattern.exec(content)) !== null) {
|
|
111
117
|
const line = findLineNumber(content, match.index);
|
|
112
118
|
const lineContent = content.split("\n")[line - 1] || "";
|
|
113
|
-
|
|
114
|
-
if (trimmed.startsWith("//") || trimmed.startsWith("#") || trimmed.startsWith("*"))
|
|
119
|
+
if (isComment(lineContent))
|
|
115
120
|
continue;
|
|
116
121
|
// Check for restrictions in surrounding context
|
|
117
122
|
const start = Math.max(0, match.index - 800);
|
|
@@ -119,6 +124,9 @@ export function detectUnrestrictedFilesystem(context) {
|
|
|
119
124
|
const ctx = content.slice(start, end);
|
|
120
125
|
if (RESTRICT_PATTERNS.some((p) => p.test(ctx)))
|
|
121
126
|
continue;
|
|
127
|
+
// Only flag if in a tool handler context
|
|
128
|
+
if (!isInToolHandler(content, match.index, context.language))
|
|
129
|
+
continue;
|
|
122
130
|
findings.push({
|
|
123
131
|
ruleId: "MCS-PERM-002",
|
|
124
132
|
severity: "high",
|
|
@@ -140,23 +148,27 @@ export function detectArbitraryCodeExecution(context) {
|
|
|
140
148
|
context.language === "typescript" ? CODE_EXEC_PATTERNS_TS :
|
|
141
149
|
[...CODE_EXEC_PATTERNS_TS, ...CODE_EXEC_PATTERNS_PY];
|
|
142
150
|
for (const [file, content] of context.sources) {
|
|
151
|
+
if (shouldSkipFile(file))
|
|
152
|
+
continue;
|
|
143
153
|
for (const pattern of patterns) {
|
|
144
154
|
pattern.lastIndex = 0;
|
|
145
155
|
let match;
|
|
146
156
|
while ((match = pattern.exec(content)) !== null) {
|
|
147
157
|
const line = findLineNumber(content, match.index);
|
|
148
158
|
const lineContent = content.split("\n")[line - 1] || "";
|
|
149
|
-
|
|
150
|
-
if (trimmed.startsWith("//") || trimmed.startsWith("#") || trimmed.startsWith("*"))
|
|
159
|
+
if (isComment(lineContent))
|
|
151
160
|
continue;
|
|
152
161
|
// In Python, exec() with a string literal is less dangerous — skip those
|
|
153
162
|
if (context.language === "python" && /\bexec\s*\(\s*["']/.test(lineContent))
|
|
154
163
|
continue;
|
|
164
|
+
// Only flag if in a tool handler context
|
|
165
|
+
if (!isInToolHandler(content, match.index, context.language))
|
|
166
|
+
continue;
|
|
155
167
|
findings.push({
|
|
156
168
|
ruleId: "MCS-PERM-003",
|
|
157
169
|
severity: "critical",
|
|
158
170
|
title: "Tool Can Execute Arbitrary Code",
|
|
159
|
-
message: `Code evaluation function (eval/exec/Function)
|
|
171
|
+
message: `Code evaluation function (eval/exec/Function) detected in tool handler context. If tool input reaches this, it enables arbitrary code execution.`,
|
|
160
172
|
location: { file, startLine: line, endLine: line },
|
|
161
173
|
fix: {
|
|
162
174
|
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;AA2BD;;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,115 @@
|
|
|
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
|
+
// Connection strings with placeholder credentials
|
|
96
|
+
/:\/\/user(?:name)?:pass(?:word)?@/i,
|
|
97
|
+
/:\/\/root:(?:root|password|pass|secret)@/i,
|
|
98
|
+
/:\/\/admin:(?:admin|password|pass|secret)@/i,
|
|
99
|
+
/:\/\/\w+:password@/i,
|
|
100
|
+
/:\/\/[^:]+:[^@]+@example\.com/i,
|
|
101
|
+
];
|
|
102
|
+
/**
|
|
103
|
+
* Returns true if the matched credential value looks like a test/placeholder.
|
|
104
|
+
*/
|
|
105
|
+
export function isPlaceholderCredential(value) {
|
|
106
|
+
return PLACEHOLDER_PATTERNS.some((p) => p.test(value));
|
|
107
|
+
}
|
|
108
|
+
export function isComment(line) {
|
|
109
|
+
const trimmed = line.trimStart();
|
|
110
|
+
return (trimmed.startsWith("//") ||
|
|
111
|
+
trimmed.startsWith("#") ||
|
|
112
|
+
trimmed.startsWith("*") ||
|
|
113
|
+
trimmed.startsWith("/*"));
|
|
114
|
+
}
|
|
115
|
+
//# 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.1
|
|
9
|
+
const PKG_VERSION = "0.2.1";
|
|
10
10
|
const SEVERITY_ORDER = {
|
|
11
11
|
critical: 0,
|
|
12
12
|
high: 1,
|
package/package.json
CHANGED