@kernlang/review 3.1.6 → 3.1.7

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 (150) hide show
  1. package/dist/cache.d.ts +1 -1
  2. package/dist/cache.js +5 -3
  3. package/dist/cache.js.map +1 -1
  4. package/dist/call-graph.d.ts +63 -0
  5. package/dist/call-graph.js +380 -0
  6. package/dist/call-graph.js.map +1 -0
  7. package/dist/concept-rules/boundary-mutation.d.ts +1 -1
  8. package/dist/concept-rules/boundary-mutation.js.map +1 -1
  9. package/dist/concept-rules/ignored-error.d.ts +1 -1
  10. package/dist/concept-rules/ignored-error.js.map +1 -1
  11. package/dist/concept-rules/illegal-dependency.d.ts +1 -1
  12. package/dist/concept-rules/illegal-dependency.js.map +1 -1
  13. package/dist/concept-rules/index.js +1 -6
  14. package/dist/concept-rules/index.js.map +1 -1
  15. package/dist/concept-rules/unguarded-effect.d.ts +1 -1
  16. package/dist/concept-rules/unguarded-effect.js.map +1 -1
  17. package/dist/concept-rules/unrecovered-effect.d.ts +1 -1
  18. package/dist/concept-rules/unrecovered-effect.js +2 -1
  19. package/dist/concept-rules/unrecovered-effect.js.map +1 -1
  20. package/dist/confidence.js +12 -8
  21. package/dist/confidence.js.map +1 -1
  22. package/dist/differ.js +3 -7
  23. package/dist/differ.js.map +1 -1
  24. package/dist/external-tools.js +5 -6
  25. package/dist/external-tools.js.map +1 -1
  26. package/dist/file-context.d.ts +21 -0
  27. package/dist/file-context.js +234 -0
  28. package/dist/file-context.js.map +1 -0
  29. package/dist/file-role.js +14 -7
  30. package/dist/file-role.js.map +1 -1
  31. package/dist/graph.d.ts +1 -1
  32. package/dist/graph.js +24 -16
  33. package/dist/graph.js.map +1 -1
  34. package/dist/index.d.ts +44 -35
  35. package/dist/index.js +210 -121
  36. package/dist/index.js.map +1 -1
  37. package/dist/inferrer.d.ts +8 -2
  38. package/dist/inferrer.js +80 -47
  39. package/dist/inferrer.js.map +1 -1
  40. package/dist/kern-lint.d.ts +3 -4
  41. package/dist/kern-lint.js +7 -5
  42. package/dist/kern-lint.js.map +1 -1
  43. package/dist/llm-bridge.d.ts +23 -7
  44. package/dist/llm-bridge.js +267 -31
  45. package/dist/llm-bridge.js.map +1 -1
  46. package/dist/llm-review.d.ts +16 -2
  47. package/dist/llm-review.js +240 -35
  48. package/dist/llm-review.js.map +1 -1
  49. package/dist/mappers/ts-concepts.d.ts +1 -1
  50. package/dist/mappers/ts-concepts.js +303 -32
  51. package/dist/mappers/ts-concepts.js.map +1 -1
  52. package/dist/norm-miner.d.ts +31 -0
  53. package/dist/norm-miner.js +119 -0
  54. package/dist/norm-miner.js.map +1 -0
  55. package/dist/obligations.d.ts +63 -0
  56. package/dist/obligations.js +158 -0
  57. package/dist/obligations.js.map +1 -0
  58. package/dist/quality-rules.d.ts +3 -3
  59. package/dist/quality-rules.js +4 -2
  60. package/dist/quality-rules.js.map +1 -1
  61. package/dist/reporter.d.ts +7 -2
  62. package/dist/reporter.js +82 -51
  63. package/dist/reporter.js.map +1 -1
  64. package/dist/rule-eval.d.ts +1 -2
  65. package/dist/rule-eval.js +5 -9
  66. package/dist/rule-eval.js.map +1 -1
  67. package/dist/rule-loader.js +16 -14
  68. package/dist/rule-loader.js.map +1 -1
  69. package/dist/rules/base.js +153 -69
  70. package/dist/rules/base.js.map +1 -1
  71. package/dist/rules/cli.js +23 -19
  72. package/dist/rules/cli.js.map +1 -1
  73. package/dist/rules/confidence.d.ts +1 -1
  74. package/dist/rules/confidence.js +5 -5
  75. package/dist/rules/confidence.js.map +1 -1
  76. package/dist/rules/dead-code.d.ts +10 -0
  77. package/dist/rules/dead-code.js +75 -0
  78. package/dist/rules/dead-code.js.map +1 -0
  79. package/dist/rules/dead-logic.js +35 -31
  80. package/dist/rules/dead-logic.js.map +1 -1
  81. package/dist/rules/express.d.ts +2 -1
  82. package/dist/rules/express.js +380 -126
  83. package/dist/rules/express.js.map +1 -1
  84. package/dist/rules/fastapi.js +53 -19
  85. package/dist/rules/fastapi.js.map +1 -1
  86. package/dist/rules/ground-layer.js +3 -3
  87. package/dist/rules/ground-layer.js.map +1 -1
  88. package/dist/rules/index.js +574 -105
  89. package/dist/rules/index.js.map +1 -1
  90. package/dist/rules/ink.js +9 -8
  91. package/dist/rules/ink.js.map +1 -1
  92. package/dist/rules/kern-source.js +202 -63
  93. package/dist/rules/kern-source.js.map +1 -1
  94. package/dist/rules/nextjs.js +88 -33
  95. package/dist/rules/nextjs.js.map +1 -1
  96. package/dist/rules/null-safety.js +52 -26
  97. package/dist/rules/null-safety.js.map +1 -1
  98. package/dist/rules/nuxt.js +24 -29
  99. package/dist/rules/nuxt.js.map +1 -1
  100. package/dist/rules/react.js +355 -69
  101. package/dist/rules/react.js.map +1 -1
  102. package/dist/rules/security-v2.js +71 -57
  103. package/dist/rules/security-v2.js.map +1 -1
  104. package/dist/rules/security-v3.js.map +1 -1
  105. package/dist/rules/security-v4.js +54 -27
  106. package/dist/rules/security-v4.js.map +1 -1
  107. package/dist/rules/security.js +35 -5
  108. package/dist/rules/security.js.map +1 -1
  109. package/dist/rules/terminal.js +17 -5
  110. package/dist/rules/terminal.js.map +1 -1
  111. package/dist/rules/vue.js +162 -107
  112. package/dist/rules/vue.js.map +1 -1
  113. package/dist/semantic-diff.d.ts +52 -0
  114. package/dist/semantic-diff.js +342 -0
  115. package/dist/semantic-diff.js.map +1 -0
  116. package/dist/spec-checker.js +11 -10
  117. package/dist/spec-checker.js.map +1 -1
  118. package/dist/suppression/apply-suppression.d.ts +2 -3
  119. package/dist/suppression/apply-suppression.js +3 -3
  120. package/dist/suppression/apply-suppression.js.map +1 -1
  121. package/dist/suppression/index.d.ts +2 -2
  122. package/dist/suppression/index.js +1 -1
  123. package/dist/suppression/index.js.map +1 -1
  124. package/dist/suppression/parse-directives.d.ts +1 -1
  125. package/dist/suppression/parse-directives.js +9 -4
  126. package/dist/suppression/parse-directives.js.map +1 -1
  127. package/dist/taint-ast.d.ts +20 -0
  128. package/dist/taint-ast.js +427 -0
  129. package/dist/taint-ast.js.map +1 -0
  130. package/dist/taint-crossfile.d.ts +28 -0
  131. package/dist/taint-crossfile.js +174 -0
  132. package/dist/taint-crossfile.js.map +1 -0
  133. package/dist/taint-findings.d.ts +17 -0
  134. package/dist/taint-findings.js +131 -0
  135. package/dist/taint-findings.js.map +1 -0
  136. package/dist/taint-regex.d.ts +61 -0
  137. package/dist/taint-regex.js +379 -0
  138. package/dist/taint-regex.js.map +1 -0
  139. package/dist/taint-types.d.ts +128 -0
  140. package/dist/taint-types.js +174 -0
  141. package/dist/taint-types.js.map +1 -0
  142. package/dist/taint.d.ts +13 -107
  143. package/dist/taint.js +16 -1067
  144. package/dist/taint.js.map +1 -1
  145. package/dist/template-detector.d.ts +2 -2
  146. package/dist/template-detector.js +11 -16
  147. package/dist/template-detector.js.map +1 -1
  148. package/dist/types.d.ts +35 -0
  149. package/dist/types.js.map +1 -1
  150. package/package.json +2 -2
