circle-ir 3.8.4 → 3.9.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 (157) hide show
  1. package/README.md +82 -5
  2. package/dist/analysis/dfg-verifier.d.ts +3 -14
  3. package/dist/analysis/dfg-verifier.js +43 -74
  4. package/dist/analysis/dfg-verifier.js.map +1 -1
  5. package/dist/analysis/interprocedural.d.ts +5 -1
  6. package/dist/analysis/interprocedural.js +62 -60
  7. package/dist/analysis/interprocedural.js.map +1 -1
  8. package/dist/analysis/metrics/index.d.ts +2 -0
  9. package/dist/analysis/metrics/index.js +2 -0
  10. package/dist/analysis/metrics/index.js.map +1 -0
  11. package/dist/analysis/metrics/metric-pass.d.ts +27 -0
  12. package/dist/analysis/metrics/metric-pass.js +2 -0
  13. package/dist/analysis/metrics/metric-pass.js.map +1 -0
  14. package/dist/analysis/metrics/metric-runner.d.ts +21 -0
  15. package/dist/analysis/metrics/metric-runner.js +47 -0
  16. package/dist/analysis/metrics/metric-runner.js.map +1 -0
  17. package/dist/analysis/metrics/passes/cohesion-metrics-pass.d.ts +21 -0
  18. package/dist/analysis/metrics/passes/cohesion-metrics-pass.js +100 -0
  19. package/dist/analysis/metrics/passes/cohesion-metrics-pass.js.map +1 -0
  20. package/dist/analysis/metrics/passes/complexity-metrics-pass.d.ts +15 -0
  21. package/dist/analysis/metrics/passes/complexity-metrics-pass.js +76 -0
  22. package/dist/analysis/metrics/passes/complexity-metrics-pass.js.map +1 -0
  23. package/dist/analysis/metrics/passes/composite-metrics-pass.d.ts +17 -0
  24. package/dist/analysis/metrics/passes/composite-metrics-pass.js +77 -0
  25. package/dist/analysis/metrics/passes/composite-metrics-pass.js.map +1 -0
  26. package/dist/analysis/metrics/passes/coupling-metrics-pass.d.ts +19 -0
  27. package/dist/analysis/metrics/passes/coupling-metrics-pass.js +94 -0
  28. package/dist/analysis/metrics/passes/coupling-metrics-pass.js.map +1 -0
  29. package/dist/analysis/metrics/passes/data-flow-metrics-pass.d.ts +14 -0
  30. package/dist/analysis/metrics/passes/data-flow-metrics-pass.js +25 -0
  31. package/dist/analysis/metrics/passes/data-flow-metrics-pass.js.map +1 -0
  32. package/dist/analysis/metrics/passes/documentation-metrics-pass.d.ts +15 -0
  33. package/dist/analysis/metrics/passes/documentation-metrics-pass.js +64 -0
  34. package/dist/analysis/metrics/passes/documentation-metrics-pass.js.map +1 -0
  35. package/dist/analysis/metrics/passes/halstead-metrics-pass.d.ts +16 -0
  36. package/dist/analysis/metrics/passes/halstead-metrics-pass.js +95 -0
  37. package/dist/analysis/metrics/passes/halstead-metrics-pass.js.map +1 -0
  38. package/dist/analysis/metrics/passes/inheritance-metrics-pass.d.ts +18 -0
  39. package/dist/analysis/metrics/passes/inheritance-metrics-pass.js +73 -0
  40. package/dist/analysis/metrics/passes/inheritance-metrics-pass.js.map +1 -0
  41. package/dist/analysis/metrics/passes/size-metrics-pass.d.ts +11 -0
  42. package/dist/analysis/metrics/passes/size-metrics-pass.js +64 -0
  43. package/dist/analysis/metrics/passes/size-metrics-pass.js.map +1 -0
  44. package/dist/analysis/passes/circular-dependency-pass.d.ts +18 -0
  45. package/dist/analysis/passes/circular-dependency-pass.js +39 -0
  46. package/dist/analysis/passes/circular-dependency-pass.js.map +1 -0
  47. package/dist/analysis/passes/constant-propagation-pass.d.ts +22 -0
  48. package/dist/analysis/passes/constant-propagation-pass.js +44 -0
  49. package/dist/analysis/passes/constant-propagation-pass.js.map +1 -0
  50. package/dist/analysis/passes/cross-file-pass.d.ts +27 -0
  51. package/dist/analysis/passes/cross-file-pass.js +102 -0
  52. package/dist/analysis/passes/cross-file-pass.js.map +1 -0
  53. package/dist/analysis/passes/dead-code-pass.d.ts +25 -0
  54. package/dist/analysis/passes/dead-code-pass.js +117 -0
  55. package/dist/analysis/passes/dead-code-pass.js.map +1 -0
  56. package/dist/analysis/passes/dependency-fan-out-pass.d.ts +19 -0
  57. package/dist/analysis/passes/dependency-fan-out-pass.js +35 -0
  58. package/dist/analysis/passes/dependency-fan-out-pass.js.map +1 -0
  59. package/dist/analysis/passes/interprocedural-pass.d.ts +29 -0
  60. package/dist/analysis/passes/interprocedural-pass.js +169 -0
  61. package/dist/analysis/passes/interprocedural-pass.js.map +1 -0
  62. package/dist/analysis/passes/language-sources-pass.d.ts +76 -0
  63. package/dist/analysis/passes/language-sources-pass.js +491 -0
  64. package/dist/analysis/passes/language-sources-pass.js.map +1 -0
  65. package/dist/analysis/passes/leaked-global-pass.d.ts +34 -0
  66. package/dist/analysis/passes/leaked-global-pass.js +108 -0
  67. package/dist/analysis/passes/leaked-global-pass.js.map +1 -0
  68. package/dist/analysis/passes/missing-await-pass.d.ts +29 -0
  69. package/dist/analysis/passes/missing-await-pass.js +90 -0
  70. package/dist/analysis/passes/missing-await-pass.js.map +1 -0
  71. package/dist/analysis/passes/missing-public-doc-pass.d.ts +35 -0
  72. package/dist/analysis/passes/missing-public-doc-pass.js +148 -0
  73. package/dist/analysis/passes/missing-public-doc-pass.js.map +1 -0
  74. package/dist/analysis/passes/n-plus-one-pass.d.ts +29 -0
  75. package/dist/analysis/passes/n-plus-one-pass.js +100 -0
  76. package/dist/analysis/passes/n-plus-one-pass.js.map +1 -0
  77. package/dist/analysis/passes/null-deref-pass.d.ts +32 -0
  78. package/dist/analysis/passes/null-deref-pass.js +130 -0
  79. package/dist/analysis/passes/null-deref-pass.js.map +1 -0
  80. package/dist/analysis/passes/orphan-module-pass.d.ts +21 -0
  81. package/dist/analysis/passes/orphan-module-pass.js +38 -0
  82. package/dist/analysis/passes/orphan-module-pass.js.map +1 -0
  83. package/dist/analysis/passes/resource-leak-pass.d.ts +43 -0
  84. package/dist/analysis/passes/resource-leak-pass.js +156 -0
  85. package/dist/analysis/passes/resource-leak-pass.js.map +1 -0
  86. package/dist/analysis/passes/sink-filter-pass.d.ts +39 -0
  87. package/dist/analysis/passes/sink-filter-pass.js +231 -0
  88. package/dist/analysis/passes/sink-filter-pass.js.map +1 -0
  89. package/dist/analysis/passes/stale-doc-ref-pass.d.ts +21 -0
  90. package/dist/analysis/passes/stale-doc-ref-pass.js +96 -0
  91. package/dist/analysis/passes/stale-doc-ref-pass.js.map +1 -0
  92. package/dist/analysis/passes/string-concat-loop-pass.d.ts +26 -0
  93. package/dist/analysis/passes/string-concat-loop-pass.js +87 -0
  94. package/dist/analysis/passes/string-concat-loop-pass.js.map +1 -0
  95. package/dist/analysis/passes/sync-io-async-pass.d.ts +28 -0
  96. package/dist/analysis/passes/sync-io-async-pass.js +80 -0
  97. package/dist/analysis/passes/sync-io-async-pass.js.map +1 -0
  98. package/dist/analysis/passes/taint-matcher-pass.d.ts +24 -0
  99. package/dist/analysis/passes/taint-matcher-pass.js +71 -0
  100. package/dist/analysis/passes/taint-matcher-pass.js.map +1 -0
  101. package/dist/analysis/passes/taint-propagation-pass.d.ts +22 -0
  102. package/dist/analysis/passes/taint-propagation-pass.js +266 -0
  103. package/dist/analysis/passes/taint-propagation-pass.js.map +1 -0
  104. package/dist/analysis/passes/todo-in-prod-pass.d.ts +28 -0
  105. package/dist/analysis/passes/todo-in-prod-pass.js +71 -0
  106. package/dist/analysis/passes/todo-in-prod-pass.js.map +1 -0
  107. package/dist/analysis/passes/unchecked-return-pass.d.ts +34 -0
  108. package/dist/analysis/passes/unchecked-return-pass.js +106 -0
  109. package/dist/analysis/passes/unchecked-return-pass.js.map +1 -0
  110. package/dist/analysis/passes/unused-variable-pass.d.ts +36 -0
  111. package/dist/analysis/passes/unused-variable-pass.js +150 -0
  112. package/dist/analysis/passes/unused-variable-pass.js.map +1 -0
  113. package/dist/analysis/passes/variable-shadowing-pass.d.ts +41 -0
  114. package/dist/analysis/passes/variable-shadowing-pass.js +211 -0
  115. package/dist/analysis/passes/variable-shadowing-pass.js.map +1 -0
  116. package/dist/analysis/path-finder.d.ts +3 -13
  117. package/dist/analysis/path-finder.js +48 -63
  118. package/dist/analysis/path-finder.js.map +1 -1
  119. package/dist/analysis/taint-matcher.js +8 -1
  120. package/dist/analysis/taint-matcher.js.map +1 -1
  121. package/dist/analysis/taint-propagation.d.ts +5 -1
  122. package/dist/analysis/taint-propagation.js +44 -41
  123. package/dist/analysis/taint-propagation.js.map +1 -1
  124. package/dist/analyzer.d.ts +42 -1
  125. package/dist/analyzer.js +234 -1476
  126. package/dist/analyzer.js.map +1 -1
  127. package/dist/browser/circle-ir.js +3413 -1271
  128. package/dist/core/circle-ir-core.cjs +360 -106
  129. package/dist/core/circle-ir-core.js +360 -106
  130. package/dist/core/extractors/imports.js +18 -0
  131. package/dist/core/extractors/imports.js.map +1 -1
  132. package/dist/graph/analysis-pass.d.ts +68 -0
  133. package/dist/graph/analysis-pass.js +51 -0
  134. package/dist/graph/analysis-pass.js.map +1 -0
  135. package/dist/graph/code-graph.d.ts +92 -0
  136. package/dist/graph/code-graph.js +262 -0
  137. package/dist/graph/code-graph.js.map +1 -0
  138. package/dist/graph/import-graph.d.ts +33 -0
  139. package/dist/graph/import-graph.js +170 -0
  140. package/dist/graph/import-graph.js.map +1 -0
  141. package/dist/graph/index.d.ts +4 -0
  142. package/dist/graph/index.js +5 -0
  143. package/dist/graph/index.js.map +1 -0
  144. package/dist/graph/project-graph.d.ts +43 -0
  145. package/dist/graph/project-graph.js +80 -0
  146. package/dist/graph/project-graph.js.map +1 -0
  147. package/dist/graph/scope-graph.d.ts +63 -0
  148. package/dist/graph/scope-graph.js +89 -0
  149. package/dist/graph/scope-graph.js.map +1 -0
  150. package/dist/index.d.ts +2 -2
  151. package/dist/index.js +1 -1
  152. package/dist/index.js.map +1 -1
  153. package/dist/resolution/cross-file.js +52 -19
  154. package/dist/resolution/cross-file.js.map +1 -1
  155. package/dist/types/index.d.ts +151 -0
  156. package/docs/SPEC.md +10 -6
  157. package/package.json +1 -1
