circle-ir 4.7.0 → 4.7.2

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 +61 -4
  3. package/dist/analysis/config-loader.js.map +1 -1
  4. package/dist/analysis/findings.js +1 -1
  5. package/dist/analysis/findings.js.map +1 -1
  6. package/dist/analysis/interprocedural.d.ts.map +1 -1
  7. package/dist/analysis/interprocedural.js +16 -1
  8. package/dist/analysis/interprocedural.js.map +1 -1
  9. package/dist/analysis/passes/language-sources-pass.d.ts.map +1 -1
  10. package/dist/analysis/passes/language-sources-pass.js +53 -3
  11. package/dist/analysis/passes/language-sources-pass.js.map +1 -1
  12. package/dist/analysis/passes/scan-secrets-pass.d.ts.map +1 -1
  13. package/dist/analysis/passes/scan-secrets-pass.js +10 -0
  14. package/dist/analysis/passes/scan-secrets-pass.js.map +1 -1
  15. package/dist/analysis/passes/unrestricted-file-upload-pass.d.ts.map +1 -1
  16. package/dist/analysis/passes/unrestricted-file-upload-pass.js +6 -4
  17. package/dist/analysis/passes/unrestricted-file-upload-pass.js.map +1 -1
  18. package/dist/analysis/passes/unused-variable-pass.d.ts.map +1 -1
  19. package/dist/analysis/passes/unused-variable-pass.js +69 -20
  20. package/dist/analysis/passes/unused-variable-pass.js.map +1 -1
  21. package/dist/analysis/taint-matcher.d.ts.map +1 -1
  22. package/dist/analysis/taint-matcher.js +46 -0
  23. package/dist/analysis/taint-matcher.js.map +1 -1
  24. package/dist/browser/circle-ir.js +185 -27
  25. package/dist/core/circle-ir-core.cjs +96 -8
  26. package/dist/core/circle-ir-core.js +96 -8
  27. package/dist/core/extractors/dfg.js +6 -3
  28. package/dist/core/extractors/dfg.js.map +1 -1
  29. package/package.json +1 -1
@@ -9918,15 +9918,15 @@ function computeChains(defs, uses) {
9918
9918
  existing.push(use);
9919
9919
  usesByLine.set(use.line, existing);
9920
9920
  }
