circle-ir 3.27.1 → 3.29.0
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/configs/sinks/path.yaml +6 -4
- package/dist/analysis/config-loader.d.ts.map +1 -1
- package/dist/analysis/config-loader.js +76 -54
- package/dist/analysis/config-loader.js.map +1 -1
- package/dist/analysis/passes/taint-matcher-pass.js +1 -1
- package/dist/analysis/passes/taint-matcher-pass.js.map +1 -1
- package/dist/analysis/taint-matcher.d.ts +2 -2
- package/dist/analysis/taint-matcher.d.ts.map +1 -1
- package/dist/analysis/taint-matcher.js +27 -8
- package/dist/analysis/taint-matcher.js.map +1 -1
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +270 -253
- package/dist/analyzer.js.map +1 -1
- package/dist/browser/circle-ir.js +341 -277
- package/dist/core/circle-ir-core.cjs +118 -63
- package/dist/core/circle-ir-core.js +118 -63
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/parser.d.ts +19 -1
- package/dist/core/parser.d.ts.map +1 -1
- package/dist/core/parser.js +53 -2
- package/dist/core/parser.js.map +1 -1
- package/dist/types/config.d.ts +8 -1
- package/dist/types/config.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -3997,6 +3997,7 @@ var parserInitialized = false;
|
|
|
3997
3997
|
var parserInitializing = null;
|
|
3998
3998
|
var loadedLanguages = /* @__PURE__ */ new Map();
|
|
3999
3999
|
var loadingLanguages = /* @__PURE__ */ new Map();
|
|
4000
|
+
var cachedParsers = /* @__PURE__ */ new Map();
|
|
4000
4001
|
var configuredLanguagePaths = {};
|
|
4001
4002
|
var configuredLanguageModules = {};
|
|
4002
4003
|
async function initParser(options = {}) {
|
|
@@ -4066,9 +4067,14 @@ async function loadLanguage(language, wasmPath) {
|
|
|
4066
4067
|
return loadPromise;
|
|
4067
4068
|
}
|
|
4068
4069
|
async function createParser(language) {
|
|
4070
|
+
const cached = cachedParsers.get(language);
|
|
4071
|
+
if (cached) {
|
|
4072
|
+
return cached;
|
|
4073
|
+
}
|
|
4069
4074
|
const lang = await loadLanguage(language);
|
|
4070
4075
|
const parser = new Parser();
|
|
4071
4076
|
parser.setLanguage(lang);
|
|
4077
|
+
cachedParsers.set(language, parser);
|
|
4072
4078
|
return parser;
|
|
4073
4079
|
}
|
|
4074
4080
|
async function parse(code, language) {
|
|
@@ -4079,6 +4085,13 @@ async function parse(code, language) {
|
|
|
4079
4085
|
}
|
|
4080
4086
|
return tree;
|
|
4081
4087
|
}
|
|
4088
|
+
function disposeTree(tree) {
|
|
4089
|
+
if (!tree) return;
|
|
4090
|
+
try {
|
|
4091
|
+
tree.delete();
|
|
4092
|
+
} catch {
|
|
4093
|
+
}
|
|
4094
|
+
}
|
|
4082
4095
|
function walkTree(node, visitor) {
|
|
4083
4096
|
visitor(node);
|
|
4084
4097
|
for (let i2 = 0; i2 < node.childCount; i2++) {
|
|
@@ -9985,7 +9998,9 @@ var DEFAULT_SINKS = [
|
|
|
9985
9998
|
{ method: "ProcessBuilder", class: "constructor", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
9986
9999
|
{ method: "command", class: "ProcessBuilder", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
9987
10000
|
// Commons Exec
|
|
9988
|
-
|
|
10001
|
+
// Note: bare class 'Executor' removed — it collided with java.util.concurrent.Executor
|
|
10002
|
+
// (Executor.execute(Runnable) is not command injection). Apache Commons Exec users
|
|
10003
|
+
// typically declare DefaultExecutor explicitly, so we match that instead. See issue #14.
|
|
9989
10004
|
{ method: "execute", class: "DefaultExecutor", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
9990
10005
|
{ method: "CommandLine", class: "constructor", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
9991
10006
|
{ method: "parse", class: "CommandLine", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
@@ -10053,8 +10068,8 @@ var DEFAULT_SINKS = [
|
|
|
10053
10068
|
{ method: "popen", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
10054
10069
|
{ method: "system", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
10055
10070
|
// Apache Commons Exec
|
|
10056
|
-
|
|
10057
|
-
{ method: "setCommandline", class: "
|
|
10071
|
+
// Note: bare class 'Executor' removed (see comment above) — DefaultExecutor matched explicitly.
|
|
10072
|
+
{ method: "setCommandline", class: "DefaultExecutor", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
10058
10073
|
{ method: "parse", class: "CommandLine", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
10059
10074
|
{ method: "addArgument", class: "CommandLine", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
10060
10075
|
// Process-related utilities
|
|
@@ -10063,7 +10078,10 @@ var DEFAULT_SINKS = [
|
|
|
10063
10078
|
{ method: "redirectOutput", class: "ProcessBuilder", type: "command_injection", cwe: "CWE-78", severity: "medium", arg_positions: [0] },
|
|
10064
10079
|
{ method: "redirectInput", class: "ProcessBuilder", type: "command_injection", cwe: "CWE-78", severity: "medium", arg_positions: [0] },
|
|
10065
10080
|
// Path Traversal (CWE-22)
|
|
10066
|
-
|
|
10081
|
+
// File: covers both File(String pathname) and File(parent, child). The 2-arg
|
|
10082
|
+
// overload's child argument carries CVE-2018-8041 (Camel mail Content-Disposition
|
|
10083
|
+
// filename written to disk).
|
|
10084
|
+
{ method: "File", class: "constructor", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0, 1] },
|
|
10067
10085
|
{ method: "FileInputStream", class: "constructor", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
10068
10086
|
{ method: "FileOutputStream", class: "constructor", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
10069
10087
|
{ method: "FileReader", class: "constructor", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
@@ -10676,11 +10694,14 @@ var DEFAULT_SINKS = [
|
|
|
10676
10694
|
{ method: "spawn", class: "child_process", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
10677
10695
|
{ method: "spawnSync", class: "child_process", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
10678
10696
|
// Also match without receiver (destructured imports: const { exec } = require('child_process'))
|
|
10697
|
+
// `exec` is intentionally classless: catches Node.js child_process.exec AND
|
|
10698
|
+
// Java Runtime.exec (via `r.exec()` where heuristic can't resolve r → Runtime).
|
|
10679
10699
|
{ method: "exec", type: "command_injection", cwe: "CWE-78", severity: "high", arg_positions: [0] },
|
|
10680
|
-
|
|
10681
|
-
{ method: "
|
|
10682
|
-
{ method: "
|
|
10683
|
-
{ method: "
|
|
10700
|
+
// `execSync`/`spawn`/`spawnSync`/`execFile` are Node-specific — language-scope them.
|
|
10701
|
+
{ method: "execSync", type: "command_injection", cwe: "CWE-78", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
10702
|
+
{ method: "spawn", type: "command_injection", cwe: "CWE-78", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
10703
|
+
{ method: "spawnSync", type: "command_injection", cwe: "CWE-78", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
10704
|
+
{ method: "execFile", type: "command_injection", cwe: "CWE-78", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
10684
10705
|
// Node.js File System (path traversal)
|
|
10685
10706
|
{ method: "readFile", class: "fs", type: "path_traversal", cwe: "CWE-22", severity: "critical", arg_positions: [0] },
|
|
10686
10707
|
{ method: "readFileSync", class: "fs", type: "path_traversal", cwe: "CWE-22", severity: "critical", arg_positions: [0] },
|
|
@@ -10693,12 +10714,15 @@ var DEFAULT_SINKS = [
|
|
|
10693
10714
|
{ method: "createReadStream", class: "fs", type: "path_traversal", cwe: "CWE-22", severity: "critical", arg_positions: [0] },
|
|
10694
10715
|
{ method: "createWriteStream", class: "fs", type: "path_traversal", cwe: "CWE-22", severity: "critical", arg_positions: [0] },
|
|
10695
10716
|
// Node.js SQL (mysql, pg, sqlite, etc.)
|
|
10696
|
-
|
|
10697
|
-
|
|
10698
|
-
{ method: "query", class: "
|
|
10717
|
+
// Language-scoped: generic class names `Pool`/`Connection`/`Client` substring-match
|
|
10718
|
+
// unrelated Java identifiers like `cachedThreadPool`, `dbConnection`. See issue #14.
|
|
10719
|
+
{ method: "query", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
10720
|
+
{ method: "query", class: "Pool", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
10721
|
+
{ method: "query", class: "Client", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
10699
10722
|
// Note: classless { method: 'query' } removed — too many FPs (UriComponentsBuilder.query(), etc.)
|
|
10700
10723
|
// SQL query calls are covered by class-specific patterns above (Connection, Pool, Client, JdbcTemplate)
|
|
10701
|
-
|
|
10724
|
+
// Note: `raw` is shared with Python (Django ORM) — scoped to JS+TS to avoid leaking.
|
|
10725
|
+
{ method: "raw", type: "sql_injection", cwe: "CWE-89", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
10702
10726
|
// Browser DOM XSS sinks
|
|
10703
10727
|
{ method: "setAttribute", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [1] },
|
|
10704
10728
|
// Express.js XSS (response methods)
|
|
@@ -10708,7 +10732,7 @@ var DEFAULT_SINKS = [
|
|
|
10708
10732
|
{ method: "html", class: "Response", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0] },
|
|
10709
10733
|
{ method: "render", class: "Response", type: "xss", cwe: "CWE-79", severity: "medium", arg_positions: [1] },
|
|
10710
10734
|
// Node.js Code Injection (eval, vm, etc.)
|
|
10711
|
-
{ method: "eval", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
|
|
10735
|
+
{ method: "eval", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
10712
10736
|
{ method: "Function", class: "constructor", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
|
|
10713
10737
|
{ method: "runInContext", class: "vm", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
|
|
10714
10738
|
{ method: "runInNewContext", class: "vm", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
|
|
@@ -10724,7 +10748,7 @@ var DEFAULT_SINKS = [
|
|
|
10724
10748
|
{ method: "get", class: "axios", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
|
|
10725
10749
|
{ method: "post", class: "axios", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
|
|
10726
10750
|
{ method: "request", class: "axios", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
|
|
10727
|
-
{ method: "fetch", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
|
|
10751
|
+
{ method: "fetch", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
10728
10752
|
{ method: "request", class: "http", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
|
|
10729
10753
|
{ method: "get", class: "http", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
|
|
10730
10754
|
{ method: "request", class: "https", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
|
|
@@ -10753,10 +10777,12 @@ var DEFAULT_SINKS = [
|
|
|
10753
10777
|
{ method: "check_call", class: "subprocess", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
10754
10778
|
{ method: "Popen", class: "subprocess", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
10755
10779
|
// Python Code Injection
|
|
10756
|
-
|
|
10757
|
-
|
|
10758
|
-
{ method: "
|
|
10759
|
-
{ method: "
|
|
10780
|
+
// Language-scoped: classless `exec`/`eval`/`compile` collide with Java/JS builtins
|
|
10781
|
+
// and Java util.concurrent (e.g. Executor.execute / future.compile).
|
|
10782
|
+
{ method: "eval", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["python"] },
|
|
10783
|
+
{ method: "exec", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["python"] },
|
|
10784
|
+
{ method: "compile", type: "code_injection", cwe: "CWE-94", severity: "high", arg_positions: [0], languages: ["python"] },
|
|
10785
|
+
{ method: "__import__", type: "code_injection", cwe: "CWE-94", severity: "high", arg_positions: [0], languages: ["python"] },
|
|
10760
10786
|
// Python Deserialization
|
|
10761
10787
|
{ method: "loads", class: "pickle", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0] },
|
|
10762
10788
|
{ method: "load", class: "pickle", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0] },
|
|
@@ -10764,36 +10790,39 @@ var DEFAULT_SINKS = [
|
|
|
10764
10790
|
{ method: "load", class: "yaml", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0] },
|
|
10765
10791
|
{ method: "loads", class: "yaml", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0] },
|
|
10766
10792
|
// Python SQL Injection
|
|
10767
|
-
|
|
10768
|
-
|
|
10769
|
-
{ method: "
|
|
10770
|
-
{ method: "
|
|
10793
|
+
// Language-scoped: classless `execute`/`raw` collide with Java util.concurrent
|
|
10794
|
+
// (Executor.execute, ThreadPool.execute) and other languages. See issue #14.
|
|
10795
|
+
{ method: "execute", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["python"] },
|
|
10796
|
+
{ method: "executemany", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["python"] },
|
|
10797
|
+
{ method: "raw", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["python"] },
|
|
10798
|
+
{ method: "extra", type: "sql_injection", cwe: "CWE-89", severity: "high", arg_positions: [0], languages: ["python"] },
|
|
10771
10799
|
// Python Path Traversal
|
|
10772
|
-
|
|
10800
|
+
// Language-scoped: classless `open` collides with Java I/O / JS DOM.
|
|
10801
|
+
{ method: "open", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["python"] },
|
|
10773
10802
|
{ method: "remove", class: "os", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
10774
10803
|
{ method: "unlink", class: "os", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
10775
10804
|
{ method: "rmdir", class: "os", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
10776
10805
|
{ method: "rmtree", class: "shutil", type: "path_traversal", cwe: "CWE-22", severity: "critical", arg_positions: [0] },
|
|
10777
|
-
{ method: "send_file", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
10806
|
+
{ method: "send_file", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["python"] },
|
|
10778
10807
|
// Python XSS / SSTI
|
|
10779
|
-
{ method: "render_template_string", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0] },
|
|
10780
|
-
{ method: "Markup", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0] },
|
|
10781
|
-
{ method: "mark_safe", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0] },
|
|
10808
|
+
{ method: "render_template_string", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["python"] },
|
|
10809
|
+
{ method: "Markup", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["python"] },
|
|
10810
|
+
{ method: "mark_safe", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["python"] },
|
|
10782
10811
|
// Python SSRF
|
|
10783
10812
|
{ method: "get", class: "requests", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
|
|
10784
10813
|
{ method: "post", class: "requests", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
|
|
10785
10814
|
{ method: "urlopen", class: "urllib.request", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
|
|
10786
10815
|
// Python Open Redirect
|
|
10787
|
-
{ method: "redirect", type: "open_redirect", cwe: "CWE-601", severity: "medium", arg_positions: [0] },
|
|
10816
|
+
{ method: "redirect", type: "open_redirect", cwe: "CWE-601", severity: "medium", arg_positions: [0], languages: ["python"] },
|
|
10788
10817
|
// Python XPath Injection
|
|
10789
|
-
{ method: "xpath", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0] },
|
|
10818
|
+
{ method: "xpath", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0], languages: ["python"] },
|
|
10790
10819
|
{ method: "find", class: "etree", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0] },
|
|
10791
10820
|
{ method: "findall", class: "etree", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0] },
|
|
10792
10821
|
{ method: "iterfind", class: "etree", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0] },
|
|
10793
10822
|
{ method: "XPath", class: "lxml", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0] },
|
|
10794
10823
|
// elementpath library (XPath 2.0/3.0)
|
|
10795
10824
|
{ method: "select", class: "elementpath", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [1] },
|
|
10796
|
-
{ method: "select", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0] },
|
|
10825
|
+
{ method: "select", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0], languages: ["python"] },
|
|
10797
10826
|
{ method: "iter_select", class: "elementpath", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [1] },
|
|
10798
10827
|
{ method: "Selector", class: "elementpath", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0] },
|
|
10799
10828
|
// Python XXE
|
|
@@ -10885,36 +10914,42 @@ var DEFAULT_SINKS = [
|
|
|
10885
10914
|
{ method: "arg", class: "Command", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
10886
10915
|
{ method: "args", class: "Command", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
10887
10916
|
// Rust SQL Injection (sqlx, diesel, rusqlite, tokio-postgres)
|
|
10888
|
-
|
|
10889
|
-
|
|
10890
|
-
{ method: "query", class: "
|
|
10891
|
-
{ method: "execute", class: "
|
|
10892
|
-
{ method: "
|
|
10893
|
-
{ method: "
|
|
10894
|
-
{ method: "
|
|
10895
|
-
{ method: "
|
|
10896
|
-
{ method: "
|
|
10917
|
+
// Language-scoped: generic class names `Pool`/`Connection`/`Client` substring-match
|
|
10918
|
+
// unrelated Java identifiers (cachedThreadPool, dbConnection). See issue #14.
|
|
10919
|
+
{ method: "query", class: "Client", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["rust"] },
|
|
10920
|
+
{ method: "execute", class: "Client", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["rust"] },
|
|
10921
|
+
{ method: "query", class: "Pool", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["rust"] },
|
|
10922
|
+
{ method: "execute", class: "Pool", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["rust"] },
|
|
10923
|
+
{ method: "sql_query", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["rust"] },
|
|
10924
|
+
{ method: "raw_sql", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["rust"] },
|
|
10925
|
+
{ method: "execute", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["rust"] },
|
|
10926
|
+
{ method: "query_row", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["rust"] },
|
|
10927
|
+
{ method: "prepare", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["rust"] },
|
|
10897
10928
|
// sqlx::query macro — use class-specific pattern
|
|
10898
10929
|
{ method: "query", class: "sqlx", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0] },
|
|
10899
10930
|
// rusqlite specific
|
|
10900
|
-
|
|
10901
|
-
|
|
10902
|
-
{ method: "
|
|
10931
|
+
// Language-scoped: classless `execute`/`prepare`/`query_map` collide with
|
|
10932
|
+
// Java util.concurrent (Executor.execute, ExecutorService) and other languages.
|
|
10933
|
+
{ method: "prepare", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["rust"] },
|
|
10934
|
+
{ method: "execute", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["rust"] },
|
|
10935
|
+
{ method: "query_map", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["rust"] },
|
|
10903
10936
|
// Rust Path Traversal
|
|
10904
10937
|
{ method: "open", class: "File", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
10905
10938
|
{ method: "create", class: "File", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
10906
|
-
|
|
10907
|
-
|
|
10908
|
-
{ method: "
|
|
10909
|
-
{ method: "
|
|
10910
|
-
{ method: "
|
|
10911
|
-
{ method: "
|
|
10912
|
-
{ method: "
|
|
10913
|
-
{ method: "
|
|
10914
|
-
{ method: "
|
|
10915
|
-
{ method: "
|
|
10916
|
-
{ method: "
|
|
10917
|
-
{ method: "
|
|
10939
|
+
// Language-scoped: classless std::fs helpers collide with Java/JS method names
|
|
10940
|
+
// (write, copy, rename, metadata, etc.) See issue #14.
|
|
10941
|
+
{ method: "read_dir", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["rust"] },
|
|
10942
|
+
{ method: "remove_file", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["rust"] },
|
|
10943
|
+
{ method: "remove_dir", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["rust"] },
|
|
10944
|
+
{ method: "remove_dir_all", type: "path_traversal", cwe: "CWE-22", severity: "critical", arg_positions: [0], languages: ["rust"] },
|
|
10945
|
+
{ method: "copy", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0, 1], languages: ["rust"] },
|
|
10946
|
+
{ method: "rename", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0, 1], languages: ["rust"] },
|
|
10947
|
+
{ method: "write", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["rust"] },
|
|
10948
|
+
{ method: "read_to_string", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["rust"] },
|
|
10949
|
+
{ method: "create_dir", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["rust"] },
|
|
10950
|
+
{ method: "create_dir_all", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["rust"] },
|
|
10951
|
+
{ method: "metadata", type: "path_traversal", cwe: "CWE-22", severity: "medium", arg_positions: [0], languages: ["rust"] },
|
|
10952
|
+
{ method: "symlink_metadata", type: "path_traversal", cwe: "CWE-22", severity: "medium", arg_positions: [0], languages: ["rust"] },
|
|
10918
10953
|
// Tokio async fs
|
|
10919
10954
|
{ method: "read_to_string", class: "fs", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
10920
10955
|
{ method: "write", class: "fs", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
@@ -11279,9 +11314,9 @@ var PYTHON_TAINTED_PATTERNS = [
|
|
|
11279
11314
|
{ pattern: /\brequest\.query_params\b/, sourceType: "http_param" },
|
|
11280
11315
|
{ pattern: /\brequest\.path_params\b/, sourceType: "http_param" }
|
|
11281
11316
|
];
|
|
11282
|
-
function analyzeTaint(calls, types, config = getDefaultConfig(), typeHierarchy) {
|
|
11317
|
+
function analyzeTaint(calls, types, config = getDefaultConfig(), typeHierarchy, language) {
|
|
11283
11318
|
const sources = findSources(calls, types, config.sources);
|
|
11284
|
-
const sinks = findSinks(calls, config.sinks, typeHierarchy);
|
|
11319
|
+
const sinks = findSinks(calls, config.sinks, typeHierarchy, language);
|
|
11285
11320
|
const sanitizers = findSanitizers(calls, types, config.sanitizers);
|
|
11286
11321
|
return { sources, sinks, sanitizers };
|
|
11287
11322
|
}
|
|
@@ -11535,11 +11570,11 @@ function isParameterizedQueryCall(call, pattern) {
|
|
|
11535
11570
|
}
|
|
11536
11571
|
return false;
|
|
11537
11572
|
}
|
|
11538
|
-
function findSinks(calls, patterns, typeHierarchy) {
|
|
11573
|
+
function findSinks(calls, patterns, typeHierarchy, language) {
|
|
11539
11574
|
const sinkMap = /* @__PURE__ */ new Map();
|
|
11540
11575
|
for (const call of calls) {
|
|
11541
11576
|
for (const pattern of patterns) {
|
|
11542
|
-
if (matchesSinkPattern(call, pattern, typeHierarchy)) {
|
|
11577
|
+
if (matchesSinkPattern(call, pattern, typeHierarchy, language)) {
|
|
11543
11578
|
if (isParameterizedQueryCall(call, pattern)) {
|
|
11544
11579
|
continue;
|
|
11545
11580
|
}
|
|
@@ -11792,7 +11827,12 @@ function isKnownSafeReceiverForMethod(receiver, method, sinkType) {
|
|
|
11792
11827
|
}
|
|
11793
11828
|
return false;
|
|
11794
11829
|
}
|
|
11795
|
-
function matchesSinkPattern(call, pattern, typeHierarchy) {
|
|
11830
|
+
function matchesSinkPattern(call, pattern, typeHierarchy, language) {
|
|
11831
|
+
if (pattern.languages && pattern.languages.length > 0 && language !== void 0) {
|
|
11832
|
+
if (!pattern.languages.includes(language)) {
|
|
11833
|
+
return false;
|
|
11834
|
+
}
|
|
11835
|
+
}
|
|
11796
11836
|
const callMethodName = call.method_name;
|
|
11797
11837
|
const patternMethod = pattern.method;
|
|
11798
11838
|
let methodMatches = callMethodName === patternMethod;
|
|
@@ -11896,17 +11936,29 @@ function receiverMightBeClass(receiver, className) {
|
|
|
11896
11936
|
}
|
|
11897
11937
|
}
|
|
11898
11938
|
}
|
|
11899
|
-
|
|
11939
|
+
const ambiguousIdentifiers = /* @__PURE__ */ new Set([
|
|
11940
|
+
"executor",
|
|
11941
|
+
"pool",
|
|
11942
|
+
"connection",
|
|
11943
|
+
"manager",
|
|
11944
|
+
"handler",
|
|
11945
|
+
"controller",
|
|
11946
|
+
"task",
|
|
11947
|
+
"thread",
|
|
11948
|
+
"job"
|
|
11949
|
+
]);
|
|
11950
|
+
const isAmbiguous = ambiguousIdentifiers.has(lowerReceiver);
|
|
11951
|
+
if (!isAmbiguous && lowerReceiver.length >= 3 && lowerClass.includes(lowerReceiver)) {
|
|
11900
11952
|
if (lowerReceiver.length >= 5 || lowerReceiver.length / lowerClass.length >= 0.4) {
|
|
11901
11953
|
return true;
|
|
11902
11954
|
}
|
|
11903
11955
|
}
|
|
11904
|
-
if (lowerReceiver.length >= 2) {
|
|
11956
|
+
if (!isAmbiguous && lowerReceiver.length >= 2) {
|
|
11905
11957
|
if (lowerClass.startsWith(lowerReceiver) || lowerClass.endsWith(lowerReceiver)) {
|
|
11906
11958
|
return true;
|
|
11907
11959
|
}
|
|
11908
11960
|
}
|
|
11909
|
-
if (lowerReceiver.length >= 3) {
|
|
11961
|
+
if (!isAmbiguous && lowerReceiver.length >= 3) {
|
|
11910
11962
|
const words = className.replace(/([a-z])([A-Z])/g, "$1\0$2").toLowerCase().split("\0");
|
|
11911
11963
|
for (const word of words) {
|
|
11912
11964
|
if (word.startsWith(lowerReceiver) && lowerReceiver.length / word.length >= 0.4) {
|
|
@@ -20111,7 +20163,7 @@ var TaintMatcherPass = class {
|
|
|
20111
20163
|
}
|
|
20112
20164
|
const hierarchy = createWithJdkTypes();
|
|
20113
20165
|
hierarchy.addFromIR(graph.ir, graph.ir.meta.file);
|
|
20114
|
-
const taint = analyzeTaint(calls, types, mergedConfig, hierarchy);
|
|
20166
|
+
const taint = analyzeTaint(calls, types, mergedConfig, hierarchy, language);
|
|
20115
20167
|
const sanitizerMethods = [];
|
|
20116
20168
|
for (const type of types) {
|
|
20117
20169
|
for (const method of type.methods) {
|
|
@@ -26094,161 +26146,169 @@ async function analyze(code, filePath, language, options = {}) {
|
|
|
26094
26146
|
}
|
|
26095
26147
|
logger.debug("Analyzing file", { filePath, language, codeLength: code.length });
|
|
26096
26148
|
const tree = await parse(code, language);
|
|
26097
|
-
|
|
26098
|
-
|
|
26099
|
-
|
|
26100
|
-
|
|
26101
|
-
|
|
26102
|
-
|
|
26103
|
-
|
|
26104
|
-
|
|
26105
|
-
|
|
26106
|
-
|
|
26107
|
-
|
|
26108
|
-
|
|
26109
|
-
|
|
26110
|
-
|
|
26111
|
-
|
|
26112
|
-
|
|
26113
|
-
|
|
26114
|
-
|
|
26115
|
-
|
|
26116
|
-
|
|
26117
|
-
|
|
26118
|
-
|
|
26119
|
-
|
|
26120
|
-
|
|
26121
|
-
|
|
26122
|
-
|
|
26123
|
-
|
|
26124
|
-
|
|
26125
|
-
|
|
26126
|
-
|
|
26127
|
-
|
|
26128
|
-
|
|
26129
|
-
|
|
26130
|
-
|
|
26131
|
-
|
|
26132
|
-
|
|
26133
|
-
|
|
26134
|
-
|
|
26135
|
-
|
|
26136
|
-
|
|
26137
|
-
|
|
26138
|
-
|
|
26139
|
-
|
|
26140
|
-
|
|
26141
|
-
|
|
26142
|
-
|
|
26143
|
-
|
|
26144
|
-
|
|
26145
|
-
|
|
26146
|
-
|
|
26147
|
-
|
|
26148
|
-
|
|
26149
|
-
|
|
26150
|
-
|
|
26151
|
-
|
|
26152
|
-
|
|
26153
|
-
|
|
26154
|
-
|
|
26155
|
-
|
|
26156
|
-
|
|
26157
|
-
|
|
26158
|
-
|
|
26159
|
-
|
|
26160
|
-
|
|
26161
|
-
|
|
26162
|
-
|
|
26163
|
-
|
|
26164
|
-
|
|
26165
|
-
|
|
26166
|
-
|
|
26167
|
-
|
|
26168
|
-
|
|
26169
|
-
|
|
26170
|
-
|
|
26171
|
-
|
|
26172
|
-
|
|
26173
|
-
|
|
26174
|
-
|
|
26175
|
-
|
|
26176
|
-
|
|
26177
|
-
|
|
26178
|
-
|
|
26179
|
-
|
|
26180
|
-
|
|
26181
|
-
|
|
26182
|
-
|
|
26183
|
-
|
|
26184
|
-
|
|
26185
|
-
|
|
26186
|
-
|
|
26187
|
-
|
|
26188
|
-
|
|
26189
|
-
|
|
26190
|
-
|
|
26191
|
-
|
|
26192
|
-
|
|
26193
|
-
|
|
26194
|
-
|
|
26195
|
-
|
|
26196
|
-
|
|
26197
|
-
|
|
26198
|
-
|
|
26199
|
-
|
|
26200
|
-
|
|
26201
|
-
|
|
26149
|
+
try {
|
|
26150
|
+
logger.trace("Parsed AST", { rootNodeType: tree.rootNode.type });
|
|
26151
|
+
const nodeCache = collectAllNodes(tree.rootNode, getNodeTypesForLanguage(language));
|
|
26152
|
+
const meta = extractMeta(code, tree, filePath, language);
|
|
26153
|
+
const types = extractTypes(tree, nodeCache, language);
|
|
26154
|
+
const calls = extractCalls(tree, nodeCache, language);
|
|
26155
|
+
const imports = extractImports(tree, language);
|
|
26156
|
+
const exports = extractExports(types);
|
|
26157
|
+
const cfg = buildCFG(tree, language);
|
|
26158
|
+
const dfg = buildDFG(tree, nodeCache, language);
|
|
26159
|
+
const graph = new CodeGraph({
|
|
26160
|
+
meta,
|
|
26161
|
+
types,
|
|
26162
|
+
calls,
|
|
26163
|
+
cfg,
|
|
26164
|
+
dfg,
|
|
26165
|
+
taint: { sources: [], sinks: [], sanitizers: [] },
|
|
26166
|
+
imports,
|
|
26167
|
+
exports,
|
|
26168
|
+
unresolved: [],
|
|
26169
|
+
enriched: {}
|
|
26170
|
+
});
|
|
26171
|
+
const config = options.taintConfig ?? getDefaultConfig();
|
|
26172
|
+
const disabledPasses = new Set(options.disabledPasses ?? []);
|
|
26173
|
+
const passOpts = options.passOptions ?? {};
|
|
26174
|
+
const pipeline = new AnalysisPipeline();
|
|
26175
|
+
pipeline.add(new TaintMatcherPass());
|
|
26176
|
+
pipeline.add(new ConstantPropagationPass(tree));
|
|
26177
|
+
pipeline.add(new LanguageSourcesPass());
|
|
26178
|
+
pipeline.add(new SinkFilterPass());
|
|
26179
|
+
pipeline.add(new TaintPropagationPass());
|
|
26180
|
+
pipeline.add(new InterproceduralPass());
|
|
26181
|
+
if (!disabledPasses.has("scan-secrets")) pipeline.add(new ScanSecretsPass());
|
|
26182
|
+
if (!disabledPasses.has("dead-code")) pipeline.add(new DeadCodePass());
|
|
26183
|
+
if (!disabledPasses.has("missing-await")) pipeline.add(new MissingAwaitPass());
|
|
26184
|
+
if (!disabledPasses.has("n-plus-one")) pipeline.add(new NPlusOnePass());
|
|
26185
|
+
if (!disabledPasses.has("missing-public-doc")) pipeline.add(new MissingPublicDocPass());
|
|
26186
|
+
if (!disabledPasses.has("todo-in-prod")) pipeline.add(new TodoInProdPass());
|
|
26187
|
+
if (!disabledPasses.has("string-concat-loop")) pipeline.add(new StringConcatLoopPass());
|
|
26188
|
+
if (!disabledPasses.has("sync-io-async")) pipeline.add(new SyncIoAsyncPass());
|
|
26189
|
+
if (!disabledPasses.has("unchecked-return")) pipeline.add(new UncheckedReturnPass());
|
|
26190
|
+
if (!disabledPasses.has("null-deref")) pipeline.add(new NullDerefPass());
|
|
26191
|
+
if (!disabledPasses.has("resource-leak")) pipeline.add(new ResourceLeakPass());
|
|
26192
|
+
if (!disabledPasses.has("variable-shadowing")) pipeline.add(new VariableShadowingPass());
|
|
26193
|
+
if (!disabledPasses.has("leaked-global")) pipeline.add(new LeakedGlobalPass());
|
|
26194
|
+
if (!disabledPasses.has("unused-variable")) pipeline.add(new UnusedVariablePass());
|
|
26195
|
+
if (!disabledPasses.has("dependency-fan-out")) pipeline.add(new DependencyFanOutPass(passOpts.dependencyFanOut));
|
|
26196
|
+
if (!disabledPasses.has("stale-doc-ref")) pipeline.add(new StaleDocRefPass());
|
|
26197
|
+
if (!disabledPasses.has("infinite-loop")) pipeline.add(new InfiniteLoopPass());
|
|
26198
|
+
if (!disabledPasses.has("deep-inheritance")) pipeline.add(new DeepInheritancePass());
|
|
26199
|
+
if (!disabledPasses.has("redundant-loop-computation")) pipeline.add(new RedundantLoopPass());
|
|
26200
|
+
if (!disabledPasses.has("unbounded-collection")) pipeline.add(new UnboundedCollectionPass(passOpts.unboundedCollection));
|
|
26201
|
+
if (!disabledPasses.has("serial-await")) pipeline.add(new SerialAwaitPass());
|
|
26202
|
+
if (!disabledPasses.has("react-inline-jsx")) pipeline.add(new ReactInlineJsxPass());
|
|
26203
|
+
if (!disabledPasses.has("swallowed-exception")) pipeline.add(new SwallowedExceptionPass());
|
|
26204
|
+
if (!disabledPasses.has("broad-catch")) pipeline.add(new BroadCatchPass());
|
|
26205
|
+
if (!disabledPasses.has("unhandled-exception")) pipeline.add(new UnhandledExceptionPass());
|
|
26206
|
+
if (!disabledPasses.has("double-close")) pipeline.add(new DoubleClosePass());
|
|
26207
|
+
if (!disabledPasses.has("use-after-close")) pipeline.add(new UseAfterClosePass());
|
|
26208
|
+
if (!disabledPasses.has("cleanup-verify")) pipeline.add(new CleanupVerifyPass());
|
|
26209
|
+
if (!disabledPasses.has("missing-override")) pipeline.add(new MissingOverridePass());
|
|
26210
|
+
if (!disabledPasses.has("unused-interface-method")) pipeline.add(new UnusedInterfaceMethodPass());
|
|
26211
|
+
if (!disabledPasses.has("blocking-main-thread")) pipeline.add(new BlockingMainThreadPass());
|
|
26212
|
+
if (!disabledPasses.has("excessive-allocation")) pipeline.add(new ExcessiveAllocationPass());
|
|
26213
|
+
if (!disabledPasses.has("missing-stream")) pipeline.add(new MissingStreamPass());
|
|
26214
|
+
if (!disabledPasses.has("god-class")) pipeline.add(new GodClassPass());
|
|
26215
|
+
if (!disabledPasses.has("naming-convention")) pipeline.add(new NamingConventionPass(passOpts.namingConvention));
|
|
26216
|
+
if (!disabledPasses.has("security-headers")) pipeline.add(new SecurityHeadersPass(passOpts.securityHeaders));
|
|
26217
|
+
const { results, findings } = pipeline.run(graph, code, language, config);
|
|
26218
|
+
const sinkFilter = results.get("sink-filter");
|
|
26219
|
+
const interProc = results.get("interprocedural");
|
|
26220
|
+
const taint = {
|
|
26221
|
+
sources: sinkFilter.sources,
|
|
26222
|
+
sinks: [...sinkFilter.sinks, ...interProc.additionalSinks],
|
|
26223
|
+
sanitizers: sinkFilter.sanitizers,
|
|
26224
|
+
flows: interProc.additionalFlows,
|
|
26225
|
+
interprocedural: interProc.interprocedural
|
|
26226
|
+
};
|
|
26227
|
+
const unresolved = detectUnresolved(calls, types, dfg);
|
|
26228
|
+
const enriched = buildEnriched(types, calls, taint.sources, taint.sinks);
|
|
26229
|
+
const metricValues = new MetricRunner().run(
|
|
26230
|
+
{ meta, types, calls, cfg, dfg, taint, imports, exports, unresolved, enriched },
|
|
26231
|
+
code,
|
|
26232
|
+
language
|
|
26233
|
+
);
|
|
26234
|
+
logger.debug("Analysis complete", {
|
|
26235
|
+
filePath,
|
|
26236
|
+
finalSources: taint.sources.length,
|
|
26237
|
+
finalSinks: taint.sinks.length,
|
|
26238
|
+
flows: taint.flows?.length ?? 0,
|
|
26239
|
+
unresolvedItems: unresolved.length
|
|
26240
|
+
});
|
|
26241
|
+
return {
|
|
26242
|
+
meta,
|
|
26243
|
+
types,
|
|
26244
|
+
calls,
|
|
26245
|
+
cfg,
|
|
26246
|
+
dfg,
|
|
26247
|
+
taint,
|
|
26248
|
+
imports,
|
|
26249
|
+
exports,
|
|
26250
|
+
unresolved,
|
|
26251
|
+
enriched,
|
|
26252
|
+
findings: findings.length > 0 ? findings : void 0,
|
|
26253
|
+
metrics: { file: filePath, metrics: metricValues }
|
|
26254
|
+
};
|
|
26255
|
+
} finally {
|
|
26256
|
+
disposeTree(tree);
|
|
26257
|
+
}
|
|
26202
26258
|
}
|
|
26203
26259
|
async function analyzeHtmlFile(code, filePath, options) {
|
|
26204
26260
|
logger.debug("Analyzing HTML file", { filePath, codeLength: code.length });
|
|
26205
26261
|
const tree = await parse(code, "html");
|
|
26206
|
-
|
|
26207
|
-
|
|
26208
|
-
|
|
26209
|
-
|
|
26210
|
-
|
|
26211
|
-
|
|
26212
|
-
|
|
26213
|
-
|
|
26214
|
-
|
|
26215
|
-
|
|
26216
|
-
|
|
26217
|
-
|
|
26218
|
-
|
|
26219
|
-
|
|
26220
|
-
|
|
26221
|
-
|
|
26222
|
-
|
|
26223
|
-
|
|
26224
|
-
|
|
26225
|
-
|
|
26226
|
-
|
|
26262
|
+
try {
|
|
26263
|
+
const meta = extractMeta(code, tree, filePath, "html");
|
|
26264
|
+
const { scriptBlocks, eventHandlers } = extractHtmlContent(tree.rootNode);
|
|
26265
|
+
logger.debug("HTML extraction", {
|
|
26266
|
+
filePath,
|
|
26267
|
+
inlineScripts: scriptBlocks.filter((b) => b.kind === "inline").length,
|
|
26268
|
+
externalScripts: scriptBlocks.filter((b) => b.kind === "external-src").length,
|
|
26269
|
+
eventHandlers: eventHandlers.length
|
|
26270
|
+
});
|
|
26271
|
+
const scriptResults = [];
|
|
26272
|
+
for (const block of scriptBlocks) {
|
|
26273
|
+
if (block.kind !== "inline" || !block.code.trim()) continue;
|
|
26274
|
+
const scriptLang = block.scriptType === "ts" || block.scriptType === "typescript" || block.scriptType === "text/typescript" ? "typescript" : "javascript";
|
|
26275
|
+
try {
|
|
26276
|
+
const ir = await analyze(block.code, filePath, scriptLang, options);
|
|
26277
|
+
scriptResults.push({ ir, lineOffset: block.lineOffset });
|
|
26278
|
+
} catch (e) {
|
|
26279
|
+
logger.warn("Failed to analyze script block", {
|
|
26280
|
+
filePath,
|
|
26281
|
+
lineOffset: block.lineOffset,
|
|
26282
|
+
error: e instanceof Error ? e.message : String(e)
|
|
26283
|
+
});
|
|
26284
|
+
}
|
|
26227
26285
|
}
|
|
26228
|
-
|
|
26229
|
-
|
|
26230
|
-
|
|
26231
|
-
|
|
26232
|
-
|
|
26233
|
-
|
|
26234
|
-
|
|
26235
|
-
|
|
26236
|
-
|
|
26237
|
-
|
|
26238
|
-
|
|
26239
|
-
|
|
26240
|
-
}
|
|
26286
|
+
for (const handler of eventHandlers) {
|
|
26287
|
+
const wrappedCode = `function __${handler.eventName}_handler() { ${handler.code} }`;
|
|
26288
|
+
try {
|
|
26289
|
+
const ir = await analyze(wrappedCode, filePath, "javascript", options);
|
|
26290
|
+
scriptResults.push({ ir, lineOffset: handler.line });
|
|
26291
|
+
} catch (e) {
|
|
26292
|
+
logger.warn("Failed to analyze event handler", {
|
|
26293
|
+
filePath,
|
|
26294
|
+
eventName: handler.eventName,
|
|
26295
|
+
line: handler.line,
|
|
26296
|
+
error: e instanceof Error ? e.message : String(e)
|
|
26297
|
+
});
|
|
26298
|
+
}
|
|
26241
26299
|
}
|
|
26300
|
+
const attributeFindings = runHtmlAttributeSecurityChecks(tree.rootNode, filePath);
|
|
26301
|
+
const result = mergeHtmlResults(meta, scriptResults, attributeFindings);
|
|
26302
|
+
logger.debug("HTML analysis complete", {
|
|
26303
|
+
filePath,
|
|
26304
|
+
scriptBlocks: scriptResults.length,
|
|
26305
|
+
attributeFindings: attributeFindings.length,
|
|
26306
|
+
totalFindings: result.findings?.length ?? 0
|
|
26307
|
+
});
|
|
26308
|
+
return result;
|
|
26309
|
+
} finally {
|
|
26310
|
+
disposeTree(tree);
|
|
26242
26311
|
}
|
|
26243
|
-
const attributeFindings = runHtmlAttributeSecurityChecks(tree.rootNode, filePath);
|
|
26244
|
-
const result = mergeHtmlResults(meta, scriptResults, attributeFindings);
|
|
26245
|
-
logger.debug("HTML analysis complete", {
|
|
26246
|
-
filePath,
|
|
26247
|
-
scriptBlocks: scriptResults.length,
|
|
26248
|
-
attributeFindings: attributeFindings.length,
|
|
26249
|
-
totalFindings: result.findings?.length ?? 0
|
|
26250
|
-
});
|
|
26251
|
-
return result;
|
|
26252
26312
|
}
|
|
26253
26313
|
async function analyzeForAPI(code, filePath, language, options = {}) {
|
|
26254
26314
|
const startTime = performance.now();
|
|
@@ -26258,75 +26318,79 @@ async function analyzeForAPI(code, filePath, language, options = {}) {
|
|
|
26258
26318
|
const parseStart = performance.now();
|
|
26259
26319
|
const tree = await parse(code, language);
|
|
26260
26320
|
const parseTime = performance.now() - parseStart;
|
|
26261
|
-
|
|
26262
|
-
|
|
26263
|
-
|
|
26264
|
-
|
|
26265
|
-
|
|
26266
|
-
|
|
26267
|
-
|
|
26268
|
-
|
|
26269
|
-
|
|
26270
|
-
filteredSinks
|
|
26271
|
-
|
|
26272
|
-
|
|
26273
|
-
|
|
26274
|
-
|
|
26275
|
-
|
|
26276
|
-
|
|
26277
|
-
|
|
26278
|
-
|
|
26279
|
-
|
|
26280
|
-
|
|
26281
|
-
|
|
26282
|
-
|
|
26283
|
-
|
|
26284
|
-
|
|
26285
|
-
|
|
26286
|
-
|
|
26287
|
-
|
|
26288
|
-
|
|
26289
|
-
|
|
26290
|
-
|
|
26291
|
-
|
|
26292
|
-
|
|
26293
|
-
|
|
26294
|
-
|
|
26295
|
-
|
|
26296
|
-
|
|
26297
|
-
|
|
26298
|
-
|
|
26299
|
-
|
|
26300
|
-
const
|
|
26301
|
-
|
|
26302
|
-
|
|
26303
|
-
|
|
26304
|
-
|
|
26305
|
-
|
|
26306
|
-
|
|
26307
|
-
|
|
26308
|
-
|
|
26309
|
-
|
|
26310
|
-
|
|
26311
|
-
|
|
26321
|
+
try {
|
|
26322
|
+
const analysisStart = performance.now();
|
|
26323
|
+
const nodeCache = collectAllNodes(tree.rootNode, getNodeTypesForLanguage(language));
|
|
26324
|
+
const types = extractTypes(tree, nodeCache, language);
|
|
26325
|
+
const calls = extractCalls(tree, nodeCache, language);
|
|
26326
|
+
const constPropResult = analyzeConstantPropagation(tree, code);
|
|
26327
|
+
const config = options.taintConfig ?? getDefaultConfig();
|
|
26328
|
+
const taint = analyzeTaint(calls, types, config, void 0, language);
|
|
26329
|
+
let filteredSinks = taint.sinks.filter((sink) => !constPropResult.unreachableLines.has(sink.line));
|
|
26330
|
+
filteredSinks = filterCleanVariableSinks(
|
|
26331
|
+
filteredSinks,
|
|
26332
|
+
calls,
|
|
26333
|
+
constPropResult.tainted,
|
|
26334
|
+
constPropResult.symbols,
|
|
26335
|
+
void 0,
|
|
26336
|
+
constPropResult.sanitizedVars,
|
|
26337
|
+
constPropResult.synchronizedLines
|
|
26338
|
+
);
|
|
26339
|
+
filteredSinks = filterSanitizedSinks(filteredSinks, taint.sanitizers ?? [], calls);
|
|
26340
|
+
let pythonTaintedVars = /* @__PURE__ */ new Map();
|
|
26341
|
+
if (language === "python") {
|
|
26342
|
+
pythonTaintedVars = buildPythonTaintedVars(code);
|
|
26343
|
+
const pythonSanitizedVars = buildPythonSanitizedVars(code, pythonTaintedVars);
|
|
26344
|
+
const sourceLines = code.split("\n");
|
|
26345
|
+
filteredSinks = filteredSinks.filter((sink) => {
|
|
26346
|
+
if (sink.type !== "xpath_injection") return true;
|
|
26347
|
+
const sinkLineText = sourceLines[sink.line - 1] ?? "";
|
|
26348
|
+
const taintedVarOnLine = [...pythonTaintedVars.keys()].find(
|
|
26349
|
+
(v) => new RegExp(`\\b${v}\\b`).test(sinkLineText)
|
|
26350
|
+
);
|
|
26351
|
+
if (!taintedVarOnLine) return false;
|
|
26352
|
+
if (pythonSanitizedVars.has(taintedVarOnLine)) return false;
|
|
26353
|
+
if (new RegExp(`\\.xpath\\s*\\([^)]*\\b\\w+\\s*=\\s*\\b${taintedVarOnLine}\\b`).test(sinkLineText)) return false;
|
|
26354
|
+
return true;
|
|
26355
|
+
});
|
|
26356
|
+
}
|
|
26357
|
+
const vulnerabilities = findVulnerabilities(taint.sources, filteredSinks, calls, constPropResult);
|
|
26358
|
+
if (language === "python") {
|
|
26359
|
+
const trustViolations = findPythonTrustBoundaryViolations(code, pythonTaintedVars);
|
|
26360
|
+
for (const v of trustViolations) {
|
|
26361
|
+
const alreadyReported = vulnerabilities.some(
|
|
26362
|
+
(existing) => existing.sink.line === v.sinkLine && existing.type === "trust_boundary"
|
|
26363
|
+
);
|
|
26364
|
+
if (!alreadyReported) {
|
|
26365
|
+
vulnerabilities.push({
|
|
26366
|
+
type: "trust_boundary",
|
|
26367
|
+
cwe: "CWE-501",
|
|
26368
|
+
severity: "medium",
|
|
26369
|
+
source: { line: v.sourceLine, type: "http_param" },
|
|
26370
|
+
sink: { line: v.sinkLine, type: "trust_boundary" },
|
|
26371
|
+
confidence: 0.85
|
|
26372
|
+
});
|
|
26373
|
+
}
|
|
26312
26374
|
}
|
|
26313
26375
|
}
|
|
26376
|
+
const analysisTime = performance.now() - analysisStart;
|
|
26377
|
+
const totalTime = performance.now() - startTime;
|
|
26378
|
+
return {
|
|
26379
|
+
success: true,
|
|
26380
|
+
analysis: {
|
|
26381
|
+
sources: taint.sources,
|
|
26382
|
+
sinks: filteredSinks,
|
|
26383
|
+
vulnerabilities
|
|
26384
|
+
},
|
|
26385
|
+
meta: {
|
|
26386
|
+
parseTimeMs: Math.round(parseTime),
|
|
26387
|
+
analysisTimeMs: Math.round(analysisTime),
|
|
26388
|
+
totalTimeMs: Math.round(totalTime)
|
|
26389
|
+
}
|
|
26390
|
+
};
|
|
26391
|
+
} finally {
|
|
26392
|
+
disposeTree(tree);
|
|
26314
26393
|
}
|
|
26315
|
-
const analysisTime = performance.now() - analysisStart;
|
|
26316
|
-
const totalTime = performance.now() - startTime;
|
|
26317
|
-
return {
|
|
26318
|
-
success: true,
|
|
26319
|
-
analysis: {
|
|
26320
|
-
sources: taint.sources,
|
|
26321
|
-
sinks: filteredSinks,
|
|
26322
|
-
vulnerabilities
|
|
26323
|
-
},
|
|
26324
|
-
meta: {
|
|
26325
|
-
parseTimeMs: Math.round(parseTime),
|
|
26326
|
-
analysisTimeMs: Math.round(analysisTime),
|
|
26327
|
-
totalTimeMs: Math.round(totalTime)
|
|
26328
|
-
}
|
|
26329
|
-
};
|
|
26330
26394
|
}
|
|
26331
26395
|
function findVulnerabilities(sources, sinks, calls, constPropResult) {
|
|
26332
26396
|
const vulnerabilities = [];
|