@@ -0,0 +1,231 @@
1
+ /**
2
+ * SinkFilterPass
3
+ *
4
+ * Applies the four-stage sink filtering pipeline to eliminate false positives,
5
+ * followed by language-specific XPath/XSS suppression.
6
+ *
7
+ * Filter stages (applied in order):
8
+ * 1. Dead code — remove sinks on unreachable lines
9
+ * 2. Clean array elements — strong updates via constant propagation
10
+ * 3. Clean variables — arguments proven non-tainted by constant propagation
11
+ * 4. Sanitized sinks — sinks wrapped by a recognised sanitizer call
12
+ * 5. Python XPath FP reduction
13
+ * 6. JavaScript XSS FP reduction
14
+ *
15
+ * Depends on: taint-matcher, constant-propagation, language-sources
16
+ */
17
+ import { JS_TAINTED_PATTERNS } from './language-sources-pass.js';
18
+ export class SinkFilterPass {
19
+ name = 'sink-filter';
20
+ category = 'security';
21
+ run(ctx) {
22
+ const { graph, language } = ctx;
23
+ const { calls, dfg } = graph.ir;
24
+ const taintMatcher = ctx.getResult('taint-matcher');
25
+ const constProp = ctx.getResult('constant-propagation');
26
+ const langSources = ctx.getResult('language-sources');
27
+ // Merge sources and sinks from both upstream passes.
28
+ const sources = [...taintMatcher.sources, ...langSources.additionalSources];
29
+ // Build merged sinks, deduplicating JS DOM sinks that may overlap with config sinks.
30
+ const sinks = [...taintMatcher.sinks];
31
+ for (const s of langSources.additionalSinks) {
32
+ if (!sinks.some(x => x.line === s.line && x.cwe === s.cwe && x.type === s.type)) {
33
+ sinks.push(s);
34
+ }
35
+ }
36
+ const sanitizers = taintMatcher.sanitizers;
37
+ // Stage 1 — dead code
38
+ let filtered = sinks.filter(sink => !constProp.unreachableLines.has(sink.line));
39
+ // Stage 2 — clean array elements
40
+ filtered = filterCleanArraySinks(filtered, calls, constProp.taintedArrayElements, constProp.symbols);
41
+ // Stage 3 — clean variables
42
+ filtered = filterCleanVariableSinks(filtered, calls, constProp.tainted, constProp.symbols, dfg, constProp.sanitizedVars, constProp.synchronizedLines);
43
+ // Stage 4 — sanitized sinks
44
+ filtered = filterSanitizedSinks(filtered, sanitizers, calls);
45
+ // Stage 5 — Python XPath FP reduction
46
+ if (language === 'python') {
47
+ const { pyTaintedVars, pySanitizedVars } = langSources;
48
+ const sourceLines = ctx.code.split('\n');
49
+ filtered = filtered.filter(sink => {
50
+ if (sink.type !== 'xpath_injection')
51
+ return true;
52
+ const sinkLineText = sourceLines[sink.line - 1] ?? '';
53
+ const taintedVarOnLine = [...pyTaintedVars.keys()].find(v => new RegExp(`\\b${v}\\b`).test(sinkLineText));
54
+ if (!taintedVarOnLine)
55
+ return false;
56
+ if (pySanitizedVars.has(taintedVarOnLine))
57
+ return false;
58
+ if (new RegExp(`\\.xpath\\s*\\([^)]*\\b\\w+\\s*=\\s*\\b${taintedVarOnLine}\\b`).test(sinkLineText))
59
+ return false;
60
+ return true;
61
+ });
62
+ }
63
+ // Stage 6 — JavaScript XSS FP reduction
64
+ if (['javascript', 'typescript'].includes(language)) {
65
+ const { jsTaintedVars } = langSources;
66
+ if (jsTaintedVars.size > 0) {
67
+ const sourceLines = ctx.code.split('\n');
68
+ filtered = filtered.filter(sink => {
69
+ if (sink.type !== 'xss')
70
+ return true;
71
+ const sinkLineText = sourceLines[sink.line - 1] ?? '';
72
+ if ([...jsTaintedVars.keys()].some(v => new RegExp(`\\b${v}\\b`).test(sinkLineText)))
73
+ return true;
74
+ if (JS_TAINTED_PATTERNS.some(p => p.pattern.test(sinkLineText)))
75
+ return true;
76
+ return false;
77
+ });
78
+ }
79
+ }
80
+ return { sources, sinks: filtered, sanitizers };
81
+ }
82
+ }
83
+ function evaluateSimpleExpression(expr, symbols) {
84
+ let evaluated = expr;
85
+ for (const [name, val] of symbols) {
86
+ if (val.type === 'int' || val.type === 'float') {
87
+ const regex = new RegExp(`\\b${name}\\b`, 'g');
88
+ evaluated = evaluated.replace(regex, String(val.value));
89
+ }
90
+ }
91
+ try {
92
+ if (/^[\d\s+\-*/().]+$/.test(evaluated)) {
93
+ const result = Function('"use strict"; return (' + evaluated + ')')();
94
+ if (typeof result === 'number' && !isNaN(result))
95
+ return String(Math.floor(result));
96
+ }
97
+ }
98
+ catch { /* evaluation failed */ }
99
+ return expr;
100
+ }
101
+ function isStringLiteralExpression(expr) {
102
+ const trimmed = expr.trim();
103
+ return (trimmed.startsWith('"') && trimmed.endsWith('"')) ||
104
+ (trimmed.startsWith("'") && trimmed.endsWith("'"));
105
+ }
106
+ function filterCleanArraySinks(sinks, calls, taintedArrayElements, symbols) {
107
+ const callsByLine = new Map();
108
+ for (const call of calls) {
109
+ const existing = callsByLine.get(call.location.line) ?? [];
110
+ existing.push(call);
111
+ callsByLine.set(call.location.line, existing);
112
+ }
113
+ return sinks.filter(sink => {
114
+ const callsAtSink = callsByLine.get(sink.line) ?? [];
115
+ for (const call of callsAtSink) {
116
+ for (const arg of call.arguments) {
117
+ const arrayAccessMatch = arg.expression?.match(/^(\w+)\[(\d+|[^[\]]+)\]$/);
118
+ if (arrayAccessMatch) {
119
+ const arrayName = arrayAccessMatch[1];
120
+ let indexStr = arrayAccessMatch[2];
121
+ indexStr = evaluateSimpleExpression(indexStr, symbols);
122
+ const taintedIndices = taintedArrayElements.get(arrayName);
123
+ if (taintedIndices !== undefined) {
124
+ const isTainted = taintedIndices.has(indexStr) || taintedIndices.has('*');
125
+ if (!isTainted)
126
+ return false;
127
+ }
128
+ }
129
+ }
130
+ }
131
+ return true;
132
+ });
133
+ }
134
+ export function filterCleanVariableSinks(sinks, calls, taintedVars, symbols, dfg, sanitizedVars, synchronizedLines) {
135
+ const fieldNames = new Set();
136
+ if (dfg) {
137
+ for (const def of dfg.defs) {
138
+ if (def.kind === 'field')
139
+ fieldNames.add(def.variable);
140
+ }
141
+ }
142
+ const callsByLine = new Map();
143
+ for (const call of calls) {
144
+ const existing = callsByLine.get(call.location.line) ?? [];
145
+ existing.push(call);
146
+ callsByLine.set(call.location.line, existing);
147
+ }
148
+ return sinks.filter(sink => {
149
+ const callsAtSink = callsByLine.get(sink.line) ?? [];
150
+ const isInSynchronizedBlock = synchronizedLines?.has(sink.line) ?? false;
151
+ for (const call of callsAtSink) {
152
+ let allArgsAreClean = true;
153
+ const methodName = call.in_method;
154
+ for (const arg of call.arguments) {
155
+ if (arg.variable && !arg.expression?.includes('[')) {
156
+ const varName = arg.variable;
157
+ const scopedName = methodName ? `${methodName}:${varName}` : varName;
158
+ if (fieldNames.has(varName) && !isInSynchronizedBlock) {
159
+ allArgsAreClean = false;
160
+ continue;
161
+ }
162
+ if (sanitizedVars?.has(scopedName) || sanitizedVars?.has(varName))
163
+ continue;
164
+ if (taintedVars.has(scopedName) || taintedVars.has(varName)) {
165
+ allArgsAreClean = false;
166
+ continue;
167
+ }
168
+ const symbolValue = symbols.get(scopedName) ?? symbols.get(varName);
169
+ if (symbolValue && symbolValue.type !== 'unknown')
170
+ continue;
171
+ allArgsAreClean = false;
172
+ }
173
+ else {
174
+ if (arg.literal != null)
175
+ continue;
176
+ if (arg.expression && !arg.variable && isStringLiteralExpression(arg.expression))
177
+ continue;
178
+ allArgsAreClean = false;
179
+ }
180
+ }
181
+ if (allArgsAreClean && call.arguments.length > 0)
182
+ return false;
183
+ }
184
+ return true;
185
+ });
186
+ }
187
+ export function filterSanitizedSinks(sinks, sanitizers, calls) {
188
+ if (!sanitizers || sanitizers.length === 0)
189
+ return sinks;
190
+ const sanitizersByLine = new Map();
191
+ for (const san of sanitizers) {
192
+ const existing = sanitizersByLine.get(san.line) ?? [];
193
+ existing.push(san);
194
+ sanitizersByLine.set(san.line, existing);
195
+ }
196
+ const callsByLine = new Map();
197
+ for (const call of calls) {
198
+ const existing = callsByLine.get(call.location.line) ?? [];
199
+ existing.push(call);
200
+ callsByLine.set(call.location.line, existing);
201
+ }
202
+ return sinks.filter(sink => {
203
+ const lineSanitizers = sanitizersByLine.get(sink.line);
204
+ if (!lineSanitizers || lineSanitizers.length === 0)
205
+ return true;
206
+ for (const san of lineSanitizers) {
207
+ if (san.sanitizes.includes(sink.type)) {
208
+ const lineCalls = callsByLine.get(sink.line) ?? [];
209
+ for (const call of lineCalls) {
210
+ for (const arg of call.arguments) {
211
+ const expr = arg.expression || '';
212
+ const sanMethodMatch = san.method.match(/(?:(\w+)\.)?(\w+)\(\)/);
213
+ if (sanMethodMatch) {
214
+ const sanMethodName = sanMethodMatch[2];
215
+ const sanClassName = sanMethodMatch[1];
216
+ if (sanClassName) {
217
+ if (expr.includes(`${sanClassName}.${sanMethodName}(`))
218
+ return false;
219
+ }
220
+ else if (expr.includes(`${sanMethodName}(`)) {
221
+ return false;
222
+ }
223
+ }
224
+ }
225
+ }
226
+ }
227
+ }
228
+ return true;
229
+ });
230
+ }
231
+ //# sourceMappingURL=sink-filter-pass.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sink-filter-pass.js","sourceRoot":"","sources":["../../../src/analysis/passes/sink-filter-pass.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Pass #33: stale-doc-ref
3
+ *
4
+ * Flags doc comment references ({@link ClassName} and @see ClassName) that
5
+ * point to symbols not found in the file's type declarations or imports.
6
+ * Stale doc refs cause confusion and erode documentation trustworthiness.
7
+ *
8
+ * Category: maintainability | Severity: low | Level: note | CWE: none
9
+ */
10
+ import type { AnalysisPass, PassContext } from '../../graph/analysis-pass.js';
11
+ export interface StaleDocRefResult {
12
+ staleRefs: Array<{
13
+ line: number;
14
+ ref: string;
15
+ }>;
16
+ }
17
+ export declare class StaleDocRefPass implements AnalysisPass<StaleDocRefResult> {
18
+ readonly name = "stale-doc-ref";
19
+ readonly category: "maintainability";
20
+ run(ctx: PassContext): StaleDocRefResult;
21
+ }
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Pass #33: stale-doc-ref
3
+ *
4
+ * Flags doc comment references ({@link ClassName} and @see ClassName) that
5
+ * point to symbols not found in the file's type declarations or imports.
6
+ * Stale doc refs cause confusion and erode documentation trustworthiness.
7
+ *
8
+ * Category: maintainability | Severity: low | Level: note | CWE: none
9
+ */
10
+ // Matches /** ... */ blocks (non-greedy, multiline)
11
+ const DOC_BLOCK_RE = /\/\*\*([\s\S]*?)\*\//g;
12
+ // Matches {@link Foo.Bar#method} or {@link Foo}
13
+ const LINK_RE = /\{@link\s+([\w.#]+)/g;
14
+ // Matches @see Foo.Bar or @see Foo
15
+ const SEE_RE = /@see\s+([\w.#]+)/g;
16
+ /**
17
+ * Normalize a symbol reference: strip method fragment (#method) and
18
+ * take the last dot-separated segment (so "java.util.List" → "List").
19
+ */
20
+ function normalizeRef(raw) {
21
+ const withoutMethod = raw.split('#')[0];
22
+ const parts = withoutMethod.split('.');
23
+ return parts[parts.length - 1] ?? raw;
24
+ }
25
+ /**
26
+ * Return the 1-based line number of the start of a match within `code`.
27
+ */
28
+ function lineOfIndex(code, index) {
29
+ let line = 1;
30
+ for (let i = 0; i < index && i < code.length; i++) {
31
+ if (code[i] === '\n')
32
+ line++;
33
+ }
34
+ return line;
35
+ }
36
+ export class StaleDocRefPass {
37
+ name = 'stale-doc-ref';
38
+ category = 'maintainability';
39
+ run(ctx) {
40
+ const staleRefs = [];
41
+ // Build known-symbol set from types + imports
42
+ const knownSymbols = new Set();
43
+ for (const t of ctx.graph.ir.types) {
44
+ knownSymbols.add(t.name);
45
+ }
46
+ for (const imp of ctx.graph.ir.imports) {
47
+ if (imp.imported_name && imp.imported_name !== '*' && imp.imported_name !== 'default') {
48
+ knownSymbols.add(imp.imported_name);
49
+ }
50
+ if (imp.alias) {
51
+ knownSymbols.add(imp.alias);
52
+ }
53
+ }
54
+ const code = ctx.code;
55
+ DOC_BLOCK_RE.lastIndex = 0;
56
+ let blockMatch;
57
+ while ((blockMatch = DOC_BLOCK_RE.exec(code)) !== null) {
58
+ const blockStart = blockMatch.index;
59
+ const blockText = blockMatch[0];
60
+ // Extract all refs from the block
61
+ const refs = [];
62
+ LINK_RE.lastIndex = 0;
63
+ let m;
64
+ while ((m = LINK_RE.exec(blockText)) !== null) {
65
+ refs.push({ raw: m[1], offsetInBlock: m.index });
66
+ }
67
+ SEE_RE.lastIndex = 0;
68
+ while ((m = SEE_RE.exec(blockText)) !== null) {
69
+ refs.push({ raw: m[1], offsetInBlock: m.index });
70
+ }
71
+ for (const { raw, offsetInBlock } of refs) {
72
+ const normalized = normalizeRef(raw);
73
+ if (!knownSymbols.has(normalized)) {
74
+ const absIdx = blockStart + offsetInBlock;
75
+ const line = lineOfIndex(code, absIdx);
76
+ staleRefs.push({ line, ref: normalized });
77
+ const finding = {
78
+ id: `stale-doc-ref-${ctx.graph.ir.meta.file.replace(/[^a-z0-9]/gi, '-')}-${line}`,
79
+ pass: 'stale-doc-ref',
80
+ category: 'maintainability',
81
+ rule_id: 'stale-doc-ref',
82
+ severity: 'low',
83
+ level: 'note',
84
+ message: `Doc comment references unknown symbol '${normalized}'. Update or remove the stale reference.`,
85
+ file: ctx.graph.ir.meta.file,
86
+ line,
87
+ evidence: { ref: normalized, raw },
88
+ };
89
+ ctx.addFinding(finding);
90
+ }
91
+ }
92
+ }
93
+ return { staleRefs };
94
+ }
95
+ }
96
+ //# sourceMappingURL=stale-doc-ref-pass.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stale-doc-ref-pass.js","sourceRoot":"","sources":["../../../src/analysis/passes/stale-doc-ref-pass.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Pass #50: string-concat-loop (CWE-1046, category: performance)
3
+ *
4
+ * Detects string concatenation using `+=` inside loop bodies, which creates
5
+ * O(n²) string allocations. Each iteration copies the entire accumulated
6
+ * string, making this a common performance anti-pattern.
7
+ *
8
+ * Detection strategy:
9
+ * 1. Identify loop body line ranges via CFG back-edges (graph.loopBodies()).
10
+ * 2. For each line within a loop body, scan for `identifier +=` pattern.
11
+ * 3. Filter out obvious numeric variable names (i, count, sum, etc.) and
12
+ * numeric-looking RHS literals to avoid FP on arithmetic accumulation.
13
+ */
14
+ import type { AnalysisPass, PassContext } from '../../graph/analysis-pass.js';
15
+ export interface StringConcatLoopResult {
16
+ /** `+=` expressions inside loop bodies that are likely string concatenation. */
17
+ concatInLoops: Array<{
18
+ line: number;
19
+ variable: string;
20
+ }>;
21
+ }
22
+ export declare class StringConcatLoopPass implements AnalysisPass<StringConcatLoopResult> {
23
+ readonly name = "string-concat-loop";
24
+ readonly category: "performance";
25
+ run(ctx: PassContext): StringConcatLoopResult;
26
+ }
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Pass #50: string-concat-loop (CWE-1046, category: performance)
3
+ *
4
+ * Detects string concatenation using `+=` inside loop bodies, which creates
5
+ * O(n²) string allocations. Each iteration copies the entire accumulated
6
+ * string, making this a common performance anti-pattern.
7
+ *
8
+ * Detection strategy:
9
+ * 1. Identify loop body line ranges via CFG back-edges (graph.loopBodies()).
10
+ * 2. For each line within a loop body, scan for `identifier +=` pattern.
11
+ * 3. Filter out obvious numeric variable names (i, count, sum, etc.) and
12
+ * numeric-looking RHS literals to avoid FP on arithmetic accumulation.
13
+ */
14
+ /** Matches `varName +=` at a token boundary. Group 1 = variable name. */
15
+ const CONCAT_RE = /\b([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\+=/;
16
+ /**
17
+ * Variable names that almost certainly hold numeric values.
18
+ * These can safely be skipped even if they appear in `+=` expressions.
19
+ */
20
+ const NUMERIC_VAR_RE = /^(i|j|k|n|m|x|y|z|count|sum|total|size|index|len|length|num|idx|score|counter|offset|pos|col|row|result|ret|value|acc|bits|byte|bytes|flag|flags|step|delta|diff|dist|min|max|avg|mean|page|line|err|error)$/i;
21
+ /** Variable name suffixes that strongly suggest numeric accumulation. */
22
+ const NUMERIC_SUFFIX_RE = /(Count|Sum|Total|Size|Index|Length|Offset|Position|Score|Counter|Num|Amount|Val|Idx|Len|Max|Min|Avg|Delta|Diff|Step|Flag|Flags|Bits|Byte|Bytes|Calls|Items|Nodes|Edges|Blocks|Lines|Chars|Entries|Records|Rows)$/;
23
+ /**
24
+ * Matches a right-hand side that starts with a digit or decimal point —
25
+ * these are numeric literals so the `+=` is arithmetic, not string concat.
26
+ */
27
+ const NUMERIC_RHS_RE = /^\s*[\d.]/;
28
+ export class StringConcatLoopPass {
29
+ name = 'string-concat-loop';
30
+ category = 'performance';
31
+ run(ctx) {
32
+ const { graph, code } = ctx;
33
+ const file = graph.ir.meta.file;
34
+ const loops = graph.loopBodies();
35
+ if (loops.length === 0)
36
+ return { concatInLoops: [] };
37
+ const codeLines = code.split('\n');
38
+ const concatInLoops = [];
39
+ const reported = new Set(); // one finding per line
40
+ for (const loop of loops) {
41
+ for (let ln = loop.start_line; ln <= loop.end_line; ln++) {
42
+ if (reported.has(ln))
43
+ continue;
44
+ const src = codeLines[ln - 1] ?? '';
45
+ const match = CONCAT_RE.exec(src);
46
+ if (!match)
47
+ continue;
48
+ const varName = match[1];
49
+ // Skip obviously-numeric variable names
50
+ if (NUMERIC_VAR_RE.test(varName))
51
+ continue;
52
+ if (NUMERIC_SUFFIX_RE.test(varName))
53
+ continue;
54
+ // Skip if the RHS after `+=` starts with a digit/decimal (numeric literal)
55
+ const opIdx = src.indexOf('+=');
56
+ const afterOp = opIdx >= 0 ? src.slice(opIdx + 2) : '';
57
+ if (NUMERIC_RHS_RE.test(afterOp))
58
+ continue;
59
+ concatInLoops.push({ line: ln, variable: varName });
60
+ reported.add(ln);
61
+ ctx.addFinding({
62
+ id: `string-concat-loop-${file}-${ln}`,
63
+ pass: this.name,
64
+ category: this.category,
65
+ rule_id: this.name,
66
+ cwe: 'CWE-1046',
67
+ severity: 'low',
68
+ level: 'warning',
69
+ message: `String concatenation with '+=' inside a loop: '${varName}' grows O(n²). ` +
70
+ `Each iteration copies the entire accumulated string.`,
71
+ file,
72
+ line: ln,
73
+ snippet: src.trim(),
74
+ fix: `Accumulate parts in an array and join() after the loop, ` +
75
+ `or use StringBuilder (Java) / StringJoiner`,
76
+ evidence: {
77
+ variable: varName,
78
+ loop_start: loop.start_line,
79
+ loop_end: loop.end_line,
80
+ },
81
+ });
82
+ }
83
+ }
84
+ return { concatInLoops };
85
+ }
86
+ }
87
+ //# sourceMappingURL=string-concat-loop-pass.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"string-concat-loop-pass.js","sourceRoot":"","sources":["../../../src/analysis/passes/string-concat-loop-pass.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Pass #48: sync-io-async (CWE-1050, category: performance)
3
+ *
4
+ * Detects synchronous (blocking) I/O calls made inside async functions in
5
+ * JavaScript/TypeScript. Blocking calls stall the Node.js event loop,
6
+ * negating the benefits of async/await and starving other concurrent work.
7
+ *
8
+ * Detection strategy:
9
+ * 1. Collect async method line ranges from types[].methods where
10
+ * modifiers include 'async'.
11
+ * 2. For each call site within an async range, check if the method name
12
+ * ends in 'Sync' (Node.js blocking variants) or is in the curated
13
+ * BLOCKING_METHODS set.
14
+ */
15
+ import type { AnalysisPass, PassContext } from '../../graph/analysis-pass.js';
16
+ export interface SyncIoAsyncResult {
17
+ /** Blocking calls found inside async functions. */
18
+ blockingInAsyncFns: Array<{
19
+ line: number;
20
+ method: string;
21
+ enclosingMethod: string;
22
+ }>;
23
+ }
24
+ export declare class SyncIoAsyncPass implements AnalysisPass<SyncIoAsyncResult> {
25
+ readonly name = "sync-io-async";
26
+ readonly category: "performance";
27
+ run(ctx: PassContext): SyncIoAsyncResult;
28
+ }
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Pass #48: sync-io-async (CWE-1050, category: performance)
3
+ *
4
+ * Detects synchronous (blocking) I/O calls made inside async functions in
5
+ * JavaScript/TypeScript. Blocking calls stall the Node.js event loop,
6
+ * negating the benefits of async/await and starving other concurrent work.
7
+ *
8
+ * Detection strategy:
9
+ * 1. Collect async method line ranges from types[].methods where
10
+ * modifiers include 'async'.
11
+ * 2. For each call site within an async range, check if the method name
12
+ * ends in 'Sync' (Node.js blocking variants) or is in the curated
13
+ * BLOCKING_METHODS set.
14
+ */
15
+ /**
16
+ * Methods that are always blocking regardless of name convention.
17
+ * Kept intentionally small — only add names with virtually no async usage.
18
+ */
19
+ const BLOCKING_METHODS = new Set([
20
+ 'sleep', // Python time.sleep / any sync sleep utility
21
+ ]);
22
+ /** Any method whose name ends in 'Sync' is a blocking Node.js API variant. */
23
+ const SYNC_SUFFIX_RE = /Sync$/;
24
+ export class SyncIoAsyncPass {
25
+ name = 'sync-io-async';
26
+ category = 'performance';
27
+ run(ctx) {
28
+ const { graph, language } = ctx;
29
+ // Only relevant for JS/TS (and Python for sleep)
30
+ if (language !== 'javascript' && language !== 'typescript' && language !== 'python') {
31
+ return { blockingInAsyncFns: [] };
32
+ }
33
+ const file = graph.ir.meta.file;
34
+ // Collect async method line ranges
35
+ const asyncRanges = [];
36
+ for (const type of graph.ir.types) {
37
+ for (const method of type.methods) {
38
+ if (method.modifiers.includes('async')) {
39
+ asyncRanges.push({
40
+ start: method.start_line,
41
+ end: method.end_line,
42
+ name: method.name,
43
+ });
44
+ }
45
+ }
46
+ }
47
+ if (asyncRanges.length === 0)
48
+ return { blockingInAsyncFns: [] };
49
+ const blockingInAsyncFns = [];
50
+ for (const call of graph.ir.calls) {
51
+ const name = call.method_name;
52
+ if (!SYNC_SUFFIX_RE.test(name) && !BLOCKING_METHODS.has(name))
53
+ continue;
54
+ const line = call.location.line;
55
+ const range = asyncRanges.find(r => line >= r.start && line <= r.end);
56
+ if (!range)
57
+ continue;
58
+ blockingInAsyncFns.push({ line, method: name, enclosingMethod: range.name });
59
+ ctx.addFinding({
60
+ id: `sync-io-async-${file}-${line}`,
61
+ pass: this.name,
62
+ category: this.category,
63
+ rule_id: this.name,
64
+ cwe: 'CWE-1050',
65
+ severity: 'medium',
66
+ level: 'warning',
67
+ message: `Blocking call \`${name}()\` inside async function '${range.name}' stalls the event loop`,
68
+ file,
69
+ line,
70
+ fix: `Replace \`${name}\` with its async equivalent and await the result`,
71
+ evidence: {
72
+ blocking_method: name,
73
+ async_method: range.name,
74
+ },
75
+ });
76
+ }
77
+ return { blockingInAsyncFns };
78
+ }
79
+ }
80
+ //# sourceMappingURL=sync-io-async-pass.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync-io-async-pass.js","sourceRoot":"","sources":["../../../src/analysis/passes/sync-io-async-pass.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * TaintMatcherPass
3
+ *
4
+ * First pass in the analysis pipeline. Merges language-plugin built-in
5
+ * sources/sinks into the config, runs config-based taint matching, and
6
+ * extracts @sanitizer-annotated method names from type declarations.
7
+ */
8
+ import type { TaintSource, TaintSink, TaintSanitizer } from '../../types/index.js';
9
+ import type { TaintConfig } from '../../types/config.js';
10
+ import type { AnalysisPass, PassContext } from '../../graph/analysis-pass.js';
11
+ export interface TaintMatcherResult {
12
+ sources: TaintSource[];
13
+ sinks: TaintSink[];
14
+ sanitizers: TaintSanitizer[];
15
+ /** Method names annotated with @sanitizer (for ConstantPropagationPass). */
16
+ sanitizerMethods: string[];
17
+ /** Final merged config (with plugin extensions applied). */
18
+ config: TaintConfig;
19
+ }
20
+ export declare class TaintMatcherPass implements AnalysisPass<TaintMatcherResult> {
21
+ readonly name = "taint-matcher";
22
+ readonly category: "security";
23
+ run(ctx: PassContext): TaintMatcherResult;
24
+ }