circle-ir 4.9.9 → 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 +26 -5
- package/dist/analysis/passes/language-sources-pass.js.map +1 -1
- package/dist/browser/circle-ir.js +140 -14
- package/dist/core/circle-ir-core.cjs +118 -3
- package/dist/core/circle-ir-core.js +118 -3
- 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
|
@@ -6003,6 +6003,26 @@ var CSHARP_COMMAND_EXECUTE_METHODS = /* @__PURE__ */ new Set([
|
|
|
6003
6003
|
"ExecuteReaderAsync",
|
|
6004
6004
|
"ExecuteNonQueryAsync"
|
|
6005
6005
|
]);
|
|
6006
|
+
var CSHARP_RECEIVER_URL_METHODS = /* @__PURE__ */ new Set([
|
|
6007
|
+
"GetAsync",
|
|
6008
|
+
"PostAsync",
|
|
6009
|
+
"PutAsync",
|
|
6010
|
+
"PatchAsync",
|
|
6011
|
+
"DeleteAsync",
|
|
6012
|
+
"HeadAsync",
|
|
6013
|
+
"GetStringAsync",
|
|
6014
|
+
"GetByteArrayAsync",
|
|
6015
|
+
"GetStreamAsync",
|
|
6016
|
+
"GetJsonAsync"
|
|
6017
|
+
]);
|
|
6018
|
+
var CSHARP_HEADER_RECEIVER_RE = /(^|\.)Headers$/;
|
|
6019
|
+
function csharpBareName(node) {
|
|
6020
|
+
if (node.type === "generic_name") {
|
|
6021
|
+
const id = node.childForFieldName("name") ?? node.namedChild(0);
|
|
6022
|
+
if (id) return getNodeText(id);
|
|
6023
|
+
}
|
|
6024
|
+
return getNodeText(node);
|
|
6025
|
+
}
|
|
6006
6026
|
function extractCSharpCalls(tree, cache) {
|
|
6007
6027
|
const calls = [];
|
|
6008
6028
|
const typeMap = buildCSharpReceiverTypeMap(tree, cache);
|
|
@@ -6014,16 +6034,22 @@ function extractCSharpCalls(tree, cache) {
|
|
|
6014
6034
|
if (fn?.type === "member_access_expression") {
|
|
6015
6035
|
const nameNode = fn.childForFieldName("name");
|
|
6016
6036
|
const exprNode = fn.childForFieldName("expression");
|
|
6017
|
-
methodName = nameNode ?
|
|
6037
|
+
methodName = nameNode ? csharpBareName(nameNode) : "unknown";
|
|
6018
6038
|
receiver = exprNode ? getNodeText(exprNode) : null;
|
|
6019
6039
|
} else if (fn) {
|
|
6020
|
-
methodName =
|
|
6040
|
+
methodName = csharpBareName(fn);
|
|
6021
6041
|
}
|
|
6022
6042
|
const argsNode = inv.childForFieldName("arguments");
|
|
6023
6043
|
let args2 = argsNode ? extractCSharpArguments(argsNode) : [];
|
|
6024
6044
|
if (receiver && CSHARP_COMMAND_EXECUTE_METHODS.has(methodName)) {
|
|
6025
6045
|
args2 = [{ position: 0, expression: receiver, variable: receiver, literal: null, value: null }, ...args2];
|
|
6026
6046
|
}
|
|
6047
|
+
if (methodName === "Add" && receiver && CSHARP_HEADER_RECEIVER_RE.test(receiver)) {
|
|
6048
|
+
methodName = "AddHeader";
|
|
6049
|
+
}
|
|
6050
|
+
if (receiver && args2.length === 0 && CSHARP_RECEIVER_URL_METHODS.has(methodName)) {
|
|
6051
|
+
args2 = [{ position: 0, expression: receiver, variable: receiver, literal: null, value: null }];
|
|
6052
|
+
}
|
|
6027
6053
|
calls.push({
|
|
6028
6054
|
method_name: methodName,
|
|
6029
6055
|
receiver,
|
|
@@ -6039,7 +6065,7 @@ function extractCSharpCalls(tree, cache) {
|
|
|
6039
6065
|
const typeNode = creation.childForFieldName("type");
|
|
6040
6066
|
const argsNode = creation.childForFieldName("arguments");
|
|
6041
6067
|
calls.push({
|
|
6042
|
-
method_name: typeNode ?
|
|
6068
|
+
method_name: typeNode ? csharpBareName(typeNode) : "unknown",
|
|
6043
6069
|
receiver: null,
|
|
6044
6070
|
receiver_type: null,
|
|
6045
6071
|
receiver_type_fqn: null,
|
|
@@ -6052,6 +6078,39 @@ function extractCSharpCalls(tree, cache) {
|
|
|
6052
6078
|
const assignments = getNodesFromCache(tree.rootNode, "assignment_expression", cache);
|
|
6053
6079
|
for (const asn of assignments) {
|
|
6054
6080
|
const left = asn.childForFieldName("left");
|
|
6081
|
+
if (left?.type === "element_access_expression") {
|
|
6082
|
+
const target = left.childForFieldName("expression");
|
|
6083
|
+
const right2 = asn.childForFieldName("right");
|
|
6084
|
+
if (!target || !right2) continue;
|
|
6085
|
+
if (!CSHARP_HEADER_RECEIVER_RE.test(getNodeText(target))) continue;
|
|
6086
|
+
const subscript = left.childForFieldName("subscript") ?? left.namedChild(1);
|
|
6087
|
+
const rhsText2 = getNodeText(right2);
|
|
6088
|
+
calls.push({
|
|
6089
|
+
method_name: "AddHeader",
|
|
6090
|
+
receiver: getNodeText(target),
|
|
6091
|
+
receiver_type: null,
|
|
6092
|
+
receiver_type_fqn: null,
|
|
6093
|
+
arguments: [
|
|
6094
|
+
{
|
|
6095
|
+
position: 0,
|
|
6096
|
+
expression: subscript ? getNodeText(subscript) : "",
|
|
6097
|
+
variable: null,
|
|
6098
|
+
literal: subscript ? getNodeText(subscript) : null,
|
|
6099
|
+
value: null
|
|
6100
|
+
},
|
|
6101
|
+
{
|
|
6102
|
+
position: 1,
|
|
6103
|
+
expression: rhsText2,
|
|
6104
|
+
variable: right2.type === "identifier" ? rhsText2 : null,
|
|
6105
|
+
literal: right2.type === "string_literal" ? rhsText2 : null,
|
|
6106
|
+
value: null
|
|
6107
|
+
}
|
|
6108
|
+
],
|
|
6109
|
+
location: { line: asn.startPosition.row + 1, column: asn.startPosition.column },
|
|
6110
|
+
in_method: findEnclosingMethod(asn)
|
|
6111
|
+
});
|
|
6112
|
+
continue;
|
|
6113
|
+
}
|
|
6055
6114
|
if (left?.type !== "member_access_expression") continue;
|
|
6056
6115
|
const nameNode = left.childForFieldName("name");
|
|
6057
6116
|
if (!nameNode) continue;
|
|
@@ -13922,6 +13981,23 @@ var DEFAULT_SINKS = [
|
|
|
13922
13981
|
{ method: "ExecuteSqlRawAsync", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13923
13982
|
{ method: "FromSqlRaw", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13924
13983
|
{ method: "FromSqlRawAsync", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13984
|
+
// Dapper micro-ORM (cognium-dev#275). Every Dapper entry point is an
|
|
13985
|
+
// `IDbConnection` extension whose arg 0 is raw SQL text — `conn.Query<T>(sql)`
|
|
13986
|
+
// is exactly as injectable as `new SqlCommand(sql)`. Taint-gated on arg 0, so
|
|
13987
|
+
// a constant/interpolation-free query never fires, and a same-named method on
|
|
13988
|
+
// an unrelated receiver (e.g. RestSharp's `client.Execute(request)`, whose
|
|
13989
|
+
// arg 0 is a request object rather than a tainted string) stays clean.
|
|
13990
|
+
{ method: "Query", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13991
|
+
{ method: "QueryAsync", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13992
|
+
{ method: "QueryFirst", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13993
|
+
{ method: "QueryFirstAsync", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13994
|
+
{ method: "QueryFirstOrDefault", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13995
|
+
{ method: "QueryFirstOrDefaultAsync", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13996
|
+
{ method: "QuerySingle", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13997
|
+
{ method: "QuerySingleOrDefault", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13998
|
+
{ method: "QueryMultiple", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13999
|
+
{ method: "Execute", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
14000
|
+
{ method: "ExecuteAsync", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13925
14001
|
// ADO.NET `cmd.CommandText = "…" + x` — emitted as a synthetic call by
|
|
13926
14002
|
// extractCSharpCalls (property-assignment sink; the ctor-arg sink misses it).
|
|
13927
14003
|
{ method: "CommandText", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
@@ -13987,12 +14063,23 @@ var DEFAULT_SINKS = [
|
|
|
13987
14063
|
{ method: "DeleteAsync", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13988
14064
|
{ method: "GetStringAsync", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13989
14065
|
{ method: "GetByteArrayAsync", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
14066
|
+
// Remaining HttpClient/Flurl verbs (cognium-dev#275). For HttpClient these
|
|
14067
|
+
// take the URL at arg 0; for Flurl's fluent form the receiver is surfaced as
|
|
14068
|
+
// arg 0 by CSHARP_RECEIVER_URL_METHODS. Taint-gated either way.
|
|
14069
|
+
// (GetStreamAsync / PutAsync / DeleteAsync are already registered below.)
|
|
14070
|
+
{ method: "GetJsonAsync", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
14071
|
+
{ method: "PatchAsync", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
14072
|
+
{ method: "HeadAsync", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13990
14073
|
{ method: "GetStreamAsync", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13991
14074
|
{ method: "SendAsync", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13992
14075
|
{ method: "DownloadString", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13993
14076
|
{ method: "DownloadData", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13994
14077
|
{ method: "DownloadFile", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [1], languages: ["csharp"] },
|
|
13995
14078
|
{ method: "Create", class: "WebRequest", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
14079
|
+
// RestSharp (cognium-dev#275) — the base URL is fixed at construction, so
|
|
14080
|
+
// `new RestClient(userUrl)` points every later request at attacker-controlled
|
|
14081
|
+
// infrastructure. Ctor arg 0, taint-gated.
|
|
14082
|
+
{ method: "RestClient", class: "constructor", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13996
14083
|
// C# code injection — dynamic script/assembly loading (CWE-94).
|
|
13997
14084
|
{ method: "EvaluateAsync", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13998
14085
|
{ method: "RunAsync", class: "CSharpScript", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
@@ -14032,6 +14119,11 @@ var DEFAULT_SINKS = [
|
|
|
14032
14119
|
// Blazor `new MarkupString(x)` renders its argument as raw HTML (the framework's
|
|
14033
14120
|
// documented "trusted markup" escape hatch) — attacker-controlled input is XSS. (ca#275)
|
|
14034
14121
|
{ method: "MarkupString", class: "constructor", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
14122
|
+
// `return Content(html, "text/html")` on a controller writes arg 0 to the
|
|
14123
|
+
// response body unescaped (cognium-dev#275). Taint-gated on arg 0; the common
|
|
14124
|
+
// `Content(constantString)` and the JSON/plain-text content types are
|
|
14125
|
+
// unaffected because a constant argument carries no taint.
|
|
14126
|
+
{ method: "Content", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
14035
14127
|
// `String.Format(fmt, …)` with an attacker-controlled FORMAT string — CWE-134.
|
|
14036
14128
|
// Taint-gated on arg 0 (the format), so ordinary `String.Format("...", user)` where
|
|
14037
14129
|
// only an argument is tainted never fires. .NET composite formatting can't corrupt
|
|
@@ -14057,6 +14149,10 @@ var DEFAULT_SINKS = [
|
|
|
14057
14149
|
// cognium-dev#273/#275). `BsonDocument.Parse(userJson)` deserializes an
|
|
14058
14150
|
// attacker-controlled query document. Class-scoped (static receiver resolves).
|
|
14059
14151
|
{ method: "Parse", class: "BsonDocument", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
14152
|
+
// `new BsonJavaScript(userCode)` wraps a server-side JS string — the payload
|
|
14153
|
+
// MongoDB's `$where` / `$function` operators execute. Unconditionally
|
|
14154
|
+
// dangerous when tainted, so no literal gating is needed (cognium-dev#275).
|
|
14155
|
+
{ method: "BsonJavaScript", class: "constructor", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
14060
14156
|
// C# log injection — Microsoft.Extensions.Logging `ILogger` (CWE-117,
|
|
14061
14157
|
// cognium-ai#318). Only the message TEMPLATE (arg[0]) is the injectable
|
|
14062
14158
|
// position; the structured form `LogInformation("… {Id}", id)` keeps taint in
|
|
@@ -14068,6 +14164,16 @@ var DEFAULT_SINKS = [
|
|
|
14068
14164
|
{ method: "LogDebug", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0], languages: ["csharp"] },
|
|
14069
14165
|
{ method: "LogCritical", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0], languages: ["csharp"] },
|
|
14070
14166
|
{ method: "LogTrace", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0], languages: ["csharp"] },
|
|
14167
|
+
// C# ReDoS (CWE-1333, cognium-dev#275). The injectable position is the
|
|
14168
|
+
// PATTERN, not the input: a user-supplied regex can be crafted with
|
|
14169
|
+
// catastrophic backtracking. `Regex.IsMatch(input, pattern)` and friends take
|
|
14170
|
+
// the pattern at arg 1; the `new Regex(pattern)` ctor takes it at arg 0. A
|
|
14171
|
+
// tainted *input* with a constant pattern is not ReDoS and does not fire.
|
|
14172
|
+
{ method: "IsMatch", class: "Regex", type: "redos", cwe: "CWE-1333", severity: "medium", arg_positions: [1], languages: ["csharp"] },
|
|
14173
|
+
{ method: "Match", class: "Regex", type: "redos", cwe: "CWE-1333", severity: "medium", arg_positions: [1], languages: ["csharp"] },
|
|
14174
|
+
{ method: "Matches", class: "Regex", type: "redos", cwe: "CWE-1333", severity: "medium", arg_positions: [1], languages: ["csharp"] },
|
|
14175
|
+
{ method: "Replace", class: "Regex", type: "redos", cwe: "CWE-1333", severity: "medium", arg_positions: [1], languages: ["csharp"] },
|
|
14176
|
+
{ method: "Regex", class: "constructor", type: "redos", cwe: "CWE-1333", severity: "medium", arg_positions: [0], languages: ["csharp"] },
|
|
14071
14177
|
// Generic `ILogger.Log(logLevel, message, …)` — class-scoped (`Log` alone is
|
|
14072
14178
|
// too generic); the message is arg[1].
|
|
14073
14179
|
{ method: "Log", class: "ILogger", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [1], languages: ["csharp"] },
|
|
@@ -14326,6 +14432,15 @@ var DEFAULT_SANITIZERS = [
|
|
|
14326
14432
|
// this table; the drift surfaced as a SecuriBench Micro FP on
|
|
14327
14433
|
// `sanitizers/Sanitizers3.java` (`URLEncoder.encode` + `sendRedirect`).
|
|
14328
14434
|
{ method: "encodeForURL", removes: ["xss", "ssrf", "open_redirect"] },
|
|
14435
|
+
// ASP.NET Core local-redirect guards (cognium-dev#275). `Url.IsLocalUrl(x)`
|
|
14436
|
+
// returns false for any absolute/scheme-relative URL, so a `Redirect(x)` gated
|
|
14437
|
+
// on it cannot leave the site — without this credit the guarded form is a
|
|
14438
|
+
// false positive. `LocalRedirect(x)` is the same check applied by the
|
|
14439
|
+
// framework itself: it THROWS on a non-local URL, which is why it is
|
|
14440
|
+
// deliberately not registered as an open_redirect sink. Recording it here
|
|
14441
|
+
// states that intent in the model rather than leaving it as a silent omission.
|
|
14442
|
+
{ method: "IsLocalUrl", removes: ["open_redirect"] },
|
|
14443
|
+
{ method: "LocalRedirect", removes: ["open_redirect"] },
|
|
14329
14444
|
// URL encoding wrapper aliases (common patterns in benchmarks and real-world code)
|
|
14330
14445
|
{ method: "encodeURL", removes: ["xss", "ssrf", "open_redirect"] },
|
|
14331
14446
|
{ method: "urlEncode", removes: ["xss", "ssrf", "open_redirect"] },
|
|
@@ -17200,7 +17315,13 @@ function canSourceReachSink(sourceType, sinkType) {
|
|
|
17200
17315
|
// scan path (`generateFindings`) dropped every `http_*/io/network/interproc →
|
|
17201
17316
|
// prompt_injection` flow even though `taint.flows` / the trust pass reported
|
|
17202
17317
|
// it — the same "two paths disagree" shape as #129. Now emitted from scan too.
|
|
17203
|
-
|
|
17318
|
+
// xxe added (cognium-dev #282): `request.getParameter("xml")` reaching an
|
|
17319
|
+
// XML parser (DocumentBuilder.parse etc.) is XXE, but xxe was absent from
|
|
17320
|
+
// http_param/http_query (present on http_body/io_input/file_input), so the
|
|
17321
|
+
// pairing was gated out and generateFindings dropped it — same class as the
|
|
17322
|
+
// #129 log_injection/format_string/nosql_injection drop. Mirrors how
|
|
17323
|
+
// deserialization already sits on both http_param and http_query.
|
|
17324
|
+
http_param: ["sql_injection", "command_injection", "path_traversal", "xss", "xpath_injection", "ldap_injection", "ssrf", "mybatis_mapper_call", "code_injection", "crlf", "mass_assignment", "open_redirect", "trust_boundary", "deserialization", "xxe", "log_injection", "format_string", "nosql_injection", "prompt_injection"],
|
|
17204
17325
|
http_body: ["sql_injection", "command_injection", "deserialization", "xxe", "xss", "code_injection", "mybatis_mapper_call", "crlf", "mass_assignment", "open_redirect", "trust_boundary", "log_injection", "format_string", "nosql_injection", "prompt_injection"],
|
|
17205
17326
|
http_header: ["sql_injection", "xss", "ssrf", "mybatis_mapper_call", "code_injection", "crlf", "open_redirect", "trust_boundary", "log_injection", "format_string", "nosql_injection", "prompt_injection"],
|
|
17206
17327
|
http_cookie: ["sql_injection", "xss", "mybatis_mapper_call", "code_injection", "crlf", "open_redirect", "trust_boundary", "log_injection", "format_string", "nosql_injection", "prompt_injection"],
|
|
@@ -17211,7 +17332,7 @@ function canSourceReachSink(sourceType, sinkType) {
|
|
|
17211
17332
|
// annotated `/* BAD */`. Prior to 3.163.0 the reach map omitted xss
|
|
17212
17333
|
// so http_path → xss inline-colocation flows were silently dropped.
|
|
17213
17334
|
http_path: ["path_traversal", "sql_injection", "ssrf", "mybatis_mapper_call", "open_redirect", "trust_boundary", "xss", "log_injection", "format_string", "prompt_injection"],
|
|
17214
|
-
http_query: ["sql_injection", "command_injection", "xss", "ssrf", "mybatis_mapper_call", "code_injection", "crlf", "mass_assignment", "open_redirect", "trust_boundary", "deserialization", "log_injection", "format_string", "nosql_injection", "prompt_injection"],
|
|
17335
|
+
http_query: ["sql_injection", "command_injection", "xss", "ssrf", "mybatis_mapper_call", "code_injection", "crlf", "mass_assignment", "open_redirect", "trust_boundary", "deserialization", "xxe", "log_injection", "format_string", "nosql_injection", "prompt_injection"],
|
|
17215
17336
|
// ssrf added Sprint 57 #200: bash CGI/webhook handlers and scripts that
|
|
17216
17337
|
// take a URL on stdin or as a positional CLI arg (`curl "$1"`,
|
|
17217
17338
|
// `wget "$(read line)"`) and curl/wget it server-side are textbook SSRF
|
|
@@ -24724,14 +24845,11 @@ var RustPlugin = class extends BaseLanguagePlugin {
|
|
|
24724
24845
|
severity: "high",
|
|
24725
24846
|
argPositions: [0]
|
|
24726
24847
|
},
|
|
24727
|
-
// Format String
|
|
24728
|
-
|
|
24729
|
-
|
|
24730
|
-
|
|
24731
|
-
|
|
24732
|
-
severity: "medium",
|
|
24733
|
-
argPositions: [0]
|
|
24734
|
-
},
|
|
24848
|
+
// Format String — `format!` is NOT a CWE-134 sink in Rust. Its template
|
|
24849
|
+
// is a compile-time string literal; a non-literal template does not
|
|
24850
|
+
// compile, so an attacker-controlled format string is not expressible.
|
|
24851
|
+
// Interpolating data into `format!` is the ordinary, safe way to build a
|
|
24852
|
+
// string. (cognium-dev #294 part 1 — removed as a format_string sink.)
|
|
24735
24853
|
// Unsafe operations
|
|
24736
24854
|
{
|
|
24737
24855
|
method: "from_raw_parts",
|
|
@@ -27571,11 +27689,19 @@ function findCSharpRequestSources(sourceCode, language) {
|
|
|
27571
27689
|
const requestReadRe = /\bRequest\s*\.\s*(?:Query|Form|Headers|Cookies|QueryString|RouteValues|Body|Files|Params)\b/;
|
|
27572
27690
|
const consoleReadRe = /\bConsole\s*\.\s*ReadLine\s*\(/;
|
|
27573
27691
|
const envReadRe = /\bEnvironment\s*\.\s*GetEnvironmentVariables?\s*(?:\(|\[)/;
|
|
27692
|
+
const formFileParams = /* @__PURE__ */ new Set();
|
|
27693
|
+
const formFileDeclRe = /\bIFormFile\s+([A-Za-z_]\w*)/g;
|
|
27694
|
+
for (const line of lines) {
|
|
27695
|
+
const re = new RegExp(formFileDeclRe.source, "g");
|
|
27696
|
+
let d;
|
|
27697
|
+
while ((d = re.exec(line)) !== null) formFileParams.add(d[1]);
|
|
27698
|
+
}
|
|
27699
|
+
const formFileReadRe = formFileParams.size > 0 ? new RegExp(`\\b(?:${[...formFileParams].join("|")})\\s*\\.\\s*(?:FileName|ContentType)\\b`) : null;
|
|
27574
27700
|
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
27575
27701
|
const m = assignRe.exec(lines[i2]);
|
|
27576
27702
|
if (!m) continue;
|
|
27577
27703
|
const [, varName, rhs] = m;
|
|
27578
|
-
const type = requestReadRe.test(rhs) ? "http_param" : consoleReadRe.test(rhs) ? "io_input" : envReadRe.test(rhs) ? "env_input" : null;
|
|
27704
|
+
const type = requestReadRe.test(rhs) ? "http_param" : formFileReadRe?.test(rhs) ? "http_param" : consoleReadRe.test(rhs) ? "io_input" : envReadRe.test(rhs) ? "env_input" : null;
|
|
27579
27705
|
if (!type) continue;
|
|
27580
27706
|
const lineNumber = i2 + 1;
|
|
27581
27707
|
if (sources.some((s) => s.line === lineNumber && s.variable === varName)) continue;
|
|
@@ -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"] },
|