circle-ir 3.8.3 → 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 (159) hide show
  1. package/README.md +82 -5
  2. package/dist/analysis/config-loader.js +1 -1
  3. package/dist/analysis/config-loader.js.map +1 -1
  4. package/dist/analysis/dfg-verifier.d.ts +3 -14
  5. package/dist/analysis/dfg-verifier.js +43 -74
  6. package/dist/analysis/dfg-verifier.js.map +1 -1
  7. package/dist/analysis/interprocedural.d.ts +5 -1
  8. package/dist/analysis/interprocedural.js +62 -60
  9. package/dist/analysis/interprocedural.js.map +1 -1
  10. package/dist/analysis/metrics/index.d.ts +2 -0
  11. package/dist/analysis/metrics/index.js +2 -0
  12. package/dist/analysis/metrics/index.js.map +1 -0
  13. package/dist/analysis/metrics/metric-pass.d.ts +27 -0
  14. package/dist/analysis/metrics/metric-pass.js +2 -0
  15. package/dist/analysis/metrics/metric-pass.js.map +1 -0
  16. package/dist/analysis/metrics/metric-runner.d.ts +21 -0
  17. package/dist/analysis/metrics/metric-runner.js +47 -0
  18. package/dist/analysis/metrics/metric-runner.js.map +1 -0
  19. package/dist/analysis/metrics/passes/cohesion-metrics-pass.d.ts +21 -0
  20. package/dist/analysis/metrics/passes/cohesion-metrics-pass.js +100 -0
  21. package/dist/analysis/metrics/passes/cohesion-metrics-pass.js.map +1 -0
  22. package/dist/analysis/metrics/passes/complexity-metrics-pass.d.ts +15 -0
  23. package/dist/analysis/metrics/passes/complexity-metrics-pass.js +76 -0
  24. package/dist/analysis/metrics/passes/complexity-metrics-pass.js.map +1 -0
  25. package/dist/analysis/metrics/passes/composite-metrics-pass.d.ts +17 -0
  26. package/dist/analysis/metrics/passes/composite-metrics-pass.js +77 -0
  27. package/dist/analysis/metrics/passes/composite-metrics-pass.js.map +1 -0
  28. package/dist/analysis/metrics/passes/coupling-metrics-pass.d.ts +19 -0
  29. package/dist/analysis/metrics/passes/coupling-metrics-pass.js +94 -0
  30. package/dist/analysis/metrics/passes/coupling-metrics-pass.js.map +1 -0
  31. package/dist/analysis/metrics/passes/data-flow-metrics-pass.d.ts +14 -0
  32. package/dist/analysis/metrics/passes/data-flow-metrics-pass.js +25 -0
  33. package/dist/analysis/metrics/passes/data-flow-metrics-pass.js.map +1 -0
  34. package/dist/analysis/metrics/passes/documentation-metrics-pass.d.ts +15 -0
  35. package/dist/analysis/metrics/passes/documentation-metrics-pass.js +64 -0
  36. package/dist/analysis/metrics/passes/documentation-metrics-pass.js.map +1 -0
  37. package/dist/analysis/metrics/passes/halstead-metrics-pass.d.ts +16 -0
  38. package/dist/analysis/metrics/passes/halstead-metrics-pass.js +95 -0
  39. package/dist/analysis/metrics/passes/halstead-metrics-pass.js.map +1 -0
  40. package/dist/analysis/metrics/passes/inheritance-metrics-pass.d.ts +18 -0
  41. package/dist/analysis/metrics/passes/inheritance-metrics-pass.js +73 -0
  42. package/dist/analysis/metrics/passes/inheritance-metrics-pass.js.map +1 -0
  43. package/dist/analysis/metrics/passes/size-metrics-pass.d.ts +11 -0
  44. package/dist/analysis/metrics/passes/size-metrics-pass.js +64 -0
  45. package/dist/analysis/metrics/passes/size-metrics-pass.js.map +1 -0
  46. package/dist/analysis/passes/circular-dependency-pass.d.ts +18 -0
  47. package/dist/analysis/passes/circular-dependency-pass.js +39 -0
  48. package/dist/analysis/passes/circular-dependency-pass.js.map +1 -0
  49. package/dist/analysis/passes/constant-propagation-pass.d.ts +22 -0
  50. package/dist/analysis/passes/constant-propagation-pass.js +44 -0
  51. package/dist/analysis/passes/constant-propagation-pass.js.map +1 -0
  52. package/dist/analysis/passes/cross-file-pass.d.ts +27 -0
  53. package/dist/analysis/passes/cross-file-pass.js +102 -0
  54. package/dist/analysis/passes/cross-file-pass.js.map +1 -0
  55. package/dist/analysis/passes/dead-code-pass.d.ts +25 -0
  56. package/dist/analysis/passes/dead-code-pass.js +117 -0
  57. package/dist/analysis/passes/dead-code-pass.js.map +1 -0
  58. package/dist/analysis/passes/dependency-fan-out-pass.d.ts +19 -0
  59. package/dist/analysis/passes/dependency-fan-out-pass.js +35 -0
  60. package/dist/analysis/passes/dependency-fan-out-pass.js.map +1 -0
  61. package/dist/analysis/passes/interprocedural-pass.d.ts +29 -0
  62. package/dist/analysis/passes/interprocedural-pass.js +169 -0
  63. package/dist/analysis/passes/interprocedural-pass.js.map +1 -0
  64. package/dist/analysis/passes/language-sources-pass.d.ts +76 -0
  65. package/dist/analysis/passes/language-sources-pass.js +491 -0
  66. package/dist/analysis/passes/language-sources-pass.js.map +1 -0
  67. package/dist/analysis/passes/leaked-global-pass.d.ts +34 -0
  68. package/dist/analysis/passes/leaked-global-pass.js +108 -0
  69. package/dist/analysis/passes/leaked-global-pass.js.map +1 -0
  70. package/dist/analysis/passes/missing-await-pass.d.ts +29 -0
  71. package/dist/analysis/passes/missing-await-pass.js +90 -0
  72. package/dist/analysis/passes/missing-await-pass.js.map +1 -0
  73. package/dist/analysis/passes/missing-public-doc-pass.d.ts +35 -0
  74. package/dist/analysis/passes/missing-public-doc-pass.js +148 -0
  75. package/dist/analysis/passes/missing-public-doc-pass.js.map +1 -0
  76. package/dist/analysis/passes/n-plus-one-pass.d.ts +29 -0
  77. package/dist/analysis/passes/n-plus-one-pass.js +100 -0
  78. package/dist/analysis/passes/n-plus-one-pass.js.map +1 -0
  79. package/dist/analysis/passes/null-deref-pass.d.ts +32 -0
  80. package/dist/analysis/passes/null-deref-pass.js +130 -0
  81. package/dist/analysis/passes/null-deref-pass.js.map +1 -0
  82. package/dist/analysis/passes/orphan-module-pass.d.ts +21 -0
  83. package/dist/analysis/passes/orphan-module-pass.js +38 -0
  84. package/dist/analysis/passes/orphan-module-pass.js.map +1 -0
  85. package/dist/analysis/passes/resource-leak-pass.d.ts +43 -0
  86. package/dist/analysis/passes/resource-leak-pass.js +156 -0
  87. package/dist/analysis/passes/resource-leak-pass.js.map +1 -0
  88. package/dist/analysis/passes/sink-filter-pass.d.ts +39 -0
  89. package/dist/analysis/passes/sink-filter-pass.js +231 -0
  90. package/dist/analysis/passes/sink-filter-pass.js.map +1 -0
  91. package/dist/analysis/passes/stale-doc-ref-pass.d.ts +21 -0
  92. package/dist/analysis/passes/stale-doc-ref-pass.js +96 -0
  93. package/dist/analysis/passes/stale-doc-ref-pass.js.map +1 -0
  94. package/dist/analysis/passes/string-concat-loop-pass.d.ts +26 -0
  95. package/dist/analysis/passes/string-concat-loop-pass.js +87 -0
  96. package/dist/analysis/passes/string-concat-loop-pass.js.map +1 -0
  97. package/dist/analysis/passes/sync-io-async-pass.d.ts +28 -0
  98. package/dist/analysis/passes/sync-io-async-pass.js +80 -0
  99. package/dist/analysis/passes/sync-io-async-pass.js.map +1 -0
  100. package/dist/analysis/passes/taint-matcher-pass.d.ts +24 -0
  101. package/dist/analysis/passes/taint-matcher-pass.js +71 -0
  102. package/dist/analysis/passes/taint-matcher-pass.js.map +1 -0
  103. package/dist/analysis/passes/taint-propagation-pass.d.ts +22 -0
  104. package/dist/analysis/passes/taint-propagation-pass.js +266 -0
  105. package/dist/analysis/passes/taint-propagation-pass.js.map +1 -0
  106. package/dist/analysis/passes/todo-in-prod-pass.d.ts +28 -0
  107. package/dist/analysis/passes/todo-in-prod-pass.js +71 -0
  108. package/dist/analysis/passes/todo-in-prod-pass.js.map +1 -0
  109. package/dist/analysis/passes/unchecked-return-pass.d.ts +34 -0
  110. package/dist/analysis/passes/unchecked-return-pass.js +106 -0
  111. package/dist/analysis/passes/unchecked-return-pass.js.map +1 -0
  112. package/dist/analysis/passes/unused-variable-pass.d.ts +36 -0
  113. package/dist/analysis/passes/unused-variable-pass.js +150 -0
  114. package/dist/analysis/passes/unused-variable-pass.js.map +1 -0
  115. package/dist/analysis/passes/variable-shadowing-pass.d.ts +41 -0
  116. package/dist/analysis/passes/variable-shadowing-pass.js +211 -0
  117. package/dist/analysis/passes/variable-shadowing-pass.js.map +1 -0
  118. package/dist/analysis/path-finder.d.ts +3 -13
  119. package/dist/analysis/path-finder.js +48 -63
  120. package/dist/analysis/path-finder.js.map +1 -1
  121. package/dist/analysis/taint-matcher.js +8 -1
  122. package/dist/analysis/taint-matcher.js.map +1 -1
  123. package/dist/analysis/taint-propagation.d.ts +5 -1
  124. package/dist/analysis/taint-propagation.js +44 -41
  125. package/dist/analysis/taint-propagation.js.map +1 -1
  126. package/dist/analyzer.d.ts +42 -1
  127. package/dist/analyzer.js +234 -1476
  128. package/dist/analyzer.js.map +1 -1
  129. package/dist/browser/circle-ir.js +3414 -1272
  130. package/dist/core/circle-ir-core.cjs +361 -107
  131. package/dist/core/circle-ir-core.js +361 -107
  132. package/dist/core/extractors/imports.js +18 -0
  133. package/dist/core/extractors/imports.js.map +1 -1
  134. package/dist/graph/analysis-pass.d.ts +68 -0
  135. package/dist/graph/analysis-pass.js +51 -0
  136. package/dist/graph/analysis-pass.js.map +1 -0
  137. package/dist/graph/code-graph.d.ts +92 -0
  138. package/dist/graph/code-graph.js +262 -0
  139. package/dist/graph/code-graph.js.map +1 -0
  140. package/dist/graph/import-graph.d.ts +33 -0
  141. package/dist/graph/import-graph.js +170 -0
  142. package/dist/graph/import-graph.js.map +1 -0
  143. package/dist/graph/index.d.ts +4 -0
  144. package/dist/graph/index.js +5 -0
  145. package/dist/graph/index.js.map +1 -0
  146. package/dist/graph/project-graph.d.ts +43 -0
  147. package/dist/graph/project-graph.js +80 -0
  148. package/dist/graph/project-graph.js.map +1 -0
  149. package/dist/graph/scope-graph.d.ts +63 -0
  150. package/dist/graph/scope-graph.js +89 -0
  151. package/dist/graph/scope-graph.js.map +1 -0
  152. package/dist/index.d.ts +2 -2
  153. package/dist/index.js +1 -1
  154. package/dist/index.js.map +1 -1
  155. package/dist/resolution/cross-file.js +52 -19
  156. package/dist/resolution/cross-file.js.map +1 -1
  157. package/dist/types/index.d.ts +151 -0
  158. package/docs/SPEC.md +10 -6
  159. package/package.json +3 -2
