circle-ir 3.8.0 → 3.8.3
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/LICENSE +18 -12
- package/README.md +10 -11
- package/configs/sinks/nodejs.json +58 -0
- package/dist/analysis/config-loader.js +8 -19
- package/dist/analysis/config-loader.js.map +1 -1
- package/dist/analysis/interprocedural.js +14 -0
- package/dist/analysis/interprocedural.js.map +1 -1
- package/dist/analyzer.d.ts +1 -1
- package/dist/analyzer.js +113 -1
- package/dist/analyzer.js.map +1 -1
- package/dist/browser/circle-ir.js +109 -21
- package/dist/core/circle-ir-core.cjs +8 -19
- package/dist/core/circle-ir-core.js +8 -19
- package/dist/utils/logger.d.ts +1 -1
- package/dist/utils/logger.js +1 -1
- package/dist/wasm/web-tree-sitter.wasm +0 -0
- package/docs/SPEC.md +7 -7
- package/examples/node-example.ts +7 -3
- package/package.json +10 -12
- package/wasm/tree-sitter-bash.wasm +0 -0
- package/wasm/tree-sitter-java.wasm +0 -0
- package/wasm/tree-sitter-javascript.wasm +0 -0
- package/wasm/tree-sitter-python.wasm +0 -0
- package/wasm/tree-sitter-rust.wasm +0 -0
package/LICENSE
CHANGED
|
@@ -1,15 +1,21 @@
|
|
|
1
|
-
|
|
1
|
+
MIT License
|
|
2
2
|
|
|
3
3
|
Copyright (c) 2025 Cognium Labs
|
|
4
4
|
|
|
5
|
-
Permission
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,22 +1,17 @@
|
|
|
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
|
|
3
|
+
A high-performance Static Application Security Testing (SAST) library for detecting security vulnerabilities through taint analysis. 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
|
-
- **Multi-language Support**: Java, JavaScript/TypeScript, Python, Rust
|
|
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
|
-
- **Universal**: Works in Node.js
|
|
10
|
+
- **Universal**: Works in Node.js and browsers with environment-agnostic core
|
|
11
11
|
- **Zero External Dependencies**: Core analysis runs without network calls or external services
|
|
12
12
|
- **Browser Compatible**: Tree-sitter WASM for universal parsing
|
|
13
13
|
- **Configuration-Driven**: YAML/JSON patterns for sources, sinks, and sanitizers
|
|
14
14
|
|
|
15
|
-
## Related Packages
|
|
16
|
-
|
|
17
|
-
- **[circle-ir-ai](https://github.com/cogniumhq/circle-ir-ai)**: LLM-enhanced analysis with CLI
|
|
18
|
-
- **[circle-pack](https://github.com/cogniumhq/circle-pack)**: Cloudflare Workers API deployment
|
|
19
|
-
|
|
20
15
|
## Installation
|
|
21
16
|
|
|
22
17
|
```bash
|
|
@@ -127,9 +122,10 @@ const response = await analyzeForAPI(code, 'File.java', 'java');
|
|
|
127
122
|
| Language | Parser | Frameworks |
|
|
128
123
|
|----------|--------|------------|
|
|
129
124
|
| **Java** | tree-sitter-java | Spring, JAX-RS, Servlet API |
|
|
130
|
-
| **JavaScript/TypeScript** | tree-sitter-javascript | Express, Fastify, Node.js |
|
|
125
|
+
| **JavaScript/TypeScript** | tree-sitter-javascript | Express, Fastify, Koa, Node.js |
|
|
131
126
|
| **Python** | tree-sitter-python | Flask, Django, FastAPI |
|
|
132
127
|
| **Rust** | tree-sitter-rust | Actix-web, Rocket, Axum |
|
|
128
|
+
| **Bash/Shell** | tree-sitter-bash | Shell scripts (.sh, .bash, .zsh, .ksh) |
|
|
133
129
|
|
|
134
130
|
### Multi-Language Examples
|
|
135
131
|
|
|
@@ -183,12 +179,15 @@ sources:
|
|
|
183
179
|
|
|
184
180
|
## Benchmark Results
|
|
185
181
|
|
|
182
|
+
All scores below are for **circle-ir static analysis only** (no LLM).
|
|
183
|
+
|
|
186
184
|
| Benchmark | Score | Details |
|
|
187
185
|
|-----------|-------|---------|
|
|
188
186
|
| **OWASP Benchmark** | +100% | TPR 100%, FPR 0% (1415 test cases) |
|
|
189
187
|
| **Juliet Test Suite** | +100% | 156/156 test cases, 9 CWEs |
|
|
190
188
|
| **SecuriBench Micro** | 97.7% TPR | 105/108 vulns detected, 6.7% FPR |
|
|
191
|
-
| **CWE-Bench-Java** |
|
|
189
|
+
| **CWE-Bench-Java** | 42.5% | 51/120 real-world CVEs (vs CodeQL 22.5%, IRIS+GPT-4 45.8%) |
|
|
190
|
+
| **Bash Synthetic** | 68.2% TPR | 15 TP, 9 TN, 0 FP on 31 synthetic test cases |
|
|
192
191
|
|
|
193
192
|
## Documentation
|
|
194
193
|
|
|
@@ -200,4 +199,4 @@ sources:
|
|
|
200
199
|
|
|
201
200
|
## License
|
|
202
201
|
|
|
203
|
-
|
|
202
|
+
MIT
|
|
@@ -9,6 +9,14 @@
|
|
|
9
9
|
"arg_positions": [0],
|
|
10
10
|
"note": "Command execution - extremely dangerous with user input"
|
|
11
11
|
},
|
|
12
|
+
{
|
|
13
|
+
"method": "exec",
|
|
14
|
+
"type": "command_injection",
|
|
15
|
+
"cwe": "CWE-78",
|
|
16
|
+
"severity": "critical",
|
|
17
|
+
"arg_positions": [0],
|
|
18
|
+
"note": "Destructured child_process.exec import - no receiver"
|
|
19
|
+
},
|
|
12
20
|
{
|
|
13
21
|
"method": "execSync",
|
|
14
22
|
"class": "child_process",
|
|
@@ -18,6 +26,14 @@
|
|
|
18
26
|
"arg_positions": [0],
|
|
19
27
|
"note": "Synchronous command execution"
|
|
20
28
|
},
|
|
29
|
+
{
|
|
30
|
+
"method": "execSync",
|
|
31
|
+
"type": "command_injection",
|
|
32
|
+
"cwe": "CWE-78",
|
|
33
|
+
"severity": "critical",
|
|
34
|
+
"arg_positions": [0],
|
|
35
|
+
"note": "Destructured child_process.execSync import - no receiver"
|
|
36
|
+
},
|
|
21
37
|
{
|
|
22
38
|
"method": "spawn",
|
|
23
39
|
"class": "child_process",
|
|
@@ -27,6 +43,14 @@
|
|
|
27
43
|
"arg_positions": [0, 1],
|
|
28
44
|
"note": "Process spawn - arg[0] is command, arg[1] is args array"
|
|
29
45
|
},
|
|
46
|
+
{
|
|
47
|
+
"method": "spawn",
|
|
48
|
+
"type": "command_injection",
|
|
49
|
+
"cwe": "CWE-78",
|
|
50
|
+
"severity": "critical",
|
|
51
|
+
"arg_positions": [0, 1],
|
|
52
|
+
"note": "Destructured child_process.spawn import - no receiver"
|
|
53
|
+
},
|
|
30
54
|
{
|
|
31
55
|
"method": "spawnSync",
|
|
32
56
|
"class": "child_process",
|
|
@@ -36,6 +60,14 @@
|
|
|
36
60
|
"arg_positions": [0, 1],
|
|
37
61
|
"note": "Synchronous process spawn"
|
|
38
62
|
},
|
|
63
|
+
{
|
|
64
|
+
"method": "spawnSync",
|
|
65
|
+
"type": "command_injection",
|
|
66
|
+
"cwe": "CWE-78",
|
|
67
|
+
"severity": "critical",
|
|
68
|
+
"arg_positions": [0, 1],
|
|
69
|
+
"note": "Destructured child_process.spawnSync import - no receiver"
|
|
70
|
+
},
|
|
39
71
|
{
|
|
40
72
|
"method": "execFile",
|
|
41
73
|
"class": "child_process",
|
|
@@ -45,6 +77,14 @@
|
|
|
45
77
|
"arg_positions": [0, 1],
|
|
46
78
|
"note": "Execute file with arguments"
|
|
47
79
|
},
|
|
80
|
+
{
|
|
81
|
+
"method": "execFile",
|
|
82
|
+
"type": "command_injection",
|
|
83
|
+
"cwe": "CWE-78",
|
|
84
|
+
"severity": "critical",
|
|
85
|
+
"arg_positions": [0, 1],
|
|
86
|
+
"note": "Destructured child_process.execFile import - no receiver"
|
|
87
|
+
},
|
|
48
88
|
{
|
|
49
89
|
"method": "fork",
|
|
50
90
|
"class": "child_process",
|
|
@@ -293,6 +333,15 @@
|
|
|
293
333
|
"arg_positions": [0],
|
|
294
334
|
"note": "Node.js HTTP request"
|
|
295
335
|
},
|
|
336
|
+
{
|
|
337
|
+
"method": "get",
|
|
338
|
+
"class": "http",
|
|
339
|
+
"type": "ssrf",
|
|
340
|
+
"cwe": "CWE-918",
|
|
341
|
+
"severity": "high",
|
|
342
|
+
"arg_positions": [0],
|
|
343
|
+
"note": "Node.js HTTP GET request - validate URL to prevent SSRF"
|
|
344
|
+
},
|
|
296
345
|
{
|
|
297
346
|
"method": "request",
|
|
298
347
|
"class": "https",
|
|
@@ -302,6 +351,15 @@
|
|
|
302
351
|
"arg_positions": [0],
|
|
303
352
|
"note": "Node.js HTTPS request"
|
|
304
353
|
},
|
|
354
|
+
{
|
|
355
|
+
"method": "get",
|
|
356
|
+
"class": "https",
|
|
357
|
+
"type": "ssrf",
|
|
358
|
+
"cwe": "CWE-918",
|
|
359
|
+
"severity": "high",
|
|
360
|
+
"arg_positions": [0],
|
|
361
|
+
"note": "Node.js HTTPS GET request"
|
|
362
|
+
},
|
|
305
363
|
{
|
|
306
364
|
"method": "redirect",
|
|
307
365
|
"class": "Response",
|
|
@@ -539,9 +539,7 @@ export const DEFAULT_SINKS = [
|
|
|
539
539
|
{ method: 'resolveURI', type: 'path_traversal', cwe: 'CWE-22', severity: 'high', arg_positions: [0] },
|
|
540
540
|
{ method: 'resolve', class: 'SourceResolver', type: 'path_traversal', cwe: 'CWE-22', severity: 'high', arg_positions: [0] },
|
|
541
541
|
{ method: 'getSource', class: 'SourceResolver', type: 'path_traversal', cwe: 'CWE-22', severity: 'high', arg_positions: [0] },
|
|
542
|
-
// URL-
|
|
543
|
-
{ method: 'URL', class: 'constructor', type: 'path_traversal', cwe: 'CWE-22', severity: 'medium', arg_positions: [0] },
|
|
544
|
-
{ method: 'openStream', class: 'URL', type: 'path_traversal', cwe: 'CWE-22', severity: 'medium', arg_positions: [] },
|
|
542
|
+
// NOTE: new URL(userInput) is SSRF (CWE-918), not path traversal — see ssrf section below
|
|
545
543
|
// Servlet context resource loading
|
|
546
544
|
{ method: 'getResource', class: 'ServletContext', type: 'path_traversal', cwe: 'CWE-22', severity: 'high', arg_positions: [0] },
|
|
547
545
|
{ method: 'getResourceAsStream', class: 'ServletContext', type: 'path_traversal', cwe: 'CWE-22', severity: 'high', arg_positions: [0] },
|
|
@@ -578,8 +576,7 @@ export const DEFAULT_SINKS = [
|
|
|
578
576
|
{ method: 'extract', type: 'path_traversal', cwe: 'CWE-22', severity: 'critical', arg_positions: [0, 1] },
|
|
579
577
|
{ method: 'extractAll', type: 'path_traversal', cwe: 'CWE-22', severity: 'critical', arg_positions: [0, 1] },
|
|
580
578
|
{ method: 'unjar', type: 'path_traversal', cwe: 'CWE-22', severity: 'critical', arg_positions: [0, 1] },
|
|
581
|
-
// Additional file constructors
|
|
582
|
-
{ method: 'BufferedReader', class: 'constructor', type: 'path_traversal', cwe: 'CWE-22', severity: 'high', arg_positions: [0] },
|
|
579
|
+
// Additional file constructors — BufferedReader(Reader) is NOT a path traversal sink; it wraps a Reader, not a file path
|
|
583
580
|
{ method: 'PrintWriter', class: 'constructor', type: 'path_traversal', cwe: 'CWE-22', severity: 'high', arg_positions: [0] },
|
|
584
581
|
{ method: 'Scanner', class: 'constructor', type: 'path_traversal', cwe: 'CWE-22', severity: 'high', arg_positions: [0] },
|
|
585
582
|
// Topic/queue names (for message queue systems - can be exploited for path traversal)
|
|
@@ -606,7 +603,6 @@ export const DEFAULT_SINKS = [
|
|
|
606
603
|
{ method: 'getPath', class: 'BaseFileSystem', type: 'path_traversal', cwe: 'CWE-22', severity: 'high', arg_positions: [0] },
|
|
607
604
|
{ method: 'getPathMatcher', class: 'BaseFileSystem', type: 'path_traversal', cwe: 'CWE-22', severity: 'high', arg_positions: [0] },
|
|
608
605
|
{ method: 'getFileStores', class: 'RootedFileSystem', type: 'path_traversal', cwe: 'CWE-22', severity: 'high', arg_positions: [0] },
|
|
609
|
-
{ method: 'deleteRecursive', class: 'CommonTestSupportUtils', type: 'path_traversal', cwe: 'CWE-22', severity: 'high', arg_positions: [0] },
|
|
610
606
|
// SftpFileSystemProvider
|
|
611
607
|
{ method: 'move', class: 'SftpFileSystemProvider', type: 'path_traversal', cwe: 'CWE-22', severity: 'high', arg_positions: [0, 1] },
|
|
612
608
|
{ method: 'copy', class: 'SftpFileSystemProvider', type: 'path_traversal', cwe: 'CWE-22', severity: 'high', arg_positions: [0, 1] },
|
|
@@ -656,18 +652,6 @@ export const DEFAULT_SINKS = [
|
|
|
656
652
|
{ method: 'createPlainAccessConfig', class: 'MQClientAPIImpl', type: 'path_traversal', cwe: 'CWE-22', severity: 'high', arg_positions: [0] },
|
|
657
653
|
// XWiki velocity introspector
|
|
658
654
|
{ method: 'SecureIntrospector', class: 'constructor', type: 'path_traversal', cwe: 'CWE-22', severity: 'high', arg_positions: [0] },
|
|
659
|
-
// Generic test methods that process paths
|
|
660
|
-
{ method: 'testLifeCycle', type: 'path_traversal', cwe: 'CWE-22', severity: 'medium', arg_positions: [] },
|
|
661
|
-
{ method: 'testPathAccess', type: 'path_traversal', cwe: 'CWE-22', severity: 'medium', arg_positions: [] },
|
|
662
|
-
{ method: 'single', type: 'path_traversal', cwe: 'CWE-22', severity: 'medium', arg_positions: [] },
|
|
663
|
-
{ method: 'invalidPath', type: 'path_traversal', cwe: 'CWE-22', severity: 'medium', arg_positions: [] },
|
|
664
|
-
{ method: 'invalidPathWithPreviousDirectoryAllEncoded', type: 'path_traversal', cwe: 'CWE-22', severity: 'medium', arg_positions: [] },
|
|
665
|
-
// Embedded server test methods
|
|
666
|
-
{ method: 'create', class: 'EmbeddedJettyFactoryTest', type: 'path_traversal', cwe: 'CWE-22', severity: 'medium', arg_positions: [] },
|
|
667
|
-
{ method: 'create_withThreadPool', class: 'EmbeddedJettyFactoryTest', type: 'path_traversal', cwe: 'CWE-22', severity: 'medium', arg_positions: [] },
|
|
668
|
-
{ method: 'create_withNullThreadPool', class: 'EmbeddedJettyFactoryTest', type: 'path_traversal', cwe: 'CWE-22', severity: 'medium', arg_positions: [] },
|
|
669
|
-
// Camel file tests
|
|
670
|
-
{ method: 'testProducerComplexByExpression', type: 'path_traversal', cwe: 'CWE-22', severity: 'medium', arg_positions: [] },
|
|
671
655
|
// XSS (CWE-79)
|
|
672
656
|
{ method: 'write', class: 'PrintWriter', type: 'xss', cwe: 'CWE-79', severity: 'high', arg_positions: [0] },
|
|
673
657
|
{ method: 'println', class: 'PrintWriter', type: 'xss', cwe: 'CWE-79', severity: 'high', arg_positions: [0] },
|
|
@@ -1102,9 +1086,12 @@ export const DEFAULT_SINKS = [
|
|
|
1102
1086
|
{ method: 'execSync', class: 'child_process', type: 'command_injection', cwe: 'CWE-78', severity: 'critical', arg_positions: [0] },
|
|
1103
1087
|
{ method: 'spawn', class: 'child_process', type: 'command_injection', cwe: 'CWE-78', severity: 'critical', arg_positions: [0] },
|
|
1104
1088
|
{ method: 'spawnSync', class: 'child_process', type: 'command_injection', cwe: 'CWE-78', severity: 'critical', arg_positions: [0] },
|
|
1105
|
-
// Also match without receiver (destructured imports)
|
|
1089
|
+
// Also match without receiver (destructured imports: const { exec } = require('child_process'))
|
|
1106
1090
|
{ method: 'exec', type: 'command_injection', cwe: 'CWE-78', severity: 'high', arg_positions: [0] },
|
|
1107
1091
|
{ method: 'execSync', type: 'command_injection', cwe: 'CWE-78', severity: 'high', arg_positions: [0] },
|
|
1092
|
+
{ method: 'spawn', type: 'command_injection', cwe: 'CWE-78', severity: 'high', arg_positions: [0] },
|
|
1093
|
+
{ method: 'spawnSync', type: 'command_injection', cwe: 'CWE-78', severity: 'high', arg_positions: [0] },
|
|
1094
|
+
{ method: 'execFile', type: 'command_injection', cwe: 'CWE-78', severity: 'high', arg_positions: [0] },
|
|
1108
1095
|
// Node.js File System (path traversal)
|
|
1109
1096
|
{ method: 'readFile', class: 'fs', type: 'path_traversal', cwe: 'CWE-22', severity: 'critical', arg_positions: [0] },
|
|
1110
1097
|
{ method: 'readFileSync', class: 'fs', type: 'path_traversal', cwe: 'CWE-22', severity: 'critical', arg_positions: [0] },
|
|
@@ -1147,7 +1134,9 @@ export const DEFAULT_SINKS = [
|
|
|
1147
1134
|
{ method: 'request', class: 'axios', type: 'ssrf', cwe: 'CWE-918', severity: 'high', arg_positions: [0] },
|
|
1148
1135
|
{ method: 'fetch', type: 'ssrf', cwe: 'CWE-918', severity: 'high', arg_positions: [0] },
|
|
1149
1136
|
{ method: 'request', class: 'http', type: 'ssrf', cwe: 'CWE-918', severity: 'high', arg_positions: [0] },
|
|
1137
|
+
{ method: 'get', class: 'http', type: 'ssrf', cwe: 'CWE-918', severity: 'high', arg_positions: [0] },
|
|
1150
1138
|
{ method: 'request', class: 'https', type: 'ssrf', cwe: 'CWE-918', severity: 'high', arg_positions: [0] },
|
|
1139
|
+
{ method: 'get', class: 'https', type: 'ssrf', cwe: 'CWE-918', severity: 'high', arg_positions: [0] },
|
|
1151
1140
|
// needle library (used in NodeGoat)
|
|
1152
1141
|
{ method: 'get', class: 'needle', type: 'ssrf', cwe: 'CWE-918', severity: 'high', arg_positions: [0] },
|
|
1153
1142
|
{ method: 'post', class: 'needle', type: 'ssrf', cwe: 'CWE-918', severity: 'high', arg_positions: [0] },
|