mcp-security-scanner 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (205) hide show
  1. package/LICENSE +21 -0
  2. package/README.ar.md +662 -0
  3. package/README.bn.md +662 -0
  4. package/README.bs.md +662 -0
  5. package/README.da.md +662 -0
  6. package/README.de.md +662 -0
  7. package/README.el.md +662 -0
  8. package/README.es.md +662 -0
  9. package/README.fr.md +663 -0
  10. package/README.hi.md +662 -0
  11. package/README.it.md +662 -0
  12. package/README.ja.md +663 -0
  13. package/README.ko.md +662 -0
  14. package/README.md +662 -0
  15. package/README.no.md +662 -0
  16. package/README.pl.md +662 -0
  17. package/README.pt-BR.md +662 -0
  18. package/README.ru.md +662 -0
  19. package/README.th.md +662 -0
  20. package/README.tr.md +662 -0
  21. package/README.uk.md +663 -0
  22. package/README.vi.md +662 -0
  23. package/README.zh-TW.md +661 -0
  24. package/README.zh.md +661 -0
  25. package/dist/config/env-scanner.d.ts +3 -0
  26. package/dist/config/env-scanner.d.ts.map +1 -0
  27. package/dist/config/env-scanner.js +85 -0
  28. package/dist/config/env-scanner.js.map +1 -0
  29. package/dist/config/index.d.ts +3 -0
  30. package/dist/config/index.d.ts.map +1 -0
  31. package/dist/config/index.js +169 -0
  32. package/dist/config/index.js.map +1 -0
  33. package/dist/config/mcp-config-parser.d.ts +16 -0
  34. package/dist/config/mcp-config-parser.d.ts.map +1 -0
  35. package/dist/config/mcp-config-parser.js +86 -0
  36. package/dist/config/mcp-config-parser.js.map +1 -0
  37. package/dist/config/server-verification.d.ts +5 -0
  38. package/dist/config/server-verification.d.ts.map +1 -0
  39. package/dist/config/server-verification.js +221 -0
  40. package/dist/config/server-verification.js.map +1 -0
  41. package/dist/data/dangerous-sinks.d.ts +13 -0
  42. package/dist/data/dangerous-sinks.d.ts.map +1 -0
  43. package/dist/data/dangerous-sinks.js +45 -0
  44. package/dist/data/dangerous-sinks.js.map +1 -0
  45. package/dist/data/owasp-mcp-top10.d.ts +12 -0
  46. package/dist/data/owasp-mcp-top10.d.ts.map +1 -0
  47. package/dist/data/owasp-mcp-top10.js +95 -0
  48. package/dist/data/owasp-mcp-top10.js.map +1 -0
  49. package/dist/data/poisoning-patterns.d.ts +15 -0
  50. package/dist/data/poisoning-patterns.d.ts.map +1 -0
  51. package/dist/data/poisoning-patterns.js +146 -0
  52. package/dist/data/poisoning-patterns.js.map +1 -0
  53. package/dist/data/popular-packages.d.ts +2 -0
  54. package/dist/data/popular-packages.d.ts.map +1 -0
  55. package/dist/data/popular-packages.js +71 -0
  56. package/dist/data/popular-packages.js.map +1 -0
  57. package/dist/data/secret-patterns.d.ts +8 -0
  58. package/dist/data/secret-patterns.d.ts.map +1 -0
  59. package/dist/data/secret-patterns.js +129 -0
  60. package/dist/data/secret-patterns.js.map +1 -0
  61. package/dist/deps/index.d.ts +3 -0
  62. package/dist/deps/index.d.ts.map +1 -0
  63. package/dist/deps/index.js +308 -0
  64. package/dist/deps/index.js.map +1 -0
  65. package/dist/deps/install-script-detector.d.ts +9 -0
  66. package/dist/deps/install-script-detector.d.ts.map +1 -0
  67. package/dist/deps/install-script-detector.js +98 -0
  68. package/dist/deps/install-script-detector.js.map +1 -0
  69. package/dist/deps/lockfile-parser.d.ts +15 -0
  70. package/dist/deps/lockfile-parser.d.ts.map +1 -0
  71. package/dist/deps/lockfile-parser.js +123 -0
  72. package/dist/deps/lockfile-parser.js.map +1 -0
  73. package/dist/deps/typosquat-checker.d.ts +10 -0
  74. package/dist/deps/typosquat-checker.d.ts.map +1 -0
  75. package/dist/deps/typosquat-checker.js +84 -0
  76. package/dist/deps/typosquat-checker.js.map +1 -0
  77. package/dist/index.d.ts +3 -0
  78. package/dist/index.d.ts.map +1 -0
  79. package/dist/index.js +315 -0
  80. package/dist/index.js.map +1 -0
  81. package/dist/meta/sources.d.ts +3 -0
  82. package/dist/meta/sources.d.ts.map +1 -0
  83. package/dist/meta/sources.js +43 -0
  84. package/dist/meta/sources.js.map +1 -0
  85. package/dist/protocol/mcp-server.d.ts +4 -0
  86. package/dist/protocol/mcp-server.d.ts.map +1 -0
  87. package/dist/protocol/mcp-server.js +32 -0
  88. package/dist/protocol/mcp-server.js.map +1 -0
  89. package/dist/protocol/tools.d.ts +3 -0
  90. package/dist/protocol/tools.d.ts.map +1 -0
  91. package/dist/protocol/tools.js +21 -0
  92. package/dist/protocol/tools.js.map +1 -0
  93. package/dist/report/index.d.ts +3 -0
  94. package/dist/report/index.d.ts.map +1 -0
  95. package/dist/report/index.js +259 -0
  96. package/dist/report/index.js.map +1 -0
  97. package/dist/report/json-report.d.ts +4 -0
  98. package/dist/report/json-report.d.ts.map +1 -0
  99. package/dist/report/json-report.js +61 -0
  100. package/dist/report/json-report.js.map +1 -0
  101. package/dist/report/markdown.d.ts +3 -0
  102. package/dist/report/markdown.d.ts.map +1 -0
  103. package/dist/report/markdown.js +89 -0
  104. package/dist/report/markdown.js.map +1 -0
  105. package/dist/report/sarif.d.ts +3 -0
  106. package/dist/report/sarif.d.ts.map +1 -0
  107. package/dist/report/sarif.js +56 -0
  108. package/dist/report/sarif.js.map +1 -0
  109. package/dist/runtime/client.d.ts +31 -0
  110. package/dist/runtime/client.d.ts.map +1 -0
  111. package/dist/runtime/client.js +53 -0
  112. package/dist/runtime/client.js.map +1 -0
  113. package/dist/runtime/index.d.ts +3 -0
  114. package/dist/runtime/index.d.ts.map +1 -0
  115. package/dist/runtime/index.js +239 -0
  116. package/dist/runtime/index.js.map +1 -0
  117. package/dist/runtime/pinning.d.ts +21 -0
  118. package/dist/runtime/pinning.d.ts.map +1 -0
  119. package/dist/runtime/pinning.js +74 -0
  120. package/dist/runtime/pinning.js.map +1 -0
  121. package/dist/runtime/schema-analyzer.d.ts +14 -0
  122. package/dist/runtime/schema-analyzer.d.ts.map +1 -0
  123. package/dist/runtime/schema-analyzer.js +204 -0
  124. package/dist/runtime/schema-analyzer.js.map +1 -0
  125. package/dist/runtime/tool-analyzer.d.ts +6 -0
  126. package/dist/runtime/tool-analyzer.d.ts.map +1 -0
  127. package/dist/runtime/tool-analyzer.js +92 -0
  128. package/dist/runtime/tool-analyzer.js.map +1 -0
  129. package/dist/static/analyzers/code-execution.d.ts +4 -0
  130. package/dist/static/analyzers/code-execution.d.ts.map +1 -0
  131. package/dist/static/analyzers/code-execution.js +72 -0
  132. package/dist/static/analyzers/code-execution.js.map +1 -0
  133. package/dist/static/analyzers/command-injection.d.ts +4 -0
  134. package/dist/static/analyzers/command-injection.d.ts.map +1 -0
  135. package/dist/static/analyzers/command-injection.js +62 -0
  136. package/dist/static/analyzers/command-injection.js.map +1 -0
  137. package/dist/static/analyzers/info-disclosure.d.ts +4 -0
  138. package/dist/static/analyzers/info-disclosure.d.ts.map +1 -0
  139. package/dist/static/analyzers/info-disclosure.js +65 -0
  140. package/dist/static/analyzers/info-disclosure.js.map +1 -0
  141. package/dist/static/analyzers/insecure-crypto.d.ts +4 -0
  142. package/dist/static/analyzers/insecure-crypto.d.ts.map +1 -0
  143. package/dist/static/analyzers/insecure-crypto.js +65 -0
  144. package/dist/static/analyzers/insecure-crypto.js.map +1 -0
  145. package/dist/static/analyzers/logging-audit.d.ts +4 -0
  146. package/dist/static/analyzers/logging-audit.d.ts.map +1 -0
  147. package/dist/static/analyzers/logging-audit.js +81 -0
  148. package/dist/static/analyzers/logging-audit.js.map +1 -0
  149. package/dist/static/analyzers/path-traversal.d.ts +4 -0
  150. package/dist/static/analyzers/path-traversal.d.ts.map +1 -0
  151. package/dist/static/analyzers/path-traversal.js +42 -0
  152. package/dist/static/analyzers/path-traversal.js.map +1 -0
  153. package/dist/static/analyzers/prototype-pollution.d.ts +4 -0
  154. package/dist/static/analyzers/prototype-pollution.d.ts.map +1 -0
  155. package/dist/static/analyzers/prototype-pollution.js +80 -0
  156. package/dist/static/analyzers/prototype-pollution.js.map +1 -0
  157. package/dist/static/analyzers/regex-dos.d.ts +4 -0
  158. package/dist/static/analyzers/regex-dos.d.ts.map +1 -0
  159. package/dist/static/analyzers/regex-dos.js +78 -0
  160. package/dist/static/analyzers/regex-dos.js.map +1 -0
  161. package/dist/static/analyzers/secret-hardcoded.d.ts +4 -0
  162. package/dist/static/analyzers/secret-hardcoded.d.ts.map +1 -0
  163. package/dist/static/analyzers/secret-hardcoded.js +70 -0
  164. package/dist/static/analyzers/secret-hardcoded.js.map +1 -0
  165. package/dist/static/analyzers/ssrf.d.ts +4 -0
  166. package/dist/static/analyzers/ssrf.d.ts.map +1 -0
  167. package/dist/static/analyzers/ssrf.js +39 -0
  168. package/dist/static/analyzers/ssrf.js.map +1 -0
  169. package/dist/static/analyzers/unsafe-regex.d.ts +4 -0
  170. package/dist/static/analyzers/unsafe-regex.d.ts.map +1 -0
  171. package/dist/static/analyzers/unsafe-regex.js +36 -0
  172. package/dist/static/analyzers/unsafe-regex.js.map +1 -0
  173. package/dist/static/ast-engine.d.ts +22 -0
  174. package/dist/static/ast-engine.d.ts.map +1 -0
  175. package/dist/static/ast-engine.js +155 -0
  176. package/dist/static/ast-engine.js.map +1 -0
  177. package/dist/static/index.d.ts +3 -0
  178. package/dist/static/index.d.ts.map +1 -0
  179. package/dist/static/index.js +114 -0
  180. package/dist/static/index.js.map +1 -0
  181. package/dist/static/taint-tracker.d.ts +15 -0
  182. package/dist/static/taint-tracker.d.ts.map +1 -0
  183. package/dist/static/taint-tracker.js +70 -0
  184. package/dist/static/taint-tracker.js.map +1 -0
  185. package/dist/types/findings.d.ts +60 -0
  186. package/dist/types/findings.d.ts.map +1 -0
  187. package/dist/types/findings.js +9 -0
  188. package/dist/types/findings.js.map +1 -0
  189. package/dist/types/index.d.ts +23 -0
  190. package/dist/types/index.d.ts.map +1 -0
  191. package/dist/types/index.js +8 -0
  192. package/dist/types/index.js.map +1 -0
  193. package/dist/utils/crypto.d.ts +4 -0
  194. package/dist/utils/crypto.d.ts.map +1 -0
  195. package/dist/utils/crypto.js +12 -0
  196. package/dist/utils/crypto.js.map +1 -0
  197. package/dist/utils/fs-helpers.d.ts +7 -0
  198. package/dist/utils/fs-helpers.d.ts.map +1 -0
  199. package/dist/utils/fs-helpers.js +92 -0
  200. package/dist/utils/fs-helpers.js.map +1 -0
  201. package/dist/utils/levenshtein.d.ts +7 -0
  202. package/dist/utils/levenshtein.d.ts.map +1 -0
  203. package/dist/utils/levenshtein.js +89 -0
  204. package/dist/utils/levenshtein.js.map +1 -0
  205. package/package.json +57 -0
