circle-ir 4.5.0 → 4.7.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.
Files changed (29) hide show
  1. package/dist/analysis/config-loader.d.ts.map +1 -1
  2. package/dist/analysis/config-loader.js +20 -1
  3. package/dist/analysis/config-loader.js.map +1 -1
  4. package/dist/analysis/passes/insecure-deserialization-config-pass.d.ts +38 -0
  5. package/dist/analysis/passes/insecure-deserialization-config-pass.d.ts.map +1 -0
  6. package/dist/analysis/passes/insecure-deserialization-config-pass.js +69 -0
  7. package/dist/analysis/passes/insecure-deserialization-config-pass.js.map +1 -0
  8. package/dist/analysis/passes/language-sources-pass.d.ts.map +1 -1
  9. package/dist/analysis/passes/language-sources-pass.js +8 -1
  10. package/dist/analysis/passes/language-sources-pass.js.map +1 -1
  11. package/dist/analysis/passes/missing-public-doc-pass.d.ts.map +1 -1
  12. package/dist/analysis/passes/missing-public-doc-pass.js +6 -1
  13. package/dist/analysis/passes/missing-public-doc-pass.js.map +1 -1
  14. package/dist/analysis/passes/sink-filter-pass.d.ts.map +1 -1
  15. package/dist/analysis/passes/sink-filter-pass.js +10 -0
  16. package/dist/analysis/passes/sink-filter-pass.js.map +1 -1
  17. package/dist/analysis/passes/weak-crypto-pass.d.ts.map +1 -1
  18. package/dist/analysis/passes/weak-crypto-pass.js +66 -0
  19. package/dist/analysis/passes/weak-crypto-pass.js.map +1 -1
  20. package/dist/analysis/passes/weak-hash-pass.d.ts.map +1 -1
  21. package/dist/analysis/passes/weak-hash-pass.js +46 -0
  22. package/dist/analysis/passes/weak-hash-pass.js.map +1 -1
  23. package/dist/analyzer.d.ts.map +1 -1
  24. package/dist/analyzer.js +3 -0
  25. package/dist/analyzer.js.map +1 -1
  26. package/dist/browser/circle-ir.js +151 -3
  27. package/dist/core/circle-ir-core.cjs +20 -1
  28. package/dist/core/circle-ir-core.js +20 -1
  29. package/package.json +1 -1
@@ -13793,7 +13793,10 @@ var DEFAULT_SINKS = [
13793
13793
  { method: "ExecuteReaderAsync", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
13794
13794
  { method: "ExecuteNonQueryAsync", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
13795
13795
  // C# command injection — Process.Start / ProcessStartInfo (CWE-78).
13796
- { method: "Start", class: "Process", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0], languages: ["csharp"] },
13796
+ // Both the single-string overload `Process.Start("sh -c " + x)` and the
13797
+ // `(fileName, arguments)` overload `Process.Start("/bin/sh", "-c " + x)` are
13798
+ // injectable — the second is the argv path where taint rides arg[1] (#276).
13799
+ { method: "Start", class: "Process", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0, 1], languages: ["csharp"] },
13797
13800
  { method: "ProcessStartInfo", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0, 1], languages: ["csharp"] },
13798
13801
  // C# path traversal — System.IO file APIs (CWE-22). Distinctive method names.
