circle-ir 4.9.9 → 4.9.11
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/dist/analysis/config-loader.d.ts.map +1 -1
- package/dist/analysis/config-loader.js +56 -0
- package/dist/analysis/config-loader.js.map +1 -1
- package/dist/analysis/findings.d.ts.map +1 -1
- package/dist/analysis/findings.js +8 -2
- package/dist/analysis/findings.js.map +1 -1
- package/dist/analysis/passes/language-sources-pass.d.ts.map +1 -1
- package/dist/analysis/passes/language-sources-pass.js +26 -5
- package/dist/analysis/passes/language-sources-pass.js.map +1 -1
- package/dist/analysis/passes/taint-propagation-pass.d.ts.map +1 -1
- package/dist/analysis/passes/taint-propagation-pass.js +76 -4
- package/dist/analysis/passes/taint-propagation-pass.js.map +1 -1
- package/dist/analysis/taint-matcher.d.ts +0 -8
- package/dist/analysis/taint-matcher.d.ts.map +1 -1
- package/dist/analysis/taint-matcher.js +105 -3
- package/dist/analysis/taint-matcher.js.map +1 -1
- package/dist/analysis/taint-propagation.d.ts.map +1 -1
- package/dist/analysis/taint-propagation.js +15 -0
- package/dist/analysis/taint-propagation.js.map +1 -1
- package/dist/browser/circle-ir.js +231 -21
- package/dist/core/circle-ir-core.cjs +154 -5
- package/dist/core/circle-ir-core.js +154 -5
- package/dist/core/extractors/calls.js +102 -3
- package/dist/core/extractors/calls.js.map +1 -1
- package/dist/languages/plugins/rust.d.ts.map +1 -1
- package/dist/languages/plugins/rust.js +5 -8
- package/dist/languages/plugins/rust.js.map +1 -1
- package/package.json +1 -1
|
@@ -6049,6 +6049,26 @@ var CSHARP_COMMAND_EXECUTE_METHODS = /* @__PURE__ */ new Set([
|
|
|
6049
6049
|
"ExecuteReaderAsync",
|
|
6050
6050
|
"ExecuteNonQueryAsync"
|
|
6051
6051
|
]);
|
|
6052
|
+
var CSHARP_RECEIVER_URL_METHODS = /* @__PURE__ */ new Set([
|
|
6053
|
+
"GetAsync",
|
|
6054
|
+
"PostAsync",
|
|
6055
|
+
"PutAsync",
|
|
6056
|
+
"PatchAsync",
|
|
6057
|
+
"DeleteAsync",
|
|
6058
|
+
"HeadAsync",
|
|
6059
|
+
"GetStringAsync",
|
|
6060
|
+
"GetByteArrayAsync",
|
|
6061
|
+
"GetStreamAsync",
|
|
6062
|
+
"GetJsonAsync"
|
|
6063
|
+
]);
|
|
6064
|
+
var CSHARP_HEADER_RECEIVER_RE = /(^|\.)Headers$/;
|
|
6065
|
+
function csharpBareName(node) {
|
|
6066
|
+
if (node.type === "generic_name") {
|
|
6067
|
+
const id = node.childForFieldName("name") ?? node.namedChild(0);
|
|
6068
|
+
if (id) return getNodeText(id);
|
|
6069
|
+
}
|
|
6070
|
+
return getNodeText(node);
|
|
6071
|
+
}
|
|
6052
6072
|
function extractCSharpCalls(tree, cache) {
|
|
6053
6073
|
const calls = [];
|
|
6054
6074
|
const typeMap = buildCSharpReceiverTypeMap(tree, cache);
|
|
@@ -6060,16 +6080,22 @@ function extractCSharpCalls(tree, cache) {
|
|
|
6060
6080
|
if (fn?.type === "member_access_expression") {
|
|
6061
6081
|
const nameNode = fn.childForFieldName("name");
|
|
6062
6082
|
const exprNode = fn.childForFieldName("expression");
|
|
6063
|
-
methodName = nameNode ?
|
|
6083
|
+
methodName = nameNode ? csharpBareName(nameNode) : "unknown";
|
|
6064
6084
|
receiver = exprNode ? getNodeText(exprNode) : null;
|
|
6065
6085
|
} else if (fn) {
|
|
6066
|
-
methodName =
|
|
6086
|
+
methodName = csharpBareName(fn);
|
|
6067
6087
|
}
|
|
6068
6088
|
const argsNode = inv.childForFieldName("arguments");
|
|
6069
6089
|
let args2 = argsNode ? extractCSharpArguments(argsNode) : [];
|
|
6070
6090
|
if (receiver && CSHARP_COMMAND_EXECUTE_METHODS.has(methodName)) {
|
|
6071
6091
|
args2 = [{ position: 0, expression: receiver, variable: receiver, literal: null, value: null }, ...args2];
|
|
6072
6092
|
}
|
|
6093
|
+
if (methodName === "Add" && receiver && CSHARP_HEADER_RECEIVER_RE.test(receiver)) {
|
|
6094
|
+
methodName = "AddHeader";
|
|
6095
|
+
}
|
|
6096
|
+
if (receiver && args2.length === 0 && CSHARP_RECEIVER_URL_METHODS.has(methodName)) {
|
|
6097
|
+
args2 = [{ position: 0, expression: receiver, variable: receiver, literal: null, value: null }];
|
|
6098
|
+
}
|
|
6073
6099
|
calls.push({
|
|
6074
6100
|
method_name: methodName,
|
|
6075
6101
|
receiver,
|
|
@@ -6085,7 +6111,7 @@ function extractCSharpCalls(tree, cache) {
|
|
|
6085
6111
|
const typeNode = creation.childForFieldName("type");
|
|
6086
6112
|
const argsNode = creation.childForFieldName("arguments");
|
|
6087
6113
|
calls.push({
|
|
6088
|
-
method_name: typeNode ?
|
|
6114
|
+
method_name: typeNode ? csharpBareName(typeNode) : "unknown",
|
|
6089
6115
|
receiver: null,
|
|
6090
6116
|
receiver_type: null,
|
|
6091
6117
|
receiver_type_fqn: null,
|
|
@@ -6098,6 +6124,39 @@ function extractCSharpCalls(tree, cache) {
|
|
|
6098
6124
|
const assignments = getNodesFromCache(tree.rootNode, "assignment_expression", cache);
|
|
6099
6125
|
for (const asn of assignments) {
|
|
6100
6126
|
const left = asn.childForFieldName("left");
|
|
6127
|
+
if (left?.type === "element_access_expression") {
|
|
6128
|
+
const target = left.childForFieldName("expression");
|
|
6129
|
+
const right2 = asn.childForFieldName("right");
|
|
6130
|
+
if (!target || !right2) continue;
|
|
6131
|
+
if (!CSHARP_HEADER_RECEIVER_RE.test(getNodeText(target))) continue;
|
|
6132
|
+
const subscript = left.childForFieldName("subscript") ?? left.namedChild(1);
|
|
6133
|
+
const rhsText2 = getNodeText(right2);
|
|
6134
|
+
calls.push({
|
|
6135
|
+
method_name: "AddHeader",
|
|
6136
|
+
receiver: getNodeText(target),
|
|
6137
|
+
receiver_type: null,
|
|
6138
|
+
receiver_type_fqn: null,
|
|
6139
|
+
arguments: [
|
|
6140
|
+
{
|
|
6141
|
+
position: 0,
|
|
6142
|
+
expression: subscript ? getNodeText(subscript) : "",
|
|
6143
|
+
variable: null,
|
|
6144
|
+
literal: subscript ? getNodeText(subscript) : null,
|
|
6145
|
+
value: null
|
|
6146
|
+
},
|
|
6147
|
+
{
|
|
6148
|
+
position: 1,
|
|
6149
|
+
expression: rhsText2,
|
|
6150
|
+
variable: right2.type === "identifier" ? rhsText2 : null,
|
|
6151
|
+
literal: right2.type === "string_literal" ? rhsText2 : null,
|
|
6152
|
+
value: null
|
|
6153
|
+
}
|
|
6154
|
+
],
|
|
6155
|
+
location: { line: asn.startPosition.row + 1, column: asn.startPosition.column },
|
|
6156
|
+
in_method: findEnclosingMethod(asn)
|
|
6157
|
+
});
|
|
6158
|
+
continue;
|
|
6159
|
+
}
|
|
6101
6160
|
if (left?.type !== "member_access_expression") continue;
|
|
6102
6161
|
const nameNode = left.childForFieldName("name");
|
|
6103
6162
|
if (!nameNode) continue;
|
|
@@ -13317,6 +13376,23 @@ var DEFAULT_SINKS = [
|
|
|
13317
13376
|
{ method: "ExecuteSqlRawAsync", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13318
13377
|
{ method: "FromSqlRaw", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13319
13378
|
{ method: "FromSqlRawAsync", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13379
|
+
// Dapper micro-ORM (cognium-dev#275). Every Dapper entry point is an
|
|
13380
|
+
// `IDbConnection` extension whose arg 0 is raw SQL text — `conn.Query<T>(sql)`
|
|
13381
|
+
// is exactly as injectable as `new SqlCommand(sql)`. Taint-gated on arg 0, so
|
|
13382
|
+
// a constant/interpolation-free query never fires, and a same-named method on
|
|
13383
|
+
// an unrelated receiver (e.g. RestSharp's `client.Execute(request)`, whose
|
|
13384
|
+
// arg 0 is a request object rather than a tainted string) stays clean.
|
|
13385
|
+
{ method: "Query", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13386
|
+
{ method: "QueryAsync", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13387
|
+
{ method: "QueryFirst", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13388
|
+
{ method: "QueryFirstAsync", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13389
|
+
{ method: "QueryFirstOrDefault", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13390
|
+
{ method: "QueryFirstOrDefaultAsync", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13391
|
+
{ method: "QuerySingle", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13392
|
+
{ method: "QuerySingleOrDefault", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13393
|
+
{ method: "QueryMultiple", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13394
|
+
{ method: "Execute", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13395
|
+
{ method: "ExecuteAsync", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13320
13396
|
// ADO.NET `cmd.CommandText = "…" + x` — emitted as a synthetic call by
|
|
13321
13397
|
// extractCSharpCalls (property-assignment sink; the ctor-arg sink misses it).
|
|
13322
13398
|
{ method: "CommandText", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
@@ -13382,12 +13458,23 @@ var DEFAULT_SINKS = [
|
|
|
13382
13458
|
{ method: "DeleteAsync", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13383
13459
|
{ method: "GetStringAsync", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13384
13460
|
{ method: "GetByteArrayAsync", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13461
|
+
// Remaining HttpClient/Flurl verbs (cognium-dev#275). For HttpClient these
|
|
13462
|
+
// take the URL at arg 0; for Flurl's fluent form the receiver is surfaced as
|
|
13463
|
+
// arg 0 by CSHARP_RECEIVER_URL_METHODS. Taint-gated either way.
|
|
13464
|
+
// (GetStreamAsync / PutAsync / DeleteAsync are already registered below.)
|
|
13465
|
+
{ method: "GetJsonAsync", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13466
|
+
{ method: "PatchAsync", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13467
|
+
{ method: "HeadAsync", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13385
13468
|
{ method: "GetStreamAsync", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13386
13469
|
{ method: "SendAsync", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13387
13470
|
{ method: "DownloadString", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13388
13471
|
{ method: "DownloadData", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13389
13472
|
{ method: "DownloadFile", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [1], languages: ["csharp"] },
|
|
13390
13473
|
{ method: "Create", class: "WebRequest", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13474
|
+
// RestSharp (cognium-dev#275) — the base URL is fixed at construction, so
|
|
13475
|
+
// `new RestClient(userUrl)` points every later request at attacker-controlled
|
|
13476
|
+
// infrastructure. Ctor arg 0, taint-gated.
|
|
13477
|
+
{ method: "RestClient", class: "constructor", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13391
13478
|
// C# code injection — dynamic script/assembly loading (CWE-94).
|
|
13392
13479
|
{ method: "EvaluateAsync", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13393
13480
|
{ method: "RunAsync", class: "CSharpScript", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
@@ -13427,6 +13514,11 @@ var DEFAULT_SINKS = [
|
|
|
13427
13514
|
// Blazor `new MarkupString(x)` renders its argument as raw HTML (the framework's
|
|
13428
13515
|
// documented "trusted markup" escape hatch) — attacker-controlled input is XSS. (ca#275)
|
|
13429
13516
|
{ method: "MarkupString", class: "constructor", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13517
|
+
// `return Content(html, "text/html")` on a controller writes arg 0 to the
|
|
13518
|
+
// response body unescaped (cognium-dev#275). Taint-gated on arg 0; the common
|
|
13519
|
+
// `Content(constantString)` and the JSON/plain-text content types are
|
|
13520
|
+
// unaffected because a constant argument carries no taint.
|
|
13521
|
+
{ method: "Content", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13430
13522
|
// `String.Format(fmt, …)` with an attacker-controlled FORMAT string — CWE-134.
|
|
13431
13523
|
// Taint-gated on arg 0 (the format), so ordinary `String.Format("...", user)` where
|
|
13432
13524
|
// only an argument is tainted never fires. .NET composite formatting can't corrupt
|
|
@@ -13452,6 +13544,10 @@ var DEFAULT_SINKS = [
|
|
|
13452
13544
|
// cognium-dev#273/#275). `BsonDocument.Parse(userJson)` deserializes an
|
|
13453
13545
|
// attacker-controlled query document. Class-scoped (static receiver resolves).
|
|
13454
13546
|
{ method: "Parse", class: "BsonDocument", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13547
|
+
// `new BsonJavaScript(userCode)` wraps a server-side JS string — the payload
|
|
13548
|
+
// MongoDB's `$where` / `$function` operators execute. Unconditionally
|
|
13549
|
+
// dangerous when tainted, so no literal gating is needed (cognium-dev#275).
|
|
13550
|
+
{ method: "BsonJavaScript", class: "constructor", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13455
13551
|
// C# log injection — Microsoft.Extensions.Logging `ILogger` (CWE-117,
|
|
13456
13552
|
// cognium-ai#318). Only the message TEMPLATE (arg[0]) is the injectable
|
|
13457
13553
|
// position; the structured form `LogInformation("… {Id}", id)` keeps taint in
|
|
@@ -13463,6 +13559,16 @@ var DEFAULT_SINKS = [
|
|
|
13463
13559
|
{ method: "LogDebug", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0], languages: ["csharp"] },
|
|
13464
13560
|
{ method: "LogCritical", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0], languages: ["csharp"] },
|
|
13465
13561
|
{ method: "LogTrace", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0], languages: ["csharp"] },
|
|
13562
|
+
// C# ReDoS (CWE-1333, cognium-dev#275). The injectable position is the
|
|
13563
|
+
// PATTERN, not the input: a user-supplied regex can be crafted with
|
|
13564
|
+
// catastrophic backtracking. `Regex.IsMatch(input, pattern)` and friends take
|
|
13565
|
+
// the pattern at arg 1; the `new Regex(pattern)` ctor takes it at arg 0. A
|
|
13566
|
+
// tainted *input* with a constant pattern is not ReDoS and does not fire.
|
|
13567
|
+
{ method: "IsMatch", class: "Regex", type: "redos", cwe: "CWE-1333", severity: "medium", arg_positions: [1], languages: ["csharp"] },
|
|
13568
|
+
{ method: "Match", class: "Regex", type: "redos", cwe: "CWE-1333", severity: "medium", arg_positions: [1], languages: ["csharp"] },
|
|
13569
|
+
{ method: "Matches", class: "Regex", type: "redos", cwe: "CWE-1333", severity: "medium", arg_positions: [1], languages: ["csharp"] },
|
|
13570
|
+
{ method: "Replace", class: "Regex", type: "redos", cwe: "CWE-1333", severity: "medium", arg_positions: [1], languages: ["csharp"] },
|
|
13571
|
+
{ method: "Regex", class: "constructor", type: "redos", cwe: "CWE-1333", severity: "medium", arg_positions: [0], languages: ["csharp"] },
|
|
13466
13572
|
// Generic `ILogger.Log(logLevel, message, …)` — class-scoped (`Log` alone is
|
|
13467
13573
|
// too generic); the message is arg[1].
|
|
13468
13574
|
{ method: "Log", class: "ILogger", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [1], languages: ["csharp"] },
|
|
@@ -13721,6 +13827,15 @@ var DEFAULT_SANITIZERS = [
|
|
|
13721
13827
|
// this table; the drift surfaced as a SecuriBench Micro FP on
|
|
13722
13828
|
// `sanitizers/Sanitizers3.java` (`URLEncoder.encode` + `sendRedirect`).
|
|
13723
13829
|
{ method: "encodeForURL", removes: ["xss", "ssrf", "open_redirect"] },
|
|
13830
|
+
// ASP.NET Core local-redirect guards (cognium-dev#275). `Url.IsLocalUrl(x)`
|
|
13831
|
+
// returns false for any absolute/scheme-relative URL, so a `Redirect(x)` gated
|
|
13832
|
+
// on it cannot leave the site — without this credit the guarded form is a
|
|
13833
|
+
// false positive. `LocalRedirect(x)` is the same check applied by the
|
|
13834
|
+
// framework itself: it THROWS on a non-local URL, which is why it is
|
|
13835
|
+
// deliberately not registered as an open_redirect sink. Recording it here
|
|
13836
|
+
// states that intent in the model rather than leaving it as a silent omission.
|
|
13837
|
+
{ method: "IsLocalUrl", removes: ["open_redirect"] },
|
|
13838
|
+
{ method: "LocalRedirect", removes: ["open_redirect"] },
|
|
13724
13839
|
// URL encoding wrapper aliases (common patterns in benchmarks and real-world code)
|
|
13725
13840
|
{ method: "encodeURL", removes: ["xss", "ssrf", "open_redirect"] },
|
|
13726
13841
|
{ method: "urlEncode", removes: ["xss", "ssrf", "open_redirect"] },
|
|
@@ -14241,14 +14356,30 @@ var PYTHON_TAINTED_PATTERNS = [
|
|
|
14241
14356
|
// regex registry so it's recognized in subscript/var contexts.
|
|
14242
14357
|
{ pattern: /\binput\s*\(/, sourceType: "io_input" }
|
|
14243
14358
|
];
|
|
14359
|
+
function dropCSharpObjectCarriedWaypoints(sinks, calls) {
|
|
14360
|
+
const zeroArgCtors = /* @__PURE__ */ new Set();
|
|
14361
|
+
for (const call of calls) {
|
|
14362
|
+
if (call.is_constructor && call.arguments.length === 0) {
|
|
14363
|
+
zeroArgCtors.add(`${call.location.line}:${call.method_name}`);
|
|
14364
|
+
}
|
|
14365
|
+
}
|
|
14366
|
+
return sinks.filter((sink) => {
|
|
14367
|
+
if (sink.type !== "sql_injection") return true;
|
|
14368
|
+
if (sink.method && zeroArgCtors.has(`${sink.line}:${sink.method}`)) {
|
|
14369
|
+
return false;
|
|
14370
|
+
}
|
|
14371
|
+
return true;
|
|
14372
|
+
});
|
|
14373
|
+
}
|
|
14244
14374
|
function analyzeTaint(calls, types, config = getDefaultConfig(), typeHierarchy, language, code) {
|
|
14245
14375
|
const sourceLines = code !== void 0 ? code.split("\n") : void 0;
|
|
14246
14376
|
const sources = findSources(calls, types, config.sources, sourceLines, language);
|
|
14247
14377
|
let sinkPatterns = expandPromisifyAliases(config.sinks, sourceLines, language);
|
|
14248
14378
|
sinkPatterns = expandIndirectEvalAliases(sinkPatterns, sourceLines, language);
|
|
14249
14379
|
const sinks = findSinks(calls, sinkPatterns, typeHierarchy, language, sourceLines, types);
|
|
14380
|
+
const gatedSinks = language === "csharp" ? dropCSharpObjectCarriedWaypoints(sinks, calls) : sinks;
|
|
14250
14381
|
const sanitizers = findSanitizers(calls, types, config.sanitizers, sourceLines);
|
|
14251
|
-
return { sources, sinks, sanitizers };
|
|
14382
|
+
return { sources, sinks: gatedSinks, sanitizers };
|
|
14252
14383
|
}
|
|
14253
14384
|
function sinkPatternAppliesTo(pattern, language) {
|
|
14254
14385
|
if (language === void 0) return true;
|
|
@@ -14519,7 +14650,7 @@ function findSources(calls, types, patterns, sourceLines, language) {
|
|
|
14519
14650
|
}
|
|
14520
14651
|
const sourceMap = /* @__PURE__ */ new Map();
|
|
14521
14652
|
for (const source of sources) {
|
|
14522
|
-
const key = `${source.line}:${source.type}`;
|
|
14653
|
+
const key = source.type === "interprocedural_param" && source.variable ? `${source.line}:${source.type}:${source.variable}` : `${source.line}:${source.type}`;
|
|
14523
14654
|
const existing = sourceMap.get(key);
|
|
14524
14655
|
if (!existing || source.confidence > existing.confidence) {
|
|
14525
14656
|
sourceMap.set(key, source);
|
|
@@ -15137,6 +15268,15 @@ var CWE_78_RECEIVER_ALLOWLIST = /* @__PURE__ */ new Set([
|
|
|
15137
15268
|
// hutool
|
|
15138
15269
|
"RuntimeUtil"
|
|
15139
15270
|
]);
|
|
15271
|
+
function isRegexReceiver(receiver, sourceLines) {
|
|
15272
|
+
const r = (receiver ?? "").trim();
|
|
15273
|
+
if (r.length === 0) return false;
|
|
15274
|
+
if (r.startsWith("/") && /\/[a-z]*$/.test(r)) return true;
|
|
15275
|
+
if (/^(?:new\s+)?RegExp\s*\(/.test(r)) return true;
|
|
15276
|
+
if (!/^[A-Za-z_$][\w$]*$/.test(r) || !sourceLines) return false;
|
|
15277
|
+
const decl = new RegExp(`(?:const|let|var)\\s+${escapeRe(r)}\\s*=\\s*(?:/|(?:new\\s+)?RegExp\\s*\\()`);
|
|
15278
|
+
return sourceLines.some((l) => decl.test(l));
|
|
15279
|
+
}
|
|
15140
15280
|
function isFunctionCallbackArgument(arg) {
|
|
15141
15281
|
if (arg.literal !== null && arg.literal !== void 0) return false;
|
|
15142
15282
|
const expr = (arg.expression ?? "").trim();
|
|
@@ -15319,6 +15459,12 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types)
|
|
|
15319
15459
|
continue;
|
|
15320
15460
|
}
|
|
15321
15461
|
}
|
|
15462
|
+
if (pattern.type === "command_injection" && call.method_name === "exec" && (language === "javascript" || language === "typescript") && isRegexReceiver(call.receiver, sourceLines)) {
|
|
15463
|
+
continue;
|
|
15464
|
+
}
|
|
15465
|
+
if (pattern.type === "code_injection" && language === "python" && call.method_name === "compile" && call.receiver && call.receiver !== "builtins") {
|
|
15466
|
+
continue;
|
|
15467
|
+
}
|
|
15322
15468
|
if (isSafeGoJsonUnmarshalCall(call, pattern, language, sourceLines)) {
|
|
15323
15469
|
continue;
|
|
15324
15470
|
}
|
|
@@ -16634,6 +16780,9 @@ function findInitialTaint(sources, callsByLine, defsByLine) {
|
|
|
16634
16780
|
for (const source of sources) {
|
|
16635
16781
|
const defsOnLine = defsByLine.get(source.line) ?? [];
|
|
16636
16782
|
for (const def of defsOnLine) {
|
|
16783
|
+
if (source.type === "interprocedural_param" && source.variable && def.kind === "param" && def.variable !== source.variable) {
|
|
16784
|
+
continue;
|
|
16785
|
+
}
|
|
16637
16786
|
tainted.push({
|
|
16638
16787
|
variable: def.variable,
|
|
16639
16788
|
defId: def.id,
|
|
@@ -5983,6 +5983,26 @@ var CSHARP_COMMAND_EXECUTE_METHODS = /* @__PURE__ */ new Set([
|
|
|
5983
5983
|
"ExecuteReaderAsync",
|
|
5984
5984
|
"ExecuteNonQueryAsync"
|
|
5985
5985
|
]);
|
|
5986
|
+
var CSHARP_RECEIVER_URL_METHODS = /* @__PURE__ */ new Set([
|
|
5987
|
+
"GetAsync",
|
|
5988
|
+
"PostAsync",
|
|
5989
|
+
"PutAsync",
|
|
5990
|
+
"PatchAsync",
|
|
5991
|
+
"DeleteAsync",
|
|
5992
|
+
"HeadAsync",
|
|
5993
|
+
"GetStringAsync",
|
|
5994
|
+
"GetByteArrayAsync",
|
|
5995
|
+
"GetStreamAsync",
|
|
5996
|
+
"GetJsonAsync"
|
|
5997
|
+
]);
|
|
5998
|
+
var CSHARP_HEADER_RECEIVER_RE = /(^|\.)Headers$/;
|
|
5999
|
+
function csharpBareName(node) {
|
|
6000
|
+
if (node.type === "generic_name") {
|
|
6001
|
+
const id = node.childForFieldName("name") ?? node.namedChild(0);
|
|
6002
|
+
if (id) return getNodeText(id);
|
|
6003
|
+
}
|
|
6004
|
+
return getNodeText(node);
|
|
6005
|
+
}
|
|
5986
6006
|
function extractCSharpCalls(tree, cache) {
|
|
5987
6007
|
const calls = [];
|
|
5988
6008
|
const typeMap = buildCSharpReceiverTypeMap(tree, cache);
|
|
@@ -5994,16 +6014,22 @@ function extractCSharpCalls(tree, cache) {
|
|
|
5994
6014
|
if (fn?.type === "member_access_expression") {
|
|
5995
6015
|
const nameNode = fn.childForFieldName("name");
|
|
5996
6016
|
const exprNode = fn.childForFieldName("expression");
|
|
5997
|
-
methodName = nameNode ?
|
|
6017
|
+
methodName = nameNode ? csharpBareName(nameNode) : "unknown";
|
|
5998
6018
|
receiver = exprNode ? getNodeText(exprNode) : null;
|
|
5999
6019
|
} else if (fn) {
|
|
6000
|
-
methodName =
|
|
6020
|
+
methodName = csharpBareName(fn);
|
|
6001
6021
|
}
|
|
6002
6022
|
const argsNode = inv.childForFieldName("arguments");
|
|
6003
6023
|
let args2 = argsNode ? extractCSharpArguments(argsNode) : [];
|
|
6004
6024
|
if (receiver && CSHARP_COMMAND_EXECUTE_METHODS.has(methodName)) {
|
|
6005
6025
|
args2 = [{ position: 0, expression: receiver, variable: receiver, literal: null, value: null }, ...args2];
|
|
6006
6026
|
}
|
|
6027
|
+
if (methodName === "Add" && receiver && CSHARP_HEADER_RECEIVER_RE.test(receiver)) {
|
|
6028
|
+
methodName = "AddHeader";
|
|
6029
|
+
}
|
|
6030
|
+
if (receiver && args2.length === 0 && CSHARP_RECEIVER_URL_METHODS.has(methodName)) {
|
|
6031
|
+
args2 = [{ position: 0, expression: receiver, variable: receiver, literal: null, value: null }];
|
|
6032
|
+
}
|
|
6007
6033
|
calls.push({
|
|
6008
6034
|
method_name: methodName,
|
|
6009
6035
|
receiver,
|
|
@@ -6019,7 +6045,7 @@ function extractCSharpCalls(tree, cache) {
|
|
|
6019
6045
|
const typeNode = creation.childForFieldName("type");
|
|
6020
6046
|
const argsNode = creation.childForFieldName("arguments");
|
|
6021
6047
|
calls.push({
|
|
6022
|
-
method_name: typeNode ?
|
|
6048
|
+
method_name: typeNode ? csharpBareName(typeNode) : "unknown",
|
|
6023
6049
|
receiver: null,
|
|
6024
6050
|
receiver_type: null,
|
|
6025
6051
|
receiver_type_fqn: null,
|
|
@@ -6032,6 +6058,39 @@ function extractCSharpCalls(tree, cache) {
|
|
|
6032
6058
|
const assignments = getNodesFromCache(tree.rootNode, "assignment_expression", cache);
|
|
6033
6059
|
for (const asn of assignments) {
|
|
6034
6060
|
const left = asn.childForFieldName("left");
|
|
6061
|
+
if (left?.type === "element_access_expression") {
|
|
6062
|
+
const target = left.childForFieldName("expression");
|
|
6063
|
+
const right2 = asn.childForFieldName("right");
|
|
6064
|
+
if (!target || !right2) continue;
|
|
6065
|
+
if (!CSHARP_HEADER_RECEIVER_RE.test(getNodeText(target))) continue;
|
|
6066
|
+
const subscript = left.childForFieldName("subscript") ?? left.namedChild(1);
|
|
6067
|
+
const rhsText2 = getNodeText(right2);
|
|
6068
|
+
calls.push({
|
|
6069
|
+
method_name: "AddHeader",
|
|
6070
|
+
receiver: getNodeText(target),
|
|
6071
|
+
receiver_type: null,
|
|
6072
|
+
receiver_type_fqn: null,
|
|
6073
|
+
arguments: [
|
|
6074
|
+
{
|
|
6075
|
+
position: 0,
|
|
6076
|
+
expression: subscript ? getNodeText(subscript) : "",
|
|
6077
|
+
variable: null,
|
|
6078
|
+
literal: subscript ? getNodeText(subscript) : null,
|
|
6079
|
+
value: null
|
|
6080
|
+
},
|
|
6081
|
+
{
|
|
6082
|
+
position: 1,
|
|
6083
|
+
expression: rhsText2,
|
|
6084
|
+
variable: right2.type === "identifier" ? rhsText2 : null,
|
|
6085
|
+
literal: right2.type === "string_literal" ? rhsText2 : null,
|
|
6086
|
+
value: null
|
|
6087
|
+
}
|
|
6088
|
+
],
|
|
6089
|
+
location: { line: asn.startPosition.row + 1, column: asn.startPosition.column },
|
|
6090
|
+
in_method: findEnclosingMethod(asn)
|
|
6091
|
+
});
|
|
6092
|
+
continue;
|
|
6093
|
+
}
|
|
6035
6094
|
if (left?.type !== "member_access_expression") continue;
|
|
6036
6095
|
const nameNode = left.childForFieldName("name");
|
|
6037
6096
|
if (!nameNode) continue;
|
|
@@ -13251,6 +13310,23 @@ var DEFAULT_SINKS = [
|
|
|
13251
13310
|
{ method: "ExecuteSqlRawAsync", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13252
13311
|
{ method: "FromSqlRaw", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13253
13312
|
{ method: "FromSqlRawAsync", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13313
|
+
// Dapper micro-ORM (cognium-dev#275). Every Dapper entry point is an
|
|
13314
|
+
// `IDbConnection` extension whose arg 0 is raw SQL text — `conn.Query<T>(sql)`
|
|
13315
|
+
// is exactly as injectable as `new SqlCommand(sql)`. Taint-gated on arg 0, so
|
|
13316
|
+
// a constant/interpolation-free query never fires, and a same-named method on
|
|
13317
|
+
// an unrelated receiver (e.g. RestSharp's `client.Execute(request)`, whose
|
|
13318
|
+
// arg 0 is a request object rather than a tainted string) stays clean.
|
|
13319
|
+
{ method: "Query", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13320
|
+
{ method: "QueryAsync", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13321
|
+
{ method: "QueryFirst", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13322
|
+
{ method: "QueryFirstAsync", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13323
|
+
{ method: "QueryFirstOrDefault", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13324
|
+
{ method: "QueryFirstOrDefaultAsync", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13325
|
+
{ method: "QuerySingle", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13326
|
+
{ method: "QuerySingleOrDefault", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13327
|
+
{ method: "QueryMultiple", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13328
|
+
{ method: "Execute", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13329
|
+
{ method: "ExecuteAsync", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13254
13330
|
// ADO.NET `cmd.CommandText = "…" + x` — emitted as a synthetic call by
|
|
13255
13331
|
// extractCSharpCalls (property-assignment sink; the ctor-arg sink misses it).
|
|
13256
13332
|
{ method: "CommandText", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
@@ -13316,12 +13392,23 @@ var DEFAULT_SINKS = [
|
|
|
13316
13392
|
{ method: "DeleteAsync", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13317
13393
|
{ method: "GetStringAsync", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13318
13394
|
{ method: "GetByteArrayAsync", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13395
|
+
// Remaining HttpClient/Flurl verbs (cognium-dev#275). For HttpClient these
|
|
13396
|
+
// take the URL at arg 0; for Flurl's fluent form the receiver is surfaced as
|
|
13397
|
+
// arg 0 by CSHARP_RECEIVER_URL_METHODS. Taint-gated either way.
|
|
13398
|
+
// (GetStreamAsync / PutAsync / DeleteAsync are already registered below.)
|
|
13399
|
+
{ method: "GetJsonAsync", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13400
|
+
{ method: "PatchAsync", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13401
|
+
{ method: "HeadAsync", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13319
13402
|
{ method: "GetStreamAsync", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13320
13403
|
{ method: "SendAsync", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13321
13404
|
{ method: "DownloadString", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13322
13405
|
{ method: "DownloadData", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13323
13406
|
{ method: "DownloadFile", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [1], languages: ["csharp"] },
|
|
13324
13407
|
{ method: "Create", class: "WebRequest", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13408
|
+
// RestSharp (cognium-dev#275) — the base URL is fixed at construction, so
|
|
13409
|
+
// `new RestClient(userUrl)` points every later request at attacker-controlled
|
|
13410
|
+
// infrastructure. Ctor arg 0, taint-gated.
|
|
13411
|
+
{ method: "RestClient", class: "constructor", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13325
13412
|
// C# code injection — dynamic script/assembly loading (CWE-94).
|
|
13326
13413
|
{ method: "EvaluateAsync", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13327
13414
|
{ method: "RunAsync", class: "CSharpScript", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
@@ -13361,6 +13448,11 @@ var DEFAULT_SINKS = [
|
|
|
13361
13448
|
// Blazor `new MarkupString(x)` renders its argument as raw HTML (the framework's
|
|
13362
13449
|
// documented "trusted markup" escape hatch) — attacker-controlled input is XSS. (ca#275)
|
|
13363
13450
|
{ method: "MarkupString", class: "constructor", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13451
|
+
// `return Content(html, "text/html")` on a controller writes arg 0 to the
|
|
13452
|
+
// response body unescaped (cognium-dev#275). Taint-gated on arg 0; the common
|
|
13453
|
+
// `Content(constantString)` and the JSON/plain-text content types are
|
|
13454
|
+
// unaffected because a constant argument carries no taint.
|
|
13455
|
+
{ method: "Content", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13364
13456
|
// `String.Format(fmt, …)` with an attacker-controlled FORMAT string — CWE-134.
|
|
13365
13457
|
// Taint-gated on arg 0 (the format), so ordinary `String.Format("...", user)` where
|
|
13366
13458
|
// only an argument is tainted never fires. .NET composite formatting can't corrupt
|
|
@@ -13386,6 +13478,10 @@ var DEFAULT_SINKS = [
|
|
|
13386
13478
|
// cognium-dev#273/#275). `BsonDocument.Parse(userJson)` deserializes an
|
|
13387
13479
|
// attacker-controlled query document. Class-scoped (static receiver resolves).
|
|
13388
13480
|
{ method: "Parse", class: "BsonDocument", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13481
|
+
// `new BsonJavaScript(userCode)` wraps a server-side JS string — the payload
|
|
13482
|
+
// MongoDB's `$where` / `$function` operators execute. Unconditionally
|
|
13483
|
+
// dangerous when tainted, so no literal gating is needed (cognium-dev#275).
|
|
13484
|
+
{ method: "BsonJavaScript", class: "constructor", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13389
13485
|
// C# log injection — Microsoft.Extensions.Logging `ILogger` (CWE-117,
|
|
13390
13486
|
// cognium-ai#318). Only the message TEMPLATE (arg[0]) is the injectable
|
|
13391
13487
|
// position; the structured form `LogInformation("… {Id}", id)` keeps taint in
|
|
@@ -13397,6 +13493,16 @@ var DEFAULT_SINKS = [
|
|
|
13397
13493
|
{ method: "LogDebug", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0], languages: ["csharp"] },
|
|
13398
13494
|
{ method: "LogCritical", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0], languages: ["csharp"] },
|
|
13399
13495
|
{ method: "LogTrace", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0], languages: ["csharp"] },
|
|
13496
|
+
// C# ReDoS (CWE-1333, cognium-dev#275). The injectable position is the
|
|
13497
|
+
// PATTERN, not the input: a user-supplied regex can be crafted with
|
|
13498
|
+
// catastrophic backtracking. `Regex.IsMatch(input, pattern)` and friends take
|
|
13499
|
+
// the pattern at arg 1; the `new Regex(pattern)` ctor takes it at arg 0. A
|
|
13500
|
+
// tainted *input* with a constant pattern is not ReDoS and does not fire.
|
|
13501
|
+
{ method: "IsMatch", class: "Regex", type: "redos", cwe: "CWE-1333", severity: "medium", arg_positions: [1], languages: ["csharp"] },
|
|
13502
|
+
{ method: "Match", class: "Regex", type: "redos", cwe: "CWE-1333", severity: "medium", arg_positions: [1], languages: ["csharp"] },
|
|
13503
|
+
{ method: "Matches", class: "Regex", type: "redos", cwe: "CWE-1333", severity: "medium", arg_positions: [1], languages: ["csharp"] },
|
|
13504
|
+
{ method: "Replace", class: "Regex", type: "redos", cwe: "CWE-1333", severity: "medium", arg_positions: [1], languages: ["csharp"] },
|
|
13505
|
+
{ method: "Regex", class: "constructor", type: "redos", cwe: "CWE-1333", severity: "medium", arg_positions: [0], languages: ["csharp"] },
|
|
13400
13506
|
// Generic `ILogger.Log(logLevel, message, …)` — class-scoped (`Log` alone is
|
|
13401
13507
|
// too generic); the message is arg[1].
|
|
13402
13508
|
{ method: "Log", class: "ILogger", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [1], languages: ["csharp"] },
|
|
@@ -13655,6 +13761,15 @@ var DEFAULT_SANITIZERS = [
|
|
|
13655
13761
|
// this table; the drift surfaced as a SecuriBench Micro FP on
|
|
13656
13762
|
// `sanitizers/Sanitizers3.java` (`URLEncoder.encode` + `sendRedirect`).
|
|
13657
13763
|
{ method: "encodeForURL", removes: ["xss", "ssrf", "open_redirect"] },
|
|
13764
|
+
// ASP.NET Core local-redirect guards (cognium-dev#275). `Url.IsLocalUrl(x)`
|
|
13765
|
+
// returns false for any absolute/scheme-relative URL, so a `Redirect(x)` gated
|
|
13766
|
+
// on it cannot leave the site — without this credit the guarded form is a
|
|
13767
|
+
// false positive. `LocalRedirect(x)` is the same check applied by the
|
|
13768
|
+
// framework itself: it THROWS on a non-local URL, which is why it is
|
|
13769
|
+
// deliberately not registered as an open_redirect sink. Recording it here
|
|
13770
|
+
// states that intent in the model rather than leaving it as a silent omission.
|
|
13771
|
+
{ method: "IsLocalUrl", removes: ["open_redirect"] },
|
|
13772
|
+
{ method: "LocalRedirect", removes: ["open_redirect"] },
|
|
13658
13773
|
// URL encoding wrapper aliases (common patterns in benchmarks and real-world code)
|
|
13659
13774
|
{ method: "encodeURL", removes: ["xss", "ssrf", "open_redirect"] },
|
|
13660
13775
|
{ method: "urlEncode", removes: ["xss", "ssrf", "open_redirect"] },
|
|
@@ -14175,14 +14290,30 @@ var PYTHON_TAINTED_PATTERNS = [
|
|
|
14175
14290
|
// regex registry so it's recognized in subscript/var contexts.
|
|
14176
14291
|
{ pattern: /\binput\s*\(/, sourceType: "io_input" }
|
|
14177
14292
|
];
|
|
14293
|
+
function dropCSharpObjectCarriedWaypoints(sinks, calls) {
|
|
14294
|
+
const zeroArgCtors = /* @__PURE__ */ new Set();
|
|
14295
|
+
for (const call of calls) {
|
|
14296
|
+
if (call.is_constructor && call.arguments.length === 0) {
|
|
14297
|
+
zeroArgCtors.add(`${call.location.line}:${call.method_name}`);
|
|
14298
|
+
}
|
|
14299
|
+
}
|
|
14300
|
+
return sinks.filter((sink) => {
|
|
14301
|
+
if (sink.type !== "sql_injection") return true;
|
|
14302
|
+
if (sink.method && zeroArgCtors.has(`${sink.line}:${sink.method}`)) {
|
|
14303
|
+
return false;
|
|
14304
|
+
}
|
|
14305
|
+
return true;
|
|
14306
|
+
});
|
|
14307
|
+
}
|
|
14178
14308
|
function analyzeTaint(calls, types, config = getDefaultConfig(), typeHierarchy, language, code) {
|
|
14179
14309
|
const sourceLines = code !== void 0 ? code.split("\n") : void 0;
|
|
14180
14310
|
const sources = findSources(calls, types, config.sources, sourceLines, language);
|
|
14181
14311
|
let sinkPatterns = expandPromisifyAliases(config.sinks, sourceLines, language);
|
|
14182
14312
|
sinkPatterns = expandIndirectEvalAliases(sinkPatterns, sourceLines, language);
|
|
14183
14313
|
const sinks = findSinks(calls, sinkPatterns, typeHierarchy, language, sourceLines, types);
|
|
14314
|
+
const gatedSinks = language === "csharp" ? dropCSharpObjectCarriedWaypoints(sinks, calls) : sinks;
|
|
14184
14315
|
const sanitizers = findSanitizers(calls, types, config.sanitizers, sourceLines);
|
|
14185
|
-
return { sources, sinks, sanitizers };
|
|
14316
|
+
return { sources, sinks: gatedSinks, sanitizers };
|
|
14186
14317
|
}
|
|
14187
14318
|
function sinkPatternAppliesTo(pattern, language) {
|
|
14188
14319
|
if (language === void 0) return true;
|
|
@@ -14453,7 +14584,7 @@ function findSources(calls, types, patterns, sourceLines, language) {
|
|
|
14453
14584
|
}
|
|
14454
14585
|
const sourceMap = /* @__PURE__ */ new Map();
|
|
14455
14586
|
for (const source of sources) {
|
|
14456
|
-
const key = `${source.line}:${source.type}`;
|
|
14587
|
+
const key = source.type === "interprocedural_param" && source.variable ? `${source.line}:${source.type}:${source.variable}` : `${source.line}:${source.type}`;
|
|
14457
14588
|
const existing = sourceMap.get(key);
|
|
14458
14589
|
if (!existing || source.confidence > existing.confidence) {
|
|
14459
14590
|
sourceMap.set(key, source);
|
|
@@ -15071,6 +15202,15 @@ var CWE_78_RECEIVER_ALLOWLIST = /* @__PURE__ */ new Set([
|
|
|
15071
15202
|
// hutool
|
|
15072
15203
|
"RuntimeUtil"
|
|
15073
15204
|
]);
|
|
15205
|
+
function isRegexReceiver(receiver, sourceLines) {
|
|
15206
|
+
const r = (receiver ?? "").trim();
|
|
15207
|
+
if (r.length === 0) return false;
|
|
15208
|
+
if (r.startsWith("/") && /\/[a-z]*$/.test(r)) return true;
|
|
15209
|
+
if (/^(?:new\s+)?RegExp\s*\(/.test(r)) return true;
|
|
15210
|
+
if (!/^[A-Za-z_$][\w$]*$/.test(r) || !sourceLines) return false;
|
|
15211
|
+
const decl = new RegExp(`(?:const|let|var)\\s+${escapeRe(r)}\\s*=\\s*(?:/|(?:new\\s+)?RegExp\\s*\\()`);
|
|
15212
|
+
return sourceLines.some((l) => decl.test(l));
|
|
15213
|
+
}
|
|
15074
15214
|
function isFunctionCallbackArgument(arg) {
|
|
15075
15215
|
if (arg.literal !== null && arg.literal !== void 0) return false;
|
|
15076
15216
|
const expr = (arg.expression ?? "").trim();
|
|
@@ -15253,6 +15393,12 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types)
|
|
|
15253
15393
|
continue;
|
|
15254
15394
|
}
|
|
15255
15395
|
}
|
|
15396
|
+
if (pattern.type === "command_injection" && call.method_name === "exec" && (language === "javascript" || language === "typescript") && isRegexReceiver(call.receiver, sourceLines)) {
|
|
15397
|
+
continue;
|
|
15398
|
+
}
|
|
15399
|
+
if (pattern.type === "code_injection" && language === "python" && call.method_name === "compile" && call.receiver && call.receiver !== "builtins") {
|
|
15400
|
+
continue;
|
|
15401
|
+
}
|
|
15256
15402
|
if (isSafeGoJsonUnmarshalCall(call, pattern, language, sourceLines)) {
|
|
15257
15403
|
continue;
|
|
15258
15404
|
}
|
|
@@ -16568,6 +16714,9 @@ function findInitialTaint(sources, callsByLine, defsByLine) {
|
|
|
16568
16714
|
for (const source of sources) {
|
|
16569
16715
|
const defsOnLine = defsByLine.get(source.line) ?? [];
|
|
16570
16716
|
for (const def of defsOnLine) {
|
|
16717
|
+
if (source.type === "interprocedural_param" && source.variable && def.kind === "param" && def.variable !== source.variable) {
|
|
16718
|
+
continue;
|
|
16719
|
+
}
|
|
16571
16720
|
tainted.push({
|
|
16572
16721
|
variable: def.variable,
|
|
16573
16722
|
defId: def.id,
|