package/README.md CHANGED
@@ -1,12 +1,14 @@
1
1
  # circle-ir
2
2
 
3
- A high-performance Static Application Security Testing (SAST) library for detecting security vulnerabilities through taint analysis. Works in Node.js and browsers.
3
+ A high-performance Static Application Security Testing (SAST) library for detecting security vulnerabilities through taint analysis, and code quality findings through an extensible analysis-pass pipeline. Works in Node.js and browsers.
4
4
 
5
5
  ## Features
6
6
 
7
7
  - **Taint Analysis**: Track data flow from sources (user input) to sinks (dangerous operations)
8
8
  - **Multi-language Support**: Java, JavaScript/TypeScript, Python, Rust, Bash/Shell
9
9
  - **High Accuracy**: 100% on OWASP Benchmark, 100% on Juliet Test Suite, 97.7% TPR on SecuriBench Micro
10
+ - **11-Pass Pipeline**: Security taint passes + quality passes (dead code, missing await, N+1, doc coverage, TODO markers)
11
+ - **Cross-File Analysis**: `analyzeProject()` surfaces taint flows that span multiple files
10
12
  - **Universal**: Works in Node.js and browsers with environment-agnostic core
11
13
  - **Zero External Dependencies**: Core analysis runs without network calls or external services
12
14
  - **Browser Compatible**: Tree-sitter WASM for universal parsing