13799
13802
  { method: "ReadAllText", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
@@ -13834,6 +13837,22 @@ var DEFAULT_SINKS = [
13834
13837
  // filter is the constructor argument.
13835
13838
  { method: "DirectorySearcher", type: "ldap_injection", cwe: "CWE-90", severity: "high", arg_positions: [0], languages: ["csharp"] },
13836
13839
  { method: "DirectoryEntry", type: "ldap_injection", cwe: "CWE-90", severity: "high", arg_positions: [0], languages: ["csharp"] },
13840
+ // C# open redirect — ASP.NET MVC / Minimal API redirect helpers (CWE-601,
13841
+ // cognium-dev#273/#275). Classless: `return Redirect(url)` on a controller is
13842
+ // an implicit-`this` call (no receiver), and `Results.Redirect(url)` /
13843
+ // `TypedResults.Redirect(url)` also route through the same method name. Taint-
13844
+ // gated, so a constant URL never fires. `LocalRedirect` is deliberately NOT
13845
+ // registered — it rejects non-local URLs and is the documented mitigation.
13846
+ { method: "Redirect", type: "open_redirect", cwe: "CWE-601", severity: "medium", arg_positions: [0], languages: ["csharp"] },
13847
+ { method: "RedirectPermanent", type: "open_redirect", cwe: "CWE-601", severity: "medium", arg_positions: [0], languages: ["csharp"] },
13848
+ // C# CRLF / HTTP response-header injection — classic ASP.NET header writers
13849
+ // (CWE-113, cognium-dev#273/#275). The injectable value is the second arg.
13850
+ { method: "AddHeader", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [1], languages: ["csharp"] },
13851
+ { method: "AppendHeader", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [1], languages: ["csharp"] },
13852
+ // C# NoSQL injection — MongoDB filter built from raw JSON (CWE-943,
13853
+ // cognium-dev#273/#275). `BsonDocument.Parse(userJson)` deserializes an
13854
+ // attacker-controlled query document. Class-scoped (static receiver resolves).
13855
+ { method: "Parse", class: "BsonDocument", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["csharp"] },
13837
13856
  // C# XPath injection — System.Xml.XPath (CWE-643). Distinctive selector methods.
13838
13857
  { method: "SelectSingleNode", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0], languages: ["csharp"] },
13839
13858
  { method: "SelectNodes", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0], languages: ["csharp"] },
