circle-ir 4.9.28 → 4.10.0
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 +63 -2
- package/dist/analysis/config-loader.js.map +1 -1
- package/dist/analysis/passes/language-sources-pass.d.ts.map +1 -1
- package/dist/analysis/passes/language-sources-pass.js +54 -7
- package/dist/analysis/passes/language-sources-pass.js.map +1 -1
- package/dist/analysis/passes/taint-propagation-pass.js +117 -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 +41 -0
- package/dist/analysis/taint-matcher.js.map +1 -1
- package/dist/analyzer.d.ts +12 -0
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +1 -1
- package/dist/analyzer.js.map +1 -1
- package/dist/browser/circle-ir.js +260 -28
- package/dist/core/circle-ir-core.cjs +179 -12
- package/dist/core/circle-ir-core.js +179 -12
- package/dist/core/extractors/calls.js +29 -1
- package/dist/core/extractors/calls.js.map +1 -1
- package/dist/core/extractors/types.d.ts +28 -1
- package/dist/core/extractors/types.d.ts.map +1 -1
- package/dist/core/extractors/types.js +136 -10
- package/dist/core/extractors/types.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/navigation/index.d.ts +8 -0
- package/dist/navigation/index.d.ts.map +1 -0
- package/dist/navigation/index.js +6 -0
- package/dist/navigation/index.js.map +1 -0
- package/dist/navigation/navigation-index.d.ts +208 -0
- package/dist/navigation/navigation-index.d.ts.map +1 -0
- package/dist/navigation/navigation-index.js +968 -0
- package/dist/navigation/navigation-index.js.map +1 -0
- package/dist/navigation/types.d.ts +160 -0
- package/dist/navigation/types.d.ts.map +1 -0
- package/dist/navigation/types.js +17 -0
- package/dist/navigation/types.js.map +1 -0
- package/dist/types/config.d.ts +14 -0
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/index.d.ts +19 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -4412,7 +4412,7 @@ function detectLanguage(tree) {
|
|
|
4412
4412
|
if (pythonScore > jsScore && pythonScore > javaScore) return "python";
|
|
4413
4413
|
return jsScore > javaScore ? "javascript" : "java";
|
|
4414
4414
|
}
|
|
4415
|
-
function extractTypes(tree, cache, language) {
|
|
4415
|
+
function extractTypes(tree, cache, language, options) {
|
|
4416
4416
|
const effectiveLanguage = language ?? detectLanguage(tree);
|
|
4417
4417
|
const isJavaScript = effectiveLanguage === "javascript" || effectiveLanguage === "typescript" || effectiveLanguage === "tsx";
|
|
4418
4418
|
const isPython = effectiveLanguage === "python";
|
|
@@ -4432,7 +4432,22 @@ function extractTypes(tree, cache, language) {
|
|
|
4432
4432
|
if (effectiveLanguage === "csharp") {
|
|
4433
4433
|
return extractCSharpTypes(tree, cache);
|
|
4434
4434
|
}
|
|
4435
|
-
return extractJavaTypes(tree, cache);
|
|
4435
|
+
return extractJavaTypes(tree, cache, options);
|
|
4436
|
+
}
|
|
4437
|
+
function csharpMemberNodes(body2) {
|
|
4438
|
+
if (!body2) return [];
|
|
4439
|
+
const out2 = [];
|
|
4440
|
+
const visit = (parent, depth) => {
|
|
4441
|
+
if (depth > 32) return;
|
|
4442
|
+
for (let i2 = 0; i2 < parent.childCount; i2++) {
|
|
4443
|
+
const c = parent.child(i2);
|
|
4444
|
+
if (!c) continue;
|
|
4445
|
+
if (c.type.startsWith("preproc_")) visit(c, depth + 1);
|
|
4446
|
+
else out2.push(c);
|
|
4447
|
+
}
|
|
4448
|
+
};
|
|
4449
|
+
visit(body2, 0);
|
|
4450
|
+
return out2;
|
|
4436
4451
|
}
|
|
4437
4452
|
function extractCSharpTypes(tree, cache) {
|
|
4438
4453
|
const types = [];
|
|
@@ -4450,9 +4465,8 @@ function extractCSharpTypes(tree, cache) {
|
|
|
4450
4465
|
const methods = [];
|
|
4451
4466
|
const fields = extractCSharpFields(body2);
|
|
4452
4467
|
if (body2) {
|
|
4453
|
-
for (
|
|
4454
|
-
|
|
4455
|
-
if (!m || m.type !== "method_declaration") continue;
|
|
4468
|
+
for (const m of csharpMemberNodes(body2)) {
|
|
4469
|
+
if (m.type !== "method_declaration") continue;
|
|
4456
4470
|
const mName = m.childForFieldName("name");
|
|
4457
4471
|
const paramList = m.childForFieldName("parameters");
|
|
4458
4472
|
const parameters = [];
|
|
@@ -4503,9 +4517,7 @@ function extractCSharpTypes(tree, cache) {
|
|
|
4503
4517
|
function extractCSharpFields(body2) {
|
|
4504
4518
|
const fields = [];
|
|
4505
4519
|
if (!body2) return fields;
|
|
4506
|
-
for (
|
|
4507
|
-
const c = body2.child(i2);
|
|
4508
|
-
if (!c) continue;
|
|
4520
|
+
for (const c of csharpMemberNodes(body2)) {
|
|
4509
4521
|
if (c.type === "field_declaration") {
|
|
4510
4522
|
let varDecl = null;
|
|
4511
4523
|
for (let k = 0; k < c.childCount; k++) {
|
|
@@ -4561,7 +4573,7 @@ function extractCSharpModifiers(node) {
|
|
|
4561
4573
|
}
|
|
4562
4574
|
return mods;
|
|
4563
4575
|
}
|
|
4564
|
-
function extractJavaTypes(tree, cache) {
|
|
4576
|
+
function extractJavaTypes(tree, cache, options) {
|
|
4565
4577
|
const types = [];
|
|
4566
4578
|
const classes = getNodesFromCache(tree.rootNode, "class_declaration", cache);
|
|
4567
4579
|
for (const cls of classes) {
|
|
@@ -4575,8 +4587,76 @@ function extractJavaTypes(tree, cache) {
|
|
|
4575
4587
|
for (const enumDecl of enums) {
|
|
4576
4588
|
types.push(extractEnumInfo(enumDecl));
|
|
4577
4589
|
}
|
|
4590
|
+
if (!options?.navigationTypes) return types;
|
|
4591
|
+
for (const rec of getNodesFromCache(tree.rootNode, "record_declaration", cache)) {
|
|
4592
|
+
const info2 = extractClassInfo(rec);
|
|
4593
|
+
info2.is_record = true;
|
|
4594
|
+
for (const comp of recordComponents(rec)) {
|
|
4595
|
+
if (!info2.fields.some((f) => f.name === comp.name)) {
|
|
4596
|
+
info2.fields.push({
|
|
4597
|
+
name: comp.name,
|
|
4598
|
+
type: comp.type,
|
|
4599
|
+
modifiers: ["private", "final"],
|
|
4600
|
+
annotations: []
|
|
4601
|
+
});
|
|
4602
|
+
}
|
|
4603
|
+
if (!info2.methods.some((m) => m.name === comp.name)) {
|
|
4604
|
+
info2.methods.push({
|
|
4605
|
+
name: comp.name,
|
|
4606
|
+
return_type: comp.type,
|
|
4607
|
+
parameters: [],
|
|
4608
|
+
annotations: [],
|
|
4609
|
+
modifiers: ["public"],
|
|
4610
|
+
start_line: rec.startPosition.row + 1,
|
|
4611
|
+
end_line: rec.startPosition.row + 1
|
|
4612
|
+
});
|
|
4613
|
+
}
|
|
4614
|
+
}
|
|
4615
|
+
types.push(info2);
|
|
4616
|
+
}
|
|
4617
|
+
const declNodes = [
|
|
4618
|
+
...classes,
|
|
4619
|
+
...interfaces,
|
|
4620
|
+
...enums,
|
|
4621
|
+
...getNodesFromCache(tree.rootNode, "record_declaration", cache)
|
|
4622
|
+
];
|
|
4623
|
+
for (const t of types) {
|
|
4624
|
+
const node = declNodes.find(
|
|
4625
|
+
(n) => n.startPosition.row + 1 === t.start_line && getIdentifier(n, "name") === t.name
|
|
4626
|
+
);
|
|
4627
|
+
const path = node ? enclosingTypePath(node) : [];
|
|
4628
|
+
if (path.length > 0) t.enclosing_type = path.join(".");
|
|
4629
|
+
}
|
|
4578
4630
|
return types;
|
|
4579
4631
|
}
|
|
4632
|
+
function recordComponents(node) {
|
|
4633
|
+
const out2 = [];
|
|
4634
|
+
const params = node.childForFieldName("parameters");
|
|
4635
|
+
if (!params) return out2;
|
|
4636
|
+
for (let i2 = 0; i2 < params.childCount; i2++) {
|
|
4637
|
+
const c = params.child(i2);
|
|
4638
|
+
if (!c || c.type !== "formal_parameter") continue;
|
|
4639
|
+
const name2 = c.childForFieldName("name");
|
|
4640
|
+
const type = c.childForFieldName("type");
|
|
4641
|
+
if (name2) out2.push({ name: getNodeText(name2), type: type ? getNodeText(type) : null });
|
|
4642
|
+
}
|
|
4643
|
+
return out2;
|
|
4644
|
+
}
|
|
4645
|
+
function enclosingTypePath(node) {
|
|
4646
|
+
const DECLS = /* @__PURE__ */ new Set([
|
|
4647
|
+
"class_declaration",
|
|
4648
|
+
"interface_declaration",
|
|
4649
|
+
"enum_declaration",
|
|
4650
|
+
"record_declaration"
|
|
4651
|
+
]);
|
|
4652
|
+
const path = [];
|
|
4653
|
+
for (let p = node.parent; p; p = p.parent) {
|
|
4654
|
+
if (!DECLS.has(p.type)) continue;
|
|
4655
|
+
const n = getIdentifier(p, "name");
|
|
4656
|
+
if (n) path.unshift(n);
|
|
4657
|
+
}
|
|
4658
|
+
return path;
|
|
4659
|
+
}
|
|
4580
4660
|
function extractJavaScriptTypes(tree, cache) {
|
|
4581
4661
|
const types = [];
|
|
4582
4662
|
const classes = getNodesFromCache(tree.rootNode, "class_declaration", cache);
|
|
@@ -6039,6 +6119,13 @@ function extractCalls(tree, cache, language) {
|
|
|
6039
6119
|
function buildCSharpReceiverTypeMap(tree, cache) {
|
|
6040
6120
|
const map = /* @__PURE__ */ new Map();
|
|
6041
6121
|
const simple = (t) => t.replace(/<[^>]*>/g, "").split(".").pop()?.trim() ?? t;
|
|
6122
|
+
for (const prm of getNodesFromCache(tree.rootNode, "parameter", cache)) {
|
|
6123
|
+
const nameNode = prm.childForFieldName("name");
|
|
6124
|
+
const typeNode = prm.childForFieldName("type");
|
|
6125
|
+
if (nameNode?.type !== "identifier" || !typeNode) continue;
|
|
6126
|
+
const t = getNodeText(typeNode);
|
|
6127
|
+
if (t && t !== "var") map.set(getNodeText(nameNode), simple(t));
|
|
6128
|
+
}
|
|
6042
6129
|
for (const vd of getNodesFromCache(tree.rootNode, "variable_declaration", cache)) {
|
|
6043
6130
|
const typeNode = vd.childForFieldName("type");
|
|
6044
6131
|
const declaredType = typeNode ? getNodeText(typeNode) : null;
|
|
@@ -6068,6 +6155,7 @@ function findFirstDescendant(node, type) {
|
|
|
6068
6155
|
}
|
|
6069
6156
|
return null;
|
|
6070
6157
|
}
|
|
6158
|
+
var CSHARP_RESPONSE_TYPES = /* @__PURE__ */ new Set(["HttpResponse", "HttpResponseBase"]);
|
|
6071
6159
|
var CSHARP_COMMAND_EXECUTE_METHODS = /* @__PURE__ */ new Set([
|
|
6072
6160
|
"ExecuteScalar",
|
|
6073
6161
|
"ExecuteReader",
|
|
@@ -6220,7 +6308,8 @@ function extractCSharpCalls(tree, cache) {
|
|
|
6220
6308
|
if (recvType !== "DirectorySearcher") continue;
|
|
6221
6309
|
} else if (propName === "StatusDescription") {
|
|
6222
6310
|
const recv = exprNode ? getNodeText(exprNode) : "";
|
|
6223
|
-
|
|
6311
|
+
const recvType = recv ? typeMap.get(recv) : void 0;
|
|
6312
|
+
if (!/(^|\.)(Response|HttpResponse|Context\.Response)$/.test(recv) && !(recvType && CSHARP_RESPONSE_TYPES.has(recvType))) continue;
|
|
6224
6313
|
} else if (propName === "BaseAddress") {
|
|
6225
6314
|
const recv = exprNode ? getNodeText(exprNode) : null;
|
|
6226
6315
|
const recvType = recv ? typeMap.get(recv) : void 0;
|
|
@@ -11720,6 +11809,23 @@ var DEFAULT_SOURCES = [
|
|
|
11720
11809
|
{ method: "next", class: "Scanner", type: "io_input", severity: "high", return_tainted: true },
|
|
11721
11810
|
{ method: "nextInt", class: "Scanner", type: "io_input", severity: "high", return_tainted: true },
|
|
11722
11811
|
// Database result sources
|
|
11812
|
+
// C# I/O, network and database reads (cognium-dev#502). Mirrors the Java
|
|
11813
|
+
// rows above one-for-one, including the severity split: console/file I/O is
|
|
11814
|
+
// `io_input` at high, a database row read is `db_input` at medium. There is
|
|
11815
|
+
// no separate confidence tier — `type` + `severity` IS the tier, exactly as
|
|
11816
|
+
// for `BufferedReader.readLine` vs `ResultSet.getString`.
|
|
11817
|
+
{ method: "ReadLine", class: "StreamReader", type: "io_input", severity: "high", return_tainted: true, languages: ["csharp"] },
|
|
11818
|
+
{ method: "ReadLineAsync", class: "StreamReader", type: "io_input", severity: "high", return_tainted: true, languages: ["csharp"] },
|
|
11819
|
+
{ method: "ReadToEnd", class: "StreamReader", type: "io_input", severity: "high", return_tainted: true, languages: ["csharp"] },
|
|
11820
|
+
{ method: "ReadToEndAsync", class: "StreamReader", type: "io_input", severity: "high", return_tainted: true, languages: ["csharp"] },
|
|
11821
|
+
// Socket and HTTP-client reads. `GetStream` / `AcceptTcpClient` /
|
|
11822
|
+
// `OpenRead` return the channel; the read off it is covered by the
|
|
11823
|
+
// StreamReader rows above, and seeding the channel too lets a flow form when
|
|
11824
|
+
// the read is inlined.
|
|
11825
|
+
{ method: "GetStream", class: "TcpClient", type: "network_input", severity: "high", return_tainted: true, languages: ["csharp"] },
|
|
11826
|
+
{ method: "AcceptTcpClient", class: "TcpListener", type: "network_input", severity: "high", return_tainted: true, languages: ["csharp"] },
|
|
11827
|
+
{ method: "OpenRead", class: "WebClient", type: "network_input", severity: "high", return_tainted: true, languages: ["csharp"] },
|
|
11828
|
+
{ method: "DownloadString", class: "WebClient", type: "network_input", severity: "high", return_tainted: true, languages: ["csharp"] },
|
|
11723
11829
|
{ method: "getString", class: "ResultSet", type: "db_input", severity: "medium", return_tainted: true },
|
|
11724
11830
|
{ method: "getObject", class: "ResultSet", type: "db_input", severity: "medium", return_tainted: true },
|
|
11725
11831
|
{ method: "getInt", class: "ResultSet", type: "db_input", severity: "medium", return_tainted: true },
|
|
@@ -13960,6 +14066,29 @@ var DEFAULT_SINKS = [
|
|
|
13960
14066
|
{ method: "deserialize", class: "bincode", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0] },
|
|
13961
14067
|
{ method: "from_str", class: "toml", type: "deserialization", cwe: "CWE-502", severity: "high", arg_positions: [0] },
|
|
13962
14068
|
{ method: "from_str", class: "ron", type: "deserialization", cwe: "CWE-502", severity: "high", arg_positions: [0] },
|
|
14069
|
+
// cognium-dev#541 — the crates the removed classless rows were silently
|
|
14070
|
+
// covering. #484 replaced a classless `from_str` / `from_slice` with
|
|
14071
|
+
// class-scoped rows, but only for the four formats that already had entries;
|
|
14072
|
+
// every other serde data format lost its sink, and `rmp_serde::from_slice`
|
|
14073
|
+
// on bytes read straight off a `TcpStream` stopped being reported. Each row
|
|
14074
|
+
// below is a serde *data format* entry point — a function whose whole job is
|
|
14075
|
+
// to build a typed value out of untrusted bytes or text.
|
|
14076
|
+
{ method: "from_slice", class: "rmp_serde", type: "deserialization", cwe: "CWE-502", severity: "high", arg_positions: [0] },
|
|
14077
|
+
{ method: "from_read", class: "rmp_serde", type: "deserialization", cwe: "CWE-502", severity: "high", arg_positions: [0] },
|
|
14078
|
+
{ method: "from_read_ref", class: "rmp_serde", type: "deserialization", cwe: "CWE-502", severity: "high", arg_positions: [0] },
|
|
14079
|
+
{ method: "from_slice", class: "serde_yaml", type: "deserialization", cwe: "CWE-502", severity: "high", arg_positions: [0] },
|
|
14080
|
+
{ method: "from_slice", class: "toml", type: "deserialization", cwe: "CWE-502", severity: "high", arg_positions: [0] },
|
|
14081
|
+
{ method: "from_reader", class: "ron", type: "deserialization", cwe: "CWE-502", severity: "high", arg_positions: [0] },
|
|
14082
|
+
{ method: "from_bytes", class: "postcard", type: "deserialization", cwe: "CWE-502", severity: "high", arg_positions: [0] },
|
|
14083
|
+
{ method: "from_slice", class: "serde_cbor", type: "deserialization", cwe: "CWE-502", severity: "high", arg_positions: [0] },
|
|
14084
|
+
{ method: "from_reader", class: "serde_cbor", type: "deserialization", cwe: "CWE-502", severity: "high", arg_positions: [0] },
|
|
14085
|
+
{ method: "from_reader", class: "ciborium", type: "deserialization", cwe: "CWE-502", severity: "high", arg_positions: [0] },
|
|
14086
|
+
{ method: "from_str", class: "serde_xml_rs", type: "deserialization", cwe: "CWE-502", severity: "high", arg_positions: [0] },
|
|
14087
|
+
{ method: "from_reader", class: "serde_xml_rs", type: "deserialization", cwe: "CWE-502", severity: "high", arg_positions: [0] },
|
|
14088
|
+
{ method: "from_str", class: "quick_xml", type: "deserialization", cwe: "CWE-502", severity: "high", arg_positions: [0] },
|
|
14089
|
+
{ method: "from_reader", class: "quick_xml", type: "deserialization", cwe: "CWE-502", severity: "high", arg_positions: [0] },
|
|
14090
|
+
{ method: "from_slice", class: "bincode", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0] },
|
|
14091
|
+
{ method: "deserialize_from", class: "bincode", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0] },
|
|
13963
14092
|
// #294/#484: no classless `from_str` / `from_slice` deserialization sink.
|
|
13964
14093
|
// Those rows matched EVERY `T::from_str` / `from_slice` — `u32::from_str`,
|
|
13965
14094
|
// `Url::from_str`, `PublicKey::from_slice` — i.e. ordinary `FromStr` / byte
|
|
@@ -14284,8 +14413,12 @@ var DEFAULT_SINKS = [
|
|
|
14284
14413
|
{ method: "OpenRead", class: "File", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
14285
14414
|
{ method: "OpenWrite", class: "File", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
14286
14415
|
{ method: "FileStream", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
14287
|
-
|
|
14288
|
-
|
|
14416
|
+
// #502: the `(Stream)` overload takes no path — `new StreamReader(tcp.GetStream())`
|
|
14417
|
+
// is not CWE-22. The `(string path)` overload still matches.
|
|
14418
|
+
{ method: "StreamReader", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], safe_if_stream_arg_at: 0, languages: ["csharp"] },
|
|
14419
|
+
// #502: the `(Stream)` overload takes no path — `new StreamWriter(tcp.GetStream())`
|
|
14420
|
+
// is not CWE-22. The `(string path)` overload still matches.
|
|
14421
|
+
{ method: "StreamWriter", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], safe_if_stream_arg_at: 0, languages: ["csharp"] },
|
|
14289
14422
|
// Remaining System.IO.File path APIs — class-scoped so they don't collide
|
|
14290
14423
|
// with unrelated methods. Copy/Move take a source AND destination path (both
|
|
14291
14424
|
// attacker-controllable → read-anywhere / write-anywhere), hence [0, 1].
|
|
@@ -14329,6 +14462,23 @@ var DEFAULT_SINKS = [
|
|
|
14329
14462
|
// `new RestClient(userUrl)` points every later request at attacker-controlled
|
|
14330
14463
|
// infrastructure. Ctor arg 0, taint-gated.
|
|
14331
14464
|
{ method: "RestClient", class: "constructor", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
14465
|
+
// C# code injection — CodeDOM runtime compilation (CWE-94), cognium-dev#503
|
|
14466
|
+
// part 2. `provider.CompileAssemblyFromSource(parameters, source)` compiles
|
|
14467
|
+
// and can then execute attacker-supplied C#, so the source argument is the
|
|
14468
|
+
// injection point. The signature is
|
|
14469
|
+
// `CompileAssemblyFromSource(CompilerParameters, params string[] sources)`,
|
|
14470
|
+
// so the sources begin at position 1 — position 0 is the options object.
|
|
14471
|
+
//
|
|
14472
|
+
// These rows are only useful together with the #530 StringBuilder
|
|
14473
|
+
// propagation fix: the Juliet CWE-94 corpus accumulates the compiled source
|
|
14474
|
+
// through `sb.Append(...)` and reads it back with `ToString()`, so before
|
|
14475
|
+
// that fix the sink registered but no taint ever reached it.
|
|
14476
|
+
{ method: "CompileAssemblyFromSource", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [1, 2], languages: ["csharp"] },
|
|
14477
|
+
{ method: "CompileAssemblyFromSourceBatch", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [1, 2], languages: ["csharp"] },
|
|
14478
|
+
// Roslyn's equivalent pair. `ParseText` turns text into a syntax tree and
|
|
14479
|
+
// `Create` compiles it; either is the point where attacker text becomes code.
|
|
14480
|
+
{ method: "ParseText", class: "CSharpSyntaxTree", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
14481
|
+
{ method: "ParseText", class: "SyntaxFactory", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
14332
14482
|
// C# code injection — dynamic script/assembly loading (CWE-94).
|
|
14333
14483
|
{ method: "EvaluateAsync", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
14334
14484
|
{ method: "RunAsync", class: "CSharpScript", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
@@ -16229,6 +16379,20 @@ function escapeRe(s) {
|
|
|
16229
16379
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
16230
16380
|
}
|
|
16231
16381
|
var TYPE_TOKEN_RE = /^new\s+(?:TypeReference|TypeToken)\s*<[\s\S]*>\s*\(\s*\)\s*\{\s*\}$/;
|
|
16382
|
+
function argIsStreamExpression(call, position) {
|
|
16383
|
+
const arg = call.arguments.find((a) => a.position === position);
|
|
16384
|
+
if (!arg) return false;
|
|
16385
|
+
const expr = (arg.expression ?? "").trim();
|
|
16386
|
+
if (!expr) return false;
|
|
16387
|
+
return (
|
|
16388
|
+
// `new MemoryStream(...)`, `new FileStream(...)`, `new GZipStream(...)`
|
|
16389
|
+
/^new\s+[A-Za-z_][\w.]*Stream\s*\(/.test(expr) || // `tcp.GetStream()`, `resp.GetResponseStream()`, `file.OpenReadStream()`
|
|
16390
|
+
/\.\s*Get(?:Response)?Stream\s*\(/.test(expr) || /\.\s*OpenReadStream\s*\(/.test(expr) || // `wc.OpenRead(url)` returns a Stream (WebClient), as does `File.OpenRead`
|
|
16391
|
+
/\.\s*OpenRead\s*\(/.test(expr) || // `Request.Body`, `x.BaseStream`
|
|
16392
|
+
/\.\s*(?:Body|BaseStream)\s*$/.test(expr) || // a local whose name is stream-ish and is not a path string
|
|
16393
|
+
/^(?:[A-Za-z_]\w*\.)*[A-Za-z_]*[sS]tream\d*$/.test(expr)
|
|
16394
|
+
);
|
|
16395
|
+
}
|
|
16232
16396
|
function argIsStringLiteral(call, position) {
|
|
16233
16397
|
const arg = call.arguments.find((a) => a.position === position);
|
|
16234
16398
|
if (!arg) return false;
|
|
@@ -16466,6 +16630,9 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types)
|
|
|
16466
16630
|
if (pattern.safe_if_string_literal_at !== void 0 && argIsStringLiteral(call, pattern.safe_if_string_literal_at)) {
|
|
16467
16631
|
continue;
|
|
16468
16632
|
}
|
|
16633
|
+
if (pattern.safe_if_stream_arg_at !== void 0 && argIsStreamExpression(call, pattern.safe_if_stream_arg_at)) {
|
|
16634
|
+
continue;
|
|
16635
|
+
}
|
|
16469
16636
|
if (pattern.type === "command_injection") {
|
|
16470
16637
|
if (call.is_constructor) {
|
|
16471
16638
|
if (!CWE_78_RECEIVER_ALLOWLIST.has(call.method_name)) {
|
|
@@ -28647,23 +28814,35 @@ function findCSharpRequestSources(sourceCode, language) {
|
|
|
28647
28814
|
const formFileDeclRe = /\bIFormFile\s+([A-Za-z_]\w*)/g;
|
|
28648
28815
|
const httpRequestReceivers = /* @__PURE__ */ new Set(["Request"]);
|
|
28649
28816
|
const httpRequestDeclRe = /\b(?:HttpRequest|HttpRequestBase)\??\s+([A-Za-z_]\w*)/g;
|
|
28817
|
+
const streamReaderVars = /* @__PURE__ */ new Set();
|
|
28818
|
+
const streamReaderDeclRe = /\b(?:StreamReader|TextReader|BinaryReader)\??\s+([A-Za-z_]\w*)/g;
|
|
28819
|
+
const dbReaderVars = /* @__PURE__ */ new Set();
|
|
28820
|
+
const dbReaderDeclRe = /\b(?:SqlDataReader|DbDataReader|IDataReader|OdbcDataReader|OleDbDataReader)\??\s+([A-Za-z_]\w*)/g;
|
|
28650
28821
|
for (const line of lines) {
|
|
28651
28822
|
const formRe = new RegExp(formFileDeclRe.source, "g");
|
|
28652
28823
|
let d;
|
|
28653
28824
|
while ((d = formRe.exec(line)) !== null) formFileParams.add(d[1]);
|
|
28654
28825
|
const reqRe = new RegExp(httpRequestDeclRe.source, "g");
|
|
28655
28826
|
while ((d = reqRe.exec(line)) !== null) httpRequestReceivers.add(d[1]);
|
|
28827
|
+
const srRe = new RegExp(streamReaderDeclRe.source, "g");
|
|
28828
|
+
while ((d = srRe.exec(line)) !== null) streamReaderVars.add(d[1]);
|
|
28829
|
+
const dbRe = new RegExp(dbReaderDeclRe.source, "g");
|
|
28830
|
+
while ((d = dbRe.exec(line)) !== null) dbReaderVars.add(d[1]);
|
|
28656
28831
|
}
|
|
28832
|
+
const streamReadRe = streamReaderVars.size > 0 ? new RegExp(`\\b(?:${[...streamReaderVars].join("|")})\\s*\\.\\s*(?:ReadLine|ReadToEnd|ReadLineAsync|ReadToEndAsync|ReadBlock|Read)\\s*\\(`) : null;
|
|
28833
|
+
const dbReadRe = dbReaderVars.size > 0 ? new RegExp(`\\b(?:${[...dbReaderVars].join("|")})\\s*\\.\\s*(?:GetString|GetValue|GetInt32|GetInt64|GetDecimal|GetDateTime|GetFieldValue|\\[)`) : null;
|
|
28657
28834
|
const formFileReadRe = formFileParams.size > 0 ? new RegExp(`\\b(?:${[...formFileParams].join("|")})\\s*\\.\\s*(?:FileName|ContentType)\\b`) : null;
|
|
28658
28835
|
const requestReadRe = new RegExp(
|
|
28659
28836
|
`\\b(?:${[...httpRequestReceivers].join("|")})\\s*\\.\\s*(?:Query|Form|Headers|Cookies|QueryString|RouteValues|Body|Files|Params)\\b`
|
|
28660
28837
|
);
|
|
28661
|
-
const classify = (text) => requestReadRe.test(text) ? "http_param" : formFileReadRe?.test(text) ? "http_param" : consoleReadRe.test(text) ? "io_input" : envReadRe.test(text) ? "env_input" : null;
|
|
28838
|
+
const classify = (text) => requestReadRe.test(text) ? "http_param" : formFileReadRe?.test(text) ? "http_param" : consoleReadRe.test(text) ? "io_input" : envReadRe.test(text) ? "env_input" : streamReadRe?.test(text) ? "io_input" : dbReadRe?.test(text) ? "db_input" : null;
|
|
28662
28839
|
const inlineReads = [
|
|
28663
28840
|
[requestReadRe, "http_param"],
|
|
28664
28841
|
[formFileReadRe, "http_param"],
|
|
28665
28842
|
[consoleReadRe, "io_input"],
|
|
28666
|
-
[envReadRe, "env_input"]
|
|
28843
|
+
[envReadRe, "env_input"],
|
|
28844
|
+
[streamReadRe, "io_input"],
|
|
28845
|
+
[dbReadRe, "db_input"]
|
|
28667
28846
|
];
|
|
28668
28847
|
const inlineRead = (text) => {
|
|
28669
28848
|
const argsStart = text.indexOf("(");
|
|
@@ -28684,7 +28863,8 @@ function findCSharpRequestSources(sourceCode, language) {
|
|
|
28684
28863
|
const embeddedAssignRe = /(?:^|[{;])\s*(?:await\s+)?(?:using\s*\(?\s*)?(?:var\s+|[A-Za-z_][\w.<>\[\]]*\s+)?([A-Za-z_]\w*)\s*=(?!=)\s*([^;{}]+)/;
|
|
28685
28864
|
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
28686
28865
|
const m = assignRe.exec(lines[i2]);
|
|
28687
|
-
|
|
28866
|
+
const rhsCrossesBlock = m !== null && m[2].includes("{");
|
|
28867
|
+
if (!m || rhsCrossesBlock) {
|
|
28688
28868
|
const stmt = lines[i2];
|
|
28689
28869
|
if (stmt.includes("{") && !/^\s*(?:\/\/|\/\*|\*)/.test(stmt)) {
|
|
28690
28870
|
const emb = new RegExp(embeddedAssignRe.source, "g");
|
|
@@ -28700,7 +28880,8 @@ function findCSharpRequestSources(sourceCode, language) {
|
|
|
28700
28880
|
sources.push({
|
|
28701
28881
|
type: type3,
|
|
28702
28882
|
location: `${varName2} = ${rhs2.trim().substring(0, 50)}${rhs2.length > 50 ? "..." : ""}`,
|
|
28703
|
-
|
|
28883
|
+
// #502 — Java parity: a database row read is medium, I/O is high.
|
|
28884
|
+
severity: type3 === "db_input" ? "medium" : "high",
|
|
28704
28885
|
line: lineNumber3,
|
|
28705
28886
|
confidence: 1,
|
|
28706
28887
|
variable: varName2
|
|
@@ -28719,7 +28900,7 @@ function findCSharpRequestSources(sourceCode, language) {
|
|
|
28719
28900
|
sources.push({
|
|
28720
28901
|
type: type2,
|
|
28721
28902
|
location: `${read} in ${code.substring(0, 50)}${code.length > 50 ? "..." : ""}`,
|
|
28722
|
-
severity: "high",
|
|
28903
|
+
severity: type2 === "db_input" ? "medium" : "high",
|
|
28723
28904
|
line: lineNumber2,
|
|
28724
28905
|
confidence: 1,
|
|
28725
28906
|
variable: "<inline>",
|
|
@@ -28735,7 +28916,8 @@ function findCSharpRequestSources(sourceCode, language) {
|
|
|
28735
28916
|
sources.push({
|
|
28736
28917
|
type,
|
|
28737
28918
|
location: `${varName} = ${rhs.trim().substring(0, 50)}${rhs.length > 50 ? "..." : ""}`,
|
|
28738
|
-
|
|
28919
|
+
// #502 — Java parity: a database row read is medium, I/O is high.
|
|
28920
|
+
severity: type === "db_input" ? "medium" : "high",
|
|
28739
28921
|
line: lineNumber,
|
|
28740
28922
|
confidence: 1,
|
|
28741
28923
|
variable: varName
|
|
@@ -28902,7 +29084,8 @@ function findPythonAssignmentSources(sourceCode, language) {
|
|
|
28902
29084
|
sources.push({
|
|
28903
29085
|
type,
|
|
28904
29086
|
location: `${varName} = ${rhs.trim().substring(0, 50)}${rhs.length > 50 ? "..." : ""}`,
|
|
28905
|
-
|
|
29087
|
+
// #502 — Java parity: a database row read is medium, I/O is high.
|
|
29088
|
+
severity: type === "db_input" ? "medium" : "high",
|
|
28906
29089
|
line: lineNumber,
|
|
28907
29090
|
confidence: 0.95,
|
|
28908
29091
|
variable: varName
|
|
@@ -38432,13 +38615,22 @@ function maskStringLiteralsForTokenScan(expr) {
|
|
|
38432
38615
|
}
|
|
38433
38616
|
return out2;
|
|
38434
38617
|
}
|
|
38618
|
+
function argsReferenceTaintedVar(expr, sources) {
|
|
38619
|
+
const codePart = expr.replace(/\$@?"(?:[^"\\]|\\.)*"/g, (lit) => " " + [...lit.matchAll(/\{([^}]*)\}/g)].map((x) => x[1]).join(" ") + " ").replace(/@?"(?:[^"\\]|\\.)*"/g, " ").replace(/\/\*[\s\S]*?\*\//g, " ").replace(/\/\/[^\n]*/g, " ");
|
|
38620
|
+
return sources.some(
|
|
38621
|
+
(s) => new RegExp(
|
|
38622
|
+
`(?<![\\p{L}\\p{N}_])${s.variable.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?![\\p{L}\\p{N}_])`,
|
|
38623
|
+
"u"
|
|
38624
|
+
).test(codePart)
|
|
38625
|
+
);
|
|
38626
|
+
}
|
|
38435
38627
|
function detectExpressionScanFlows(calls, sources, sinks, sanitizers, unreachableLines, tainted, code, language, types) {
|
|
38436
38628
|
const flows = [];
|
|
38437
|
-
const
|
|
38438
|
-
const
|
|
38629
|
+
const methodRanges = (langs) => language && langs.includes(language) && types ? types.flatMap((t) => t.methods.map((m) => [m.start_line, m.end_line])) : [];
|
|
38630
|
+
const enclosingStart = (ranges, line) => {
|
|
38439
38631
|
let best = -1;
|
|
38440
38632
|
let bestSpan = -1;
|
|
38441
|
-
for (const [start2, end] of
|
|
38633
|
+
for (const [start2, end] of ranges) {
|
|
38442
38634
|
if (line >= start2 && line <= end && end - start2 > bestSpan) {
|
|
38443
38635
|
best = start2;
|
|
38444
38636
|
bestSpan = end - start2;
|
|
@@ -38446,6 +38638,10 @@ function detectExpressionScanFlows(calls, sources, sinks, sanitizers, unreachabl
|
|
|
38446
38638
|
}
|
|
38447
38639
|
return best;
|
|
38448
38640
|
};
|
|
38641
|
+
const goFnRanges = methodRanges(["go"]);
|
|
38642
|
+
const goFnStart = (line) => enclosingStart(goFnRanges, line);
|
|
38643
|
+
const csFnRanges = methodRanges(["csharp"]);
|
|
38644
|
+
const csFnStart = (line) => enclosingStart(csFnRanges, line);
|
|
38449
38645
|
const sourcesWithVar = sources.filter(
|
|
38450
38646
|
(s) => typeof s.variable === "string" && s.variable.length > 0
|
|
38451
38647
|
);
|
|
@@ -38747,16 +38943,47 @@ function detectExpressionScanFlows(calls, sources, sinks, sanitizers, unreachabl
|
|
|
38747
38943
|
if (!m) continue;
|
|
38748
38944
|
const [, obj, rhs] = m;
|
|
38749
38945
|
if (known.has(obj)) continue;
|
|
38750
|
-
|
|
38751
|
-
const rhsTainted = sourcesWithVar.some(
|
|
38752
|
-
(s) => new RegExp(`(?<![\\p{L}\\p{N}_])${s.variable.replace(/[.*+?^${}()|[\\]\\\\]/g, "\\$&")}(?![\\p{L}\\p{N}_])`, "u").test(codePart)
|
|
38753
|
-
);
|
|
38754
|
-
if (!rhsTainted) continue;
|
|
38946
|
+
if (!argsReferenceTaintedVar(rhs, sourcesWithVar)) continue;
|
|
38755
38947
|
sourcesWithVar.push({ ...anchor, variable: obj, line: i2 + 1 });
|
|
38756
38948
|
added = true;
|
|
38757
38949
|
}
|
|
38758
38950
|
}
|
|
38759
38951
|
}
|
|
38952
|
+
const builderSeeds = /* @__PURE__ */ new Set();
|
|
38953
|
+
if (language === "csharp" && typeof code === "string" && sourcesWithVar.length > 0) {
|
|
38954
|
+
const lines = code.split("\n");
|
|
38955
|
+
const builders = /* @__PURE__ */ new Set();
|
|
38956
|
+
for (const line of lines) {
|
|
38957
|
+
for (const m of line.matchAll(/\bStringBuilder\s+([\p{L}_][\p{L}\p{N}_]*)/gu)) builders.add(m[1]);
|
|
38958
|
+
for (const m of line.matchAll(/\b([\p{L}_][\p{L}\p{N}_]*)\s*=\s*new\s+StringBuilder\b/gu)) builders.add(m[1]);
|
|
38959
|
+
}
|
|
38960
|
+
if (builders.size > 0) {
|
|
38961
|
+
const appendRe = /([\p{L}_][\p{L}\p{N}_]*)\s*\.\s*(?:Append|AppendLine|AppendFormat|Insert)\s*\(([\s\S]*)/u;
|
|
38962
|
+
const anchor = sourcesWithVar.reduce((a, s) => s.line < a.line ? s : a, sourcesWithVar[0]);
|
|
38963
|
+
let added = true;
|
|
38964
|
+
let guard = 0;
|
|
38965
|
+
while (added && guard < lines.length + 2) {
|
|
38966
|
+
added = false;
|
|
38967
|
+
guard++;
|
|
38968
|
+
const known = new Set(sourcesWithVar.map((s) => s.variable));
|
|
38969
|
+
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
38970
|
+
const m = appendRe.exec(lines[i2]);
|
|
38971
|
+
if (!m) continue;
|
|
38972
|
+
const [, obj, args2] = m;
|
|
38973
|
+
if (!builders.has(obj) || known.has(obj)) continue;
|
|
38974
|
+
const appendFn = csFnStart(i2 + 1);
|
|
38975
|
+
const reachable = sourcesWithVar.filter(
|
|
38976
|
+
(sv) => appendFn === -1 || csFnStart(sv.line) === -1 || csFnStart(sv.line) === appendFn
|
|
38977
|
+
);
|
|
38978
|
+
if (reachable.length > 0 && argsReferenceTaintedVar(args2, reachable)) {
|
|
38979
|
+
sourcesWithVar.push({ ...anchor, variable: obj, line: i2 + 1 });
|
|
38980
|
+
builderSeeds.add(`${obj}@${i2 + 1}`);
|
|
38981
|
+
added = true;
|
|
38982
|
+
}
|
|
38983
|
+
}
|
|
38984
|
+
}
|
|
38985
|
+
}
|
|
38986
|
+
}
|
|
38760
38987
|
const SIMPLE_IDENT = /^[\p{L}\p{N}_]+$/u;
|
|
38761
38988
|
const reCache = /* @__PURE__ */ new Map();
|
|
38762
38989
|
for (const s of sourcesWithVar) {
|
|
@@ -38819,6 +39046,11 @@ function detectExpressionScanFlows(calls, sources, sinks, sanitizers, unreachabl
|
|
|
38819
39046
|
const sinkFn = goFnStart(sink.line);
|
|
38820
39047
|
if (srcFn !== -1 && sinkFn !== -1 && srcFn !== sinkFn) continue;
|
|
38821
39048
|
}
|
|
39049
|
+
if (csFnRanges.length > 0 && builderSeeds.has(`${source.variable}@${source.line}`)) {
|
|
39050
|
+
const srcFn = csFnStart(source.line);
|
|
39051
|
+
const sinkFn = csFnStart(sink.line);
|
|
39052
|
+
if (srcFn !== sinkFn) continue;
|
|
39053
|
+
}
|
|
38822
39054
|
if (complex) {
|
|
38823
39055
|
const re = reCache.get(source.variable);
|
|
38824
39056
|
if (!re || !re.test(expr)) continue;
|
|
@@ -48179,7 +48411,7 @@ async function analyze(code, filePath, language, options = {}) {
|
|
|
48179
48411
|
};
|
|
48180
48412
|
}
|
|
48181
48413
|
const tTypes = phaseStart();
|
|
48182
|
-
const types = extractTypes(tree, nodeCache, language);
|
|
48414
|
+
const types = extractTypes(tree, nodeCache, language, { navigationTypes: options.navigationTypes });
|
|
48183
48415
|
phaseEnd("extractTypes", tTypes);
|
|
48184
48416
|
const tCalls = phaseStart();
|
|
48185
48417
|
const calls = extractCalls(tree, nodeCache, language);
|