@@ -0,0 +1,174 @@
1
+ /**
2
+ * Taint Tracking — cross-file analysis.
3
+ *
4
+ * Traces tainted data across import boundaries:
5
+ * handler(req) → importedFn(req.body) → exec() in another file.
6
+ */
7
+ import { classifyParams, detectSanitizers, findClosingParen, findTaintedSinks, propagateTaint } from './taint-regex.js';
8
+ // ── Export Map ───────────────────────────────────────────────────────────
9
+ /**
10
+ * Build a map of exported functions across all files.
11
+ * Maps "filePath::fnName" → ExportedFunction with sink info.
12
+ */
13
+ export function buildExportMap(inferredPerFile) {
14
+ const exportMap = new Map();
15
+ for (const [filePath, inferred] of inferredPerFile) {
16
+ for (const r of inferred) {
17
+ if (r.node.type !== 'fn')
18
+ continue;
19
+ const fnName = r.node.props?.name || '';
20
+ if (!fnName)
21
+ continue;
22
+ // Check if function is exported (absence of export='false' means exported)
23
+ const isExported = r.node.props?.export !== 'false';
24
+ if (!isExported)
25
+ continue;
26
+ const params = r.node.props?.params || '';
27
+ const handler = r.node.children?.find((c) => c.type === 'handler');
28
+ const code = handler?.props?.code || '';
29
+ // Check if the function body contains dangerous sinks
30
+ const sinks = [];
31
+ if (code) {
32
+ const dummyTaint = [];
33
+ // Parse params to get variable names for sink detection
34
+ const paramNames = params
35
+ .split(',')
36
+ .map((p) => p.trim().split(':')[0]?.trim())
37
+ .filter(Boolean);
38
+ for (const name of paramNames) {
39
+ dummyTaint.push({ name, origin: `param:${name}` });
40
+ }
41
+ if (dummyTaint.length > 0) {
42
+ sinks.push(...findTaintedSinks(code, dummyTaint));
43
+ }
44
+ }
45
+ exportMap.set(`${filePath}::${fnName}`, {
46
+ filePath,
47
+ fnName,
48
+ params,
49
+ hasSink: sinks.length > 0,
50
+ sinks,
51
+ });
52
+ }
53
+ }
54
+ return exportMap;
55
+ }
56
+ // ── Import Map ──────────────────────────────────────────────────────────
57
+ /**
58
+ * Build import→function resolution map.
59
+ * Maps "importingFile::importedName" → absolute file path of the definition.
60
+ */
61
+ export function buildImportMap(inferredPerFile, graphImports) {
62
+ const importMap = new Map();
63
+ for (const [filePath, inferred] of inferredPerFile) {
64
+ const resolvedImports = graphImports.get(filePath) || [];
65
+ for (const r of inferred) {
66
+ if (r.node.type !== 'import')
67
+ continue;
68
+ const from = r.node.props?.from || '';
69
+ const names = r.node.props?.names || '';
70
+ const defaultImport = r.node.props?.default || '';
71
+ if (!from)
72
+ continue;
73
+ // Find the resolved path for this import specifier
74
+ const resolvedPath = resolvedImports.find((p) => p.includes(from.replace(/^\.\//, '').replace(/\.(js|ts|tsx)$/, '')));
75
+ if (!resolvedPath)
76
+ continue;
77
+ // Map each imported name to its resolved file
78
+ if (names) {
79
+ for (const name of names.split(',').map((n) => n.trim())) {
80
+ if (name)
81
+ importMap.set(`${filePath}::${name}`, resolvedPath);
82
+ }
83
+ }
84
+ if (defaultImport) {
85
+ importMap.set(`${filePath}::${defaultImport}`, resolvedPath);
86
+ }
87
+ }
88
+ }
89
+ return importMap;
90
+ }
91
+ // ── Cross-File Analysis ─────────────────────────────────────────────────
92
+ /**
93
+ * Cross-file taint analysis.
94
+ *
95
+ * For each handler function with tainted params:
96
+ * 1. Find calls to imported functions in the handler body
97
+ * 2. Check if tainted data is passed as an argument
98
+ * 3. Look up the target function — does it have a dangerous sink?
99
+ * 4. If yes and no sanitizer in between → cross-file taint path
100
+ */
101
+ export function analyzeTaintCrossFile(inferredPerFile, graphImports) {
102
+ const exportMap = buildExportMap(inferredPerFile);
103
+ const importMap = buildImportMap(inferredPerFile, graphImports);
104
+ const results = [];
105
+ for (const [filePath, inferred] of inferredPerFile) {
106
+ for (const r of inferred) {
107
+ if (r.node.type !== 'fn')
108
+ continue;
109
+ const fnName = r.node.props?.name || 'anonymous';
110
+ const paramsStr = r.node.props?.params || '';
111
+ const handler = r.node.children?.find((c) => c.type === 'handler');
112
+ const code = handler?.props?.code || '';
113
+ if (!code)
114
+ continue;
115
+ // Only analyze functions with tainted params
116
+ const taintedParams = classifyParams(paramsStr);
117
+ if (taintedParams.length === 0)
118
+ continue;
119
+ const taintedVars = propagateTaint(code, taintedParams);
120
+ const taintedNames = new Set(taintedVars.map((v) => v.name));
121
+ // Find calls to imported functions: importedFn(taintedVar)
122
+ const callRegex = /\b(\w+)\s*\(/g;
123
+ let callMatch;
124
+ while ((callMatch = callRegex.exec(code)) !== null) {
125
+ const calledFn = callMatch[0].replace(/\s*\($/, '');
126
+ // Is this an imported function?
127
+ const resolvedFile = importMap.get(`${filePath}::${calledFn}`);
128
+ if (!resolvedFile)
129
+ continue;
130
+ // Does the target have dangerous sinks?
131
+ const targetFn = exportMap.get(`${resolvedFile}::${calledFn}`);
132
+ if (!targetFn?.hasSink)
133
+ continue;
134
+ // Extract arguments passed to this call
135
+ const callStart = callMatch.index + callMatch[0].length;
136
+ const parenEnd = findClosingParen(code, callStart);
137
+ const argText = code.slice(callStart, parenEnd);
138
+ // Check if any tainted variable is passed as argument
139
+ const taintedArgs = [];
140
+ for (const tName of taintedNames) {
141
+ if (new RegExp(`\\b${tName}\\b`).test(argText)) {
142
+ taintedArgs.push(tName);
143
+ }
144
+ }
145
+ if (taintedArgs.length === 0)
146
+ continue;
147
+ // Check for sanitizers between the taint and the call
148
+ const beforeCall = code.slice(0, callMatch.index);
149
+ const foundSanitizers = detectSanitizers(beforeCall);
150
+ const hasSanitizer = taintedArgs.some((arg) => foundSanitizers.some((s) => new RegExp(`\\b${arg}\\b`).test(s.context)));
151
+ if (hasSanitizer)
152
+ continue; // Sanitized before passing to callee
153
+ // Found cross-file taint path
154
+ for (const sink of targetFn.sinks) {
155
+ const source = taintedVars.find((v) => taintedArgs.includes(v.name));
156
+ if (!source)
157
+ continue;
158
+ results.push({
159
+ callerFile: filePath,
160
+ callerFn: fnName,
161
+ callerLine: r.startLine,
162
+ calleeFile: resolvedFile,
163
+ calleeFn: calledFn,
164
+ taintedArgs,
165
+ sinkInCallee: sink,
166
+ source,
167
+ });
168
+ }
169
+ }
170
+ }
171
+ }
172
+ return results;
173
+ }
174
+ //# sourceMappingURL=taint-crossfile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"taint-crossfile.js","sourceRoot":"","sources":["../src/taint-crossfile.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Taint Tracking — unified finding generation.
3
+ *
4
+ * Converts TaintResult and CrossFileTaintResult into ReviewFinding[].
5
+ * Shared category labels and suggestion logic eliminates duplication.
6
+ */
7
+ import type { CrossFileTaintResult, TaintResult, TaintSink } from './taint-types.js';
8
+ import type { ReviewFinding } from './types.js';
9
+ export declare function getSuggestion(category: TaintSink['category']): string;
10
+ /**
11
+ * Convert taint results into ReviewFinding[] for the unified pipeline.
12
+ */
13
+ export declare function taintToFindings(results: TaintResult[]): ReviewFinding[];
14
+ /**
15
+ * Convert cross-file taint results into ReviewFinding[].
16
+ */
17
+ export declare function crossFileTaintToFindings(results: CrossFileTaintResult[]): ReviewFinding[];
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Taint Tracking — unified finding generation.
3
+ *
4
+ * Converts TaintResult and CrossFileTaintResult into ReviewFinding[].
5
+ * Shared category labels and suggestion logic eliminates duplication.
6
+ */
7
+ import { createFingerprint } from './types.js';
8
+ // ── Shared Constants ────────────────────────────────────────────────────
9
+ const categoryLabels = {
10
+ command: 'command injection',
11
+ fs: 'path traversal / file write',
12
+ sql: 'SQL injection',
13
+ redirect: 'open redirect',
14
+ eval: 'code injection',
15
+ template: 'template injection',
16
+ codegen: 'code generation injection',
17
+ };
18
+ export function getSuggestion(category) {
19
+ switch (category) {
20
+ case 'command':
21
+ return 'Use spawn() with array arguments, or validate/escape input before passing to exec()';
22
+ case 'fs':
23
+ return 'Use path.resolve() + path.normalize() and verify the result stays within allowed directory';
24
+ case 'sql':
25
+ return 'Use parameterized queries ($1, ?) instead of string interpolation';
26
+ case 'redirect':
27
+ return 'Validate redirect URL against an allowlist of safe destinations';
28
+ case 'eval':
29
+ return 'Never pass user input to eval() or new Function() — use safe alternatives';
30
+ case 'template':
31
+ return 'Sanitize user input before embedding in templates';
32
+ case 'codegen':
33
+ return 'Validate type and format of external values before interpolating into generated source code (e.g., parseInt for numeric values)';
34
+ }
35
+ }
36
+ // ── Intra-File Findings ─────────────────────────────────────────────────
37
+ /**
38
+ * Convert taint results into ReviewFinding[] for the unified pipeline.
39
+ */
40
+ export function taintToFindings(results) {
41
+ const findings = [];
42
+ for (const r of results) {
43
+ // Report unsanitized paths AND insufficient sanitizer paths
44
+ const reportable = r.paths.filter((p) => !p.sanitized);
45
+ if (reportable.length === 0)
46
+ continue;
47
+ for (const path of reportable) {
48
+ const severity = path.sink.category === 'command' || path.sink.category === 'eval'
49
+ ? 'error'
50
+ : path.sink.category === 'codegen'
51
+ ? 'warning' // codegen injection: external values in generated source — validate type/format
52
+ : 'warning';
53
+ const primarySpan = {
54
+ file: r.filePath,
55
+ startLine: r.startLine,
56
+ startCol: 1,
57
+ endLine: r.startLine,
58
+ endCol: 1,
59
+ };
60
+ if (path.insufficientSanitizer) {
61
+ // Sanitizer present but wrong for this sink type
62
+ findings.push({
63
+ source: 'kern',
64
+ ruleId: `taint-insufficient-sanitizer`,
65
+ severity,
66
+ category: 'bug',
67
+ message: `Insufficient sanitizer: '${path.insufficientSanitizer}' does not protect against ${categoryLabels[path.sink.category]}. ` +
68
+ `${path.source.origin} → ${path.sink.name}() is still exploitable.`,
69
+ primarySpan,
70
+ suggestion: `${path.insufficientSanitizer} is not sufficient for ${path.sink.category} sinks. ${getSuggestion(path.sink.category)}`,
71
+ fingerprint: createFingerprint(`taint-insufficient`, r.startLine, 1),
72
+ });
73
+ }
74
+ else {
75
+ // No sanitizer at all
76
+ findings.push({
77
+ source: 'kern',
78
+ ruleId: `taint-${path.sink.category}`,
79
+ severity,
80
+ category: 'bug',
81
+ message: `Taint flow: ${path.source.origin} → ${path.sink.name}() — potential ${categoryLabels[path.sink.category]}. ` +
82
+ `Variable '${path.sink.taintedArg}' reaches dangerous sink without sanitization.`,
83
+ primarySpan,
84
+ suggestion: getSuggestion(path.sink.category),
85
+ fingerprint: createFingerprint(`taint-${path.sink.category}`, r.startLine, 1),
86
+ });
87
+ }
88
+ }
89
+ }
90
+ return findings;
91
+ }
92
+ // ── Cross-File Findings ─────────────────────────────────────────────────
93
+ /**
94
+ * Convert cross-file taint results into ReviewFinding[].
95
+ */
96
+ export function crossFileTaintToFindings(results) {
97
+ const findings = [];
98
+ for (const r of results) {
99
+ const severity = r.sinkInCallee.category === 'command' || r.sinkInCallee.category === 'eval'
100
+ ? 'error'
101
+ : 'warning';
102
+ findings.push({
103
+ source: 'kern',
104
+ ruleId: `taint-crossfile-${r.sinkInCallee.category}`,
105
+ severity,
106
+ category: 'bug',
107
+ message: `Cross-file taint: ${r.source.origin} in ${r.callerFn}() → ${r.calleeFn}() → ${r.sinkInCallee.name}(). ` +
108
+ `Tainted data crosses file boundary to reach ${categoryLabels[r.sinkInCallee.category]} sink.`,
109
+ primarySpan: {
110
+ file: r.callerFile,
111
+ startLine: r.callerLine,
112
+ startCol: 1,
113
+ endLine: r.callerLine,
114
+ endCol: 1,
115
+ },
116
+ relatedSpans: [
117
+ {
118
+ file: r.calleeFile,
119
+ startLine: 1,
120
+ startCol: 1,
121
+ endLine: 1,
122
+ endCol: 1,
123
+ },
124
+ ],
125
+ suggestion: `Validate '${r.taintedArgs.join(', ')}' before passing to ${r.calleeFn}(). ${getSuggestion(r.sinkInCallee.category)}`,
126
+ fingerprint: createFingerprint(`taint-xfile-${r.sinkInCallee.category}`, r.callerLine, 1),
127
+ });
128
+ }
129
+ return findings;
130
+ }
131
+ //# sourceMappingURL=taint-findings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"taint-findings.js","sourceRoot":"","sources":["../src/taint-findings.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Taint Tracking — Regex-based fallback engine.
3
+ *
4
+ * Used when no ts-morph SourceFile is available.
5
+ * Works on handler body strings from KERN IR nodes.
6
+ */
7
+ import type { TaintPath, TaintResult, TaintSink, TaintSource } from './taint-types.js';
8
+ import type { InferResult } from './types.js';
9
+ /**
10
+ * Regex-based taint analysis — legacy fallback for when no SourceFile is available.
11
+ */
12
+ export declare function analyzeTaintRegex(inferred: InferResult[], filePath: string): TaintResult[];
13
+ /**
14
+ * Classify function parameters as tainted or safe.
15
+ */
16
+ export declare function classifyParams(paramsStr: string): TaintSource[];
17
+ /**
18
+ * Multi-hop taint propagation using worklist algorithm.
19
+ * Propagates until fixed point or configurable depth limit.
20
+ *
21
+ * Handles all assignment patterns:
22
+ * - const b = a
23
+ * - const b = a.trim()
24
+ * - const {x} = obj
25
+ * - let b; b = a
26
+ *
27
+ * @param code - Handler code string
28
+ * @param initialTainted - Set of initially tainted variable names
29
+ * @param maxDepth - Maximum propagation depth (default: 3)
30
+ * @returns Set of all tainted variable names after fixed point or depth limit
31
+ */
32
+ export declare function propagateTaintMultiHop(code: string, initialTainted: Set<string>, maxDepth?: number): Set<string>;
33
+ interface Assignment {
34
+ lhs: string;
35
+ rhs: string;
36
+ assignId: string;
37
+ }
38
+ export declare function extractAllAssignments(code: string): Assignment[];
39
+ export declare function parseLineAssignments(line: string, lineNum: number): Assignment[];
40
+ export declare function extractDependencies(rhs: string): Set<string>;
41
+ export declare function isCircularAssignment(lhs: string, rhs: string, allAssignments: Assignment[]): boolean;
42
+ /**
43
+ * Propagate taint through variable assignments in handler body.
44
+ * Tracks: const x = req.body.foo → x is tainted.
45
+ * Returns all tainted variable names with their origins.
46
+ */
47
+ export declare function propagateTaint(code: string, params: TaintSource[]): TaintSource[];
48
+ /**
49
+ * Find sink calls that use tainted variables.
50
+ */
51
+ export declare function findTaintedSinks(code: string, taintedVars: TaintSource[]): TaintSink[];
52
+ /**
53
+ * Build taint paths and check for sanitizers between source and sink.
54
+ */
55
+ export declare function buildPaths(code: string, taintedVars: TaintSource[], sinks: TaintSink[]): TaintPath[];
56
+ export declare function detectSanitizers(code: string): Array<{
57
+ name: string;
58
+ context: string;
59
+ }>;
60
+ export declare function findClosingParen(code: string, start: number): number;
61
+ export {};