@@ -27036,11 +27055,12 @@ function findCSharpRequestSources(sourceCode, language) {
27036
27055
  const assignRe = /^\s*(?:var\s+|[A-Za-z_][\w.<>\[\]]*\s+)?([A-Za-z_]\w*)\s*=\s*(.+?);?\s*$/;
27037
27056
  const requestReadRe = /\bRequest\s*\.\s*(?:Query|Form|Headers|Cookies|QueryString|RouteValues|Body|Files|Params)\b/;
27038
27057
  const consoleReadRe = /\bConsole\s*\.\s*ReadLine\s*\(/;
27058
+ const envReadRe = /\bEnvironment\s*\.\s*GetEnvironmentVariables?\s*(?:\(|\[)/;
27039
27059
  for (let i2 = 0; i2 < lines.length; i2++) {
27040
27060
  const m = assignRe.exec(lines[i2]);
27041
27061
  if (!m) continue;
27042
27062
  const [, varName, rhs] = m;
27043
- const type = requestReadRe.test(rhs) ? "http_param" : consoleReadRe.test(rhs) ? "io_input" : null;
27063
+ const type = requestReadRe.test(rhs) ? "http_param" : consoleReadRe.test(rhs) ? "io_input" : envReadRe.test(rhs) ? "env_input" : null;
27044
27064
  if (!type) continue;
27045
27065
  const lineNumber = i2 + 1;
27046
27066
  if (sources.some((s) => s.line === lineNumber && s.variable === varName)) continue;
@@ -33928,7 +33948,9 @@ var SinkFilterPass = class {
33928
33948
  if (language === "csharp") {
33929
33949
  const sourceLines = ctx.code.split("\n");
33930
33950
  const sanitizedByType = csharpSanitizedVarsByType(ctx.code);
33951
+ const xxeHardened = /\bXmlResolver\s*=\s*null\b/.test(ctx.code) || /\bDtdProcessing\s*=\s*(?:DtdProcessing\s*\.\s*)?(?:Prohibit|Ignore)\b/.test(ctx.code);
33931
33952
  filtered = filtered.filter((sink) => {
33953
+ if (sink.type === "xxe" && xxeHardened) return false;
33932
33954
  const sinkLineText = sourceLines[sink.line - 1] ?? "";
33933
33955
  for (const { re, type } of CSHARP_SANITIZER_RES) {
33934
33956
  if (type === sink.type && re.test(sinkLineText)) return false;
@@ -37084,6 +37106,8 @@ function isPublicMethod(method, language) {
37084
37106
  switch (language) {
37085
37107
  case "java":
37086
37108
  return method.modifiers.includes("public");
37109
+ case "csharp":
37110
+ return method.modifiers.includes("public");
37087
37111
  case "javascript":
37088
37112
  case "typescript":
37089
37113
  case "tsx":
@@ -37105,7 +37129,7 @@ var MissingPublicDocPass = class {
37105
37129
  if (UTIL_DIR_RE.test(graph.ir.meta.file)) {
37106
37130
  return { missingDocMethods: [], missingDocTypes: [] };
37107
37131
  }
37108
- if (!["java", "javascript", "typescript", "tsx", "python"].includes(language)) {
37132
+ if (!["java", "javascript", "typescript", "tsx", "python", "csharp"].includes(language)) {
37109
37133
  return { missingDocMethods: [], missingDocTypes: [] };
37110
37134
  }
37111
37135
  const lines = code.split("\n");
@@ -41538,6 +41562,28 @@ var WEAK_HASH_NAMES = /* @__PURE__ */ new Set([
41538
41562
  "sha-1",
41539
41563
  "sha1"
41540
41564
  ]);
41565
+ var CSHARP_WEAK_HASH_CLASSES = /* @__PURE__ */ new Set(["MD5", "SHA1"]);
41566
+ var CSHARP_HASH_FACTORY_RECEIVERS = /* @__PURE__ */ new Set([
41567
+ "HashAlgorithm",
41568
+ "KeyedHashAlgorithm",
41569
+ "HMAC",
41570
+ "CryptoConfig"
41571
+ ]);
41572
+ var CSHARP_WEAK_HASH_CTORS = {
41573
+ MD5CryptoServiceProvider: "md5",
41574
+ MD5Cng: "md5",
41575
+ SHA1CryptoServiceProvider: "sha1",
41576
+ SHA1Managed: "sha1",
41577
+ SHA1Cng: "sha1",
41578
+ HMACMD5: "md5",
41579
+ HMACSHA1: "sha1"
41580
+ };
41581
+ function csharpWeakHashAlgo(cleaned) {
41582
+ if (!cleaned) return null;
41583
+ const base = (cleaned.split(".").pop() ?? cleaned).replace(/^hmac/, "");
41584
+ if (base === "sha") return "sha1";
41585
+ return WEAK_HASH_NAMES.has(base) ? base : null;
41586
+ }
41541
41587
  var COMMONS_DIGEST_METHODS = /* @__PURE__ */ new Set([
41542
41588
  "md2",
41543
41589
  "md2Hex",
@@ -41697,6 +41743,19 @@ var WeakHashPass = class {
41697
41743
  }
41698
41744
  return null;
41699
41745
  }
41746
+ if (language === "csharp") {
41747
+ if ((method === "Create" || method === "HashData") && CSHARP_WEAK_HASH_CLASSES.has(receiver)) {
41748
+ return { algorithm: receiver.toLowerCase(), api: `${receiver}.${method}` };
41749
+ }
41750
+ if ((method === "Create" || method === "CreateFromName") && CSHARP_HASH_FACTORY_RECEIVERS.has(receiver)) {
41751
+ const algo = csharpWeakHashAlgo(literalAlgo(call, 0));
41752
+ if (algo) return { algorithm: algo, api: `${receiver}.${method}` };
41753
+ }
41754
+ if (call.is_constructor && CSHARP_WEAK_HASH_CTORS[method]) {
41755
+ return { algorithm: CSHARP_WEAK_HASH_CTORS[method], api: `new ${method}()` };
41756
+ }
41757
+ return null;
41758
+ }
41700
41759
  return null;
41701
41760
  }
41702
41761
  };
@@ -41758,6 +41817,27 @@ var WEAK_CIPHER_BASES = /* @__PURE__ */ new Set([
41758
41817
  "seed",
41759
41818
  "cast5"
41760
41819
  ]);
41820
+ var CSHARP_WEAK_CIPHER_CLASSES = /* @__PURE__ */ new Set(["DES", "TripleDES", "RC2", "RC4"]);
41821
+ var CSHARP_CIPHER_FACTORY_RECEIVERS = /* @__PURE__ */ new Set(["SymmetricAlgorithm", "CryptoConfig"]);
41822
+ var CSHARP_WEAK_CIPHER_CTORS = {
41823
+ DESCryptoServiceProvider: "des",
41824
+ TripleDESCryptoServiceProvider: "3des",
41825
+ RC2CryptoServiceProvider: "rc2"
41826
+ };
41827
+ var CSHARP_RSA_FACTORY_CLASSES = /* @__PURE__ */ new Set(["RSA", "DSA"]);
41828
+ var CSHARP_RSA_CTORS = /* @__PURE__ */ new Set([
41829
+ "RSACryptoServiceProvider",
41830
+ "DSACryptoServiceProvider",
41831
+ "RSACng",
41832
+ "DSACng"
41833
+ ]);
41834
+ function csharpWeakCipher(cleaned) {
41835
+ if (!cleaned) return null;
41836
+ const base = (cleaned.split(".").pop() ?? cleaned).toLowerCase();
41837
+ if (base === "tripledes" || base === "desede") return "3des";
41838
+ if (base === "arc4") return "rc4";
41839
+ return WEAK_CIPHER_BASES.has(base) ? base : null;
41840
+ }
41761
41841
  function classifyJavaCipherSpec(spec) {
41762
41842
  const parts2 = spec.split("/").map((p) => p.trim().toLowerCase());
41763
41843
  const base = parts2[0] ?? "";
@@ -42149,6 +42229,34 @@ var WeakCryptoPass = class {
42149
42229
  }
42150
42230
  return out2;
42151
42231
  }
42232
+ if (language === "csharp") {
42233
+ if (method === "Create" && CSHARP_WEAK_CIPHER_CLASSES.has(receiver)) {
42234
+ const base = receiver === "TripleDES" ? "3des" : receiver.toLowerCase();
42235
+ out2.push({ issue: "weak-cipher", detail: base, api: `${receiver}.Create` });
42236
+ }
42237
+ if ((method === "Create" || method === "CreateFromName") && CSHARP_CIPHER_FACTORY_RECEIVERS.has(receiver)) {
42238
+ const base = csharpWeakCipher(literalAlgo2(call, 0));
42239
+ if (base) out2.push({ issue: "weak-cipher", detail: base, api: `${receiver}.${method}` });
42240
+ }
42241
+ if (call.is_constructor && CSHARP_WEAK_CIPHER_CTORS[method]) {
42242
+ out2.push({ issue: "weak-cipher", detail: CSHARP_WEAK_CIPHER_CTORS[method], api: `new ${method}()` });
42243
+ }
42244
+ const rsaFactory = method === "Create" && CSHARP_RSA_FACTORY_CLASSES.has(receiver);
42245
+ const rsaCtor = call.is_constructor && CSHARP_RSA_CTORS.has(method);
42246
+ if (rsaFactory || rsaCtor) {
42247
+ const sizeArg = call.arguments.find((a) => a.position === 0);
42248
+ const expr = (sizeArg?.literal ?? sizeArg?.expression ?? "").trim();
42249
+ const n = parseInt(expr, 10);
42250
+ if (Number.isFinite(n) && n > 0 && n < 2048) {
42251
+ out2.push({
42252
+ issue: "weak-rsa-key",
42253
+ detail: String(n),
42254
+ api: rsaCtor ? `new ${method}()` : `${receiver}.Create`
42255
+ });
42256
+ }
42257
+ }
42258
+ return out2;
42259
+ }
42152
42260
  return out2;
42153
42261
  }
42154
42262
  };
@@ -43162,6 +43270,45 @@ function methodAcceptsHtmlShapedParam(method) {
43162
43270
  return false;
43163
43271
  }
43164
43272
 
43273
+ // src/analysis/passes/insecure-deserialization-config-pass.ts
43274
+ var ANY_TYPE_PERMISSION_RE = /\bAnyTypePermission\b/;
43275
+ var InsecureDeserializationConfigPass = class {
43276
+ name = "insecure-deserialization-config";
43277
+ category = "security";
43278
+ run(ctx) {
43279
+ const { graph, language } = ctx;
43280
+ if (language !== "java") return { findings: [] };
43281
+ const file = graph.ir.meta.file;
43282
+ const findings = [];
43283
+ for (const call of graph.ir.calls) {
43284
+ if (!this.isPermissiveXStreamConfig(call)) continue;
43285
+ const line = call.location.line;
43286
+ const api = `addPermission(${call.arguments[0]?.expression ?? "AnyTypePermission"})`;
43287
+ findings.push({ line, api });
43288
+ ctx.addFinding({
43289
+ id: `${this.name}-${file}-${line}`,
43290
+ pass: this.name,
43291
+ category: this.category,
43292
+ rule_id: this.name,
43293
+ cwe: "CWE-502",
43294
+ severity: "high",
43295
+ level: "error",
43296
+ message: "XStream configured with AnyTypePermission (grant-all): untrusted XML passed to fromXML/unmarshal can instantiate arbitrary types (deserialization RCE)",
43297
+ file,
43298
+ line,
43299
+ fix: "Replace AnyTypePermission with an explicit type allow-list: xstream.addPermission(NoTypePermission.NONE) then allowTypes/allowTypesByWildcard for the classes you deserialize.",
43300
+ evidence: { api, language }
43301
+ });
43302
+ }
43303
+ return { findings };
43304
+ }
43305
+ isPermissiveXStreamConfig(call) {
43306
+ if (call.method_name !== "addPermission") return false;
43307
+ const arg0 = call.arguments[0]?.expression;
43308
+ return typeof arg0 === "string" && ANY_TYPE_PERMISSION_RE.test(arg0);
43309
+ }
43310
+ };
43311
+
43165
43312
  // src/analysis/passes/plaintext-password-storage-pass.ts
43166
43313
  function isWriteStorageCall(call, language) {
43167
43314
  const method = call.method_name ?? "";
@@ -45518,6 +45665,7 @@ async function analyze(code, filePath, language, options = {}) {
45518
45665
  if (!disabledPasses.has("info-disclosure-stacktrace")) pipeline.add(new InfoDisclosureStacktracePass());
45519
45666
  if (!disabledPasses.has("unrestricted-file-upload")) pipeline.add(new UnrestrictedFileUploadPass());
45520
45667
  if (!disabledPasses.has("missing-sanitizer-gate")) pipeline.add(new MissingSanitizerGatePass());
45668
+ if (!disabledPasses.has("insecure-deserialization-config")) pipeline.add(new InsecureDeserializationConfigPass());
45521
45669
  const { results, findings } = pipeline.run(graph, code, language, config);
45522
45670
  const sinkFilter = results.get("sink-filter");
45523
45671
  const interProc = results.get("interprocedural");
@@ -13188,7 +13188,10 @@ var DEFAULT_SINKS = [
13188
13188
  { method: "ExecuteReaderAsync", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
13189
13189
  { method: "ExecuteNonQueryAsync", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
13190
13190
  // C# command injection — Process.Start / ProcessStartInfo (CWE-78).
13191
- { method: "Start", class: "Process", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0], languages: ["csharp"] },
13191
+ // Both the single-string overload `Process.Start("sh -c " + x)` and the
13192
+ // `(fileName, arguments)` overload `Process.Start("/bin/sh", "-c " + x)` are
13193
+ // injectable — the second is the argv path where taint rides arg[1] (#276).
13194
+ { method: "Start", class: "Process", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0, 1], languages: ["csharp"] },
13192
13195
  { method: "ProcessStartInfo", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0, 1], languages: ["csharp"] },
13193
13196
  // C# path traversal — System.IO file APIs (CWE-22). Distinctive method names.
13194
13197
  { method: "ReadAllText", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
@@ -13229,6 +13232,22 @@ var DEFAULT_SINKS = [
13229
13232
  // filter is the constructor argument.
13230
13233
  { method: "DirectorySearcher", type: "ldap_injection", cwe: "CWE-90", severity: "high", arg_positions: [0], languages: ["csharp"] },
13231
13234
  { method: "DirectoryEntry", type: "ldap_injection", cwe: "CWE-90", severity: "high", arg_positions: [0], languages: ["csharp"] },
13235
+ // C# open redirect — ASP.NET MVC / Minimal API redirect helpers (CWE-601,
13236
+ // cognium-dev#273/#275). Classless: `return Redirect(url)` on a controller is
13237
+ // an implicit-`this` call (no receiver), and `Results.Redirect(url)` /
13238
+ // `TypedResults.Redirect(url)` also route through the same method name. Taint-
13239
+ // gated, so a constant URL never fires. `LocalRedirect` is deliberately NOT
13240
+ // registered — it rejects non-local URLs and is the documented mitigation.
13241
+ { method: "Redirect", type: "open_redirect", cwe: "CWE-601", severity: "medium", arg_positions: [0], languages: ["csharp"] },
13242
+ { method: "RedirectPermanent", type: "open_redirect", cwe: "CWE-601", severity: "medium", arg_positions: [0], languages: ["csharp"] },
13243
+ // C# CRLF / HTTP response-header injection — classic ASP.NET header writers
13244
+ // (CWE-113, cognium-dev#273/#275). The injectable value is the second arg.
13245
+ { method: "AddHeader", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [1], languages: ["csharp"] },
13246
+ { method: "AppendHeader", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [1], languages: ["csharp"] },
13247
+ // C# NoSQL injection — MongoDB filter built from raw JSON (CWE-943,
13248
+ // cognium-dev#273/#275). `BsonDocument.Parse(userJson)` deserializes an
13249
+ // attacker-controlled query document. Class-scoped (static receiver resolves).
13250
+ { method: "Parse", class: "BsonDocument", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["csharp"] },
13232
13251
  // C# XPath injection — System.Xml.XPath (CWE-643). Distinctive selector methods.
13233
13252
  { method: "SelectSingleNode", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0], languages: ["csharp"] },
13234
13253
  { method: "SelectNodes", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0], languages: ["csharp"] },
@@ -13122,7 +13122,10 @@ var DEFAULT_SINKS = [
13122
13122
  { method: "ExecuteReaderAsync", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
13123
13123
  { method: "ExecuteNonQueryAsync", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
13124
13124
  // C# command injection — Process.Start / ProcessStartInfo (CWE-78).
13125
- { method: "Start", class: "Process", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0], languages: ["csharp"] },
13125
+ // Both the single-string overload `Process.Start("sh -c " + x)` and the
13126
+ // `(fileName, arguments)` overload `Process.Start("/bin/sh", "-c " + x)` are
13127
+ // injectable — the second is the argv path where taint rides arg[1] (#276).
13128
+ { method: "Start", class: "Process", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0, 1], languages: ["csharp"] },
13126
13129
  { method: "ProcessStartInfo", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0, 1], languages: ["csharp"] },
13127
13130
  // C# path traversal — System.IO file APIs (CWE-22). Distinctive method names.
13128
13131
  { method: "ReadAllText", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
@@ -13163,6 +13166,22 @@ var DEFAULT_SINKS = [
13163
13166
  // filter is the constructor argument.
13164
13167
  { method: "DirectorySearcher", type: "ldap_injection", cwe: "CWE-90", severity: "high", arg_positions: [0], languages: ["csharp"] },
13165
13168
  { method: "DirectoryEntry", type: "ldap_injection", cwe: "CWE-90", severity: "high", arg_positions: [0], languages: ["csharp"] },
13169
+ // C# open redirect — ASP.NET MVC / Minimal API redirect helpers (CWE-601,
13170
+ // cognium-dev#273/#275). Classless: `return Redirect(url)` on a controller is
13171
+ // an implicit-`this` call (no receiver), and `Results.Redirect(url)` /
13172
+ // `TypedResults.Redirect(url)` also route through the same method name. Taint-
13173
+ // gated, so a constant URL never fires. `LocalRedirect` is deliberately NOT
13174
+ // registered — it rejects non-local URLs and is the documented mitigation.
13175
+ { method: "Redirect", type: "open_redirect", cwe: "CWE-601", severity: "medium", arg_positions: [0], languages: ["csharp"] },
13176
+ { method: "RedirectPermanent", type: "open_redirect", cwe: "CWE-601", severity: "medium", arg_positions: [0], languages: ["csharp"] },
13177
+ // C# CRLF / HTTP response-header injection — classic ASP.NET header writers
13178
+ // (CWE-113, cognium-dev#273/#275). The injectable value is the second arg.
13179
+ { method: "AddHeader", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [1], languages: ["csharp"] },
13180
+ { method: "AppendHeader", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [1], languages: ["csharp"] },
13181
+ // C# NoSQL injection — MongoDB filter built from raw JSON (CWE-943,
13182
+ // cognium-dev#273/#275). `BsonDocument.Parse(userJson)` deserializes an
13183
+ // attacker-controlled query document. Class-scoped (static receiver resolves).
13184
+ { method: "Parse", class: "BsonDocument", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["csharp"] },
13166
13185
  // C# XPath injection — System.Xml.XPath (CWE-643). Distinctive selector methods.
13167
13186
  { method: "SelectSingleNode", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0], languages: ["csharp"] },
13168
13187
  { method: "SelectNodes", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0], languages: ["csharp"] },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "circle-ir",
3
- "version": "4.5.0",
3
+ "version": "4.7.0",
4
4
  "description": "High-performance Static Application Security Testing (SAST) library for detecting security vulnerabilities through taint analysis",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",