circle-ir 3.8.4 → 3.9.8
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 +82 -5
- package/dist/analysis/dfg-verifier.d.ts +3 -14
- package/dist/analysis/dfg-verifier.js +43 -74
- package/dist/analysis/dfg-verifier.js.map +1 -1
- package/dist/analysis/interprocedural.d.ts +5 -1
- package/dist/analysis/interprocedural.js +62 -60
- package/dist/analysis/interprocedural.js.map +1 -1
- package/dist/analysis/metrics/index.d.ts +2 -0
- package/dist/analysis/metrics/index.js +2 -0
- package/dist/analysis/metrics/index.js.map +1 -0
- package/dist/analysis/metrics/metric-pass.d.ts +27 -0
- package/dist/analysis/metrics/metric-pass.js +2 -0
- package/dist/analysis/metrics/metric-pass.js.map +1 -0
- package/dist/analysis/metrics/metric-runner.d.ts +21 -0
- package/dist/analysis/metrics/metric-runner.js +47 -0
- package/dist/analysis/metrics/metric-runner.js.map +1 -0
- package/dist/analysis/metrics/passes/cohesion-metrics-pass.d.ts +21 -0
- package/dist/analysis/metrics/passes/cohesion-metrics-pass.js +100 -0
- package/dist/analysis/metrics/passes/cohesion-metrics-pass.js.map +1 -0
- package/dist/analysis/metrics/passes/complexity-metrics-pass.d.ts +15 -0
- package/dist/analysis/metrics/passes/complexity-metrics-pass.js +76 -0
- package/dist/analysis/metrics/passes/complexity-metrics-pass.js.map +1 -0
- package/dist/analysis/metrics/passes/composite-metrics-pass.d.ts +17 -0
- package/dist/analysis/metrics/passes/composite-metrics-pass.js +77 -0
- package/dist/analysis/metrics/passes/composite-metrics-pass.js.map +1 -0
- package/dist/analysis/metrics/passes/coupling-metrics-pass.d.ts +19 -0
- package/dist/analysis/metrics/passes/coupling-metrics-pass.js +94 -0
- package/dist/analysis/metrics/passes/coupling-metrics-pass.js.map +1 -0
- package/dist/analysis/metrics/passes/data-flow-metrics-pass.d.ts +14 -0
- package/dist/analysis/metrics/passes/data-flow-metrics-pass.js +25 -0
- package/dist/analysis/metrics/passes/data-flow-metrics-pass.js.map +1 -0
- package/dist/analysis/metrics/passes/documentation-metrics-pass.d.ts +15 -0
- package/dist/analysis/metrics/passes/documentation-metrics-pass.js +64 -0
- package/dist/analysis/metrics/passes/documentation-metrics-pass.js.map +1 -0
- package/dist/analysis/metrics/passes/halstead-metrics-pass.d.ts +16 -0
- package/dist/analysis/metrics/passes/halstead-metrics-pass.js +95 -0
- package/dist/analysis/metrics/passes/halstead-metrics-pass.js.map +1 -0
- package/dist/analysis/metrics/passes/inheritance-metrics-pass.d.ts +18 -0
- package/dist/analysis/metrics/passes/inheritance-metrics-pass.js +73 -0
- package/dist/analysis/metrics/passes/inheritance-metrics-pass.js.map +1 -0
- package/dist/analysis/metrics/passes/size-metrics-pass.d.ts +11 -0
- package/dist/analysis/metrics/passes/size-metrics-pass.js +64 -0
- package/dist/analysis/metrics/passes/size-metrics-pass.js.map +1 -0
- package/dist/analysis/passes/circular-dependency-pass.d.ts +18 -0
- package/dist/analysis/passes/circular-dependency-pass.js +39 -0
- package/dist/analysis/passes/circular-dependency-pass.js.map +1 -0
- package/dist/analysis/passes/constant-propagation-pass.d.ts +22 -0
- package/dist/analysis/passes/constant-propagation-pass.js +44 -0
- package/dist/analysis/passes/constant-propagation-pass.js.map +1 -0
- package/dist/analysis/passes/cross-file-pass.d.ts +27 -0
- package/dist/analysis/passes/cross-file-pass.js +102 -0
- package/dist/analysis/passes/cross-file-pass.js.map +1 -0
- package/dist/analysis/passes/dead-code-pass.d.ts +25 -0
- package/dist/analysis/passes/dead-code-pass.js +117 -0
- package/dist/analysis/passes/dead-code-pass.js.map +1 -0
- package/dist/analysis/passes/deep-inheritance-pass.d.ts +30 -0
- package/dist/analysis/passes/deep-inheritance-pass.js +82 -0
- package/dist/analysis/passes/deep-inheritance-pass.js.map +1 -0
- package/dist/analysis/passes/dependency-fan-out-pass.d.ts +19 -0
- package/dist/analysis/passes/dependency-fan-out-pass.js +35 -0
- package/dist/analysis/passes/dependency-fan-out-pass.js.map +1 -0
- package/dist/analysis/passes/infinite-loop-pass.d.ts +31 -0
- package/dist/analysis/passes/infinite-loop-pass.js +126 -0
- package/dist/analysis/passes/infinite-loop-pass.js.map +1 -0
- package/dist/analysis/passes/interprocedural-pass.d.ts +29 -0
- package/dist/analysis/passes/interprocedural-pass.js +169 -0
- package/dist/analysis/passes/interprocedural-pass.js.map +1 -0
- package/dist/analysis/passes/language-sources-pass.d.ts +76 -0
- package/dist/analysis/passes/language-sources-pass.js +491 -0
- package/dist/analysis/passes/language-sources-pass.js.map +1 -0
- package/dist/analysis/passes/leaked-global-pass.d.ts +34 -0
- package/dist/analysis/passes/leaked-global-pass.js +108 -0
- package/dist/analysis/passes/leaked-global-pass.js.map +1 -0
- package/dist/analysis/passes/missing-await-pass.d.ts +29 -0
- package/dist/analysis/passes/missing-await-pass.js +90 -0
- package/dist/analysis/passes/missing-await-pass.js.map +1 -0
- package/dist/analysis/passes/missing-public-doc-pass.d.ts +35 -0
- package/dist/analysis/passes/missing-public-doc-pass.js +148 -0
- package/dist/analysis/passes/missing-public-doc-pass.js.map +1 -0
- package/dist/analysis/passes/n-plus-one-pass.d.ts +29 -0
- package/dist/analysis/passes/n-plus-one-pass.js +100 -0
- package/dist/analysis/passes/n-plus-one-pass.js.map +1 -0
- package/dist/analysis/passes/null-deref-pass.d.ts +32 -0
- package/dist/analysis/passes/null-deref-pass.js +130 -0
- package/dist/analysis/passes/null-deref-pass.js.map +1 -0
- package/dist/analysis/passes/orphan-module-pass.d.ts +21 -0
- package/dist/analysis/passes/orphan-module-pass.js +38 -0
- package/dist/analysis/passes/orphan-module-pass.js.map +1 -0
- package/dist/analysis/passes/react-inline-jsx-pass.d.ts +36 -0
- package/dist/analysis/passes/react-inline-jsx-pass.js +140 -0
- package/dist/analysis/passes/react-inline-jsx-pass.js.map +1 -0
- package/dist/analysis/passes/redundant-loop-pass.d.ts +30 -0
- package/dist/analysis/passes/redundant-loop-pass.js +146 -0
- package/dist/analysis/passes/redundant-loop-pass.js.map +1 -0
- package/dist/analysis/passes/resource-leak-pass.d.ts +43 -0
- package/dist/analysis/passes/resource-leak-pass.js +156 -0
- package/dist/analysis/passes/resource-leak-pass.js.map +1 -0
- package/dist/analysis/passes/serial-await-pass.d.ts +36 -0
- package/dist/analysis/passes/serial-await-pass.js +132 -0
- package/dist/analysis/passes/serial-await-pass.js.map +1 -0
- package/dist/analysis/passes/sink-filter-pass.d.ts +39 -0
- package/dist/analysis/passes/sink-filter-pass.js +231 -0
- package/dist/analysis/passes/sink-filter-pass.js.map +1 -0
- package/dist/analysis/passes/stale-doc-ref-pass.d.ts +21 -0
- package/dist/analysis/passes/stale-doc-ref-pass.js +96 -0
- package/dist/analysis/passes/stale-doc-ref-pass.js.map +1 -0
- package/dist/analysis/passes/string-concat-loop-pass.d.ts +26 -0
- package/dist/analysis/passes/string-concat-loop-pass.js +87 -0
- package/dist/analysis/passes/string-concat-loop-pass.js.map +1 -0
- package/dist/analysis/passes/sync-io-async-pass.d.ts +28 -0
- package/dist/analysis/passes/sync-io-async-pass.js +80 -0
- package/dist/analysis/passes/sync-io-async-pass.js.map +1 -0
- package/dist/analysis/passes/taint-matcher-pass.d.ts +24 -0
- package/dist/analysis/passes/taint-matcher-pass.js +71 -0
- package/dist/analysis/passes/taint-matcher-pass.js.map +1 -0
- package/dist/analysis/passes/taint-propagation-pass.d.ts +22 -0
- package/dist/analysis/passes/taint-propagation-pass.js +266 -0
- package/dist/analysis/passes/taint-propagation-pass.js.map +1 -0
- package/dist/analysis/passes/todo-in-prod-pass.d.ts +28 -0
- package/dist/analysis/passes/todo-in-prod-pass.js +71 -0
- package/dist/analysis/passes/todo-in-prod-pass.js.map +1 -0
- package/dist/analysis/passes/unbounded-collection-pass.d.ts +32 -0
- package/dist/analysis/passes/unbounded-collection-pass.js +128 -0
- package/dist/analysis/passes/unbounded-collection-pass.js.map +1 -0
- package/dist/analysis/passes/unchecked-return-pass.d.ts +34 -0
- package/dist/analysis/passes/unchecked-return-pass.js +106 -0
- package/dist/analysis/passes/unchecked-return-pass.js.map +1 -0
- package/dist/analysis/passes/unused-variable-pass.d.ts +36 -0
- package/dist/analysis/passes/unused-variable-pass.js +150 -0
- package/dist/analysis/passes/unused-variable-pass.js.map +1 -0
- package/dist/analysis/passes/variable-shadowing-pass.d.ts +41 -0
- package/dist/analysis/passes/variable-shadowing-pass.js +211 -0
- package/dist/analysis/passes/variable-shadowing-pass.js.map +1 -0
- package/dist/analysis/path-finder.d.ts +3 -13
- package/dist/analysis/path-finder.js +48 -63
- package/dist/analysis/path-finder.js.map +1 -1
- package/dist/analysis/taint-matcher.js +8 -1
- package/dist/analysis/taint-matcher.js.map +1 -1
- package/dist/analysis/taint-propagation.d.ts +5 -1
- package/dist/analysis/taint-propagation.js +44 -41
- package/dist/analysis/taint-propagation.js.map +1 -1
- package/dist/analyzer.d.ts +48 -1
- package/dist/analyzer.js +252 -1476
- package/dist/analyzer.js.map +1 -1
- package/dist/browser/circle-ir.js +3952 -1270
- package/dist/core/circle-ir-core.cjs +360 -106
- package/dist/core/circle-ir-core.js +360 -106
- package/dist/core/extractors/imports.js +18 -0
- package/dist/core/extractors/imports.js.map +1 -1
- package/dist/graph/analysis-pass.d.ts +68 -0
- package/dist/graph/analysis-pass.js +51 -0
- package/dist/graph/analysis-pass.js.map +1 -0
- package/dist/graph/code-graph.d.ts +92 -0
- package/dist/graph/code-graph.js +262 -0
- package/dist/graph/code-graph.js.map +1 -0
- package/dist/graph/dominator-graph.d.ts +53 -0
- package/dist/graph/dominator-graph.js +256 -0
- package/dist/graph/dominator-graph.js.map +1 -0
- package/dist/graph/import-graph.d.ts +33 -0
- package/dist/graph/import-graph.js +170 -0
- package/dist/graph/import-graph.js.map +1 -0
- package/dist/graph/index.d.ts +5 -0
- package/dist/graph/index.js +6 -0
- package/dist/graph/index.js.map +1 -0
- package/dist/graph/project-graph.d.ts +43 -0
- package/dist/graph/project-graph.js +80 -0
- package/dist/graph/project-graph.js.map +1 -0
- package/dist/graph/scope-graph.d.ts +63 -0
- package/dist/graph/scope-graph.js +89 -0
- package/dist/graph/scope-graph.js.map +1 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/resolution/cross-file.js +52 -19
- package/dist/resolution/cross-file.js.map +1 -1
- package/dist/types/index.d.ts +151 -0
- package/docs/SPEC.md +10 -6
- package/package.json +1 -1
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pass #28: infinite-loop (CWE-835, category: reliability)
|
|
3
|
+
*
|
|
4
|
+
* Detects loops with no reachable exit edge — i.e., loops that can run
|
|
5
|
+
* forever because every execution path through the loop body leads back to
|
|
6
|
+
* the loop header without a break, return, throw, or continue-to-outer.
|
|
7
|
+
*
|
|
8
|
+
* Detection strategy:
|
|
9
|
+
* 1. Identify loop headers: back-edge targets in the CFG (`edge.type === 'back'`).
|
|
10
|
+
* 2. For each loop, collect the loop body blocks via BFS from the header,
|
|
11
|
+
* stopping at back-edge sources (the "tail" blocks).
|
|
12
|
+
* 3. Check whether any block in the body has an outgoing edge that exits
|
|
13
|
+
* the loop (target not in body set and not back to header).
|
|
14
|
+
* 4. As a text-level fallback, scan source lines in the loop body for
|
|
15
|
+
* `return`, `throw`/`raise`, `break`, `System.exit` keywords.
|
|
16
|
+
* 5. Emit a finding at the loop header's start_line if no exit is found.
|
|
17
|
+
*
|
|
18
|
+
* Languages: Java, JavaScript, TypeScript, Python, Rust. Skip Bash.
|
|
19
|
+
*/
|
|
20
|
+
/** Exit keywords to scan for as a text-level fallback. */
|
|
21
|
+
const EXIT_KEYWORDS = /\b(return|throw|raise|break|System\.exit|process\.exit|os\._exit|exit!\()\b/;
|
|
22
|
+
export class InfiniteLoopPass {
|
|
23
|
+
name = 'infinite-loop';
|
|
24
|
+
category = 'reliability';
|
|
25
|
+
run(ctx) {
|
|
26
|
+
const { graph, code, language } = ctx;
|
|
27
|
+
if (language === 'bash') {
|
|
28
|
+
return { potentialInfiniteLoops: [] };
|
|
29
|
+
}
|
|
30
|
+
const { blocks, edges } = graph.ir.cfg;
|
|
31
|
+
if (blocks.length === 0)
|
|
32
|
+
return { potentialInfiniteLoops: [] };
|
|
33
|
+
const file = graph.ir.meta.file;
|
|
34
|
+
const codeLines = code.split('\n');
|
|
35
|
+
// Build adjacency maps
|
|
36
|
+
const outgoing = new Map();
|
|
37
|
+
for (const edge of edges) {
|
|
38
|
+
const list = outgoing.get(edge.from) ?? [];
|
|
39
|
+
list.push({ to: edge.to, type: edge.type });
|
|
40
|
+
outgoing.set(edge.from, list);
|
|
41
|
+
}
|
|
42
|
+
// Find back-edges: each back-edge defines a loop
|
|
43
|
+
const backEdges = edges.filter(e => e.type === 'back');
|
|
44
|
+
const potentialInfiniteLoops = [];
|
|
45
|
+
const reportedHeaders = new Set();
|
|
46
|
+
for (const backEdge of backEdges) {
|
|
47
|
+
const headerId = backEdge.to;
|
|
48
|
+
const tailId = backEdge.from;
|
|
49
|
+
const header = graph.blockById.get(headerId);
|
|
50
|
+
const tail = graph.blockById.get(tailId);
|
|
51
|
+
if (!header || !tail)
|
|
52
|
+
continue;
|
|
53
|
+
// Deduplicate: one finding per header
|
|
54
|
+
if (reportedHeaders.has(headerId))
|
|
55
|
+
continue;
|
|
56
|
+
// Collect loop body blocks via BFS from header, stopping at tail
|
|
57
|
+
const bodyIds = new Set();
|
|
58
|
+
const queue = [headerId];
|
|
59
|
+
bodyIds.add(headerId);
|
|
60
|
+
while (queue.length > 0) {
|
|
61
|
+
const cur = queue.shift();
|
|
62
|
+
for (const { to, type } of outgoing.get(cur) ?? []) {
|
|
63
|
+
// Don't follow back edges (stay inside the loop)
|
|
64
|
+
if (type === 'back')
|
|
65
|
+
continue;
|
|
66
|
+
if (!bodyIds.has(to)) {
|
|
67
|
+
bodyIds.add(to);
|
|
68
|
+
queue.push(to);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
// Stop expanding from tail (the back-edge source)
|
|
72
|
+
if (cur === tailId)
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
// Check for any exit edge: an edge from a body block to a non-body block
|
|
76
|
+
let hasExit = false;
|
|
77
|
+
for (const bodyId of bodyIds) {
|
|
78
|
+
for (const { to, type } of outgoing.get(bodyId) ?? []) {
|
|
79
|
+
if (type === 'back')
|
|
80
|
+
continue;
|
|
81
|
+
if (!bodyIds.has(to)) {
|
|
82
|
+
hasExit = true;
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (hasExit)
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
if (hasExit)
|
|
90
|
+
continue;
|
|
91
|
+
// Text-level fallback: scan source lines for exit keywords
|
|
92
|
+
const bodyStart = header.start_line;
|
|
93
|
+
const bodyEnd = tail.end_line;
|
|
94
|
+
let hasKeywordExit = false;
|
|
95
|
+
for (let ln = bodyStart; ln <= bodyEnd && ln <= codeLines.length; ln++) {
|
|
96
|
+
if (EXIT_KEYWORDS.test(codeLines[ln - 1] ?? '')) {
|
|
97
|
+
hasKeywordExit = true;
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (hasKeywordExit)
|
|
102
|
+
continue;
|
|
103
|
+
reportedHeaders.add(headerId);
|
|
104
|
+
potentialInfiniteLoops.push({ headerLine: header.start_line, bodyEndLine: bodyEnd });
|
|
105
|
+
const loc = bodyStart === bodyEnd
|
|
106
|
+
? `line ${bodyStart}`
|
|
107
|
+
: `lines ${bodyStart}–${bodyEnd}`;
|
|
108
|
+
ctx.addFinding({
|
|
109
|
+
id: `infinite-loop-${file}-${header.start_line}`,
|
|
110
|
+
pass: this.name,
|
|
111
|
+
category: this.category,
|
|
112
|
+
rule_id: this.name,
|
|
113
|
+
cwe: 'CWE-835',
|
|
114
|
+
severity: 'medium',
|
|
115
|
+
level: 'warning',
|
|
116
|
+
message: `Potential infinite loop: no reachable break, return, or throw found in loop body (${loc})`,
|
|
117
|
+
file,
|
|
118
|
+
line: header.start_line,
|
|
119
|
+
end_line: bodyEnd > header.start_line ? bodyEnd : undefined,
|
|
120
|
+
fix: 'Ensure the loop has a reachable exit condition (break, return, or throw) on all paths',
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
return { potentialInfiniteLoops };
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=infinite-loop-pass.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"infinite-loop-pass.js","sourceRoot":"","sources":["../../../src/analysis/passes/infinite-loop-pass.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* InterproceduralPass
|
|
3
|
+
*
|
|
4
|
+
* Performs inter-procedural taint analysis: finds taint escaping the current
|
|
5
|
+
* method into callees and surfaces sinks inside those callees.
|
|
6
|
+
*
|
|
7
|
+
* Handles two scenarios:
|
|
8
|
+
* A) Sources + sinks already found → find additional sinks inside callees
|
|
9
|
+
* and generate inter-procedural flows between them.
|
|
10
|
+
* B) Sources found but no sinks yet → detect external taint escapes
|
|
11
|
+
* (CWE-668 "external_taint_escape") as a fallback.
|
|
12
|
+
*
|
|
13
|
+
* Depends on: sink-filter, constant-propagation, taint-propagation
|
|
14
|
+
*/
|
|
15
|
+
import type { TaintSink, TaintFlowInfo, InterproceduralInfo } from '../../types/index.js';
|
|
16
|
+
import type { AnalysisPass, PassContext } from '../../graph/analysis-pass.js';
|
|
17
|
+
export interface InterproceduralPassResult {
|
|
18
|
+
/** Additional sinks surfaced by inter-procedural analysis. */
|
|
19
|
+
additionalSinks: TaintSink[];
|
|
20
|
+
/** Additional flows generated from inter-procedural paths. */
|
|
21
|
+
additionalFlows: TaintFlowInfo[];
|
|
22
|
+
/** Structured inter-procedural summary for the IR output. */
|
|
23
|
+
interprocedural?: InterproceduralInfo;
|
|
24
|
+
}
|
|
25
|
+
export declare class InterproceduralPass implements AnalysisPass<InterproceduralPassResult> {
|
|
26
|
+
readonly name = "interprocedural";
|
|
27
|
+
readonly category: "security";
|
|
28
|
+
run(ctx: PassContext): InterproceduralPassResult;
|
|
29
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* InterproceduralPass
|
|
3
|
+
*
|
|
4
|
+
* Performs inter-procedural taint analysis: finds taint escaping the current
|
|
5
|
+
* method into callees and surfaces sinks inside those callees.
|
|
6
|
+
*
|
|
7
|
+
* Handles two scenarios:
|
|
8
|
+
* A) Sources + sinks already found → find additional sinks inside callees
|
|
9
|
+
* and generate inter-procedural flows between them.
|
|
10
|
+
* B) Sources found but no sinks yet → detect external taint escapes
|
|
11
|
+
* (CWE-668 "external_taint_escape") as a fallback.
|
|
12
|
+
*
|
|
13
|
+
* Depends on: sink-filter, constant-propagation, taint-propagation
|
|
14
|
+
*/
|
|
15
|
+
import { analyzeInterprocedural, findTaintBridges } from '../interprocedural.js';
|
|
16
|
+
export class InterproceduralPass {
|
|
17
|
+
name = 'interprocedural';
|
|
18
|
+
category = 'security';
|
|
19
|
+
run(ctx) {
|
|
20
|
+
const { graph } = ctx;
|
|
21
|
+
const constProp = ctx.getResult('constant-propagation');
|
|
22
|
+
const sinkFilter = ctx.getResult('sink-filter');
|
|
23
|
+
const taintProp = ctx.getResult('taint-propagation');
|
|
24
|
+
const { sources, sinks, sanitizers } = sinkFilter;
|
|
25
|
+
if (sources.length === 0) {
|
|
26
|
+
return { additionalSinks: [], additionalFlows: [] };
|
|
27
|
+
}
|
|
28
|
+
const additionalSinks = [];
|
|
29
|
+
const additionalFlows = [...taintProp.flows];
|
|
30
|
+
let interprocedural;
|
|
31
|
+
// --- Scenario A: sources AND sinks present --------------------------------
|
|
32
|
+
if (sinks.length > 0) {
|
|
33
|
+
const interProc = analyzeInterprocedural(graph, sources, sinks, sanitizers, {
|
|
34
|
+
taintedVariables: constProp.tainted,
|
|
35
|
+
});
|
|
36
|
+
// Collect propagated sinks (skip external_taint_escape — only used in fallback)
|
|
37
|
+
for (const sink of interProc.propagatedSinks) {
|
|
38
|
+
if (sink.type === 'external_taint_escape')
|
|
39
|
+
continue;
|
|
40
|
+
if (!sinks.some(s => s.line === sink.line)) {
|
|
41
|
+
additionalSinks.push(sink);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
// Build inter-procedural flows for newly surfaced sinks
|
|
45
|
+
if (interProc.propagatedSinks.length > 0) {
|
|
46
|
+
const sanitizerMethodNames = new Set();
|
|
47
|
+
for (const san of sanitizers) {
|
|
48
|
+
if (san.type === 'javadoc_sanitizer') {
|
|
49
|
+
const match = san.method.match(/^(\w+)\(\)$/);
|
|
50
|
+
sanitizerMethodNames.add(match ? match[1] : san.method);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
for (const sink of interProc.propagatedSinks) {
|
|
54
|
+
if (sink.type === 'external_taint_escape')
|
|
55
|
+
continue;
|
|
56
|
+
for (const edge of interProc.callEdges) {
|
|
57
|
+
if (!interProc.taintedMethods.has(edge.calleeMethod))
|
|
58
|
+
continue;
|
|
59
|
+
const method = interProc.methodNodes.get(edge.calleeMethod);
|
|
60
|
+
if (!method)
|
|
61
|
+
continue;
|
|
62
|
+
if (sink.line < method.startLine || sink.line > method.endLine)
|
|
63
|
+
continue;
|
|
64
|
+
if (sanitizerMethodNames.has(method.name))
|
|
65
|
+
continue;
|
|
66
|
+
for (const source of sources) {
|
|
67
|
+
if (source.line > edge.callLine)
|
|
68
|
+
continue;
|
|
69
|
+
if (source.type === 'interprocedural_param' && source.confidence < 0.6)
|
|
70
|
+
continue;
|
|
71
|
+
if (additionalFlows.some(f => f.source_line === source.line && f.sink_line === sink.line))
|
|
72
|
+
continue;
|
|
73
|
+
additionalFlows.push({
|
|
74
|
+
source_line: source.line,
|
|
75
|
+
sink_line: sink.line,
|
|
76
|
+
source_type: source.type,
|
|
77
|
+
sink_type: sink.type,
|
|
78
|
+
path: [
|
|
79
|
+
{ variable: source.location, line: source.line, type: 'source' },
|
|
80
|
+
{ variable: `call to ${method.name}()`, line: edge.callLine, type: 'use' },
|
|
81
|
+
{ variable: sink.location, line: sink.line, type: 'sink' },
|
|
82
|
+
],
|
|
83
|
+
confidence: sink.confidence * source.confidence * 0.85,
|
|
84
|
+
sanitized: false,
|
|
85
|
+
});
|
|
86
|
+
break; // one source per sink is enough
|
|
87
|
+
}
|
|
88
|
+
break; // one call edge per sink is enough
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
const taintBridges = findTaintBridges(interProc);
|
|
93
|
+
interprocedural = {
|
|
94
|
+
tainted_methods: Array.from(interProc.taintedMethods),
|
|
95
|
+
taint_bridges: taintBridges,
|
|
96
|
+
method_flows: interProc.callEdges
|
|
97
|
+
.filter(edge => interProc.taintedMethods.has(edge.calleeMethod))
|
|
98
|
+
.map(edge => ({
|
|
99
|
+
caller: edge.callerMethod,
|
|
100
|
+
callee: edge.calleeMethod,
|
|
101
|
+
call_line: edge.callLine,
|
|
102
|
+
tainted_args: edge.taintedArgs,
|
|
103
|
+
returns_taint: interProc.taintedReturns.has(edge.calleeMethod),
|
|
104
|
+
})),
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
// --- Scenario B: sources present, no sinks --------------------------------
|
|
108
|
+
if (sinks.length === 0) {
|
|
109
|
+
// `constructor_field` sources are generated by the Java getter pattern
|
|
110
|
+
// detector and are not real external inputs in TypeScript/library code.
|
|
111
|
+
// `interprocedural_param` sources represent "this method's parameter MIGHT be
|
|
112
|
+
// tainted when called with tainted data" — they are speculative signals, not
|
|
113
|
+
// confirmed external inputs. Using them in Scenario B (no YAML sinks) produces
|
|
114
|
+
// false-positive `external_taint_escape` findings on every internal library
|
|
115
|
+
// method whose typed parameters flow into arithmetic or other operations
|
|
116
|
+
// (e.g. `run(ctx: MetricContext)` computing metrics from `ctx.accumulated`).
|
|
117
|
+
// Real cross-file flows from true web inputs are surfaced by CrossFilePass;
|
|
118
|
+
// there is no value in re-surfacing them here as unvalidated escapes.
|
|
119
|
+
const fallbackSources = sources.filter(s => s.type !== 'constructor_field' &&
|
|
120
|
+
s.type !== 'interprocedural_param');
|
|
121
|
+
if (fallbackSources.length === 0) {
|
|
122
|
+
return { additionalSinks, additionalFlows, interprocedural };
|
|
123
|
+
}
|
|
124
|
+
const interProc = analyzeInterprocedural(graph, fallbackSources, [], sanitizers, {
|
|
125
|
+
taintedVariables: constProp.tainted,
|
|
126
|
+
});
|
|
127
|
+
for (const sink of interProc.propagatedSinks) {
|
|
128
|
+
if (!constProp.unreachableLines.has(sink.line)) {
|
|
129
|
+
additionalSinks.push(sink);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (interProc.taintedMethods.size > 0 || interProc.propagatedSinks.length > 0) {
|
|
133
|
+
const taintBridges = findTaintBridges(interProc);
|
|
134
|
+
interprocedural = {
|
|
135
|
+
tainted_methods: Array.from(interProc.taintedMethods),
|
|
136
|
+
taint_bridges: taintBridges,
|
|
137
|
+
method_flows: interProc.callEdges
|
|
138
|
+
.filter(edge => interProc.taintedMethods.has(edge.calleeMethod))
|
|
139
|
+
.map(edge => ({
|
|
140
|
+
caller: edge.callerMethod,
|
|
141
|
+
callee: edge.calleeMethod,
|
|
142
|
+
call_line: edge.callLine,
|
|
143
|
+
tainted_args: edge.taintedArgs,
|
|
144
|
+
returns_taint: interProc.taintedReturns.has(edge.calleeMethod),
|
|
145
|
+
})),
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
// Generate simple source→sink flows for any newly-found sinks
|
|
149
|
+
if (additionalSinks.length > 0 && sources.length > 0) {
|
|
150
|
+
for (const sink of additionalSinks) {
|
|
151
|
+
additionalFlows.push({
|
|
152
|
+
source_line: sources[0].line,
|
|
153
|
+
sink_line: sink.line,
|
|
154
|
+
source_type: sources[0].type,
|
|
155
|
+
sink_type: sink.type,
|
|
156
|
+
path: [
|
|
157
|
+
{ variable: 'input', line: sources[0].line, type: 'source' },
|
|
158
|
+
{ variable: 'input', line: sink.line, type: 'sink' },
|
|
159
|
+
],
|
|
160
|
+
confidence: sources[0].confidence * sink.confidence,
|
|
161
|
+
sanitized: false,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return { additionalSinks, additionalFlows, interprocedural };
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
//# sourceMappingURL=interprocedural-pass.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interprocedural-pass.js","sourceRoot":"","sources":["../../../src/analysis/passes/interprocedural-pass.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LanguageSourcesPass
|
|
3
|
+
*
|
|
4
|
+
* Detects taint sources and sinks that are not covered by config-based
|
|
5
|
+
* pattern matching (analyzer.js / taint-matcher). Handles language-specific
|
|
6
|
+
* patterns that require text-level heuristics:
|
|
7
|
+
* - Java: getter methods returning tainted constructor fields
|
|
8
|
+
* - JavaScript/TypeScript: assignment sources, DOM XSS property sinks
|
|
9
|
+
* - Python: assignment sources, return-XSS sinks, trust-boundary violations
|
|
10
|
+
*
|
|
11
|
+
* Also computes the forward-taint maps (pyTaintedVars / jsTaintedVars) that
|
|
12
|
+
* SinkFilterPass uses to reduce false positives.
|
|
13
|
+
*
|
|
14
|
+
* Depends on: taint-matcher, constant-propagation
|
|
15
|
+
*/
|
|
16
|
+
import type { TaintSource, TaintSink } from '../../types/index.js';
|
|
17
|
+
import type { AnalysisPass, PassContext } from '../../graph/analysis-pass.js';
|
|
18
|
+
export declare const JS_TAINTED_PATTERNS: ({
|
|
19
|
+
pattern: RegExp;
|
|
20
|
+
type: "http_param";
|
|
21
|
+
} | {
|
|
22
|
+
pattern: RegExp;
|
|
23
|
+
type: "http_body";
|
|
24
|
+
} | {
|
|
25
|
+
pattern: RegExp;
|
|
26
|
+
type: "http_header";
|
|
27
|
+
} | {
|
|
28
|
+
pattern: RegExp;
|
|
29
|
+
type: "http_cookie";
|
|
30
|
+
} | {
|
|
31
|
+
pattern: RegExp;
|
|
32
|
+
type: "http_path";
|
|
33
|
+
} | {
|
|
34
|
+
pattern: RegExp;
|
|
35
|
+
type: "file_input";
|
|
36
|
+
} | {
|
|
37
|
+
pattern: RegExp;
|
|
38
|
+
type: "env_input";
|
|
39
|
+
} | {
|
|
40
|
+
pattern: RegExp;
|
|
41
|
+
type: "io_input";
|
|
42
|
+
} | {
|
|
43
|
+
pattern: RegExp;
|
|
44
|
+
type: "dom_input";
|
|
45
|
+
})[];
|
|
46
|
+
export interface LanguageSourcesResult {
|
|
47
|
+
additionalSources: TaintSource[];
|
|
48
|
+
additionalSinks: TaintSink[];
|
|
49
|
+
/**
|
|
50
|
+
* Python forward-taint map: variable name → first tainted line.
|
|
51
|
+
* Used by SinkFilterPass to reduce XPath/XSS false positives.
|
|
52
|
+
*/
|
|
53
|
+
pyTaintedVars: Map<string, number>;
|
|
54
|
+
/**
|
|
55
|
+
* Python sanitized-variable set (apostrophe-guard + .replace() sanitizers).
|
|
56
|
+
* Used by SinkFilterPass to suppress sanitized XPath sinks.
|
|
57
|
+
*/
|
|
58
|
+
pySanitizedVars: Set<string>;
|
|
59
|
+
/**
|
|
60
|
+
* JavaScript forward-taint map: variable name → first tainted line.
|
|
61
|
+
* Used by SinkFilterPass to suppress spurious XSS sinks.
|
|
62
|
+
*/
|
|
63
|
+
jsTaintedVars: Map<string, number>;
|
|
64
|
+
}
|
|
65
|
+
export declare class LanguageSourcesPass implements AnalysisPass<LanguageSourcesResult> {
|
|
66
|
+
readonly name = "language-sources";
|
|
67
|
+
readonly category: "security";
|
|
68
|
+
run(ctx: PassContext): LanguageSourcesResult;
|
|
69
|
+
}
|
|
70
|
+
export declare function buildPythonTaintedVars(sourceCode: string): Map<string, number>;
|
|
71
|
+
export declare function buildPythonSanitizedVars(sourceCode: string, pyTaintedVars: Map<string, number>): Set<string>;
|
|
72
|
+
export declare function findPythonTrustBoundaryViolations(sourceCode: string, taintedVars: Map<string, number>): Array<{
|
|
73
|
+
sourceLine: number;
|
|
74
|
+
sinkLine: number;
|
|
75
|
+
}>;
|
|
76
|
+
export declare function buildJavaScriptTaintedVars(sourceCode: string, language: string): Map<string, number>;
|