@@ -31,12 +33,19 @@ await initAnalyzer();
31
33
  // Analyze Java code
32
34
  const result = await analyze(code, 'MyClass.java', 'java');
33
35
 
34
- // Check for vulnerabilities
36
+ // Security taint flows
35
37
  for (const flow of result.taint.flows || []) {
36
38
  console.log(`Found ${flow.sink_type} vulnerability`);
37
39
  console.log(` Source: line ${flow.source_line}`);
38
40
  console.log(` Sink: line ${flow.sink_line}`);
39
41
  }
42
+
43
+ // Quality findings from analysis passes (dead-code, missing-await, n-plus-one, etc.)
44
+ for (const finding of result.findings || []) {
45
+ console.log(`[${finding.severity}] ${finding.rule_id} at line ${finding.line}`);
46
+ console.log(` ${finding.message}`);
47
+ if (finding.fix) console.log(` Fix: ${finding.fix}`);
48
+ }
40
49
  ```
41
50
 
42
51
  ### Browser
@@ -78,7 +87,7 @@ interface AnalyzerOptions {
78
87
 
79
88
  ### `analyze(code, filePath, language, options?)`
80
89
 
81
- Analyze source code and return Circle-IR output.
90
+ Analyze a single file and return Circle-IR output.
82
91
 
83
92
  ```typescript
