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 CHANGED
@@ -1,15 +1,21 @@
1
- ISC License
1
+ MIT License
2
2
 
3
3
  Copyright (c) 2025 Cognium Labs
4
4
 
5
- Permission to use, copy, modify, and/or distribute this software for any
6
- purpose with or without fee is hereby granted, provided that the above
7
- copyright notice and this permission notice appear in all copies.
8
-
9
- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
10
- REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
11
- AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
12
- INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
13
- LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
14
- OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
15
- PERFORMANCE OF THIS SOFTWARE.
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, browsers, and Cloudflare Workers.
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, browsers, and Cloudflare Workers
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** | 81.7% (with LLM) | 98/120 projects (vs CodeQL 22.5%, IRIS+GPT-4 45.8%) |
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
- ISC
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-based resource loading
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] },