circle-ir 4.9.8 → 4.9.10
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 +47 -7
- package/dist/analysis/passes/language-sources-pass.js.map +1 -1
- package/dist/analysis/passes/taint-propagation-pass.js +85 -12
- package/dist/analysis/passes/taint-propagation-pass.js.map +1 -1
- package/dist/analysis/taint-matcher.d.ts.map +1 -1
- package/dist/analysis/taint-matcher.js +84 -39
- package/dist/analysis/taint-matcher.js.map +1 -1
- package/dist/browser/circle-ir.js +252 -46
- package/dist/core/circle-ir-core.cjs +174 -23
- package/dist/core/circle-ir-core.js +174 -23
- 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"] },
|
|
@@ -14862,15 +14977,25 @@ function isSafeCSharpProcessStartCall(call, pattern, language, sourceLines) {
|
|
|
14862
14977
|
function stripCsLiterals(line) {
|
|
14863
14978
|
return line.replace(/\/\/.*$/, "").replace(/@?"(?:[^"\\]|\\.)*"/g, '""').replace(/'(?:[^'\\]|\\.)'/g, "''");
|
|
14864
14979
|
}
|
|
14980
|
+
var _csDepthLines;
|
|
14981
|
+
var _csDepthPrefix;
|
|
14865
14982
|
function csBraceDepthBefore(lines, idx) {
|
|
14866
|
-
|
|
14867
|
-
|
|
14868
|
-
|
|
14869
|
-
|
|
14870
|
-
|
|
14983
|
+
if (lines !== _csDepthLines) {
|
|
14984
|
+
const pref = new Int32Array(lines.length + 1);
|
|
14985
|
+
let depth = 0;
|
|
14986
|
+
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
14987
|
+
pref[i2] = depth;
|
|
14988
|
+
for (const ch of stripCsLiterals(lines[i2])) {
|
|
14989
|
+
if (ch === "{") depth++;
|
|
14990
|
+
else if (ch === "}") depth--;
|
|
14991
|
+
}
|
|
14871
14992
|
}
|
|
14993
|
+
pref[lines.length] = depth;
|
|
14994
|
+
_csDepthLines = lines;
|
|
14995
|
+
_csDepthPrefix = pref;
|
|
14872
14996
|
}
|
|
14873
|
-
|
|
14997
|
+
const clamped = idx < 0 ? 0 : idx > lines.length ? lines.length : idx;
|
|
14998
|
+
return _csDepthPrefix[clamped];
|
|
14874
14999
|
}
|
|
14875
15000
|
function netBraces(fragment) {
|
|
14876
15001
|
let n = 0;
|
|
@@ -14967,6 +15092,30 @@ function csThenBlock(lines, ifIdx, rest) {
|
|
|
14967
15092
|
}
|
|
14968
15093
|
return { start: ifIdx, end: firstIdx, earlyExit: isExit(firstText.trim()) };
|
|
14969
15094
|
}
|
|
15095
|
+
var _csGuardLines;
|
|
15096
|
+
var _csGuardMap;
|
|
15097
|
+
function csEqualityGuardIndex(lines) {
|
|
15098
|
+
if (lines === _csGuardLines && _csGuardMap) return _csGuardMap;
|
|
15099
|
+
const map = /* @__PURE__ */ new Map();
|
|
15100
|
+
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
15101
|
+
const parts2 = splitCsIf(lines[i2]);
|
|
15102
|
+
if (!parts2) continue;
|
|
15103
|
+
const guard = csEqualityGuard(parts2.cond);
|
|
15104
|
+
if (!guard) continue;
|
|
15105
|
+
const entry = { i: i2, rest: parts2.rest, ifCol: parts2.ifCol, op: guard.op };
|
|
15106
|
+
for (const id of csIdentifiers(guard.exprSide)) {
|
|
15107
|
+
let arr = map.get(id);
|
|
15108
|
+
if (!arr) {
|
|
15109
|
+
arr = [];
|
|
15110
|
+
map.set(id, arr);
|
|
15111
|
+
}
|
|
15112
|
+
arr.push(entry);
|
|
15113
|
+
}
|
|
15114
|
+
}
|
|
15115
|
+
_csGuardLines = lines;
|
|
15116
|
+
_csGuardMap = map;
|
|
15117
|
+
return map;
|
|
15118
|
+
}
|
|
14970
15119
|
function isCSharpSsrfHostAllowlistGuarded(call, pattern, language, sourceLines) {
|
|
14971
15120
|
if (language !== "csharp") return false;
|
|
14972
15121
|
if (pattern.type !== "ssrf") return false;
|
|
@@ -14979,20 +15128,22 @@ function isCSharpSsrfHostAllowlistGuarded(call, pattern, language, sourceLines)
|
|
|
14979
15128
|
if (candidates.size === 0) return false;
|
|
14980
15129
|
const sinkIdx = call.location.line - 1;
|
|
14981
15130
|
const sinkDepth = csBraceDepthBefore(sourceLines, sinkIdx);
|
|
14982
|
-
|
|
14983
|
-
|
|
14984
|
-
|
|
14985
|
-
const
|
|
14986
|
-
if (!
|
|
14987
|
-
const
|
|
14988
|
-
|
|
14989
|
-
|
|
14990
|
-
|
|
14991
|
-
if (
|
|
14992
|
-
|
|
14993
|
-
|
|
14994
|
-
|
|
14995
|
-
|
|
15131
|
+
const guardMap = csEqualityGuardIndex(sourceLines);
|
|
15132
|
+
const checked = /* @__PURE__ */ new Set();
|
|
15133
|
+
for (const cand of candidates) {
|
|
15134
|
+
const guards = guardMap.get(cand);
|
|
15135
|
+
if (!guards) continue;
|
|
15136
|
+
for (const g of guards) {
|
|
15137
|
+
if (g.i >= sinkIdx || checked.has(g)) continue;
|
|
15138
|
+
checked.add(g);
|
|
15139
|
+
const block = csThenBlock(sourceLines, g.i, g.rest);
|
|
15140
|
+
if (g.op === "==") {
|
|
15141
|
+
if (sinkIdx >= block.start && sinkIdx <= block.end) return true;
|
|
15142
|
+
} else {
|
|
15143
|
+
const guardDepth = csBraceDepthBefore(sourceLines, g.i) + netBraces(stripCsLiterals(sourceLines[g.i]).slice(0, g.ifCol));
|
|
15144
|
+
if (block.earlyExit && sinkIdx > block.end && guardDepth === sinkDepth) {
|
|
15145
|
+
return true;
|
|
15146
|
+
}
|
|
14996
15147
|
}
|
|
14997
15148
|
}
|
|
14998
15149
|
}
|
|
@@ -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"] },
|
|
@@ -14796,15 +14911,25 @@ function isSafeCSharpProcessStartCall(call, pattern, language, sourceLines) {
|
|
|
14796
14911
|
function stripCsLiterals(line) {
|
|
14797
14912
|
return line.replace(/\/\/.*$/, "").replace(/@?"(?:[^"\\]|\\.)*"/g, '""').replace(/'(?:[^'\\]|\\.)'/g, "''");
|
|
14798
14913
|
}
|
|
14914
|
+
var _csDepthLines;
|
|
14915
|
+
var _csDepthPrefix;
|
|
14799
14916
|
function csBraceDepthBefore(lines, idx) {
|
|
14800
|
-
|
|
14801
|
-
|
|
14802
|
-
|
|
14803
|
-
|
|
14804
|
-
|
|
14917
|
+
if (lines !== _csDepthLines) {
|
|
14918
|
+
const pref = new Int32Array(lines.length + 1);
|
|
14919
|
+
let depth = 0;
|
|
14920
|
+
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
14921
|
+
pref[i2] = depth;
|
|
14922
|
+
for (const ch of stripCsLiterals(lines[i2])) {
|
|
14923
|
+
if (ch === "{") depth++;
|
|
14924
|
+
else if (ch === "}") depth--;
|
|
14925
|
+
}
|
|
14805
14926
|
}
|
|
14927
|
+
pref[lines.length] = depth;
|
|
14928
|
+
_csDepthLines = lines;
|
|
14929
|
+
_csDepthPrefix = pref;
|
|
14806
14930
|
}
|
|
14807
|
-
|
|
14931
|
+
const clamped = idx < 0 ? 0 : idx > lines.length ? lines.length : idx;
|
|
14932
|
+
return _csDepthPrefix[clamped];
|
|
14808
14933
|
}
|
|
14809
14934
|
function netBraces(fragment) {
|
|
14810
14935
|
let n = 0;
|
|
@@ -14901,6 +15026,30 @@ function csThenBlock(lines, ifIdx, rest) {
|
|
|
14901
15026
|
}
|
|
14902
15027
|
return { start: ifIdx, end: firstIdx, earlyExit: isExit(firstText.trim()) };
|
|
14903
15028
|
}
|
|
15029
|
+
var _csGuardLines;
|
|
15030
|
+
var _csGuardMap;
|
|
15031
|
+
function csEqualityGuardIndex(lines) {
|
|
15032
|
+
if (lines === _csGuardLines && _csGuardMap) return _csGuardMap;
|
|
15033
|
+
const map = /* @__PURE__ */ new Map();
|
|
15034
|
+
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
15035
|
+
const parts2 = splitCsIf(lines[i2]);
|
|
15036
|
+
if (!parts2) continue;
|
|
15037
|
+
const guard = csEqualityGuard(parts2.cond);
|
|
15038
|
+
if (!guard) continue;
|
|
15039
|
+
const entry = { i: i2, rest: parts2.rest, ifCol: parts2.ifCol, op: guard.op };
|
|
15040
|
+
for (const id of csIdentifiers(guard.exprSide)) {
|
|
15041
|
+
let arr = map.get(id);
|
|
15042
|
+
if (!arr) {
|
|
15043
|
+
arr = [];
|
|
15044
|
+
map.set(id, arr);
|
|
15045
|
+
}
|
|
15046
|
+
arr.push(entry);
|
|
15047
|
+
}
|
|
15048
|
+
}
|
|
15049
|
+
_csGuardLines = lines;
|
|
15050
|
+
_csGuardMap = map;
|
|
15051
|
+
return map;
|
|
15052
|
+
}
|
|
14904
15053
|
function isCSharpSsrfHostAllowlistGuarded(call, pattern, language, sourceLines) {
|
|
14905
15054
|
if (language !== "csharp") return false;
|
|
14906
15055
|
if (pattern.type !== "ssrf") return false;
|
|
@@ -14913,20 +15062,22 @@ function isCSharpSsrfHostAllowlistGuarded(call, pattern, language, sourceLines)
|
|
|
14913
15062
|
if (candidates.size === 0) return false;
|
|
14914
15063
|
const sinkIdx = call.location.line - 1;
|
|
14915
15064
|
const sinkDepth = csBraceDepthBefore(sourceLines, sinkIdx);
|
|
14916
|
-
|
|
14917
|
-
|
|
14918
|
-
|
|
14919
|
-
const
|
|
14920
|
-
if (!
|
|
14921
|
-
const
|
|
14922
|
-
|
|
14923
|
-
|
|
14924
|
-
|
|
14925
|
-
if (
|
|
14926
|
-
|
|
14927
|
-
|
|
14928
|
-
|
|
14929
|
-
|
|
15065
|
+
const guardMap = csEqualityGuardIndex(sourceLines);
|
|
15066
|
+
const checked = /* @__PURE__ */ new Set();
|
|
15067
|
+
for (const cand of candidates) {
|
|
15068
|
+
const guards = guardMap.get(cand);
|
|
15069
|
+
if (!guards) continue;
|
|
15070
|
+
for (const g of guards) {
|
|
15071
|
+
if (g.i >= sinkIdx || checked.has(g)) continue;
|
|
15072
|
+
checked.add(g);
|
|
15073
|
+
const block = csThenBlock(sourceLines, g.i, g.rest);
|
|
15074
|
+
if (g.op === "==") {
|
|
15075
|
+
if (sinkIdx >= block.start && sinkIdx <= block.end) return true;
|
|
15076
|
+
} else {
|
|
15077
|
+
const guardDepth = csBraceDepthBefore(sourceLines, g.i) + netBraces(stripCsLiterals(sourceLines[g.i]).slice(0, g.ifCol));
|
|
15078
|
+
if (block.earlyExit && sinkIdx > block.end && guardDepth === sinkDepth) {
|
|
15079
|
+
return true;
|
|
15080
|
+
}
|
|
14930
15081
|
}
|
|
14931
15082
|
}
|
|
14932
15083
|
}
|