84
93
  const result = await analyze(code, 'File.java', 'java');
@@ -92,6 +101,38 @@ result.dfg // Data flow graph
92
101
  result.taint // Taint sources, sinks, flows
93
102
  result.imports // Import statements
94
103
  result.exports // Exported symbols
104
+ result.findings // SastFinding[] from all 11 analysis passes
105
+ ```
106
+
107
+ ### `analyzeProject(files, options?)`
108
+
109
+ Analyze multiple files together to detect cross-file taint flows.
110
+
111
+ ```typescript
112
+ import { analyzeProject } from 'circle-ir';
113
+
114
+ const result = await analyzeProject([
115
+ { code: controllerCode, filePath: 'UserController.java', language: 'java' },
116
+ { code: serviceCode, filePath: 'UserService.java', language: 'java' },
117
+ { code: daoCode, filePath: 'UserDao.java', language: 'java' },
118
+ ]);
119
+
120
+ // Per-file analysis (same as analyze() per file)
121
+ for (const { file, analysis } of result.files) {
122
+ console.log(`${file}: ${analysis.taint.flows?.length ?? 0} intra-file flows`);
123
+ }
124
+
125
+ // Cross-file taint paths (the key deliverable)
126
+ for (const path of result.taint_paths) {
127
+ console.log(`Cross-file ${path.sink.type}: ${path.source.file} → ${path.sink.file}`);
128
+ console.log(` Confidence: ${path.confidence.toFixed(2)}, CWE: ${path.sink.cwe}`);
129
+ }
130
+
131
+ // Resolved inter-file method calls
132
+ console.log(`${result.cross_file_calls.length} cross-file calls resolved`);
133
+
134
+ // Project metadata
135
+ console.log(`${result.meta.total_files} files, ${result.meta.total_loc} LOC`);
95
136
  ```
96
137
 
97
138
  ### `analyzeForAPI(code, filePath, language, options?)`
@@ -169,6 +210,42 @@ sources:
169
210
  tainted_args: [return]
170
211
  ```
