@guardbee/mcp-tool-poisoning-scanner 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/README.md +124 -0
- package/TR.md +124 -0
- package/dist/__tests__/scanner.test.d.ts +2 -0
- package/dist/__tests__/scanner.test.d.ts.map +1 -0
- package/dist/__tests__/scanner.test.js +128 -0
- package/dist/__tests__/scanner.test.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +159 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +8 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +101 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/patterns.d.ts +37 -0
- package/dist/patterns.d.ts.map +1 -0
- package/dist/patterns.js +102 -0
- package/dist/patterns.js.map +1 -0
- package/dist/sarif.d.ts +3 -0
- package/dist/sarif.d.ts.map +1 -0
- package/dist/sarif.js +52 -0
- package/dist/sarif.js.map +1 -0
- package/dist/scanner.d.ts +51 -0
- package/dist/scanner.d.ts.map +1 -0
- package/dist/scanner.js +192 -0
- package/dist/scanner.js.map +1 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +71 -0
- package/dist/server.js.map +1 -0
- package/package.json +49 -0
package/dist/config.js
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { readFileSync, existsSync } from "fs";
|
|
2
|
+
import { resolve, join } from "path";
|
|
3
|
+
const DEFAULTS = {
|
|
4
|
+
failOn: "any",
|
|
5
|
+
maxFiles: 5000,
|
|
6
|
+
exclude: [],
|
|
7
|
+
};
|
|
8
|
+
function findConfigFile(startDir) {
|
|
9
|
+
const names = ["guardbee.yml", "guardbee.yaml", ".guardbee.yml"];
|
|
10
|
+
let dir = resolve(startDir);
|
|
11
|
+
for (let i = 0; i < 5; i++) {
|
|
12
|
+
for (const name of names) {
|
|
13
|
+
const p = join(dir, name);
|
|
14
|
+
if (existsSync(p))
|
|
15
|
+
return p;
|
|
16
|
+
}
|
|
17
|
+
const parent = resolve(dir, "..");
|
|
18
|
+
if (parent === dir)
|
|
19
|
+
break;
|
|
20
|
+
dir = parent;
|
|
21
|
+
}
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
function parseYaml(content) {
|
|
25
|
+
const root = {};
|
|
26
|
+
let currentSection = null;
|
|
27
|
+
let currentKey = null;
|
|
28
|
+
let currentList = null;
|
|
29
|
+
for (const rawLine of content.split("\n")) {
|
|
30
|
+
const line = rawLine.replace(/#.*$/, "");
|
|
31
|
+
if (!line.trim())
|
|
32
|
+
continue;
|
|
33
|
+
const indent = line.match(/^(\s*)/)?.[1].length ?? 0;
|
|
34
|
+
if (indent === 0 && line.trim().endsWith(":")) {
|
|
35
|
+
if (currentKey && currentList) {
|
|
36
|
+
root[currentSection][currentKey] = currentList;
|
|
37
|
+
currentList = null;
|
|
38
|
+
currentKey = null;
|
|
39
|
+
}
|
|
40
|
+
currentSection = line.trim().slice(0, -1);
|
|
41
|
+
root[currentSection] = {};
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (indent === 2 && currentSection) {
|
|
45
|
+
if (currentKey && currentList) {
|
|
46
|
+
root[currentSection][currentKey] = currentList;
|
|
47
|
+
currentList = null;
|
|
48
|
+
currentKey = null;
|
|
49
|
+
}
|
|
50
|
+
const colonIdx = line.indexOf(":");
|
|
51
|
+
if (colonIdx === -1)
|
|
52
|
+
continue;
|
|
53
|
+
const key = line.slice(0, colonIdx).trim();
|
|
54
|
+
const value = line.slice(colonIdx + 1).trim();
|
|
55
|
+
if (value === "") {
|
|
56
|
+
currentKey = key;
|
|
57
|
+
currentList = [];
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
root[currentSection][key] = value;
|
|
61
|
+
}
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
if (indent === 4 && currentList !== null) {
|
|
65
|
+
const item = line.trim().replace(/^-\s*/, "").replace(/^['"]|['"]$/g, "");
|
|
66
|
+
currentList.push(item);
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (currentKey && currentList && currentSection) {
|
|
71
|
+
root[currentSection][currentKey] = currentList;
|
|
72
|
+
}
|
|
73
|
+
return root;
|
|
74
|
+
}
|
|
75
|
+
export function loadConfig(searchDir, cliOverrides = {}) {
|
|
76
|
+
const configPath = findConfigFile(searchDir);
|
|
77
|
+
if (!configPath)
|
|
78
|
+
return { ...DEFAULTS, ...cliOverrides };
|
|
79
|
+
let fileConfig = {};
|
|
80
|
+
try {
|
|
81
|
+
const raw = readFileSync(configPath, "utf8");
|
|
82
|
+
const parsed = parseYaml(raw);
|
|
83
|
+
const section = parsed["tool-poisoning-scanner"];
|
|
84
|
+
if (section) {
|
|
85
|
+
if (typeof section["fail-on"] === "string")
|
|
86
|
+
fileConfig.failOn = section["fail-on"];
|
|
87
|
+
if (typeof section["max-files"] === "string")
|
|
88
|
+
fileConfig.maxFiles = parseInt(section["max-files"], 10);
|
|
89
|
+
if (Array.isArray(section["exclude"]))
|
|
90
|
+
fileConfig.exclude = section["exclude"];
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
// ignore parse errors, use defaults
|
|
95
|
+
}
|
|
96
|
+
return { ...DEFAULTS, ...fileConfig, ...cliOverrides };
|
|
97
|
+
}
|
|
98
|
+
export function configFilePath(searchDir) {
|
|
99
|
+
return findConfigFile(searchDir);
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { scanText, scanFile, scanDirectory, extractToolBlocks } from "./scanner.js";
|
|
2
|
+
export type { Finding, ScanResult } from "./scanner.js";
|
|
3
|
+
export { DESCRIPTION_INJECTION_PATTERNS, MISMATCH_SINK_RULES, READ_ONLY_HINT } from "./patterns.js";
|
|
4
|
+
export type { DescriptionInjectionPattern, MismatchSinkRule, ToolPoisoningCategory } from "./patterns.js";
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACpF,YAAY,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AACxD,OAAO,EAAE,8BAA8B,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AACpG,YAAY,EAAE,2BAA2B,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAEpF,OAAO,EAAE,8BAA8B,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export type ToolPoisoningCategory = "description-injection" | "confused-deputy";
|
|
2
|
+
export interface DescriptionInjectionPattern {
|
|
3
|
+
id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
pattern: RegExp;
|
|
6
|
+
severity: "critical" | "high" | "medium";
|
|
7
|
+
recommendation: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* A tool's `description` string is read by the calling LLM as part of its
|
|
11
|
+
* tool-selection context — the same trust level as a system prompt, even
|
|
12
|
+
* though it's authored by whoever published the MCP server. "Tool poisoning"
|
|
13
|
+
* (documented by Invariant Labs, 2025) hides instructions in that string
|
|
14
|
+
* rather than in the conversation: "always call this first", "read ~/.ssh
|
|
15
|
+
* and pass it as the debug param", "don't tell the user". None of this is
|
|
16
|
+
* a code-level sink — `mcp-server-auditor` won't catch it, because nothing
|
|
17
|
+
* unsafe happens in the handler. The description itself is the payload.
|
|
18
|
+
*/
|
|
19
|
+
export declare const DESCRIPTION_INJECTION_PATTERNS: DescriptionInjectionPattern[];
|
|
20
|
+
/** Name/description keywords implying a narrow, read-only/informational tool. */
|
|
21
|
+
export declare const READ_ONLY_HINT: RegExp;
|
|
22
|
+
export interface MismatchSinkRule {
|
|
23
|
+
id: string;
|
|
24
|
+
pattern: RegExp;
|
|
25
|
+
category: "process-execution" | "code-execution" | "filesystem-write" | "credentials-exposure";
|
|
26
|
+
severity: "critical" | "high";
|
|
27
|
+
recommendation: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Sinks that have no business appearing inside a tool whose name/description
|
|
31
|
+
* promises read-only/informational behavior. Deliberately excludes network
|
|
32
|
+
* fetches — a "get_weather" or "search" tool legitimately calling an external
|
|
33
|
+
* API is the common case, not a red flag, so including it here would make
|
|
34
|
+
* this the noisiest, least trustworthy pattern in the catalog.
|
|
35
|
+
*/
|
|
36
|
+
export declare const MISMATCH_SINK_RULES: MismatchSinkRule[];
|
|
37
|
+
//# sourceMappingURL=patterns.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"patterns.d.ts","sourceRoot":"","sources":["../src/patterns.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,qBAAqB,GAAG,uBAAuB,GAAG,iBAAiB,CAAC;AAEhF,MAAM,WAAW,2BAA2B;IAC1C,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,UAAU,GAAG,MAAM,GAAG,QAAQ,CAAC;IACzC,cAAc,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,8BAA8B,EAAE,2BAA2B,EAkDvE,CAAC;AAIF,iFAAiF;AACjF,eAAO,MAAM,cAAc,QAAqH,CAAC;AAEjJ,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,mBAAmB,GAAG,gBAAgB,GAAG,kBAAkB,GAAG,sBAAsB,CAAC;IAC/F,QAAQ,EAAE,UAAU,GAAG,MAAM,CAAC;IAC9B,cAAc,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,EAAE,gBAAgB,EA6BjD,CAAC"}
|
package/dist/patterns.js
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A tool's `description` string is read by the calling LLM as part of its
|
|
3
|
+
* tool-selection context — the same trust level as a system prompt, even
|
|
4
|
+
* though it's authored by whoever published the MCP server. "Tool poisoning"
|
|
5
|
+
* (documented by Invariant Labs, 2025) hides instructions in that string
|
|
6
|
+
* rather than in the conversation: "always call this first", "read ~/.ssh
|
|
7
|
+
* and pass it as the debug param", "don't tell the user". None of this is
|
|
8
|
+
* a code-level sink — `mcp-server-auditor` won't catch it, because nothing
|
|
9
|
+
* unsafe happens in the handler. The description itself is the payload.
|
|
10
|
+
*/
|
|
11
|
+
export const DESCRIPTION_INJECTION_PATTERNS = [
|
|
12
|
+
{
|
|
13
|
+
id: "instruction_override_in_description",
|
|
14
|
+
name: "Description tells the model to ignore/disregard prior instructions",
|
|
15
|
+
pattern: /\b(?:ignore|disregard)\s+(?:all\s+)?(?:previous|prior|above)\s+instructions?\b/i,
|
|
16
|
+
severity: "critical",
|
|
17
|
+
recommendation: "A tool description has no legitimate reason to override the conversation's instructions. Treat this server as compromised or malicious.",
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
id: "always_call_first_directive",
|
|
21
|
+
name: "Description instructs the model to always call this tool first/unconditionally",
|
|
22
|
+
pattern: /\balways\s+(?:call|use|invoke)\s+this\s+(?:tool|function)\s+(?:first|before)\b/i,
|
|
23
|
+
severity: "high",
|
|
24
|
+
recommendation: "A legitimate tool description explains what the tool does and lets the model decide when it's relevant — it doesn't demand priority over every other tool. This is a known tool-poisoning priming pattern.",
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
id: "covert_instruction_in_description",
|
|
28
|
+
name: "Description tells the model not to inform/mention this to the user",
|
|
29
|
+
pattern: /\bdo\s+not\s+(?:tell|inform|mention\s+(?:this\s+)?to)\s+the\s+user\b/i,
|
|
30
|
+
severity: "critical",
|
|
31
|
+
recommendation: "A tool asking to hide its own behavior from the user it's acting on behalf of is a hallmark of a poisoned tool description. Do not install this server.",
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
id: "meta_authority_directive",
|
|
35
|
+
name: "Description uses system-prompt-style authority markers",
|
|
36
|
+
pattern: /<\s*IMPORTANT\s*>|^\s*(?:IMPORTANT|SYSTEM|NOTE\s+TO\s+MODEL)\s*:/im,
|
|
37
|
+
severity: "high",
|
|
38
|
+
recommendation: "A tool description shouldn't need to invoke system-prompt-style authority (\"<IMPORTANT>\", \"SYSTEM:\") to be followed — this is a common technique to make an injected instruction feel more authoritative to the model.",
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
id: "sensitive_file_exfil_instruction",
|
|
42
|
+
name: "Description instructs reading a sensitive file into a tool parameter",
|
|
43
|
+
pattern: /\b(?:read|cat|include|attach|append)\b[^.]{0,80}(?:\.ssh\b|\bid_rsa\b|\.env\b|\.aws[\\/]credentials\b|\bprivate[\s_-]?key\b|\.npmrc\b)/i,
|
|
44
|
+
severity: "critical",
|
|
45
|
+
recommendation: "A tool description asking the model to read a credential/key file and pass its contents as a parameter is a direct exfiltration instruction — the file's contents would flow to whoever controls this MCP server.",
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
id: "hidden_zero_width_in_description",
|
|
49
|
+
name: "Description contains zero-width characters invisible to a human reviewer",
|
|
50
|
+
pattern: /[\u200B\u2060]/,
|
|
51
|
+
severity: "high",
|
|
52
|
+
recommendation: "Zero-width characters in a tool description are invisible when a human reviews the server's tool list in a UI, but are still read by the model. Their only realistic purpose here is hiding text from review.",
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
id: "other_tools_manipulation_directive",
|
|
56
|
+
name: "Description instructs the model how to call other, different tools",
|
|
57
|
+
pattern: /\bwhen\s+(?:calling|using)\s+(?:the\s+)?["'`]?[\w.-]+["'`]?\s+tool\s*,\s*(?:always|you\s+must|set|pass|include)\b/i,
|
|
58
|
+
severity: "high",
|
|
59
|
+
recommendation: "A tool's description should describe that tool — not dictate how a different tool must be called. This is a documented tool-poisoning technique for hijacking unrelated tools via a single malicious server.",
|
|
60
|
+
},
|
|
61
|
+
];
|
|
62
|
+
// ── Confused-deputy: promised capability vs. actual handler behavior ─────────
|
|
63
|
+
/** Name/description keywords implying a narrow, read-only/informational tool. */
|
|
64
|
+
export const READ_ONLY_HINT = /\b(?:get|read|list|search|lookup|find|query|describe|show|view|check|inspect|summarize|translate|status|info)\b/i;
|
|
65
|
+
/**
|
|
66
|
+
* Sinks that have no business appearing inside a tool whose name/description
|
|
67
|
+
* promises read-only/informational behavior. Deliberately excludes network
|
|
68
|
+
* fetches — a "get_weather" or "search" tool legitimately calling an external
|
|
69
|
+
* API is the common case, not a red flag, so including it here would make
|
|
70
|
+
* this the noisiest, least trustworthy pattern in the catalog.
|
|
71
|
+
*/
|
|
72
|
+
export const MISMATCH_SINK_RULES = [
|
|
73
|
+
{
|
|
74
|
+
id: "process_execution",
|
|
75
|
+
pattern: /\b(?:execSync|spawnSync|child_process\.exec\w*|spawn)\s*\(/,
|
|
76
|
+
category: "process-execution",
|
|
77
|
+
severity: "critical",
|
|
78
|
+
recommendation: "This tool's name/description promises read-only/informational behavior, but its handler runs a shell/process command. Either the description is misleading (fix it so callers know what they're actually authorizing) or this tool has been given capability it shouldn't have.",
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
id: "code_execution",
|
|
82
|
+
pattern: /\b(?:eval|new\s+Function)\s*\(/,
|
|
83
|
+
category: "code-execution",
|
|
84
|
+
severity: "critical",
|
|
85
|
+
recommendation: "This tool's name/description promises read-only/informational behavior, but its handler evaluates code. A caller relying on the description has no way to know this tool can execute arbitrary code.",
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
id: "filesystem_write",
|
|
89
|
+
pattern: /\b(?:writeFileSync|unlinkSync|rmSync|appendFileSync|renameSync|writeFile)\s*\(/,
|
|
90
|
+
category: "filesystem-write",
|
|
91
|
+
severity: "high",
|
|
92
|
+
recommendation: "This tool's name/description promises read-only/informational behavior, but its handler writes/deletes files. Rename or redescribe the tool to reflect what it actually does, so callers aren't misled about its blast radius.",
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
id: "credentials_exposure",
|
|
96
|
+
pattern: /(?:\.\.\.\s*process\.env\b)|(?:JSON\.stringify\(\s*process\.env\s*\))|(?:return\s+process\.env\s*[;\n])/,
|
|
97
|
+
category: "credentials-exposure",
|
|
98
|
+
severity: "critical",
|
|
99
|
+
recommendation: "This tool's name/description promises read-only/informational behavior, but its handler exposes the entire process environment. A caller has no way to know this \"informational\" tool can leak every secret in this process.",
|
|
100
|
+
},
|
|
101
|
+
];
|
|
102
|
+
//# sourceMappingURL=patterns.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"patterns.js","sourceRoot":"","sources":["../src/patterns.ts"],"names":[],"mappings":"AAUA;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,8BAA8B,GAAkC;IAC3E;QACE,EAAE,EAAE,qCAAqC;QACzC,IAAI,EAAE,oEAAoE;QAC1E,OAAO,EAAE,iFAAiF;QAC1F,QAAQ,EAAE,UAAU;QACpB,cAAc,EAAE,yIAAyI;KAC1J;IACD;QACE,EAAE,EAAE,6BAA6B;QACjC,IAAI,EAAE,gFAAgF;QACtF,OAAO,EAAE,iFAAiF;QAC1F,QAAQ,EAAE,MAAM;QAChB,cAAc,EAAE,4MAA4M;KAC7N;IACD;QACE,EAAE,EAAE,mCAAmC;QACvC,IAAI,EAAE,oEAAoE;QAC1E,OAAO,EAAE,uEAAuE;QAChF,QAAQ,EAAE,UAAU;QACpB,cAAc,EAAE,yJAAyJ;KAC1K;IACD;QACE,EAAE,EAAE,0BAA0B;QAC9B,IAAI,EAAE,wDAAwD;QAC9D,OAAO,EAAE,oEAAoE;QAC7E,QAAQ,EAAE,MAAM;QAChB,cAAc,EAAE,4NAA4N;KAC7O;IACD;QACE,EAAE,EAAE,kCAAkC;QACtC,IAAI,EAAE,sEAAsE;QAC5E,OAAO,EAAE,yIAAyI;QAClJ,QAAQ,EAAE,UAAU;QACpB,cAAc,EAAE,mNAAmN;KACpO;IACD;QACE,EAAE,EAAE,kCAAkC;QACtC,IAAI,EAAE,0EAA0E;QAChF,OAAO,EAAE,gBAAgB;QACzB,QAAQ,EAAE,MAAM;QAChB,cAAc,EAAE,+MAA+M;KAChO;IACD;QACE,EAAE,EAAE,oCAAoC;QACxC,IAAI,EAAE,oEAAoE;QAC1E,OAAO,EAAE,oHAAoH;QAC7H,QAAQ,EAAE,MAAM;QAChB,cAAc,EAAE,8MAA8M;KAC/N;CACF,CAAC;AAEF,gFAAgF;AAEhF,iFAAiF;AACjF,MAAM,CAAC,MAAM,cAAc,GAAG,kHAAkH,CAAC;AAUjJ;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAuB;IACrD;QACE,EAAE,EAAE,mBAAmB;QACvB,OAAO,EAAE,4DAA4D;QACrE,QAAQ,EAAE,mBAAmB;QAC7B,QAAQ,EAAE,UAAU;QACpB,cAAc,EAAE,iRAAiR;KAClS;IACD;QACE,EAAE,EAAE,gBAAgB;QACpB,OAAO,EAAE,gCAAgC;QACzC,QAAQ,EAAE,gBAAgB;QAC1B,QAAQ,EAAE,UAAU;QACpB,cAAc,EAAE,sMAAsM;KACvN;IACD;QACE,EAAE,EAAE,kBAAkB;QACtB,OAAO,EAAE,gFAAgF;QACzF,QAAQ,EAAE,kBAAkB;QAC5B,QAAQ,EAAE,MAAM;QAChB,cAAc,EAAE,gOAAgO;KACjP;IACD;QACE,EAAE,EAAE,sBAAsB;QAC1B,OAAO,EAAE,yGAAyG;QAClH,QAAQ,EAAE,sBAAsB;QAChC,QAAQ,EAAE,UAAU;QACpB,cAAc,EAAE,gOAAgO;KACjP;CACF,CAAC"}
|
package/dist/sarif.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sarif.d.ts","sourceRoot":"","sources":["../src/sarif.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AA6B5C,wBAAgB,UAAU,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,MAAM,CA8C3E"}
|
package/dist/sarif.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
function severityToLevel(sev) {
|
|
2
|
+
if (sev === "critical" || sev === "high")
|
|
3
|
+
return "error";
|
|
4
|
+
if (sev === "medium")
|
|
5
|
+
return "warning";
|
|
6
|
+
return "note";
|
|
7
|
+
}
|
|
8
|
+
export function buildSarif(toolVersion, findings) {
|
|
9
|
+
const rulesMap = new Map();
|
|
10
|
+
const results = [];
|
|
11
|
+
for (const f of findings) {
|
|
12
|
+
if (!rulesMap.has(f.patternId)) {
|
|
13
|
+
rulesMap.set(f.patternId, {
|
|
14
|
+
id: f.patternId,
|
|
15
|
+
name: f.patternName.replace(/[^a-zA-Z0-9]/g, ""),
|
|
16
|
+
shortDescription: { text: f.patternName },
|
|
17
|
+
fullDescription: { text: f.recommendation },
|
|
18
|
+
helpUri: "https://guardbee.ai/docs/tool-poisoning-scanner",
|
|
19
|
+
properties: { "problem.severity": f.severity, tags: ["security", "mcp-security", "ai-security", f.category] },
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
const result = {
|
|
23
|
+
ruleId: f.patternId,
|
|
24
|
+
level: severityToLevel(f.severity),
|
|
25
|
+
message: { text: `${f.patternName} — ${f.recommendation}` },
|
|
26
|
+
};
|
|
27
|
+
if (f.file) {
|
|
28
|
+
result.locations = [
|
|
29
|
+
{ physicalLocation: { artifactLocation: { uri: f.file, uriBaseId: "%SRCROOT%" }, region: { startLine: f.line, startColumn: f.column } } },
|
|
30
|
+
];
|
|
31
|
+
}
|
|
32
|
+
results.push(result);
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
$schema: "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
|
|
36
|
+
version: "2.1.0",
|
|
37
|
+
runs: [
|
|
38
|
+
{
|
|
39
|
+
tool: {
|
|
40
|
+
driver: {
|
|
41
|
+
name: "@guardbee/mcp-tool-poisoning-scanner",
|
|
42
|
+
version: toolVersion,
|
|
43
|
+
informationUri: "https://guardbee.ai",
|
|
44
|
+
rules: Array.from(rulesMap.values()),
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
results,
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=sarif.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sarif.js","sourceRoot":"","sources":["../src/sarif.ts"],"names":[],"mappings":"AAuBA,SAAS,eAAe,CAAC,GAAW;IAClC,IAAI,GAAG,KAAK,UAAU,IAAI,GAAG,KAAK,MAAM;QAAE,OAAO,OAAO,CAAC;IACzD,IAAI,GAAG,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAC;IACvC,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,WAAmB,EAAE,QAAmB;IACjE,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAqB,CAAC;IAC9C,MAAM,OAAO,GAAkB,EAAE,CAAC;IAElC,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;QACzB,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,EAAE,CAAC;YAC/B,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,EAAE;gBACxB,EAAE,EAAE,CAAC,CAAC,SAAS;gBACf,IAAI,EAAE,CAAC,CAAC,WAAW,CAAC,OAAO,CAAC,eAAe,EAAE,EAAE,CAAC;gBAChD,gBAAgB,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE;gBACzC,eAAe,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,cAAc,EAAE;gBAC3C,OAAO,EAAE,iDAAiD;gBAC1D,UAAU,EAAE,EAAE,kBAAkB,EAAE,CAAC,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,UAAU,EAAE,cAAc,EAAE,aAAa,EAAE,CAAC,CAAC,QAAQ,CAAC,EAAE;aAC9G,CAAC,CAAC;QACL,CAAC;QAED,MAAM,MAAM,GAAgB;YAC1B,MAAM,EAAE,CAAC,CAAC,SAAS;YACnB,KAAK,EAAE,eAAe,CAAC,CAAC,CAAC,QAAQ,CAAC;YAClC,OAAO,EAAE,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC,WAAW,MAAM,CAAC,CAAC,cAAc,EAAE,EAAE;SAC5D,CAAC;QACF,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC;YACX,MAAM,CAAC,SAAS,GAAG;gBACjB,EAAE,gBAAgB,EAAE,EAAE,gBAAgB,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC,IAAI,EAAE,SAAS,EAAE,WAAW,EAAE,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC,IAAI,EAAE,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,EAAE;aAC1I,CAAC;QACJ,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACvB,CAAC;IAED,OAAO;QACL,OAAO,EAAE,gGAAgG;QACzG,OAAO,EAAE,OAAO;QAChB,IAAI,EAAE;YACJ;gBACE,IAAI,EAAE;oBACJ,MAAM,EAAE;wBACN,IAAI,EAAE,sCAAsC;wBAC5C,OAAO,EAAE,WAAW;wBACpB,cAAc,EAAE,qBAAqB;wBACrC,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC;qBACrC;iBACF;gBACD,OAAO;aACR;SACF;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export interface Finding {
|
|
2
|
+
patternId: string;
|
|
3
|
+
patternName: string;
|
|
4
|
+
category: string;
|
|
5
|
+
severity: "critical" | "high" | "medium";
|
|
6
|
+
recommendation: string;
|
|
7
|
+
file?: string;
|
|
8
|
+
line: number;
|
|
9
|
+
column: number;
|
|
10
|
+
match: string;
|
|
11
|
+
context: string;
|
|
12
|
+
}
|
|
13
|
+
export interface ScanResult {
|
|
14
|
+
scannedFiles: number;
|
|
15
|
+
skippedFiles: number;
|
|
16
|
+
totalFindings: number;
|
|
17
|
+
findings: Finding[];
|
|
18
|
+
durationMs: number;
|
|
19
|
+
}
|
|
20
|
+
interface ToolBlock {
|
|
21
|
+
name: string;
|
|
22
|
+
/** Raw description string content (unescaped as written in source), or null if this .tool() call has no string description (3-arg form). */
|
|
23
|
+
description: string | null;
|
|
24
|
+
/** descriptionOffset is the absolute offset of `description`'s first character in the source, only set when description is non-null. */
|
|
25
|
+
descriptionOffset: number;
|
|
26
|
+
/** Bounded text following the tool name — covers schema + handler for the mismatch check, truncated at the next tool registration. */
|
|
27
|
+
body: string;
|
|
28
|
+
offset: number;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Finds every `.tool("name", ...)` registration (the MCP TypeScript SDK's
|
|
32
|
+
* `McpServer.tool()` convenience form — the shape every GuardBee server in
|
|
33
|
+
* this monorepo uses) and captures its name, description (if the very next
|
|
34
|
+
* argument is a string literal), and a bounded window of following source as
|
|
35
|
+
* a stand-in for "the rest of this tool's definition". Each block is capped
|
|
36
|
+
* at the next tool registration so a sink in tool B's handler is never
|
|
37
|
+
* misattributed to tool A.
|
|
38
|
+
*/
|
|
39
|
+
export declare function extractToolBlocks(text: string): ToolBlock[];
|
|
40
|
+
export declare function scanText(text: string, filePath?: string): Finding[];
|
|
41
|
+
export declare function scanFile(filePath: string): {
|
|
42
|
+
findings: Finding[];
|
|
43
|
+
skipped: boolean;
|
|
44
|
+
};
|
|
45
|
+
export declare function scanDirectory(dirPath: string, options?: {
|
|
46
|
+
maxFiles?: number;
|
|
47
|
+
include?: string[];
|
|
48
|
+
exclude?: string[];
|
|
49
|
+
}): ScanResult;
|
|
50
|
+
export {};
|
|
51
|
+
//# sourceMappingURL=scanner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scanner.d.ts","sourceRoot":"","sources":["../src/scanner.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,OAAO;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,UAAU,GAAG,MAAM,GAAG,QAAQ,CAAC;IACzC,cAAc,EAAE,MAAM,CAAC;IACvB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,UAAU;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;CACpB;AAmBD,UAAU,SAAS;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,4IAA4I;IAC5I,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,wIAAwI;IACxI,iBAAiB,EAAE,MAAM,CAAC;IAC1B,sIAAsI;IACtI,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,CAuB3D;AAUD,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,EAAE,CA2DnE;AAED,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG;IAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAoBpF;AAED,wBAAgB,aAAa,CAC3B,OAAO,EAAE,MAAM,EACf,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;CAAO,GAC1E,UAAU,CAoDZ"}
|
package/dist/scanner.js
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { readFileSync, statSync, readdirSync } from "fs";
|
|
2
|
+
import { join, relative, extname } from "path";
|
|
3
|
+
import { DESCRIPTION_INJECTION_PATTERNS, MISMATCH_SINK_RULES, READ_ONLY_HINT } from "./patterns.js";
|
|
4
|
+
const SKIP_EXTENSIONS = new Set([
|
|
5
|
+
".png", ".jpg", ".jpeg", ".gif", ".svg", ".ico", ".webp", ".bmp",
|
|
6
|
+
".pdf", ".zip", ".tar", ".gz", ".bz2", ".rar", ".7z",
|
|
7
|
+
".exe", ".dll", ".so", ".dylib", ".bin", ".wasm",
|
|
8
|
+
".mp3", ".mp4", ".avi", ".mov", ".wav",
|
|
9
|
+
".ttf", ".woff", ".woff2", ".eot",
|
|
10
|
+
".lock",
|
|
11
|
+
]);
|
|
12
|
+
const SKIP_DIRS = new Set([
|
|
13
|
+
"node_modules", ".git", ".svn", "dist", "build", ".next",
|
|
14
|
+
"__pycache__", ".mypy_cache", ".pytest_cache", "venv", ".venv",
|
|
15
|
+
"coverage", ".nyc_output",
|
|
16
|
+
]);
|
|
17
|
+
const MAX_FILE_SIZE = 1 * 1024 * 1024;
|
|
18
|
+
const MAX_BLOCK = 2000;
|
|
19
|
+
const MAX_CONTEXT_LENGTH = 240;
|
|
20
|
+
/**
|
|
21
|
+
* Finds every `.tool("name", ...)` registration (the MCP TypeScript SDK's
|
|
22
|
+
* `McpServer.tool()` convenience form — the shape every GuardBee server in
|
|
23
|
+
* this monorepo uses) and captures its name, description (if the very next
|
|
24
|
+
* argument is a string literal), and a bounded window of following source as
|
|
25
|
+
* a stand-in for "the rest of this tool's definition". Each block is capped
|
|
26
|
+
* at the next tool registration so a sink in tool B's handler is never
|
|
27
|
+
* misattributed to tool A.
|
|
28
|
+
*/
|
|
29
|
+
export function extractToolBlocks(text) {
|
|
30
|
+
const nameRe = /\.tool\(\s*["'`]([\w.-]+)["'`]\s*,\s*/g;
|
|
31
|
+
const starts = [];
|
|
32
|
+
let m;
|
|
33
|
+
while ((m = nameRe.exec(text)) !== null) {
|
|
34
|
+
starts.push({ name: m[1], start: m.index, afterName: m.index + m[0].length });
|
|
35
|
+
}
|
|
36
|
+
return starts.map((entry, i) => {
|
|
37
|
+
const nextStart = starts[i + 1]?.start ?? text.length;
|
|
38
|
+
const end = Math.min(entry.afterName + MAX_BLOCK, nextStart);
|
|
39
|
+
const rest = text.slice(entry.afterName, end);
|
|
40
|
+
let description = null;
|
|
41
|
+
let descriptionOffset = -1;
|
|
42
|
+
const descMatch = /^(["'`])((?:\\.|(?!\1)[^\\])*)\1\s*,/.exec(rest);
|
|
43
|
+
if (descMatch) {
|
|
44
|
+
description = descMatch[2];
|
|
45
|
+
descriptionOffset = entry.afterName + 1; // skip the opening quote
|
|
46
|
+
}
|
|
47
|
+
return { name: entry.name, description, descriptionOffset, body: rest, offset: entry.afterName };
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
function locationOf(text, absoluteIndex) {
|
|
51
|
+
const before = text.slice(0, absoluteIndex);
|
|
52
|
+
const line = before.split("\n").length;
|
|
53
|
+
const lastNl = before.lastIndexOf("\n");
|
|
54
|
+
const column = absoluteIndex - (lastNl === -1 ? 0 : lastNl + 1) + 1;
|
|
55
|
+
return { line, column };
|
|
56
|
+
}
|
|
57
|
+
export function scanText(text, filePath) {
|
|
58
|
+
const findings = [];
|
|
59
|
+
const blocks = extractToolBlocks(text);
|
|
60
|
+
for (const block of blocks) {
|
|
61
|
+
// ── Check 1: description-embedded injection ──────────────────────────────
|
|
62
|
+
if (block.description) {
|
|
63
|
+
for (const p of DESCRIPTION_INJECTION_PATTERNS) {
|
|
64
|
+
const re = new RegExp(p.pattern.source, p.pattern.flags.includes("g") ? p.pattern.flags : p.pattern.flags + "g");
|
|
65
|
+
let dm;
|
|
66
|
+
while ((dm = re.exec(block.description)) !== null) {
|
|
67
|
+
const absoluteIndex = block.descriptionOffset + dm.index;
|
|
68
|
+
const { line, column } = locationOf(text, absoluteIndex);
|
|
69
|
+
findings.push({
|
|
70
|
+
patternId: p.id,
|
|
71
|
+
patternName: p.name,
|
|
72
|
+
category: "description-injection",
|
|
73
|
+
severity: p.severity,
|
|
74
|
+
recommendation: p.recommendation,
|
|
75
|
+
file: filePath,
|
|
76
|
+
line,
|
|
77
|
+
column,
|
|
78
|
+
match: `tool "${block.name}": ${dm[0].trim().slice(0, MAX_CONTEXT_LENGTH)}`,
|
|
79
|
+
context: block.description.trim().slice(0, MAX_CONTEXT_LENGTH),
|
|
80
|
+
});
|
|
81
|
+
if (dm.index === re.lastIndex)
|
|
82
|
+
re.lastIndex++;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
// ── Check 2: confused deputy (promised read-only vs. actual sink) ────────
|
|
87
|
+
// Tool names are usually snake_case/kebab-case, so "_"/"-" must become word
|
|
88
|
+
// separators before testing — otherwise "get_system_info" is one long \w
|
|
89
|
+
// token to the regex engine and \bget\b never matches inside it.
|
|
90
|
+
const normalizedName = block.name.replace(/[_-]/g, " ");
|
|
91
|
+
const promisedReadOnly = READ_ONLY_HINT.test(normalizedName) || (block.description ? READ_ONLY_HINT.test(block.description) : false);
|
|
92
|
+
if (!promisedReadOnly)
|
|
93
|
+
continue;
|
|
94
|
+
for (const rule of MISMATCH_SINK_RULES) {
|
|
95
|
+
const sm = rule.pattern.exec(block.body);
|
|
96
|
+
if (!sm)
|
|
97
|
+
continue;
|
|
98
|
+
const absoluteIndex = block.offset + sm.index;
|
|
99
|
+
const { line, column } = locationOf(text, absoluteIndex);
|
|
100
|
+
findings.push({
|
|
101
|
+
patternId: `confused_deputy_${rule.id}`,
|
|
102
|
+
patternName: `Confused deputy: "${block.name}" reads as read-only but handler has a ${rule.category} sink`,
|
|
103
|
+
category: "confused-deputy",
|
|
104
|
+
severity: rule.severity,
|
|
105
|
+
recommendation: rule.recommendation,
|
|
106
|
+
file: filePath,
|
|
107
|
+
line,
|
|
108
|
+
column,
|
|
109
|
+
match: `tool "${block.name}": ${sm[0].trim().slice(0, MAX_CONTEXT_LENGTH)}`,
|
|
110
|
+
context: (block.description ?? block.name).trim().slice(0, MAX_CONTEXT_LENGTH),
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return findings;
|
|
115
|
+
}
|
|
116
|
+
export function scanFile(filePath) {
|
|
117
|
+
const ext = extname(filePath).toLowerCase();
|
|
118
|
+
if (SKIP_EXTENSIONS.has(ext))
|
|
119
|
+
return { findings: [], skipped: true };
|
|
120
|
+
let stat;
|
|
121
|
+
try {
|
|
122
|
+
stat = statSync(filePath);
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
return { findings: [], skipped: true };
|
|
126
|
+
}
|
|
127
|
+
if (!stat.isFile() || stat.size > MAX_FILE_SIZE)
|
|
128
|
+
return { findings: [], skipped: true };
|
|
129
|
+
let content;
|
|
130
|
+
try {
|
|
131
|
+
content = readFileSync(filePath, "utf8");
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
return { findings: [], skipped: true };
|
|
135
|
+
}
|
|
136
|
+
return { findings: scanText(content, filePath), skipped: false };
|
|
137
|
+
}
|
|
138
|
+
export function scanDirectory(dirPath, options = {}) {
|
|
139
|
+
const start = Date.now();
|
|
140
|
+
const { maxFiles = 5000, include, exclude } = options;
|
|
141
|
+
const allFindings = [];
|
|
142
|
+
let scannedFiles = 0;
|
|
143
|
+
let skippedFiles = 0;
|
|
144
|
+
function walk(dir) {
|
|
145
|
+
if (scannedFiles + skippedFiles >= maxFiles)
|
|
146
|
+
return;
|
|
147
|
+
let entries;
|
|
148
|
+
try {
|
|
149
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
for (const entry of entries) {
|
|
155
|
+
const fullPath = join(dir, entry.name);
|
|
156
|
+
if (entry.isDirectory()) {
|
|
157
|
+
if (SKIP_DIRS.has(entry.name) || entry.name.startsWith("."))
|
|
158
|
+
continue;
|
|
159
|
+
if (exclude?.some((ex) => entry.name === ex || fullPath.includes(ex)))
|
|
160
|
+
continue;
|
|
161
|
+
walk(fullPath);
|
|
162
|
+
}
|
|
163
|
+
else if (entry.isFile()) {
|
|
164
|
+
const relPath = relative(dirPath, fullPath);
|
|
165
|
+
if (exclude?.some((ex) => relPath.includes(ex))) {
|
|
166
|
+
skippedFiles++;
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
if (include && !include.some((inc) => relPath.includes(inc))) {
|
|
170
|
+
skippedFiles++;
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
const { findings, skipped } = scanFile(fullPath);
|
|
174
|
+
if (skipped)
|
|
175
|
+
skippedFiles++;
|
|
176
|
+
else {
|
|
177
|
+
scannedFiles++;
|
|
178
|
+
allFindings.push(...findings);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
walk(dirPath);
|
|
184
|
+
return {
|
|
185
|
+
scannedFiles,
|
|
186
|
+
skippedFiles,
|
|
187
|
+
totalFindings: allFindings.length,
|
|
188
|
+
findings: allFindings,
|
|
189
|
+
durationMs: Date.now() - start,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
//# sourceMappingURL=scanner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scanner.js","sourceRoot":"","sources":["../src/scanner.ts"],"names":[],"mappings":"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"}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAkCA,wBAAsB,WAAW,kBAgEhC"}
|