@@ -0,0 +1,92 @@
1
+ import { POISONING_PATTERNS, ANSI_PATTERNS, UNICODE_STEGO_PATTERNS } from "../data/poisoning-patterns.js";
2
+ export function analyzePoisoning(tools) {
3
+ const findings = [];
4
+ let counter = 1;
5
+ for (const tool of tools) {
6
+ const textsToCheck = [
7
+ { label: "description", value: tool.description ?? "" },
8
+ ];
9
+ // Also check schema field descriptions
10
+ if (tool.inputSchema && typeof tool.inputSchema === "object") {
11
+ const props = tool.inputSchema.properties;
12
+ if (props && typeof props === "object") {
13
+ for (const [key, val] of Object.entries(props)) {
14
+ if (val && typeof val === "object" && "description" in val) {
15
+ textsToCheck.push({ label: `schema.${key}.description`, value: val.description ?? "" });
16
+ }
17
+ }
18
+ }
19
+ }
20
+ for (const { label, value } of textsToCheck) {
21
+ for (const pattern of POISONING_PATTERNS) {
22
+ if (pattern.pattern.test(value)) {
23
+ findings.push({
24
+ id: `RT-POISON-${String(counter++).padStart(3, "0")}`,
25
+ title: `Tool Poisoning: ${pattern.name}`,
26
+ severity: pattern.severity,
27
+ owasp_mcp: "MCP03",
28
+ owasp_mcp_title: "Tool Poisoning via Description Injection",
29
+ category: "runtime",
30
+ evidence: `Tool "${tool.name}" ${label}: matched pattern "${pattern.name}" — "${value.substring(0, 200)}"`,
31
+ remediation: "Review and sanitize tool description. Pin tool definitions and verify hashes regularly.",
32
+ cwe: "CWE-94",
33
+ references: ["https://invariantlabs.ai/blog/mcp-security-notification-tool-poisoning-attacks"],
34
+ });
35
+ }
36
+ }
37
+ }
38
+ }
39
+ return findings;
40
+ }
41
+ export function analyzeAnsiInjection(tools) {
42
+ const findings = [];
43
+ let counter = 1;
44
+ for (const tool of tools) {
45
+ const desc = tool.description ?? "";
46
+ for (const pattern of ANSI_PATTERNS) {
47
+ if (pattern.test(desc)) {
48
+ findings.push({
49
+ id: `RT-ANSI-${String(counter++).padStart(3, "0")}`,
50
+ title: `ANSI Escape Injection in "${tool.name}"`,
51
+ severity: "high",
52
+ owasp_mcp: "MCP03",
53
+ owasp_mcp_title: "Tool Poisoning via Description Injection",
54
+ category: "runtime",
55
+ evidence: `Tool "${tool.name}" description contains ANSI escape sequences that can hide text from terminal display while LLM still processes it`,
56
+ remediation: "Strip all ANSI escape sequences from tool descriptions. Use a sanitization library.",
57
+ cwe: "CWE-116",
58
+ });
59
+ break; // one finding per tool for ANSI
60
+ }
61
+ }
62
+ }
63
+ return findings;
64
+ }
65
+ export function analyzeUnicodeSteganography(tools) {
66
+ const findings = [];
67
+ let counter = 1;
68
+ for (const tool of tools) {
69
+ const desc = tool.description ?? "";
70
+ const matched = [];
71
+ for (const stego of UNICODE_STEGO_PATTERNS) {
72
+ if (stego.pattern.test(desc)) {
73
+ matched.push(`${stego.name} (${stego.description})`);
74
+ }
75
+ }
76
+ if (matched.length > 0) {
77
+ findings.push({
78
+ id: `RT-UNICODE-${String(counter++).padStart(3, "0")}`,
79
+ title: `Hidden Unicode Characters in "${tool.name}"`,
80
+ severity: "high",
81
+ owasp_mcp: "MCP03",
82
+ owasp_mcp_title: "Tool Poisoning via Description Injection",
83
+ category: "runtime",
84
+ evidence: `Tool "${tool.name}" description contains ${matched.length} hidden Unicode character types: ${matched.join(", ")}`,
85
+ remediation: "Strip all zero-width and invisible Unicode characters from tool descriptions.",
86
+ cwe: "CWE-116",
87
+ });
88
+ }
89
+ }
90
+ return findings;
91
+ }
92
+ //# sourceMappingURL=tool-analyzer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-analyzer.js","sourceRoot":"","sources":["../../src/runtime/tool-analyzer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import type { SourceFile } from "ts-morph";
2
+ import type { Finding } from "../../types/findings.js";
3
+ export declare function analyzeCodeExecution(sourceFiles: SourceFile[]): Finding[];
4
+ //# sourceMappingURL=code-execution.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"code-execution.d.ts","sourceRoot":"","sources":["../../../src/static/analyzers/code-execution.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAC3C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAQvD,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,UAAU,EAAE,GAAG,OAAO,EAAE,CAqEzE"}
@@ -0,0 +1,72 @@
1
+ import { findCallExpressions, getCallName, getQualifiedCallName, getLocation, SyntaxKind } from "../ast-engine.js";
2
+ import { getCallEvidence } from "../taint-tracker.js";
3
+ const DANGEROUS_GLOBALS = new Set(["eval"]);
4
+ const DANGEROUS_QUALIFIED = new Set(["vm.runInNewContext", "vm.runInThisContext", "vm.compileFunction"]);
5
+ const DANGEROUS_NEW = new Set(["Function", "vm.Script"]);
6
+ export function analyzeCodeExecution(sourceFiles) {
7
+ const findings = [];
8
+ let counter = 0;
9
+ for (const sf of sourceFiles) {
10
+ // Check call expressions
11
+ const calls = findCallExpressions(sf);
12
+ for (const call of calls) {
13
+ const name = getCallName(call);
14
+ const qname = getQualifiedCallName(call);
15
+ let found = false;
16
+ if (DANGEROUS_GLOBALS.has(name))
17
+ found = true;
18
+ if (DANGEROUS_QUALIFIED.has(qname))
19
+ found = true;
20
+ // setTimeout/setInterval with string argument
21
+ if ((name === "setTimeout" || name === "setInterval") && call.getArguments().length > 0) {
22
+ const firstArg = call.getArguments()[0];
23
+ if (firstArg.getKind() === SyntaxKind.StringLiteral || firstArg.getKind() === SyntaxKind.TemplateExpression) {
24
+ found = true;
25
+ }
26
+ }
27
+ if (found) {
28
+ const loc = getLocation(call);
29
+ counter++;
30
+ findings.push({
31
+ id: `SAST-EXEC-${String(counter).padStart(3, "0")}`,
32
+ title: `Dangerous Code Execution: ${qname || name}()`,
33
+ severity: "critical",
34
+ owasp_mcp: "MCP05",
35
+ owasp_mcp_title: "Command Injection & Code Execution",
36
+ category: "static",
37
+ file: loc.file,
38
+ line: loc.line,
39
+ column: loc.column,
40
+ evidence: getCallEvidence(call),
41
+ remediation: "Remove eval/Function/vm usage entirely. If dynamic behavior is needed, use a safe sandboxing approach or predefined function maps.",
42
+ cwe: "CWE-94",
43
+ });
44
+ }
45
+ }
46
+ // Check new expressions: new Function(...)
47
+ const newExprs = sf.getDescendantsOfKind(SyntaxKind.NewExpression);
48
+ for (const expr of newExprs) {
49
+ const name = expr.getExpression().getText();
50
+ if (DANGEROUS_NEW.has(name)) {
51
+ const loc = getLocation(expr);
52
+ counter++;
53
+ findings.push({
54
+ id: `SAST-EXEC-${String(counter).padStart(3, "0")}`,
55
+ title: `Dangerous Code Execution: new ${name}()`,
56
+ severity: "critical",
57
+ owasp_mcp: "MCP05",
58
+ owasp_mcp_title: "Command Injection & Code Execution",
59
+ category: "static",
60
+ file: loc.file,
61
+ line: loc.line,
62
+ column: loc.column,
63
+ evidence: expr.getText().substring(0, 200),
64
+ remediation: "Never use new Function() or new vm.Script() with untrusted input. Use predefined function maps instead.",
65
+ cwe: "CWE-94",
66
+ });
67
+ }
68
+ }
69
+ }
70
+ return findings;
71
+ }
72
+ //# sourceMappingURL=code-execution.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"code-execution.js","sourceRoot":"","sources":["../../../src/static/analyzers/code-execution.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import type { SourceFile } from "ts-morph";
2
+ import type { Finding } from "../../types/findings.js";
3
+ export declare function analyzeCommandInjection(sourceFiles: SourceFile[]): Finding[];
4
+ //# sourceMappingURL=command-injection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command-injection.d.ts","sourceRoot":"","sources":["../../../src/static/analyzers/command-injection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAC3C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAOvD,wBAAgB,uBAAuB,CAAC,WAAW,EAAE,UAAU,EAAE,GAAG,OAAO,EAAE,CA2D5E"}
@@ -0,0 +1,62 @@
1
+ import { findCallExpressions, getCallName, getLocation, containsUserInput, hasShellTrue } from "../ast-engine.js";
2
+ import { getCallEvidence } from "../taint-tracker.js";
3
+ import { COMMAND_INJECTION_SINKS } from "../../data/dangerous-sinks.js";
4
+ const sinkNames = new Set(COMMAND_INJECTION_SINKS.map(s => s.name));
5
+ export function analyzeCommandInjection(sourceFiles) {
6
+ const findings = [];
7
+ let counter = 0;
8
+ for (const sf of sourceFiles) {
9
+ const calls = findCallExpressions(sf);
10
+ for (const call of calls) {
11
+ const name = getCallName(call);
12
+ if (!sinkNames.has(name))
13
+ continue;
14
+ const sink = COMMAND_INJECTION_SINKS.find(s => s.name === name);
15
+ // For spawn/spawnSync, only flag if shell:true
16
+ if ((name === "spawn" || name === "spawnSync") && !hasShellTrue(call))
17
+ continue;
18
+ // Check if arguments contain user input
19
+ const tainted = containsUserInput(call);
20
+ const loc = getLocation(call);
21
+ if (tainted) {
22
+ counter++;
23
+ findings.push({
24
+ id: `SAST-CMD-${String(counter).padStart(3, "0")}`,
25
+ title: `Command Injection via ${name}()`,
26
+ severity: sink.severity,
27
+ owasp_mcp: "MCP05",
28
+ owasp_mcp_title: "Command Injection & Code Execution",
29
+ category: "static",
30
+ file: loc.file,
31
+ line: loc.line,
32
+ column: loc.column,
33
+ evidence: getCallEvidence(call),
34
+ remediation: name === "exec" || name === "execSync"
35
+ ? "Replace exec/execSync with execFile and pass arguments as an array. Never interpolate user input into shell commands."
36
+ : "Ensure shell:true is not used with user-controlled arguments. Use argument arrays instead of command strings.",
37
+ cwe: "CWE-78",
38
+ });
39
+ }
40
+ else if (name === "exec" || name === "execSync") {
41
+ // exec/execSync are always risky even without detected taint
42
+ counter++;
43
+ findings.push({
44
+ id: `SAST-CMD-${String(counter).padStart(3, "0")}`,
45
+ title: `Potentially Dangerous ${name}() Usage`,
46
+ severity: "medium",
47
+ owasp_mcp: "MCP05",
48
+ owasp_mcp_title: "Command Injection & Code Execution",
49
+ category: "static",
50
+ file: loc.file,
51
+ line: loc.line,
52
+ column: loc.column,
53
+ evidence: getCallEvidence(call),
54
+ remediation: "Prefer execFile() with argument arrays. Audit all inputs to exec/execSync for injection risks.",
55
+ cwe: "CWE-78",
56
+ });
57
+ }
58
+ }
59
+ }
60
+ return findings;
61
+ }
62
+ //# sourceMappingURL=command-injection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command-injection.js","sourceRoot":"","sources":["../../../src/static/analyzers/command-injection.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import type { SourceFile } from "ts-morph";
2
+ import type { Finding } from "../../types/findings.js";
3
+ export declare function analyzeInfoDisclosure(sourceFiles: SourceFile[]): Finding[];
4
+ //# sourceMappingURL=info-disclosure.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"info-disclosure.d.ts","sourceRoot":"","sources":["../../../src/static/analyzers/info-disclosure.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAC3C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAIvD,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,UAAU,EAAE,GAAG,OAAO,EAAE,CAiE1E"}
@@ -0,0 +1,65 @@
1
+ import { findCallExpressions, getQualifiedCallName, getLocation, SyntaxKind } from "../ast-engine.js";
2
+ import { getCallEvidence } from "../taint-tracker.js";
3
+ export function analyzeInfoDisclosure(sourceFiles) {
4
+ const findings = [];
5
+ let counter = 0;
6
+ for (const sf of sourceFiles) {
7
+ const calls = findCallExpressions(sf);
8
+ for (const call of calls) {
9
+ const qname = getQualifiedCallName(call);
10
+ // console.log with sensitive variable names
11
+ if (qname === "console.log" || qname === "console.debug") {
12
+ const text = call.getText();
13
+ if (/(?:apiKey|api_key|secret|password|token|credential|auth)/i.test(text)) {
14
+ const loc = getLocation(call);
15
+ counter++;
16
+ findings.push({
17
+ id: `SAST-INFO-${String(counter).padStart(3, "0")}`,
18
+ title: "Sensitive Data in Console Log",
19
+ severity: "medium",
20
+ owasp_mcp: "MCP08",
21
+ owasp_mcp_title: "Insufficient Logging & Error Handling",
22
+ category: "static",
23
+ file: loc.file,
24
+ line: loc.line,
25
+ column: loc.column,
26
+ evidence: getCallEvidence(call),
27
+ remediation: "Remove console.log statements that output sensitive data. Use a structured logger with redaction capabilities.",
28
+ cwe: "CWE-532",
29
+ });
30
+ }
31
+ }
32
+ }
33
+ // Check for process.env serialization in responses
34
+ const propAccess = sf.getDescendantsOfKind(SyntaxKind.PropertyAccessExpression);
35
+ for (const pa of propAccess) {
36
+ if (pa.getText() === "process.env") {
37
+ // Check if it's being serialized (JSON.stringify, spread, etc.)
38
+ const parent = pa.getParent();
39
+ if (parent) {
40
+ const parentText = parent.getText();
41
+ if (/JSON\.stringify|\.\.\.process\.env|Object\.(keys|values|entries)\(process\.env\)/.test(parentText)) {
42
+ const loc = getLocation(pa);
43
+ counter++;
44
+ findings.push({
45
+ id: `SAST-INFO-${String(counter).padStart(3, "0")}`,
46
+ title: "Full process.env Serialization",
47
+ severity: "high",
48
+ owasp_mcp: "MCP10",
49
+ owasp_mcp_title: "Context Over-sharing & Data Exposure",
50
+ category: "static",
51
+ file: loc.file,
52
+ line: loc.line,
53
+ column: loc.column,
54
+ evidence: parentText.substring(0, 200),
55
+ remediation: "Never serialize the entire process.env. Access only the specific environment variables needed.",
56
+ cwe: "CWE-200",
57
+ });
58
+ }
59
+ }
60
+ }
61
+ }
62
+ }
63
+ return findings;
64
+ }
65
+ //# sourceMappingURL=info-disclosure.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"info-disclosure.js","sourceRoot":"","sources":["../../../src/static/analyzers/info-disclosure.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,mBAAmB,EAAe,oBAAoB,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnH,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAEtD,MAAM,UAAU,qBAAqB,CAAC,WAAyB;IAC7D,MAAM,QAAQ,GAAc,EAAE,CAAC;IAC/B,IAAI,OAAO,GAAG,CAAC,CAAC;IAEhB,KAAK,MAAM,EAAE,IAAI,WAAW,EAAE,CAAC;QAC7B,MAAM,KAAK,GAAG,mBAAmB,CAAC,EAAE,CAAC,CAAC;QACtC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,MAAM,KAAK,GAAG,oBAAoB,CAAC,IAAI,CAAC,CAAC;YAEzC,4CAA4C;YAC5C,IAAI,KAAK,KAAK,aAAa,IAAI,KAAK,KAAK,eAAe,EAAE,CAAC;gBACzD,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;gBAC5B,IAAI,2DAA2D,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;oBAC3E,MAAM,GAAG,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;oBAC9B,OAAO,EAAE,CAAC;oBACV,QAAQ,CAAC,IAAI,CAAC;wBACZ,EAAE,EAAE,aAAa,MAAM,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE;wBACnD,KAAK,EAAE,+BAA+B;wBACtC,QAAQ,EAAE,QAAQ;wBAClB,SAAS,EAAE,OAAO;wBAClB,eAAe,EAAE,uCAAuC;wBACxD,QAAQ,EAAE,QAAQ;wBAClB,IAAI,EAAE,GAAG,CAAC,IAAI;wBACd,IAAI,EAAE,GAAG,CAAC,IAAI;wBACd,MAAM,EAAE,GAAG,CAAC,MAAM;wBAClB,QAAQ,EAAE,eAAe,CAAC,IAAI,CAAC;wBAC/B,WAAW,EAAE,gHAAgH;wBAC7H,GAAG,EAAE,SAAS;qBACf,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;QACH,CAAC;QAED,mDAAmD;QACnD,MAAM,UAAU,GAAG,EAAE,CAAC,oBAAoB,CAAC,UAAU,CAAC,wBAAwB,CAAC,CAAC;QAChF,KAAK,MAAM,EAAE,IAAI,UAAU,EAAE,CAAC;YAC5B,IAAI,EAAE,CAAC,OAAO,EAAE,KAAK,aAAa,EAAE,CAAC;gBACnC,gEAAgE;gBAChE,MAAM,MAAM,GAAG,EAAE,CAAC,SAAS,EAAE,CAAC;gBAC9B,IAAI,MAAM,EAAE,CAAC;oBACX,MAAM,UAAU,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC;oBACpC,IAAI,kFAAkF,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;wBACxG,MAAM,GAAG,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC;wBAC5B,OAAO,EAAE,CAAC;wBACV,QAAQ,CAAC,IAAI,CAAC;4BACZ,EAAE,EAAE,aAAa,MAAM,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE;4BACnD,KAAK,EAAE,gCAAgC;4BACvC,QAAQ,EAAE,MAAM;4BAChB,SAAS,EAAE,OAAO;4BAClB,eAAe,EAAE,sCAAsC;4BACvD,QAAQ,EAAE,QAAQ;4BAClB,IAAI,EAAE,GAAG,CAAC,IAAI;4BACd,IAAI,EAAE,GAAG,CAAC,IAAI;4BACd,MAAM,EAAE,GAAG,CAAC,MAAM;4BAClB,QAAQ,EAAE,UAAU,CAAC,SAAS,CAAC,CAAC,EAAE,GAAG,CAAC;4BACtC,WAAW,EAAE,gGAAgG;4BAC7G,GAAG,EAAE,SAAS;yBACf,CAAC,CAAC;oBACL,CAAC;gBACH,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { SourceFile } from "ts-morph";
2
+ import type { Finding } from "../../types/findings.js";
3
+ export declare function analyzeInsecureCrypto(sourceFiles: SourceFile[]): Finding[];
4
+ //# sourceMappingURL=insecure-crypto.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"insecure-crypto.d.ts","sourceRoot":"","sources":["../../../src/static/analyzers/insecure-crypto.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAC3C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAIvD,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,UAAU,EAAE,GAAG,OAAO,EAAE,CAkE1E"}
@@ -0,0 +1,65 @@
1
+ import { findCallExpressions, getCallName, getQualifiedCallName, getLocation } from "../ast-engine.js";
2
+ import { getCallEvidence } from "../taint-tracker.js";
3
+ export function analyzeInsecureCrypto(sourceFiles) {
4
+ const findings = [];
5
+ let counter = 0;
6
+ for (const sf of sourceFiles) {
7
+ const calls = findCallExpressions(sf);
8
+ for (const call of calls) {
9
+ const name = getCallName(call);
10
+ const qname = getQualifiedCallName(call);
11
+ // createHash with weak algorithm
12
+ if (name === "createHash") {
13
+ const args = call.getArguments();
14
+ if (args.length > 0) {
15
+ const algo = args[0].getText().replace(/['"]/g, "").toLowerCase();
16
+ if (algo === "md5" || algo === "sha1") {
17
+ const loc = getLocation(call);
18
+ counter++;
19
+ findings.push({
20
+ id: `SAST-CRYPTO-${String(counter).padStart(3, "0")}`,
21
+ title: `Weak Hash Algorithm: ${algo.toUpperCase()}`,
22
+ severity: "medium",
23
+ owasp_mcp: "MCP05",
24
+ owasp_mcp_title: "Command Injection & Code Execution",
25
+ category: "static",
26
+ file: loc.file,
27
+ line: loc.line,
28
+ column: loc.column,
29
+ evidence: getCallEvidence(call),
30
+ remediation: `Replace ${algo} with SHA-256 or SHA-3 for security-sensitive hashing. MD5 and SHA-1 are cryptographically broken.`,
31
+ cwe: "CWE-328",
32
+ });
33
+ }
34
+ }
35
+ }
36
+ // Math.random() for security
37
+ if (qname === "Math.random") {
38
+ // Check context — is it used for tokens, keys, nonces?
39
+ const parent = call.getParent();
40
+ const grandparent = parent?.getParent();
41
+ const contextText = (grandparent?.getText() ?? parent?.getText() ?? "").toLowerCase();
42
+ if (/token|secret|key|nonce|salt|random.*id|session|csrf|otp/.test(contextText)) {
43
+ const loc = getLocation(call);
44
+ counter++;
45
+ findings.push({
46
+ id: `SAST-CRYPTO-${String(counter).padStart(3, "0")}`,
47
+ title: "Math.random() Used for Security-Sensitive Value",
48
+ severity: "high",
49
+ owasp_mcp: "MCP05",
50
+ owasp_mcp_title: "Command Injection & Code Execution",
51
+ category: "static",
52
+ file: loc.file,
53
+ line: loc.line,
54
+ column: loc.column,
55
+ evidence: getCallEvidence(call),
56
+ remediation: "Use crypto.randomBytes() or crypto.randomUUID() instead of Math.random() for security-sensitive random values.",
57
+ cwe: "CWE-338",
58
+ });
59
+ }
60
+ }
61
+ }
62
+ }
63
+ return findings;
64
+ }
65
+ //# sourceMappingURL=insecure-crypto.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"insecure-crypto.js","sourceRoot":"","sources":["../../../src/static/analyzers/insecure-crypto.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import type { SourceFile } from "ts-morph";
2
+ import type { Finding } from "../../types/findings.js";
3
+ export declare function analyzeLogging(sourceFiles: SourceFile[]): Finding[];
4
+ //# sourceMappingURL=logging-audit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logging-audit.d.ts","sourceRoot":"","sources":["../../../src/static/analyzers/logging-audit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAC3C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAIvD,wBAAgB,cAAc,CAAC,WAAW,EAAE,UAAU,EAAE,GAAG,OAAO,EAAE,CAiFnE"}
@@ -0,0 +1,81 @@
1
+ import { getLocation, SyntaxKind } from "../ast-engine.js";
2
+ import { findToolHandlers, isEmptyCatch } from "../taint-tracker.js";
3
+ export function analyzeLogging(sourceFiles) {
4
+ const findings = [];
5
+ let counter = 0;
6
+ for (const sf of sourceFiles) {
7
+ // Check for empty catch blocks
8
+ const catchClauses = sf.getDescendantsOfKind(SyntaxKind.CatchClause);
9
+ for (const cc of catchClauses) {
10
+ if (isEmptyCatch(cc)) {
11
+ const loc = getLocation(cc);
12
+ counter++;
13
+ findings.push({
14
+ id: `SAST-LOG-${String(counter).padStart(3, "0")}`,
15
+ title: "Empty Catch Block",
16
+ severity: "medium",
17
+ owasp_mcp: "MCP08",
18
+ owasp_mcp_title: "Insufficient Logging & Error Handling",
19
+ category: "static",
20
+ file: loc.file,
21
+ line: loc.line,
22
+ column: loc.column,
23
+ evidence: "catch block with no statements — errors are silently swallowed",
24
+ remediation: "Log or re-throw caught errors. Silent catch blocks hide failures and make debugging impossible.",
25
+ cwe: "CWE-390",
26
+ });
27
+ }
28
+ // Check if catch block exposes stack trace
29
+ const block = cc.getChildrenOfKind(SyntaxKind.Block)[0];
30
+ if (block) {
31
+ const text = block.getText();
32
+ if (/err\.stack|error\.stack|e\.stack/.test(text) && /return|content|text|json|respond/.test(text)) {
33
+ const loc = getLocation(cc);
34
+ counter++;
35
+ findings.push({
36
+ id: `SAST-LOG-${String(counter).padStart(3, "0")}`,
37
+ title: "Stack Trace Exposure in Response",
38
+ severity: "medium",
39
+ owasp_mcp: "MCP08",
40
+ owasp_mcp_title: "Insufficient Logging & Error Handling",
41
+ category: "static",
42
+ file: loc.file,
43
+ line: loc.line,
44
+ column: loc.column,
45
+ evidence: "err.stack is included in response — reveals internal paths and code structure",
46
+ remediation: "Return generic error messages to clients. Log stack traces server-side only.",
47
+ cwe: "CWE-209",
48
+ });
49
+ }
50
+ }
51
+ }
52
+ // Check tool handlers for missing try-catch
53
+ const handlers = findToolHandlers(sf);
54
+ for (const handler of handlers) {
55
+ const body = handler.getText();
56
+ if (!body.includes("try") && !body.includes("catch")) {
57
+ // Only flag if the handler has meaningful logic (not just a return)
58
+ if (body.split("\n").length > 5) {
59
+ const loc = getLocation(handler);
60
+ counter++;
61
+ findings.push({
62
+ id: `SAST-LOG-${String(counter).padStart(3, "0")}`,
63
+ title: "Tool Handler Without Error Handling",
64
+ severity: "low",
65
+ owasp_mcp: "MCP08",
66
+ owasp_mcp_title: "Insufficient Logging & Error Handling",
67
+ category: "static",
68
+ file: loc.file,
69
+ line: loc.line,
70
+ column: loc.column,
71
+ evidence: "Tool execute function has no try-catch block",
72
+ remediation: "Wrap tool handler bodies in try-catch to handle errors gracefully and return meaningful error messages.",
73
+ cwe: "CWE-755",
74
+ });
75
+ }
76
+ }
77
+ }
78
+ }
79
+ return findings;
80
+ }
81
+ //# sourceMappingURL=logging-audit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logging-audit.js","sourceRoot":"","sources":["../../../src/static/analyzers/logging-audit.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import type { SourceFile } from "ts-morph";
2
+ import type { Finding } from "../../types/findings.js";
3
+ export declare function analyzePathTraversal(sourceFiles: SourceFile[]): Finding[];
4
+ //# sourceMappingURL=path-traversal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"path-traversal.d.ts","sourceRoot":"","sources":["../../../src/static/analyzers/path-traversal.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAC3C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAOvD,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,UAAU,EAAE,GAAG,OAAO,EAAE,CAuCzE"}
@@ -0,0 +1,42 @@
1
+ import { findCallExpressions, getCallName, getLocation, containsUserInput } from "../ast-engine.js";
2
+ import { getCallEvidence } from "../taint-tracker.js";
3
+ import { PATH_TRAVERSAL_SINKS } from "../../data/dangerous-sinks.js";
4
+ const sinkNames = new Set(PATH_TRAVERSAL_SINKS.map(s => s.name));
5
+ export function analyzePathTraversal(sourceFiles) {
6
+ const findings = [];
7
+ let counter = 0;
8
+ for (const sf of sourceFiles) {
9
+ const calls = findCallExpressions(sf);
10
+ for (const call of calls) {
11
+ const name = getCallName(call);
12
+ if (!sinkNames.has(name))
13
+ continue;
14
+ const args = call.getArguments();
15
+ if (args.length === 0)
16
+ continue;
17
+ // The first argument is usually the path
18
+ const pathArg = args[0];
19
+ if (!containsUserInput(pathArg))
20
+ continue;
21
+ const sink = PATH_TRAVERSAL_SINKS.find(s => s.name === name);
22
+ const loc = getLocation(call);
23
+ counter++;
24
+ findings.push({
25
+ id: `SAST-PATH-${String(counter).padStart(3, "0")}`,
26
+ title: `Path Traversal via ${name}()`,
27
+ severity: sink.severity,
28
+ owasp_mcp: "MCP05",
29
+ owasp_mcp_title: "Command Injection & Code Execution",
30
+ category: "static",
31
+ file: loc.file,
32
+ line: loc.line,
33
+ column: loc.column,
34
+ evidence: getCallEvidence(call),
35
+ remediation: "Resolve paths with path.resolve() and validate they fall within an allowed base directory. Never pass user input directly to fs operations.",
36
+ cwe: "CWE-22",
37
+ });
38
+ }
39
+ }
40
+ return findings;
41
+ }
42
+ //# sourceMappingURL=path-traversal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"path-traversal.js","sourceRoot":"","sources":["../../../src/static/analyzers/path-traversal.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,mBAAmB,EAAE,WAAW,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACpG,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AAErE,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,oBAAoB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;AAEjE,MAAM,UAAU,oBAAoB,CAAC,WAAyB;IAC5D,MAAM,QAAQ,GAAc,EAAE,CAAC;IAC/B,IAAI,OAAO,GAAG,CAAC,CAAC;IAEhB,KAAK,MAAM,EAAE,IAAI,WAAW,EAAE,CAAC;QAC7B,MAAM,KAAK,GAAG,mBAAmB,CAAC,EAAE,CAAC,CAAC;QACtC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,MAAM,IAAI,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;YAC/B,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC;gBAAE,SAAS;YAEnC,MAAM,IAAI,GAAG,IAAI,CAAC,YAAY,EAAE,CAAC;YACjC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;gBAAE,SAAS;YAEhC,yCAAyC;YACzC,MAAM,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;YACxB,IAAI,CAAC,iBAAiB,CAAC,OAAO,CAAC;gBAAE,SAAS;YAE1C,MAAM,IAAI,GAAG,oBAAoB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAE,CAAC;YAC9D,MAAM,GAAG,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;YAC9B,OAAO,EAAE,CAAC;YAEV,QAAQ,CAAC,IAAI,CAAC;gBACZ,EAAE,EAAE,aAAa,MAAM,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE;gBACnD,KAAK,EAAE,sBAAsB,IAAI,IAAI;gBACrC,QAAQ,EAAE,IAAI,CAAC,QAA+B;gBAC9C,SAAS,EAAE,OAAO;gBAClB,eAAe,EAAE,oCAAoC;gBACrD,QAAQ,EAAE,QAAQ;gBAClB,IAAI,EAAE,GAAG,CAAC,IAAI;gBACd,IAAI,EAAE,GAAG,CAAC,IAAI;gBACd,MAAM,EAAE,GAAG,CAAC,MAAM;gBAClB,QAAQ,EAAE,eAAe,CAAC,IAAI,CAAC;gBAC/B,WAAW,EAAE,6IAA6I;gBAC1J,GAAG,EAAE,QAAQ;aACd,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { SourceFile } from "ts-morph";
2
+ import type { Finding } from "../../types/findings.js";
3
+ export declare function analyzePrototypePollution(sourceFiles: SourceFile[]): Finding[];
4
+ //# sourceMappingURL=prototype-pollution.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prototype-pollution.d.ts","sourceRoot":"","sources":["../../../src/static/analyzers/prototype-pollution.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAC3C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAIvD,wBAAgB,yBAAyB,CAAC,WAAW,EAAE,UAAU,EAAE,GAAG,OAAO,EAAE,CAiF9E"}