171
212
 
213
+ ## SAST Findings & Quality Passes
214
+
215
+ The 11-pass pipeline emits `SastFinding[]` via `result.findings`. Each finding is SARIF 2.1.0-aligned:
216
+
217
+ ```typescript
218
+ interface SastFinding {
219
+ id: string; // e.g. "dead-code-42"
220
+ rule_id: string; // e.g. "dead-code"
221
+ category: PassCategory; // 'security' | 'reliability' | 'performance' | 'maintainability' | 'architecture'
222
+ severity: string; // 'critical' | 'high' | 'medium' | 'low'
223
+ level: SarifLevel; // 'error' | 'warning' | 'note' | 'none'
224
+ message: string;
225
+ file: string;
226
+ line: number;
227
+ cwe?: string; // e.g. "CWE-561"
228
+ fix?: string; // Instance-specific remediation hint
229
+ evidence?: Record<string, unknown>;
230
+ }
231
+ ```
232
+
233
+ **Current passes** (see [docs/PASSES.md](docs/PASSES.md) for the full registry):
234
+
235
+ | Pass | rule_id | Category | CWE | Level |
236
+ |------|---------|----------|-----|-------|
237
+ | TaintMatcherPass | _(produces flows)_ | security | — | error |
238
+ | ConstantPropagationPass | _(reduces FP)_ | security | — | — |
239
+ | LanguageSourcesPass | _(enriches sources)_ | security | — | — |
240
+ | SinkFilterPass | _(filters sinks)_ | security | — | — |
241
+ | TaintPropagationPass | _(propagates taint)_ | security | — | error |
242
+ | InterproceduralPass | _(cross-method)_ | security | — | error |
243
+ | DeadCodePass | `dead-code` | reliability | CWE-561 | warning |
244
+ | MissingAwaitPass | `missing-await` | reliability | CWE-252 | warning |
245
+ | NPlusOnePass | `n-plus-one` | performance | CWE-1049 | warning |
246
+ | MissingPublicDocPass | `missing-public-doc` | maintainability | — | note |
247
+ | TodoInProdPass | `todo-in-prod` | maintainability | — | note |
248
+
172
249
  ## Key Analysis Features
173
250
 
174
251
  - **Constant Propagation**: Eliminates false positives by tracking variable values and detecting dead code
@@ -191,11 +268,11 @@ All scores below are for **circle-ir static analysis only** (no LLM).
191
268
 
192
269
  ## Documentation
193
270
 
271
+ - [Pass & Metric Registry](docs/PASSES.md) - Canonical list of every pass and metric with rule_id, CWE, and status
194
272
  - [Circle-IR Specification](docs/SPEC.md) - IR format specification
195
273
  - [Architecture Guide](docs/ARCHITECTURE.md) - Detailed system architecture
196
- - [Contributing Guide](CONTRIBUTING.md) - How to contribute
197
274
  - [Changelog](CHANGELOG.md) - Version history
198
- - [TODO](TODO.md) - Pending improvements and roadmap
275
+ - [TODO](TODO.md) - Phase-based roadmap
199
276
 
200
277
  ## License
201
278
 
@@ -851,7 +851,7 @@ export const DEFAULT_SINKS = [
851
851
  // Jenkins/CI Pipeline execution
852
852
  { method: 'executeScript', type: 'code_injection', cwe: 'CWE-94', severity: 'critical', arg_positions: [0] },
853
853
  { method: 'runScript', type: 'code_injection', cwe: 'CWE-94', severity: 'critical', arg_positions: [0] },
854
- { method: 'evaluate', type: 'code_injection', cwe: 'CWE-94', severity: 'critical', arg_positions: [0] },
854
+ { method: 'evaluate', class: 'ScriptEngine', type: 'code_injection', cwe: 'CWE-94', severity: 'critical', arg_positions: [0] },
855
855
  { method: 'execute', class: 'Script', type: 'code_injection', cwe: 'CWE-94', severity: 'critical', arg_positions: [] },
856
856
  { method: 'run', class: 'Script', type: 'code_injection', cwe: 'CWE-94', severity: 'critical', arg_positions: [] },
857
857
  { method: 'checkout', class: 'SCM', type: 'code_injection', cwe: 'CWE-94', severity: 'high', arg_positions: [0] },