circle-ir 3.22.0 → 3.22.1

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.
@@ -6424,7 +6424,7 @@ function extractBashCommandInfo(node) {
6424
6424
  for (let i2 = 0; i2 < node.childCount; i2++) {
6425
6425
  const child = node.child(i2);
6426
6426
  if (!child) continue;
6427
- if (child === nameNode) continue;
6427
+ if (child === nameNode || child.id === nameNode.id) continue;
6428
6428
  if (child.type.includes("redirect") || child.type === "heredoc_body" || child.type === "file_descriptor") {
6429
6429
  continue;
6430
6430
  }
@@ -10096,9 +10096,8 @@ var DEFAULT_SINKS = [
10096
10096
  { method: "FlowExecution", class: "constructor", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
10097
10097
  // ActiveMQ control commands
10098
10098
  { method: "processControlCommand", class: "TransportConnection", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
10099
- // XStream deserialization (leads to RCE via gadget chains)
10100
- { method: "fromXML", class: "XStream", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
10101
- { method: "unmarshal", class: "XStream", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
10099
+ // XStream deserialization — classified as CWE-502 (deserialization), not CWE-78 (command injection).
10100
+ // The deserialization sink entries at lines ~1059 handle this correctly.
10102
10101
  { method: "fromString", class: "FileConverter", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
10103
10102
  // Plexus command line
10104
10103
  { method: "getPosition", class: "Commandline", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
@@ -10782,7 +10781,8 @@ var DEFAULT_SINKS = [
10782
10781
  { method: "query", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0] },
10783
10782
  { method: "query", class: "Pool", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0] },
10784
10783
  { method: "query", class: "Client", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0] },
10785
- { method: "query", type: "sql_injection", cwe: "CWE-89", severity: "high", arg_positions: [0] },
10784
+ // Note: classless { method: 'query' } removed too many FPs (UriComponentsBuilder.query(), etc.)
10785
+ // SQL query calls are covered by class-specific patterns above (Connection, Pool, Client, JdbcTemplate)
10786
10786
  { method: "raw", type: "sql_injection", cwe: "CWE-89", severity: "high", arg_positions: [0] },
10787
10787
  // Browser DOM XSS sinks
10788
10788
  { method: "setAttribute", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [1] },
@@ -10979,8 +10979,8 @@ var DEFAULT_SINKS = [
10979
10979
  { method: "execute", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0] },
10980
10980
  { method: "query_row", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0] },
10981
10981
  { method: "prepare", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0] },
10982
- // sqlx::query macro
10983
- { method: "query", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0] },
10982
+ // sqlx::query macro — use class-specific pattern
10983
+ { method: "query", class: "sqlx", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0] },
10984
10984
  // rusqlite specific
10985
10985
  { method: "prepare", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0] },
10986
10986
  { method: "execute", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0] },
@@ -11495,15 +11495,24 @@ function isInterproceduralTaintableType(typeName) {
11495
11495
  }
11496
11496
  function isParameterizedQueryCall(call, pattern) {
11497
11497
  if (pattern.type !== "sql_injection") return false;
11498
- if (call.arguments.length < 2) return false;
11499
- const secondArg = call.arguments.find((a) => a.position === 1);
11500
- if (!secondArg) return false;
11501
- if (secondArg.expression) {
11502
- const expr = secondArg.expression.trim();
11503
- if (expr.startsWith("[")) {
11498
+ const queryArg = call.arguments.find((a) => a.position === 0);
11499
+ if (queryArg) {
11500
+ const queryText = queryArg.literal ?? queryArg.expression ?? "";
11501
+ const hasPlaceholders = /(\?(?:\s|,|$|\))|\$\d+|:\w+|%s)/.test(queryText);
11502
+ const hasConcatenation = /\+\s*[^+]/.test(queryText) || queryText.includes("${");
11503
+ if (hasPlaceholders && !hasConcatenation && call.arguments.length >= 2) {
11504
11504
  return true;
11505
11505
  }
11506
11506
  }
11507
+ if (call.arguments.length >= 2) {
11508
+ const secondArg = call.arguments.find((a) => a.position === 1);
11509
+ if (secondArg?.expression) {
11510
+ const expr = secondArg.expression.trim();
11511
+ if (expr.startsWith("[")) {
11512
+ return true;
11513
+ }
11514
+ }
11515
+ }
11507
11516
  return false;
11508
11517
  }
11509
11518
  function findSinks(calls, patterns, typeHierarchy) {
@@ -11629,9 +11638,130 @@ var SAFE_RECEIVERS_BY_METHOD = {
11629
11638
  "stmt",
11630
11639
  "statement",
11631
11640
  "cursor"
11641
+ ]),
11642
+ // query() is only a SQL sink when receiver is a database handle — not URL builders,
11643
+ // DOM selectors, GraphQL clients, DNS resolvers, etc.
11644
+ query: /* @__PURE__ */ new Set([
11645
+ "uri",
11646
+ "url",
11647
+ "builder",
11648
+ "uribuilder",
11649
+ "uricomponents",
11650
+ "uricomponentsbuilder",
11651
+ "servleturicomponentsbuilder",
11652
+ "httpurl",
11653
+ "urlbuilder",
11654
+ "webclient",
11655
+ "request",
11656
+ "req",
11657
+ "router",
11658
+ "route",
11659
+ "app",
11660
+ "express",
11661
+ "parser",
11662
+ "selector",
11663
+ "jquery",
11664
+ "dom",
11665
+ "document",
11666
+ "element",
11667
+ "xmlpath",
11668
+ "xpath",
11669
+ "dns",
11670
+ "resolver",
11671
+ "graphql",
11672
+ "apollo",
11673
+ "querybuilder",
11674
+ "criteria"
11675
+ ]),
11676
+ // authenticate() — safe on auth framework objects (token verification, not code exec)
11677
+ authenticate: /* @__PURE__ */ new Set([
11678
+ "auth",
11679
+ "authenticator",
11680
+ "authmanager",
11681
+ "authprovider",
11682
+ "authenticationmanager",
11683
+ "authservice",
11684
+ "oauth",
11685
+ "token",
11686
+ "jwt",
11687
+ "passport",
11688
+ "session",
11689
+ "security",
11690
+ "credentials",
11691
+ "identityprovider",
11692
+ "ldap",
11693
+ "saml",
11694
+ "oidc"
11695
+ ]),
11696
+ // add() is extremely generic — safe on collections, UI containers, builders, etc.
11697
+ add: /* @__PURE__ */ new Set([
11698
+ "list",
11699
+ "set",
11700
+ "map",
11701
+ "collection",
11702
+ "array",
11703
+ "queue",
11704
+ "deque",
11705
+ "stack",
11706
+ "vector",
11707
+ "builder",
11708
+ "panel",
11709
+ "container",
11710
+ "group",
11711
+ "layout",
11712
+ "menu",
11713
+ "toolbar",
11714
+ "model",
11715
+ "registry",
11716
+ "context",
11717
+ "config",
11718
+ "options",
11719
+ "params",
11720
+ "headers",
11721
+ "attributes",
11722
+ "listeners",
11723
+ "handlers",
11724
+ "filters",
11725
+ "interceptors",
11726
+ "validators",
11727
+ "extensions",
11728
+ "plugins",
11729
+ "modules",
11730
+ "components",
11731
+ "children",
11732
+ "items",
11733
+ "elements",
11734
+ "entries",
11735
+ "rows",
11736
+ "columns",
11737
+ "fields",
11738
+ "properties",
11739
+ "descriptors",
11740
+ "nodes",
11741
+ "actions",
11742
+ "results",
11743
+ "errors",
11744
+ "warnings",
11745
+ "messages",
11746
+ "notifications",
11747
+ "events",
11748
+ "subscribers",
11749
+ "observers",
11750
+ "providers",
11751
+ "services",
11752
+ "beans",
11753
+ "tasks",
11754
+ "jobs",
11755
+ "workers",
11756
+ "threads",
11757
+ "schedulers"
11632
11758
  ])
11633
11759
  };
11634
- function isKnownSafeReceiverForMethod(receiver, method, _sinkType) {
11760
+ function isKnownSafeReceiverForMethod(receiver, method, sinkType) {
11761
+ const lowerMethod = method.toLowerCase();
11762
+ if ((lowerMethod === "fromxml" || lowerMethod === "unmarshal") && sinkType === "command_injection") {
11763
+ return true;
11764
+ }
11635
11765
  const safeReceivers = SAFE_RECEIVERS_BY_METHOD[method];
11636
11766
  if (!safeReceivers) return false;
11637
11767
  const lowerReceiver = receiver.toLowerCase();
@@ -11737,11 +11867,23 @@ function receiverMightBeClass(receiver, className) {
11737
11867
  }
11738
11868
  }
11739
11869
  }
11740
- if (lowerClass.includes(lowerReceiver)) {
11741
- return true;
11870
+ if (lowerReceiver.length >= 3 && lowerClass.includes(lowerReceiver)) {
11871
+ if (lowerReceiver.length >= 5 || lowerReceiver.length / lowerClass.length >= 0.4) {
11872
+ return true;
11873
+ }
11742
11874
  }
11743
- if (lowerClass.startsWith(lowerReceiver.substring(0, 3))) {
11744
- return true;
11875
+ if (lowerReceiver.length >= 2) {
11876
+ if (lowerClass.startsWith(lowerReceiver) || lowerClass.endsWith(lowerReceiver)) {
11877
+ return true;
11878
+ }
11879
+ }
11880
+ if (lowerReceiver.length >= 3) {
11881
+ const words = className.replace(/([a-z])([A-Z])/g, "$1\0$2").toLowerCase().split("\0");
11882
+ for (const word of words) {
11883
+ if (word.startsWith(lowerReceiver) && lowerReceiver.length / word.length >= 0.4) {
11884
+ return true;
11885
+ }
11886
+ }
11745
11887
  }
11746
11888
  const commonMappings = {
11747
11889
  // HTTP/Servlet
@@ -11762,8 +11904,10 @@ function receiverMightBeClass(receiver, className) {
11762
11904
  // Process/Runtime
11763
11905
  runtime: ["Runtime"],
11764
11906
  pb: ["ProcessBuilder"],
11765
- // Scripting
11907
+ // Scripting / Expression evaluation
11766
11908
  engine: ["ScriptEngine"],
11909
+ ev: ["ExpressionEvaluator", "ScriptEvaluator", "ClassBodyEvaluator"],
11910
+ evaluator: ["ExpressionEvaluator", "ScriptEvaluator", "ClassBodyEvaluator"],
11767
11911
  // LDAP
11768
11912
  ctx: ["Context", "InitialContext", "DirContext", "InitialDirContext", "LdapContext"],
11769
11913
  context: ["Context", "InitialContext", "DirContext", "InitialDirContext", "LdapContext"],
@@ -11853,12 +11997,25 @@ function receiverMightBeClass(receiver, className) {
11853
11997
  knex: ["knex"],
11854
11998
  prisma: ["prisma"],
11855
11999
  axios: ["axios"],
11856
- fetch: ["fetch"]
12000
+ fetch: ["fetch"],
12001
+ // Go idioms (single-letter receivers)
12002
+ r: ["Request"],
12003
+ w: ["ResponseWriter"]
11857
12004
  };
11858
12005
  const mappings = commonMappings[lowerReceiver];
11859
12006
  if (mappings && Array.isArray(mappings) && mappings.includes(className)) {
11860
12007
  return true;
11861
12008
  }
12009
+ const strippedReceiver = lowerReceiver.replace(/\d+$/, "");
12010
+ if (strippedReceiver !== lowerReceiver && strippedReceiver.length >= 2) {
12011
+ const strippedMappings = commonMappings[strippedReceiver];
12012
+ if (strippedMappings && Array.isArray(strippedMappings) && strippedMappings.includes(className)) {
12013
+ return true;
12014
+ }
12015
+ if (lowerClass.startsWith(strippedReceiver) || strippedReceiver.startsWith(lowerClass)) {
12016
+ return true;
12017
+ }
12018
+ }
11862
12019
  return false;
11863
12020
  }
11864
12021
  function formatCallLocation(call) {
@@ -6359,7 +6359,7 @@ function extractBashCommandInfo(node) {
6359
6359
  for (let i2 = 0; i2 < node.childCount; i2++) {
6360
6360
  const child = node.child(i2);
6361
6361
  if (!child) continue;
6362
- if (child === nameNode) continue;
6362
+ if (child === nameNode || child.id === nameNode.id) continue;
6363
6363
  if (child.type.includes("redirect") || child.type === "heredoc_body" || child.type === "file_descriptor") {
6364
6364
  continue;
6365
6365
  }
@@ -10031,9 +10031,8 @@ var DEFAULT_SINKS = [
10031
10031
  { method: "FlowExecution", class: "constructor", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
10032
10032
  // ActiveMQ control commands
10033
10033
  { method: "processControlCommand", class: "TransportConnection", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
10034
- // XStream deserialization (leads to RCE via gadget chains)
10035
- { method: "fromXML", class: "XStream", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
10036
- { method: "unmarshal", class: "XStream", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
10034
+ // XStream deserialization — classified as CWE-502 (deserialization), not CWE-78 (command injection).
10035
+ // The deserialization sink entries at lines ~1059 handle this correctly.
10037
10036
  { method: "fromString", class: "FileConverter", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
10038
10037
  // Plexus command line
10039
10038
  { method: "getPosition", class: "Commandline", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
@@ -10717,7 +10716,8 @@ var DEFAULT_SINKS = [
10717
10716
  { method: "query", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0] },
10718
10717
  { method: "query", class: "Pool", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0] },
10719
10718
  { method: "query", class: "Client", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0] },
10720
- { method: "query", type: "sql_injection", cwe: "CWE-89", severity: "high", arg_positions: [0] },
10719
+ // Note: classless { method: 'query' } removed too many FPs (UriComponentsBuilder.query(), etc.)
10720
+ // SQL query calls are covered by class-specific patterns above (Connection, Pool, Client, JdbcTemplate)
10721
10721
  { method: "raw", type: "sql_injection", cwe: "CWE-89", severity: "high", arg_positions: [0] },
10722
10722
  // Browser DOM XSS sinks
10723
10723
  { method: "setAttribute", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [1] },
@@ -10914,8 +10914,8 @@ var DEFAULT_SINKS = [
10914
10914
  { method: "execute", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0] },
10915
10915
  { method: "query_row", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0] },
10916
10916
  { method: "prepare", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0] },
10917
- // sqlx::query macro
10918
- { method: "query", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0] },
10917
+ // sqlx::query macro — use class-specific pattern
10918
+ { method: "query", class: "sqlx", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0] },
10919
10919
  // rusqlite specific
10920
10920
  { method: "prepare", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0] },
10921
10921
  { method: "execute", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0] },
@@ -11430,15 +11430,24 @@ function isInterproceduralTaintableType(typeName) {
11430
11430
  }
11431
11431
  function isParameterizedQueryCall(call, pattern) {
11432
11432
  if (pattern.type !== "sql_injection") return false;
11433
- if (call.arguments.length < 2) return false;
11434
- const secondArg = call.arguments.find((a) => a.position === 1);
11435
- if (!secondArg) return false;
11436
- if (secondArg.expression) {
11437
- const expr = secondArg.expression.trim();
11438
- if (expr.startsWith("[")) {
11433
+ const queryArg = call.arguments.find((a) => a.position === 0);
11434
+ if (queryArg) {
11435
+ const queryText = queryArg.literal ?? queryArg.expression ?? "";
11436
+ const hasPlaceholders = /(\?(?:\s|,|$|\))|\$\d+|:\w+|%s)/.test(queryText);
11437
+ const hasConcatenation = /\+\s*[^+]/.test(queryText) || queryText.includes("${");
11438
+ if (hasPlaceholders && !hasConcatenation && call.arguments.length >= 2) {
11439
11439
  return true;
11440
11440
  }
11441
11441
  }
11442
+ if (call.arguments.length >= 2) {
11443
+ const secondArg = call.arguments.find((a) => a.position === 1);
11444
+ if (secondArg?.expression) {
11445
+ const expr = secondArg.expression.trim();
11446
+ if (expr.startsWith("[")) {
11447
+ return true;
11448
+ }
11449
+ }
11450
+ }
11442
11451
  return false;
11443
11452
  }
11444
11453
  function findSinks(calls, patterns, typeHierarchy) {
@@ -11564,9 +11573,130 @@ var SAFE_RECEIVERS_BY_METHOD = {
11564
11573
  "stmt",
11565
11574
  "statement",
11566
11575
  "cursor"
11576
+ ]),
11577
+ // query() is only a SQL sink when receiver is a database handle — not URL builders,
11578
+ // DOM selectors, GraphQL clients, DNS resolvers, etc.
11579
+ query: /* @__PURE__ */ new Set([
11580
+ "uri",
11581
+ "url",
11582
+ "builder",
11583
+ "uribuilder",
11584
+ "uricomponents",
11585
+ "uricomponentsbuilder",
11586
+ "servleturicomponentsbuilder",
11587
+ "httpurl",
11588
+ "urlbuilder",
11589
+ "webclient",
11590
+ "request",
11591
+ "req",
11592
+ "router",
11593
+ "route",
11594
+ "app",
11595
+ "express",
11596
+ "parser",
11597
+ "selector",
11598
+ "jquery",
11599
+ "dom",
11600
+ "document",
11601
+ "element",
11602
+ "xmlpath",
11603
+ "xpath",
11604
+ "dns",
11605
+ "resolver",
11606
+ "graphql",
11607
+ "apollo",
11608
+ "querybuilder",
11609
+ "criteria"
11610
+ ]),
11611
+ // authenticate() — safe on auth framework objects (token verification, not code exec)
11612
+ authenticate: /* @__PURE__ */ new Set([
11613
+ "auth",
11614
+ "authenticator",
11615
+ "authmanager",
11616
+ "authprovider",
11617
+ "authenticationmanager",
11618
+ "authservice",
11619
+ "oauth",
11620
+ "token",
11621
+ "jwt",
11622
+ "passport",
11623
+ "session",
11624
+ "security",
11625
+ "credentials",
11626
+ "identityprovider",
11627
+ "ldap",
11628
+ "saml",
11629
+ "oidc"
11630
+ ]),
11631
+ // add() is extremely generic — safe on collections, UI containers, builders, etc.
11632
+ add: /* @__PURE__ */ new Set([
11633
+ "list",
11634
+ "set",
11635
+ "map",
11636
+ "collection",
11637
+ "array",
11638
+ "queue",
11639
+ "deque",
11640
+ "stack",
11641
+ "vector",
11642
+ "builder",
11643
+ "panel",
11644
+ "container",
11645
+ "group",
11646
+ "layout",
11647
+ "menu",
11648
+ "toolbar",
11649
+ "model",
11650
+ "registry",
11651
+ "context",
11652
+ "config",
11653
+ "options",
11654
+ "params",
11655
+ "headers",
11656
+ "attributes",
11657
+ "listeners",
11658
+ "handlers",
11659
+ "filters",
11660
+ "interceptors",
11661
+ "validators",
11662
+ "extensions",
11663
+ "plugins",
11664
+ "modules",
11665
+ "components",
11666
+ "children",
11667
+ "items",
11668
+ "elements",
11669
+ "entries",
11670
+ "rows",
11671
+ "columns",
11672
+ "fields",
11673
+ "properties",
11674
+ "descriptors",
11675
+ "nodes",
11676
+ "actions",
11677
+ "results",
11678
+ "errors",
11679
+ "warnings",
11680
+ "messages",
11681
+ "notifications",
11682
+ "events",
11683
+ "subscribers",
11684
+ "observers",
11685
+ "providers",
11686
+ "services",
11687
+ "beans",
11688
+ "tasks",
11689
+ "jobs",
11690
+ "workers",
11691
+ "threads",
11692
+ "schedulers"
11567
11693
  ])
11568
11694
  };
11569
- function isKnownSafeReceiverForMethod(receiver, method, _sinkType) {
11695
+ function isKnownSafeReceiverForMethod(receiver, method, sinkType) {
11696
+ const lowerMethod = method.toLowerCase();
11697
+ if ((lowerMethod === "fromxml" || lowerMethod === "unmarshal") && sinkType === "command_injection") {
11698
+ return true;
11699
+ }
11570
11700
  const safeReceivers = SAFE_RECEIVERS_BY_METHOD[method];
11571
11701
  if (!safeReceivers) return false;
11572
11702
  const lowerReceiver = receiver.toLowerCase();
@@ -11672,11 +11802,23 @@ function receiverMightBeClass(receiver, className) {
11672
11802
  }
11673
11803
  }
11674
11804
  }
11675
- if (lowerClass.includes(lowerReceiver)) {
11676
- return true;
11805
+ if (lowerReceiver.length >= 3 && lowerClass.includes(lowerReceiver)) {
11806
+ if (lowerReceiver.length >= 5 || lowerReceiver.length / lowerClass.length >= 0.4) {
11807
+ return true;
11808
+ }
11677
11809
  }
11678
- if (lowerClass.startsWith(lowerReceiver.substring(0, 3))) {
11679
- return true;
11810
+ if (lowerReceiver.length >= 2) {
11811
+ if (lowerClass.startsWith(lowerReceiver) || lowerClass.endsWith(lowerReceiver)) {
11812
+ return true;
11813
+ }
11814
+ }
11815
+ if (lowerReceiver.length >= 3) {
11816
+ const words = className.replace(/([a-z])([A-Z])/g, "$1\0$2").toLowerCase().split("\0");
11817
+ for (const word of words) {
11818
+ if (word.startsWith(lowerReceiver) && lowerReceiver.length / word.length >= 0.4) {
11819
+ return true;
11820
+ }
11821
+ }
11680
11822
  }
11681
11823
  const commonMappings = {
11682
11824
  // HTTP/Servlet
@@ -11697,8 +11839,10 @@ function receiverMightBeClass(receiver, className) {
11697
11839
  // Process/Runtime
11698
11840
  runtime: ["Runtime"],
11699
11841
  pb: ["ProcessBuilder"],
11700
- // Scripting
11842
+ // Scripting / Expression evaluation
11701
11843
  engine: ["ScriptEngine"],
11844
+ ev: ["ExpressionEvaluator", "ScriptEvaluator", "ClassBodyEvaluator"],
11845
+ evaluator: ["ExpressionEvaluator", "ScriptEvaluator", "ClassBodyEvaluator"],
11702
11846
  // LDAP
11703
11847
  ctx: ["Context", "InitialContext", "DirContext", "InitialDirContext", "LdapContext"],
11704
11848
  context: ["Context", "InitialContext", "DirContext", "InitialDirContext", "LdapContext"],
@@ -11788,12 +11932,25 @@ function receiverMightBeClass(receiver, className) {
11788
11932
  knex: ["knex"],
11789
11933
  prisma: ["prisma"],
11790
11934
  axios: ["axios"],
11791
- fetch: ["fetch"]
11935
+ fetch: ["fetch"],
11936
+ // Go idioms (single-letter receivers)
11937
+ r: ["Request"],
11938
+ w: ["ResponseWriter"]
11792
11939
  };
11793
11940
  const mappings = commonMappings[lowerReceiver];
11794
11941
  if (mappings && Array.isArray(mappings) && mappings.includes(className)) {
11795
11942
  return true;
11796
11943
  }
11944
+ const strippedReceiver = lowerReceiver.replace(/\d+$/, "");
11945
+ if (strippedReceiver !== lowerReceiver && strippedReceiver.length >= 2) {
11946
+ const strippedMappings = commonMappings[strippedReceiver];
11947
+ if (strippedMappings && Array.isArray(strippedMappings) && strippedMappings.includes(className)) {
11948
+ return true;
11949
+ }
11950
+ if (lowerClass.startsWith(strippedReceiver) || strippedReceiver.startsWith(lowerClass)) {
11951
+ return true;
11952
+ }
11953
+ }
11797
11954
  return false;
11798
11955
  }
11799
11956
  function formatCallLocation(call) {
@@ -954,7 +954,7 @@ function extractBashCommandInfo(node) {
954
954
  const child = node.child(i);
955
955
  if (!child)
956
956
  continue;
957
- if (child === nameNode)
957
+ if (child === nameNode || child.id === nameNode.id)
958
958
  continue;
959
959
  // Skip I/O redirects and heredoc operators
960
960
  if (child.type.includes('redirect') ||