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.
@@ -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
- { method: "execute", class: "Executor", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
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
- { method: "execute", class: "Executor", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
10057
- { method: "setCommandline", class: "Executor", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
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
- { method: "File", class: "constructor", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
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
- { method: "execSync", type: "command_injection", cwe: "CWE-78", severity: "high", arg_positions: [0] },
10681
- { method: "spawn", type: "command_injection", cwe: "CWE-78", severity: "high", arg_positions: [0] },
10682
- { method: "spawnSync", type: "command_injection", cwe: "CWE-78", severity: "high", arg_positions: [0] },
10683
- { method: "execFile", type: "command_injection", cwe: "CWE-78", severity: "high", arg_positions: [0] },
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
- { method: "query", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0] },
10697
- { method: "query", class: "Pool", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0] },
10698
- { method: "query", class: "Client", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0] },
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
- { method: "raw", type: "sql_injection", cwe: "CWE-89", severity: "high", arg_positions: [0] },
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
- { method: "eval", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
10757
- { method: "exec", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
10758
- { method: "compile", type: "code_injection", cwe: "CWE-94", severity: "high", arg_positions: [0] },
10759
- { method: "__import__", type: "code_injection", cwe: "CWE-94", severity: "high", arg_positions: [0] },
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
- { method: "execute", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0] },
10768
- { method: "executemany", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0] },
10769
- { method: "raw", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0] },
10770
- { method: "extra", type: "sql_injection", cwe: "CWE-89", severity: "high", arg_positions: [0] },
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
- { method: "open", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
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
- { method: "query", class: "Client", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0] },
10889
- { method: "execute", class: "Client", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0] },
10890
- { method: "query", class: "Pool", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0] },
10891
- { method: "execute", class: "Pool", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0] },
10892
- { method: "sql_query", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0] },
10893
- { method: "raw_sql", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0] },
10894
- { method: "execute", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0] },
10895
- { method: "query_row", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0] },
10896
- { method: "prepare", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0] },
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
- { method: "prepare", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0] },
10901
- { method: "execute", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0] },
10902
- { method: "query_map", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0] },
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
- { method: "read_dir", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
10907
- { method: "remove_file", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
10908
- { method: "remove_dir", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
10909
- { method: "remove_dir_all", type: "path_traversal", cwe: "CWE-22", severity: "critical", arg_positions: [0] },
10910
- { method: "copy", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0, 1] },
10911
- { method: "rename", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0, 1] },
10912
- { method: "write", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
10913
- { method: "read_to_string", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
10914
- { method: "create_dir", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
10915
- { method: "create_dir_all", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
10916
- { method: "metadata", type: "path_traversal", cwe: "CWE-22", severity: "medium", arg_positions: [0] },
10917
- { method: "symlink_metadata", type: "path_traversal", cwe: "CWE-22", severity: "medium", arg_positions: [0] },
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
- if (lowerReceiver.length >= 3 && lowerClass.includes(lowerReceiver)) {
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
- logger.trace("Parsed AST", { rootNodeType: tree.rootNode.type });
26098
- const nodeCache = collectAllNodes(tree.rootNode, getNodeTypesForLanguage(language));
26099
- const meta = extractMeta(code, tree, filePath, language);
26100
- const types = extractTypes(tree, nodeCache, language);
26101
- const calls = extractCalls(tree, nodeCache, language);
26102
- const imports = extractImports(tree, language);
26103
- const exports = extractExports(types);
26104
- const cfg = buildCFG(tree, language);
26105
- const dfg = buildDFG(tree, nodeCache, language);
26106
- const graph = new CodeGraph({
26107
- meta,
26108
- types,
26109
- calls,
26110
- cfg,
26111
- dfg,
26112
- taint: { sources: [], sinks: [], sanitizers: [] },
26113
- imports,
26114
- exports,
26115
- unresolved: [],
26116
- enriched: {}
26117
- });
26118
- const config = options.taintConfig ?? getDefaultConfig();
26119
- const disabledPasses = new Set(options.disabledPasses ?? []);
26120
- const passOpts = options.passOptions ?? {};
26121
- const pipeline = new AnalysisPipeline();
26122
- pipeline.add(new TaintMatcherPass());
26123
- pipeline.add(new ConstantPropagationPass(tree));
26124
- pipeline.add(new LanguageSourcesPass());
26125
- pipeline.add(new SinkFilterPass());
26126
- pipeline.add(new TaintPropagationPass());
26127
- pipeline.add(new InterproceduralPass());
26128
- if (!disabledPasses.has("scan-secrets")) pipeline.add(new ScanSecretsPass());
26129
- if (!disabledPasses.has("dead-code")) pipeline.add(new DeadCodePass());
26130
- if (!disabledPasses.has("missing-await")) pipeline.add(new MissingAwaitPass());
26131
- if (!disabledPasses.has("n-plus-one")) pipeline.add(new NPlusOnePass());
26132
- if (!disabledPasses.has("missing-public-doc")) pipeline.add(new MissingPublicDocPass());
26133
- if (!disabledPasses.has("todo-in-prod")) pipeline.add(new TodoInProdPass());
26134
- if (!disabledPasses.has("string-concat-loop")) pipeline.add(new StringConcatLoopPass());
26135
- if (!disabledPasses.has("sync-io-async")) pipeline.add(new SyncIoAsyncPass());
26136
- if (!disabledPasses.has("unchecked-return")) pipeline.add(new UncheckedReturnPass());
26137
- if (!disabledPasses.has("null-deref")) pipeline.add(new NullDerefPass());
26138
- if (!disabledPasses.has("resource-leak")) pipeline.add(new ResourceLeakPass());
26139
- if (!disabledPasses.has("variable-shadowing")) pipeline.add(new VariableShadowingPass());
26140
- if (!disabledPasses.has("leaked-global")) pipeline.add(new LeakedGlobalPass());
26141
- if (!disabledPasses.has("unused-variable")) pipeline.add(new UnusedVariablePass());
26142
- if (!disabledPasses.has("dependency-fan-out")) pipeline.add(new DependencyFanOutPass(passOpts.dependencyFanOut));
26143
- if (!disabledPasses.has("stale-doc-ref")) pipeline.add(new StaleDocRefPass());
26144
- if (!disabledPasses.has("infinite-loop")) pipeline.add(new InfiniteLoopPass());
26145
- if (!disabledPasses.has("deep-inheritance")) pipeline.add(new DeepInheritancePass());
26146
- if (!disabledPasses.has("redundant-loop-computation")) pipeline.add(new RedundantLoopPass());
26147
- if (!disabledPasses.has("unbounded-collection")) pipeline.add(new UnboundedCollectionPass(passOpts.unboundedCollection));
26148
- if (!disabledPasses.has("serial-await")) pipeline.add(new SerialAwaitPass());
26149
- if (!disabledPasses.has("react-inline-jsx")) pipeline.add(new ReactInlineJsxPass());
26150
- if (!disabledPasses.has("swallowed-exception")) pipeline.add(new SwallowedExceptionPass());
26151
- if (!disabledPasses.has("broad-catch")) pipeline.add(new BroadCatchPass());
26152
- if (!disabledPasses.has("unhandled-exception")) pipeline.add(new UnhandledExceptionPass());
26153
- if (!disabledPasses.has("double-close")) pipeline.add(new DoubleClosePass());
26154
- if (!disabledPasses.has("use-after-close")) pipeline.add(new UseAfterClosePass());
26155
- if (!disabledPasses.has("cleanup-verify")) pipeline.add(new CleanupVerifyPass());
26156
- if (!disabledPasses.has("missing-override")) pipeline.add(new MissingOverridePass());
26157
- if (!disabledPasses.has("unused-interface-method")) pipeline.add(new UnusedInterfaceMethodPass());
26158
- if (!disabledPasses.has("blocking-main-thread")) pipeline.add(new BlockingMainThreadPass());
26159
- if (!disabledPasses.has("excessive-allocation")) pipeline.add(new ExcessiveAllocationPass());
26160
- if (!disabledPasses.has("missing-stream")) pipeline.add(new MissingStreamPass());
26161
- if (!disabledPasses.has("god-class")) pipeline.add(new GodClassPass());
26162
- if (!disabledPasses.has("naming-convention")) pipeline.add(new NamingConventionPass(passOpts.namingConvention));
26163
- if (!disabledPasses.has("security-headers")) pipeline.add(new SecurityHeadersPass(passOpts.securityHeaders));
26164
- const { results, findings } = pipeline.run(graph, code, language, config);
26165
- const sinkFilter = results.get("sink-filter");
26166
- const interProc = results.get("interprocedural");
26167
- const taint = {
26168
- sources: sinkFilter.sources,
26169
- sinks: [...sinkFilter.sinks, ...interProc.additionalSinks],
26170
- sanitizers: sinkFilter.sanitizers,
26171
- flows: interProc.additionalFlows,
26172
- interprocedural: interProc.interprocedural
26173
- };
26174
- const unresolved = detectUnresolved(calls, types, dfg);
26175
- const enriched = buildEnriched(types, calls, taint.sources, taint.sinks);
26176
- const metricValues = new MetricRunner().run(
26177
- { meta, types, calls, cfg, dfg, taint, imports, exports, unresolved, enriched },
26178
- code,
26179
- language
26180
- );
26181
- logger.debug("Analysis complete", {
26182
- filePath,
26183
- finalSources: taint.sources.length,
26184
- finalSinks: taint.sinks.length,
26185
- flows: taint.flows?.length ?? 0,
26186
- unresolvedItems: unresolved.length
26187
- });
26188
- return {
26189
- meta,
26190
- types,
26191
- calls,
26192
- cfg,
26193
- dfg,
26194
- taint,
26195
- imports,
26196
- exports,
26197
- unresolved,
26198
- enriched,
26199
- findings: findings.length > 0 ? findings : void 0,
26200
- metrics: { file: filePath, metrics: metricValues }
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
- const meta = extractMeta(code, tree, filePath, "html");
26207
- const { scriptBlocks, eventHandlers } = extractHtmlContent(tree.rootNode);
26208
- logger.debug("HTML extraction", {
26209
- filePath,
26210
- inlineScripts: scriptBlocks.filter((b) => b.kind === "inline").length,
26211
- externalScripts: scriptBlocks.filter((b) => b.kind === "external-src").length,
26212
- eventHandlers: eventHandlers.length
26213
- });
26214
- const scriptResults = [];
26215
- for (const block of scriptBlocks) {
26216
- if (block.kind !== "inline" || !block.code.trim()) continue;
26217
- const scriptLang = block.scriptType === "ts" || block.scriptType === "typescript" || block.scriptType === "text/typescript" ? "typescript" : "javascript";
26218
- try {
26219
- const ir = await analyze(block.code, filePath, scriptLang, options);
26220
- scriptResults.push({ ir, lineOffset: block.lineOffset });
26221
- } catch (e) {
26222
- logger.warn("Failed to analyze script block", {
26223
- filePath,
26224
- lineOffset: block.lineOffset,
26225
- error: e instanceof Error ? e.message : String(e)
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
- for (const handler of eventHandlers) {
26230
- const wrappedCode = `function __${handler.eventName}_handler() { ${handler.code} }`;
26231
- try {
26232
- const ir = await analyze(wrappedCode, filePath, "javascript", options);
26233
- scriptResults.push({ ir, lineOffset: handler.line });
26234
- } catch (e) {
26235
- logger.warn("Failed to analyze event handler", {
26236
- filePath,
26237
- eventName: handler.eventName,
26238
- line: handler.line,
26239
- error: e instanceof Error ? e.message : String(e)
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
- const analysisStart = performance.now();
26262
- const nodeCache = collectAllNodes(tree.rootNode, getNodeTypesForLanguage(language));
26263
- const types = extractTypes(tree, nodeCache, language);
26264
- const calls = extractCalls(tree, nodeCache, language);
26265
- const constPropResult = analyzeConstantPropagation(tree, code);
26266
- const config = options.taintConfig ?? getDefaultConfig();
26267
- const taint = analyzeTaint(calls, types, config);
26268
- let filteredSinks = taint.sinks.filter((sink) => !constPropResult.unreachableLines.has(sink.line));
26269
- filteredSinks = filterCleanVariableSinks(
26270
- filteredSinks,
26271
- calls,
26272
- constPropResult.tainted,
26273
- constPropResult.symbols,
26274
- void 0,
26275
- constPropResult.sanitizedVars,
26276
- constPropResult.synchronizedLines
26277
- );
26278
- filteredSinks = filterSanitizedSinks(filteredSinks, taint.sanitizers ?? [], calls);
26279
- let pythonTaintedVars = /* @__PURE__ */ new Map();
26280
- if (language === "python") {
26281
- pythonTaintedVars = buildPythonTaintedVars(code);
26282
- const pythonSanitizedVars = buildPythonSanitizedVars(code, pythonTaintedVars);
26283
- const sourceLines = code.split("\n");
26284
- filteredSinks = filteredSinks.filter((sink) => {
26285
- if (sink.type !== "xpath_injection") return true;
26286
- const sinkLineText = sourceLines[sink.line - 1] ?? "";
26287
- const taintedVarOnLine = [...pythonTaintedVars.keys()].find(
26288
- (v) => new RegExp(`\\b${v}\\b`).test(sinkLineText)
26289
- );
26290
- if (!taintedVarOnLine) return false;
26291
- if (pythonSanitizedVars.has(taintedVarOnLine)) return false;
26292
- if (new RegExp(`\\.xpath\\s*\\([^)]*\\b\\w+\\s*=\\s*\\b${taintedVarOnLine}\\b`).test(sinkLineText)) return false;
26293
- return true;
26294
- });
26295
- }
26296
- const vulnerabilities = findVulnerabilities(taint.sources, filteredSinks, calls, constPropResult);
26297
- if (language === "python") {
26298
- const trustViolations = findPythonTrustBoundaryViolations(code, pythonTaintedVars);
26299
- for (const v of trustViolations) {
26300
- const alreadyReported = vulnerabilities.some(
26301
- (existing) => existing.sink.line === v.sinkLine && existing.type === "trust_boundary"
26302
- );
26303
- if (!alreadyReported) {
26304
- vulnerabilities.push({
26305
- type: "trust_boundary",
26306
- cwe: "CWE-501",
26307
- severity: "medium",
26308
- source: { line: v.sourceLine, type: "http_param" },
26309
- sink: { line: v.sinkLine, type: "trust_boundary" },
26310
- confidence: 0.85
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 = [];