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.
- package/README.md +5 -1
- package/dist/analysis/config-loader.js +6 -6
- package/dist/analysis/config-loader.js.map +1 -1
- package/dist/analysis/taint-matcher.js +115 -22
- package/dist/analysis/taint-matcher.js.map +1 -1
- package/dist/browser/circle-ir.js +177 -20
- package/dist/core/circle-ir-core.cjs +177 -20
- package/dist/core/circle-ir-core.js +177 -20
- package/dist/core/extractors/calls.js +1 -1
- package/dist/core/extractors/calls.js.map +1 -1
- package/docs/SPEC.md +1 -1
- package/package.json +3 -1
|
@@ -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 (
|
|
10100
|
-
|
|
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:
|
|
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
|
-
|
|
11499
|
-
|
|
11500
|
-
|
|
11501
|
-
|
|
11502
|
-
const
|
|
11503
|
-
if (
|
|
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,
|
|
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
|
-
|
|
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 (
|
|
11744
|
-
|
|
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 (
|
|
10035
|
-
|
|
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:
|
|
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
|
-
|
|
11434
|
-
|
|
11435
|
-
|
|
11436
|
-
|
|
11437
|
-
const
|
|
11438
|
-
if (
|
|
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,
|
|
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
|
-
|
|
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 (
|
|
11679
|
-
|
|
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') ||
|