9921
+ const seenChains = /* @__PURE__ */ new Set();
9921
9922
  for (const def of defs) {
9922
9923
  if (def.kind === "local") {
9923
9924
  const usesOnLine = usesByLine.get(def.line) ?? [];
9924
9925
  for (const use of usesOnLine) {
9925
9926
  if (use.def_id !== null && use.def_id !== def.id) {
9926
- const exists = chains.some(
9927
- (c) => c.from_def === use.def_id && c.to_def === def.id && c.via === use.variable
9928
- );
9929
- if (!exists) {
9927
+ const key = `${use.def_id}\0${def.id}\0${use.variable}`;
9928
+ if (!seenChains.has(key)) {
9929
+ seenChains.add(key);
9930
9930
  chains.push({
9931
9931
  from_def: use.def_id,
9932
9932
  to_def: def.id,
@@ -12258,7 +12258,11 @@ var DEFAULT_SINKS = [
12258
12258
  // File: covers both File(String pathname) and File(parent, child). The 2-arg
12259
12259
  // overload's child argument carries CVE-2018-8041 (Camel mail Content-Disposition
12260
12260
  // filename written to disk).
12261
- { method: "File", class: "constructor", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0, 1] },
12261
+ // Java `new File(path)` idiom. Excluded for C#: there is no `new File()` in
12262
+ // .NET (it's `System.IO.File` statics + `FileStream`, covered separately), and
12263
+ // this over-matched ASP.NET `ControllerBase.File(stream|bytes, contentType)` —
12264
+ // a result helper that returns a file, not a path sink (cognium-ai#326 B).
12265
+ { method: "File", class: "constructor", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0, 1], exclude_languages: ["csharp"] },
12262
12266
  { method: "FileInputStream", class: "constructor", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
12263
12267
  { method: "FileOutputStream", class: "constructor", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
12264
12268
  { method: "FileReader", class: "constructor", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
@@ -12501,8 +12505,11 @@ var DEFAULT_SINKS = [
12501
12505
  // SAX handler sinks (can lead to XSS in parsed content)
12502
12506
  { method: "startElement", class: "ContentHandler", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0, 1, 2] },
12503
12507
  { method: "characters", class: "ContentHandler", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0] },
12504
- // Template output sinks
12505
- { method: "output", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0] },
12508
+ // Template output sinks. `output` is classless (Java template engines /
12509
+ // Velocity), but Rust `std::process::Command::…output()` is process
12510
+ // execution, not HTML — it already fires the CWE-78 command_injection sink,
12511
+ // so exclude Rust here to drop the spurious xss (cognium-dev#146).
12512
+ { method: "output", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], exclude_languages: ["rust"] },
12506
12513
  { method: "setOutput", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0] },
12507
12514
  { method: "writeAttribute", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0, 1] },
12508
12515
  // AntiSamy specific (SAX filters)
@@ -13810,6 +13817,22 @@ var DEFAULT_SINKS = [
13810
13817
  { method: "FileStream", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
13811
13818
  { method: "StreamReader", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
13812
13819
  { method: "StreamWriter", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
13820
+ // Remaining System.IO.File path APIs — class-scoped so they don't collide
13821
+ // with unrelated methods. Copy/Move take a source AND destination path (both
13822
+ // attacker-controllable → read-anywhere / write-anywhere), hence [0, 1].
13823
+ { method: "Copy", class: "File", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0, 1], languages: ["csharp"] },
13824
+ { method: "Move", class: "File", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0, 1], languages: ["csharp"] },
13825
+ { method: "Delete", class: "File", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
13826
+ { method: "Open", class: "File", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
13827
+ { method: "OpenText", class: "File", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
13828
+ { method: "Create", class: "File", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
13829
+ { method: "WriteAllLines", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
13830
+ { method: "AppendAllLines", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
13831
+ // System.IO.Directory path APIs.
13832
+ { method: "CreateDirectory", class: "Directory", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
13833
+ { method: "Delete", class: "Directory", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
13834
+ { method: "GetFiles", class: "Directory", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
13835
+ { method: "EnumerateFiles", class: "Directory", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
13813
13836
  // C# SSRF — HttpClient / WebClient / WebRequest (CWE-918).
13814
13837
  { method: "GetAsync", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
13815
13838
  { method: "PostAsync", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
@@ -13826,9 +13849,24 @@ var DEFAULT_SINKS = [
13826
13849
  // C# code injection — dynamic script/assembly loading (CWE-94).
13827
13850
  { method: "EvaluateAsync", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
13828
13851
  { method: "RunAsync", class: "CSharpScript", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
13829
- // C# insecure deserialization BinaryFormatter et al. (CWE-502).
13852
+ // Loading an assembly from an attacker-controlled name/path is arbitrary code
13853
+ // execution. Class-scoped to `Assembly` (low FP — a tainted arg here is rare
13854
+ // in benign code). `Type.GetType`/`Activator.CreateInstance` are intentionally
13855
+ // NOT added — reflection with config-driven type names is common in DI, an FP
13856
+ // trap without the reflection-suppress machinery.
13857
+ { method: "Load", class: "Assembly", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
13858
+ { method: "LoadFrom", class: "Assembly", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
13859
+ { method: "LoadFile", class: "Assembly", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
13860
+ // C# insecure deserialization — the polymorphic BCL formatters that can
13861
+ // instantiate arbitrary types named in the payload (CWE-502, cognium-ai#318).
13862
+ // Each of these classes exists only to deserialize and is unsafe on untrusted
13863
+ // input; class-scoped, so no collision with safe JSON APIs (System.Text.Json /
13864
+ // Newtonsoft-default, which are intentionally NOT sinks).
13830
13865
  { method: "Deserialize", class: "BinaryFormatter", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["csharp"] },
13831
13866
  { method: "Deserialize", class: "NetDataContractSerializer", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["csharp"] },
13867
+ { method: "Deserialize", class: "SoapFormatter", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["csharp"] },
13868
+ { method: "Deserialize", class: "LosFormatter", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["csharp"] },
13869
+ { method: "Deserialize", class: "ObjectStateFormatter", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["csharp"] },
13832
13870
  // C# XSS — raw HTML output (CWE-79).
13833
13871
  { method: "Raw", class: "Html", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["csharp"] },
13834
13872
  { method: "Write", class: "Response", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["csharp"] },
@@ -13853,6 +13891,20 @@ var DEFAULT_SINKS = [
13853
13891
  // cognium-dev#273/#275). `BsonDocument.Parse(userJson)` deserializes an
13854
13892
  // attacker-controlled query document. Class-scoped (static receiver resolves).
13855
13893
  { method: "Parse", class: "BsonDocument", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["csharp"] },
13894
+ // C# log injection — Microsoft.Extensions.Logging `ILogger` (CWE-117,
13895
+ // cognium-ai#318). Only the message TEMPLATE (arg[0]) is the injectable
13896
+ // position; the structured form `LogInformation("… {Id}", id)` keeps taint in
13897
+ // later args (the logger renders them as data), so arg_positions:[0] does not
13898
+ // flag it. Mirrors the Java `Logger` sinks; severity low.
13899
+ { method: "LogInformation", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0], languages: ["csharp"] },
13900
+ { method: "LogWarning", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0], languages: ["csharp"] },
13901
+ { method: "LogError", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0], languages: ["csharp"] },
13902
+ { method: "LogDebug", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0], languages: ["csharp"] },
13903
+ { method: "LogCritical", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0], languages: ["csharp"] },
13904
+ { method: "LogTrace", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0], languages: ["csharp"] },
13905
+ // Generic `ILogger.Log(logLevel, message, …)` — class-scoped (`Log` alone is
13906
+ // too generic); the message is arg[1].
13907
+ { method: "Log", class: "ILogger", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [1], languages: ["csharp"] },
13856
13908
  // C# XPath injection — System.Xml.XPath (CWE-643). Distinctive selector methods.
13857
13909
  { method: "SelectSingleNode", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0], languages: ["csharp"] },
13858
13910
  { method: "SelectNodes", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0], languages: ["csharp"] },
@@ -13861,6 +13913,11 @@ var DEFAULT_SINKS = [
13861
13913
  { method: "LoadXml", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0], languages: ["csharp"] },
13862
13914
  { method: "Load", class: "XmlDocument", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0], languages: ["csharp"] },
13863
13915
  { method: "Load", class: "XDocument", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0], languages: ["csharp"] },
13916
+ // `new XmlTextReader(input)` — its DtdProcessing defaults to Parse (resolves
13917
+ // external entities), unlike `XmlReader.Create` (Prohibit). CWE-611,
13918
+ // cognium-ai#318. The 4.7.0 XmlResolver=null / DtdProcessing=Prohibit
13919
+ // hardening still suppresses the safe shape.
13920
+ { method: "XmlTextReader", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0], languages: ["csharp"] },
13864
13921
  // Python SQLi — asyncpg Connection.*
13865
13922
  { method: "execute", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0] },
13866
13923
  { method: "fetch", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0] },
@@ -15273,6 +15330,34 @@ function isSafeJSChildProcessCall(call, pattern, language) {
15273
15330
  if (SHELL_PROGRAMS.has(program)) return false;
15274
15331
  return true;
15275
15332
  }
15333
+ function isSafeCSharpProcessStartCall(call, pattern, language) {
15334
+ if (language !== "csharp") return false;
15335
+ if (pattern.type !== "command_injection") return false;
15336
+ if (call.method_name !== "Start") return false;
15337
+ if (call.arguments.length < 2) return false;
15338
+ const fileArg = call.arguments.find((a) => a.position === 0);
15339
+ if (!fileArg) return false;
15340
+ let raw;
15341
+ if (fileArg.literal !== null && fileArg.literal !== void 0) {
15342
+ raw = String(fileArg.literal).trim();
15343
+ } else {
15344
+ raw = (fileArg.expression ?? "").trim();
15345
+ }
15346
+ if (!/^@?"[^"]*"$/.test(raw)) return false;
15347
+ const program = (raw.replace(/^@?"|"$/g, "").split(/[\\/]/).pop() ?? "").toLowerCase().replace(/\.exe$/, "");
15348
+ const SHELL_PROGRAMS = /* @__PURE__ */ new Set([
15349
+ "sh",
15350
+ "bash",
15351
+ "zsh",
15352
+ "dash",
15353
+ "ash",
15354
+ "ksh",
15355
+ "cmd",
15356
+ "powershell",
15357
+ "pwsh"
15358
+ ]);
15359
+ return !SHELL_PROGRAMS.has(program);
15360
+ }
15276
15361
  function isSafeRustCommandCall(call, pattern, language) {
15277
15362
  if (language !== "rust") return false;
15278
15363
  if (pattern.type !== "command_injection") return false;
@@ -15528,6 +15613,9 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types)
15528
15613
  if (isSafeJSChildProcessCall(call, pattern, language)) {
15529
15614
  continue;
15530
15615
  }
15616
+ if (isSafeCSharpProcessStartCall(call, pattern, language)) {
15617
+ continue;
15618
+ }
15531
15619
  if (pattern.safe_if_class_literal_at !== void 0 && argIsClassLiteral(call, pattern.safe_if_class_literal_at, types)) {
15532
15620
  continue;
15533
15621
  }
@@ -16758,8 +16846,8 @@ function canSourceReachSink(sourceType, sinkType) {
16758
16846
  network_input: ["sql_injection", "command_injection", "xss", "ssrf", "log_injection", "format_string", "nosql_injection", "prompt_injection"],
16759
16847
  config_param: ["sql_injection", "command_injection", "path_traversal", "xss", "ssrf", "log_injection", "format_string"],
16760
16848
  // Servlet init params
16761
- interprocedural_param: ["sql_injection", "command_injection", "path_traversal", "xss", "xpath_injection", "ldap_injection", "ssrf", "code_injection", "mybatis_mapper_call", "crlf", "mass_assignment", "open_redirect", "trust_boundary", "log_injection", "format_string", "nosql_injection", "prompt_injection"],
16762
- // Cross-method taint; Sprint 82 (#189) — open_redirect added; Sprint 91 (#117) — trust_boundary added; cognium-ai#129 — log_injection/format_string/nosql_injection added; cognium-ai#281 — prompt_injection added
16849
+ interprocedural_param: ["sql_injection", "command_injection", "path_traversal", "xss", "xpath_injection", "ldap_injection", "ssrf", "code_injection", "mybatis_mapper_call", "crlf", "mass_assignment", "open_redirect", "trust_boundary", "log_injection", "format_string", "nosql_injection", "prompt_injection", "xxe", "deserialization"],
16850
+ // Cross-method taint; Sprint 82 (#189) — open_redirect added; Sprint 91 (#117) — trust_boundary added; cognium-ai#129 — log_injection/format_string/nosql_injection added; cognium-ai#281 — prompt_injection added; cognium-ai#317 — xxe/deserialization added (C# xxe/deser sinks are reached via param-seeded sources and were silently dropped, 0% finding conversion)
16763
16851
  plugin_param: ["sql_injection", "command_injection", "path_traversal", "xss", "code_injection", "log_injection", "format_string"]
16764
16852
  // Plugin/config parameters
16765
16853
  };
@@ -18581,6 +18669,26 @@ function analyzeInterprocedural(graphOrTypes, callsOrSources, dfgOrSinks, source
18581
18669
  "Command",
18582
18670
  "CommandContext"
18583
18671
  ]);
18672
+ const ormQueryBuilderMethods = /* @__PURE__ */ new Set([
18673
+ "findMany",
18674
+ "findFirst",
18675
+ "findUnique",
18676
+ "findUniqueOrThrow",
18677
+ "findFirstOrThrow",
18678
+ "findOne",
18679
+ "findAll",
18680
+ "findByPk",
18681
+ "findAndCountAll",
18682
+ "findBy",
18683
+ "findOneBy",
18684
+ "where",
18685
+ "andWhere",
18686
+ "orWhere",
18687
+ "whereIn",
18688
+ "whereNot",
18689
+ "first",
18690
+ "bind"
18691
+ ]);
18584
18692
  const sanitizerMethods = /* @__PURE__ */ new Set();
18585
18693
  for (const san of sanitizers) {
18586
18694
  sanitizerMethods.add(san.method);
@@ -18605,7 +18713,7 @@ function analyzeInterprocedural(graphOrTypes, callsOrSources, dfgOrSinks, source
18605
18713
  }
18606
18714
  const targetMethod = getMethodNode(methodNodes, call.method_name);
18607
18715
  if (!targetMethod) {
18608
- if (taintedArgPositions.length > 0 && !collectionMethods.has(call.method_name) && !sanitizerMethods.has(call.method_name) && !safeUtilityMethods.has(call.method_name)) {
18716
+ if (taintedArgPositions.length > 0 && !collectionMethods.has(call.method_name) && !sanitizerMethods.has(call.method_name) && !safeUtilityMethods.has(call.method_name) && !ormQueryBuilderMethods.has(call.method_name)) {
18609
18717
  const isBash = graph.ir.meta.language === "bash";
18610
18718
  const bashSafeBuiltins = /* @__PURE__ */ new Set([
18611
18719
  "echo",
@@ -26394,6 +26502,7 @@ var LanguageSourcesPass = class {
26394
26502
  additionalSources.push(...findSetterChainSources(types, code, language));
26395
26503
  additionalSources.push(...findJavaScriptAssignmentSources(code, language));
26396
26504
  additionalSources.push(...findCSharpRequestSources(code, language));
26505
+ additionalSources.push(...findGoArgvSources(code, language));
26397
26506
  const jsDOMSinks = findJavaScriptDOMSinks(code, language);
26398
26507
  for (const s of jsDOMSinks) {
26399
26508
  const alreadyExists = additionalSinks.some((x) => x.line === s.line && x.cwe === s.cwe);
@@ -27048,6 +27157,31 @@ function findSetterChainSources(types, sourceCode, language) {
27048
27157
  }
27049
27158
  return sources;
27050
27159
  }
27160
+ function findGoArgvSources(sourceCode, language) {
27161
+ if (language !== "go") return [];
27162
+ const sources = [];
27163
+ const lines = sourceCode.split("\n");
27164
+ const rangeRe = /\bfor\b[^;{]*?,\s*([A-Za-z_]\w*)\s*:?=\s*range\s+os\.Args\b/;
27165
+ const assignRe = /\b([A-Za-z_]\w*)\s*:?=\s*os\.Args\b/;
27166
+ for (let i2 = 0; i2 < lines.length; i2++) {
27167
+ const line = lines[i2];
27168
+ const rm = rangeRe.exec(line);
27169
+ const m = rm ?? assignRe.exec(line);
27170
+ if (!m) continue;
27171
+ const varName = m[1];
27172
+ const lineNumber = i2 + 1;
27173
+ if (sources.some((s) => s.line === lineNumber && s.variable === varName)) continue;
27174
+ sources.push({
27175
+ type: "io_input",
27176
+ location: `${varName} = os.Args`,
27177
+ severity: "high",
27178
+ line: lineNumber,
27179
+ confidence: 1,
27180
+ variable: varName
27181
+ });
27182
+ }
27183
+ return sources;
27184
+ }
27051
27185
  function findCSharpRequestSources(sourceCode, language) {
27052
27186
  if (language !== "csharp") return [];
27053
27187
  const sources = [];
@@ -27715,8 +27849,9 @@ function buildJavaTaintedVars(sourceCode, seedVars) {
27715
27849
  if (JAVA_KEYWORDS.has(lhs)) continue;
27716
27850
  if (knownTainted.has(lhs)) continue;
27717
27851
  const escaped = (v) => v.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
27852
+ const rhsCode = rhs.replace(/\$@?"(?:[^"\\]|\\.)*"/g, (lit) => " " + [...lit.matchAll(/\{([^}]*)\}/g)].map((x) => x[1]).join(" ") + " ").replace(/@?"(?:[^"\\]|\\.)*"/g, " ").replace(/'(?:[^'\\]|\\.)'/g, " ");
27718
27853
  const ref = [...knownTainted].some(
27719
- (v) => new RegExp(`(?<![\\p{L}\\p{N}_])${escaped(v)}(?![\\p{L}\\p{N}_])`, "u").test(rhs)
27854
+ (v) => new RegExp(`(?<![\\p{L}\\p{N}_])${escaped(v)}(?![\\p{L}\\p{N}_])`, "u").test(rhsCode)
27720
27855
  );
27721
27856
  if (ref) {
27722
27857
  derived.set(lhs, i2 + 1);
@@ -27892,7 +28027,7 @@ function findBashTaintSources(sourceCode, dfg) {
27892
28027
  });
27893
28028
  }
27894
28029
  }
27895
- const envRe = /\$([A-Z][A-Z0-9_]{2,})|\$\{([A-Z][A-Z0-9_]{2,})\}/g;
28030
+ const envRe = /\$([A-Z][A-Z0-9_]{2,})|\$\{([A-Z][A-Z0-9_]{2,})(?:[:#%/^,@!*+?=-][^}]*)?\}/g;
27896
28031
  let em;
27897
28032
  while ((em = envRe.exec(line)) !== null) {
27898
28033
  const envVar = em[1] ?? em[2];
@@ -38125,6 +38260,7 @@ var SKIP_NAMES3 = /* @__PURE__ */ new Set([
38125
38260
  "unknown",
38126
38261
  "bigint"
38127
38262
  ]);
38263
+ var EMPTY_LINES = [];
38128
38264
  var UnusedVariablePass = class {
38129
38265
  name = "unused-variable";
38130
38266
  category = "reliability";
@@ -38137,6 +38273,29 @@ var UnusedVariablePass = class {
38137
38273
  const codeLines = code.split("\n");
38138
38274
  const unusedVars = [];
38139
38275
  const reported = /* @__PURE__ */ new Set();
38276
+ const defsByVar = /* @__PURE__ */ new Map();
38277
+ for (const d of graph.ir.dfg.defs) {
38278
+ const arr = defsByVar.get(d.variable);
38279
+ if (arr) arr.push(d);
38280
+ else defsByVar.set(d.variable, [d]);
38281
+ }
38282
+ const nameLines = /* @__PURE__ */ new Map();
38283
+ const idRe = /[A-Za-z_][A-Za-z0-9_]*/g;
38284
+ for (let i2 = 0; i2 < codeLines.length; i2++) {
38285
+ const line = codeLines[i2];
38286
+ idRe.lastIndex = 0;
38287
+ let seen = null;
38288
+ let m;
38289
+ while ((m = idRe.exec(line)) !== null) {
38290
+ const w = m[0];
38291
+ if (seen === null) seen = /* @__PURE__ */ new Set();
38292
+ if (seen.has(w)) continue;
38293
+ seen.add(w);
38294
+ const arr = nameLines.get(w);
38295
+ if (arr) arr.push(i2 + 1);
38296
+ else nameLines.set(w, [i2 + 1]);
38297
+ }
38298
+ }
38140
38299
  for (const def of graph.ir.dfg.defs) {
38141
38300
  if (def.kind !== "local") continue;
38142
38301
  const variable = def.variable;
@@ -38147,22 +38306,19 @@ var UnusedVariablePass = class {
38147
38306
  if (/\bexport\b/.test(lineText)) continue;
38148
38307
  const uses = graph.usesOfDef(def.id);
38149
38308
  if (uses.length > 0) continue;
38150
- const otherDefs = graph.ir.dfg.defs.filter(
38151
- (d) => d.variable === variable && d.id !== def.id
38152
- );
38309
+ const otherDefs = (defsByVar.get(variable) ?? []).filter((d) => d.id !== def.id);
38153
38310
  const otherDefLines = new Set(otherDefs.map((d) => d.line));
38154
- const escapedName = variable.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
38155
- const namePattern = new RegExp(`\\b${escapedName}\\b`);
38311
+ const plainName = /^[A-Za-z_][A-Za-z0-9_]*$/.test(variable);
38312
+ const idxLines = plainName ? nameLines.get(variable) ?? EMPTY_LINES : null;
38313
+ const namePattern = plainName ? null : new RegExp(`\\b${variable.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`);
38156
38314
  if (otherDefLines.size === 0) {
38157
- const usedElsewhere = codeLines.some(
38158
- (line, idx) => idx !== def.line - 1 && namePattern.test(line)
38159
- );
38315
+ const usedElsewhere = idxLines !== null ? idxLines.some((ln) => ln !== def.line) : codeLines.some((line, idx) => idx !== def.line - 1 && namePattern.test(line));
38160
38316
  if (usedElsewhere) continue;
38161
38317
  } else {
38162
38318
  const lineText2 = codeLines[def.line - 1] ?? "";
38163
38319
  const isTrueDeclaration = /\b(?:let|const|var)\s+[\w{[]/.test(lineText2) || /\b(?:int|long|float|double|boolean|byte|char|short|var|final)\b/.test(lineText2) || /\b[A-Z]\w*(?:<[^>]*>)?\s+\w/.test(lineText2) || /\blet\s+(?:mut\s+)?\w/.test(lineText2);
38164
38320
  if (isTrueDeclaration) {
38165
- const usedBeforeNextDef = codeLines.some((line, idx) => {
38321
+ const usedBeforeNextDef = idxLines !== null ? idxLines.some((ln) => ln !== def.line && !otherDefLines.has(ln) && !otherDefs.some((d) => d.line > def.line && d.line < ln)) : codeLines.some((line, idx) => {
38166
38322
  const lineNum = idx + 1;
38167
38323
  if (lineNum === def.line || otherDefLines.has(lineNum)) return false;
38168
38324
  if (!namePattern.test(line)) return false;
@@ -38170,7 +38326,7 @@ var UnusedVariablePass = class {
38170
38326
  });
38171
38327
  if (usedBeforeNextDef) continue;
38172
38328
  } else {
38173
- const usedOnNonDefLine = codeLines.some((line, idx) => {
38329
+ const usedOnNonDefLine = idxLines !== null ? idxLines.some((ln) => ln !== def.line && !otherDefLines.has(ln)) : codeLines.some((line, idx) => {
38174
38330
  const lineNum = idx + 1;
38175
38331
  return lineNum !== def.line && !otherDefLines.has(lineNum) && namePattern.test(line);
38176
38332
  });
@@ -40720,6 +40876,8 @@ function isLikelyCredentialAssignment(line) {
40720
40876
  if (CRED_DYNAMIC_VALUE_RE.test(value)) return null;
40721
40877
  if (value.length < 3) return null;
40722
40878
  if (isAllSameChar(value)) return null;
40879
+ if (value.toLowerCase() === name2.toLowerCase()) return null;
40880
+ if (/^(?:https?:\/\/|urn:|xmlns)/i.test(value)) return null;
40723
40881
  if (value.length < 12) return null;
40724
40882
  if (shannonEntropy(value) < 3.5) return null;
40725
40883
  if (charClassDiversity(value) < 2) return null;
@@ -42940,8 +43098,7 @@ var InfoDisclosureStacktracePass = class {
42940
43098
  // src/analysis/passes/unrestricted-file-upload-pass.ts
42941
43099
  var UPLOAD_NAME_RE = /(?:getOriginalFilename|getSubmittedFileName|originalname|originalName|\.filename|\.Filename|FileHeader\.Filename|UploadFile)/;
42942
43100
  var FILE_SAFE_CALL_RE = /(?:secure_filename|FilenameUtils\.getExtension|\.lastIndexOf\(['"]\.['"]\)|ALLOWED_EXT|ALLOWED_EXTENSIONS|allowedExtensions|\bfileFilter\b|filepath\.Ext|path\.extname)/;
42943
- function lineWindow(code, startLine, endLine) {
42944
- const lines = code.split("\n");
43101
+ function lineWindow(lines, startLine, endLine) {
42945
43102
  const s = Math.max(0, startLine - 1);
42946
43103
  const e = Math.min(lines.length, endLine);
42947
43104
  return lines.slice(s, e).join("\n");
@@ -42961,10 +43118,11 @@ var UnrestrictedFileUploadPass = class {
42961
43118
  const { graph, language, code } = ctx;
42962
43119
  const file = graph.ir.meta.file;
42963
43120
  const findings = [];
43121
+ const codeLines = code.split("\n");
42964
43122
  const safeFunctionRanges = [];
42965
43123
  for (const t of graph.ir.types) {
42966
43124
  for (const m of t.methods) {
42967
- const body2 = lineWindow(code, m.start_line, m.end_line);
43125
+ const body2 = lineWindow(codeLines, m.start_line, m.end_line);
42968
43126
  if (FILE_SAFE_CALL_RE.test(body2)) {
42969
43127
  safeFunctionRanges.push({ start: m.start_line, end: m.end_line });
42970
43128
  }
@@ -42974,7 +43132,7 @@ var UnrestrictedFileUploadPass = class {
42974
43132
  for (const r of safeFunctionRanges) {
42975
43133
  if (line >= r.start && line <= r.end) return true;
42976
43134
  }
42977
- const win = lineWindow(code, Math.max(1, line - 20), line + 5);
43135
+ const win = lineWindow(codeLines, Math.max(1, line - 20), line + 5);
42978
43136
  return FILE_SAFE_CALL_RE.test(win);
42979
43137
  };
42980
43138
  if (language === "java") {
@@ -9964,15 +9964,15 @@ function computeChains(defs, uses) {
9964
9964
  existing.push(use);
9965
9965
  usesByLine.set(use.line, existing);
9966
9966
  }
9967
+ const seenChains = /* @__PURE__ */ new Set();
9967
9968
  for (const def of defs) {
9968
9969
  if (def.kind === "local") {
9969
9970
  const usesOnLine = usesByLine.get(def.line) ?? [];
9970
9971
  for (const use of usesOnLine) {
9971
9972
  if (use.def_id !== null && use.def_id !== def.id) {
9972
- const exists = chains.some(
9973
- (c) => c.from_def === use.def_id && c.to_def === def.id && c.via === use.variable
9974
- );
9975
- if (!exists) {
9973
+ const key = `${use.def_id}\0${def.id}\0${use.variable}`;
9974
+ if (!seenChains.has(key)) {
9975
+ seenChains.add(key);
9976
9976
  chains.push({
9977
9977
  from_def: use.def_id,
9978
9978
  to_def: def.id,
@@ -11653,7 +11653,11 @@ var DEFAULT_SINKS = [
11653
11653
  // File: covers both File(String pathname) and File(parent, child). The 2-arg
11654
11654
  // overload's child argument carries CVE-2018-8041 (Camel mail Content-Disposition
11655
11655
  // filename written to disk).
11656
- { method: "File", class: "constructor", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0, 1] },
11656
+ // Java `new File(path)` idiom. Excluded for C#: there is no `new File()` in
11657
+ // .NET (it's `System.IO.File` statics + `FileStream`, covered separately), and
11658
+ // this over-matched ASP.NET `ControllerBase.File(stream|bytes, contentType)` —
11659
+ // a result helper that returns a file, not a path sink (cognium-ai#326 B).
11660
+ { method: "File", class: "constructor", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0, 1], exclude_languages: ["csharp"] },
11657
11661
  { method: "FileInputStream", class: "constructor", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
11658
11662
  { method: "FileOutputStream", class: "constructor", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
11659
11663
  { method: "FileReader", class: "constructor", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
@@ -11896,8 +11900,11 @@ var DEFAULT_SINKS = [
11896
11900
  // SAX handler sinks (can lead to XSS in parsed content)
11897
11901
  { method: "startElement", class: "ContentHandler", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0, 1, 2] },
11898
11902
  { method: "characters", class: "ContentHandler", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0] },
11899
- // Template output sinks
11900
- { method: "output", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0] },
11903
+ // Template output sinks. `output` is classless (Java template engines /
11904
+ // Velocity), but Rust `std::process::Command::…output()` is process
11905
+ // execution, not HTML — it already fires the CWE-78 command_injection sink,
11906
+ // so exclude Rust here to drop the spurious xss (cognium-dev#146).
11907
+ { method: "output", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], exclude_languages: ["rust"] },
11901
11908
  { method: "setOutput", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0] },
11902
11909
  { method: "writeAttribute", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0, 1] },
11903
11910
  // AntiSamy specific (SAX filters)
@@ -13205,6 +13212,22 @@ var DEFAULT_SINKS = [
13205
13212
  { method: "FileStream", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
13206
13213
  { method: "StreamReader", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
13207
13214
  { method: "StreamWriter", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
13215
+ // Remaining System.IO.File path APIs — class-scoped so they don't collide
13216
+ // with unrelated methods. Copy/Move take a source AND destination path (both
13217
+ // attacker-controllable → read-anywhere / write-anywhere), hence [0, 1].
13218
+ { method: "Copy", class: "File", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0, 1], languages: ["csharp"] },
13219
+ { method: "Move", class: "File", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0, 1], languages: ["csharp"] },
13220
+ { method: "Delete", class: "File", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
13221
+ { method: "Open", class: "File", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
13222
+ { method: "OpenText", class: "File", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
13223
+ { method: "Create", class: "File", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
13224
+ { method: "WriteAllLines", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
13225
+ { method: "AppendAllLines", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
13226
+ // System.IO.Directory path APIs.
13227
+ { method: "CreateDirectory", class: "Directory", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
13228
+ { method: "Delete", class: "Directory", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
13229
+ { method: "GetFiles", class: "Directory", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
13230
+ { method: "EnumerateFiles", class: "Directory", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
13208
13231
  // C# SSRF — HttpClient / WebClient / WebRequest (CWE-918).
13209
13232
  { method: "GetAsync", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
13210
13233
  { method: "PostAsync", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
@@ -13221,9 +13244,24 @@ var DEFAULT_SINKS = [
13221
13244
  // C# code injection — dynamic script/assembly loading (CWE-94).
13222
13245
  { method: "EvaluateAsync", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
13223
13246
  { method: "RunAsync", class: "CSharpScript", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
13224
- // C# insecure deserialization BinaryFormatter et al. (CWE-502).
13247
+ // Loading an assembly from an attacker-controlled name/path is arbitrary code
13248
+ // execution. Class-scoped to `Assembly` (low FP — a tainted arg here is rare
13249
+ // in benign code). `Type.GetType`/`Activator.CreateInstance` are intentionally
13250
+ // NOT added — reflection with config-driven type names is common in DI, an FP
13251
+ // trap without the reflection-suppress machinery.
13252
+ { method: "Load", class: "Assembly", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
13253
+ { method: "LoadFrom", class: "Assembly", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
13254
+ { method: "LoadFile", class: "Assembly", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
13255
+ // C# insecure deserialization — the polymorphic BCL formatters that can
13256
+ // instantiate arbitrary types named in the payload (CWE-502, cognium-ai#318).
13257
+ // Each of these classes exists only to deserialize and is unsafe on untrusted
13258
+ // input; class-scoped, so no collision with safe JSON APIs (System.Text.Json /
13259
+ // Newtonsoft-default, which are intentionally NOT sinks).
13225
13260
  { method: "Deserialize", class: "BinaryFormatter", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["csharp"] },
13226
13261
  { method: "Deserialize", class: "NetDataContractSerializer", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["csharp"] },
13262
+ { method: "Deserialize", class: "SoapFormatter", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["csharp"] },
13263
+ { method: "Deserialize", class: "LosFormatter", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["csharp"] },
13264
+ { method: "Deserialize", class: "ObjectStateFormatter", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["csharp"] },
13227
13265
  // C# XSS — raw HTML output (CWE-79).
13228
13266
  { method: "Raw", class: "Html", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["csharp"] },
13229
13267
  { method: "Write", class: "Response", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["csharp"] },
@@ -13248,6 +13286,20 @@ var DEFAULT_SINKS = [
13248
13286
  // cognium-dev#273/#275). `BsonDocument.Parse(userJson)` deserializes an
13249
13287
  // attacker-controlled query document. Class-scoped (static receiver resolves).
13250
13288
  { method: "Parse", class: "BsonDocument", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["csharp"] },
13289
+ // C# log injection — Microsoft.Extensions.Logging `ILogger` (CWE-117,
13290
+ // cognium-ai#318). Only the message TEMPLATE (arg[0]) is the injectable
13291
+ // position; the structured form `LogInformation("… {Id}", id)` keeps taint in
13292
+ // later args (the logger renders them as data), so arg_positions:[0] does not
13293
+ // flag it. Mirrors the Java `Logger` sinks; severity low.
13294
+ { method: "LogInformation", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0], languages: ["csharp"] },
13295
+ { method: "LogWarning", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0], languages: ["csharp"] },
13296
+ { method: "LogError", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0], languages: ["csharp"] },
13297
+ { method: "LogDebug", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0], languages: ["csharp"] },
13298
+ { method: "LogCritical", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0], languages: ["csharp"] },
13299
+ { method: "LogTrace", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0], languages: ["csharp"] },
13300
+ // Generic `ILogger.Log(logLevel, message, …)` — class-scoped (`Log` alone is
13301
+ // too generic); the message is arg[1].
13302
+ { method: "Log", class: "ILogger", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [1], languages: ["csharp"] },
13251
13303
  // C# XPath injection — System.Xml.XPath (CWE-643). Distinctive selector methods.
13252
13304
  { method: "SelectSingleNode", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0], languages: ["csharp"] },
13253
13305
  { method: "SelectNodes", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0], languages: ["csharp"] },
@@ -13256,6 +13308,11 @@ var DEFAULT_SINKS = [
13256
13308
  { method: "LoadXml", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0], languages: ["csharp"] },
13257
13309
  { method: "Load", class: "XmlDocument", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0], languages: ["csharp"] },
13258
13310
  { method: "Load", class: "XDocument", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0], languages: ["csharp"] },
13311
+ // `new XmlTextReader(input)` — its DtdProcessing defaults to Parse (resolves
13312
+ // external entities), unlike `XmlReader.Create` (Prohibit). CWE-611,
13313
+ // cognium-ai#318. The 4.7.0 XmlResolver=null / DtdProcessing=Prohibit
13314
+ // hardening still suppresses the safe shape.
13315
+ { method: "XmlTextReader", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0], languages: ["csharp"] },
13259
13316
  // Python SQLi — asyncpg Connection.*
13260
13317
  { method: "execute", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0] },
13261
13318
  { method: "fetch", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0] },
@@ -14573,6 +14630,34 @@ function isSafeJSChildProcessCall(call, pattern, language) {
14573
14630
  if (SHELL_PROGRAMS.has(program)) return false;
14574
14631
  return true;
14575
14632
  }
14633
+ function isSafeCSharpProcessStartCall(call, pattern, language) {
14634
+ if (language !== "csharp") return false;
14635
+ if (pattern.type !== "command_injection") return false;
14636
+ if (call.method_name !== "Start") return false;
14637
+ if (call.arguments.length < 2) return false;
14638
+ const fileArg = call.arguments.find((a) => a.position === 0);
14639
+ if (!fileArg) return false;
14640
+ let raw;
14641
+ if (fileArg.literal !== null && fileArg.literal !== void 0) {
14642
+ raw = String(fileArg.literal).trim();
14643
+ } else {
14644
+ raw = (fileArg.expression ?? "").trim();
14645
+ }
14646
+ if (!/^@?"[^"]*"$/.test(raw)) return false;
14647
+ const program = (raw.replace(/^@?"|"$/g, "").split(/[\\/]/).pop() ?? "").toLowerCase().replace(/\.exe$/, "");
14648
+ const SHELL_PROGRAMS = /* @__PURE__ */ new Set([
14649
+ "sh",
14650
+ "bash",
14651
+ "zsh",
14652
+ "dash",
14653
+ "ash",
14654
+ "ksh",
14655
+ "cmd",
14656
+ "powershell",
14657
+ "pwsh"
14658
+ ]);
14659
+ return !SHELL_PROGRAMS.has(program);
14660
+ }
14576
14661
  function isSafeRustCommandCall(call, pattern, language) {
14577
14662
  if (language !== "rust") return false;
14578
14663
  if (pattern.type !== "command_injection") return false;
@@ -14828,6 +14913,9 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types)
14828
14913
  if (isSafeJSChildProcessCall(call, pattern, language)) {
14829
14914
  continue;
14830
14915
  }
14916
+ if (isSafeCSharpProcessStartCall(call, pattern, language)) {
14917
+ continue;
14918
+ }
14831
14919
  if (pattern.safe_if_class_literal_at !== void 0 && argIsClassLiteral(call, pattern.safe_if_class_literal_at, types)) {
14832
14920
  continue;
14833
14921
  }