cognium-dev 4.9.10 → 4.9.12

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 (2) hide show
  1. package/dist/cli.js +238 -31
  2. package/package.json +4 -4
package/dist/cli.js CHANGED
@@ -5338,6 +5338,27 @@ var CSHARP_COMMAND_EXECUTE_METHODS = new Set([
5338
5338
  "ExecuteReaderAsync",
5339
5339
  "ExecuteNonQueryAsync"
5340
5340
  ]);
5341
+ var CSHARP_RECEIVER_URL_METHODS = new Set([
5342
+ "GetAsync",
5343
+ "PostAsync",
5344
+ "PutAsync",
5345
+ "PatchAsync",
5346
+ "DeleteAsync",
5347
+ "HeadAsync",
5348
+ "GetStringAsync",
5349
+ "GetByteArrayAsync",
5350
+ "GetStreamAsync",
5351
+ "GetJsonAsync"
5352
+ ]);
5353
+ var CSHARP_HEADER_RECEIVER_RE = /(^|\.)Headers$/;
5354
+ function csharpBareName(node) {
5355
+ if (node.type === "generic_name") {
5356
+ const id = node.childForFieldName("name") ?? node.namedChild(0);
5357
+ if (id)
5358
+ return getNodeText(id);
5359
+ }
5360
+ return getNodeText(node);
5361
+ }
5341
5362
  function extractCSharpCalls(tree, cache) {
5342
5363
  const calls = [];
5343
5364
  const typeMap = buildCSharpReceiverTypeMap(tree, cache);
@@ -5349,16 +5370,22 @@ function extractCSharpCalls(tree, cache) {
5349
5370
  if (fn?.type === "member_access_expression") {
5350
5371
  const nameNode = fn.childForFieldName("name");
5351
5372
  const exprNode = fn.childForFieldName("expression");
5352
- methodName = nameNode ? getNodeText(nameNode) : "unknown";
5373
+ methodName = nameNode ? csharpBareName(nameNode) : "unknown";
5353
5374
  receiver = exprNode ? getNodeText(exprNode) : null;
5354
5375
  } else if (fn) {
5355
- methodName = getNodeText(fn);
5376
+ methodName = csharpBareName(fn);
5356
5377
  }
5357
5378
  const argsNode = inv.childForFieldName("arguments");
5358
5379
  let args2 = argsNode ? extractCSharpArguments(argsNode) : [];
5359
5380
  if (receiver && CSHARP_COMMAND_EXECUTE_METHODS.has(methodName)) {
5360
5381
  args2 = [{ position: 0, expression: receiver, variable: receiver, literal: null, value: null }, ...args2];
5361
5382
  }
5383
+ if (methodName === "Add" && receiver && CSHARP_HEADER_RECEIVER_RE.test(receiver)) {
5384
+ methodName = "AddHeader";
5385
+ }
5386
+ if (receiver && args2.length === 0 && CSHARP_RECEIVER_URL_METHODS.has(methodName)) {
5387
+ args2 = [{ position: 0, expression: receiver, variable: receiver, literal: null, value: null }];
5388
+ }
5362
5389
  calls.push({
5363
5390
  method_name: methodName,
5364
5391
  receiver,
@@ -5374,7 +5401,7 @@ function extractCSharpCalls(tree, cache) {
5374
5401
  const typeNode = creation.childForFieldName("type");
5375
5402
  const argsNode = creation.childForFieldName("arguments");
5376
5403
  calls.push({
5377
- method_name: typeNode ? getNodeText(typeNode) : "unknown",
5404
+ method_name: typeNode ? csharpBareName(typeNode) : "unknown",
5378
5405
  receiver: null,
5379
5406
  receiver_type: null,
5380
5407
  receiver_type_fqn: null,
@@ -5387,6 +5414,41 @@ function extractCSharpCalls(tree, cache) {
5387
5414
  const assignments = getNodesFromCache(tree.rootNode, "assignment_expression", cache);
5388
5415
  for (const asn of assignments) {
5389
5416
  const left = asn.childForFieldName("left");
5417
+ if (left?.type === "element_access_expression") {
5418
+ const target = left.childForFieldName("expression");
5419
+ const right2 = asn.childForFieldName("right");
5420
+ if (!target || !right2)
5421
+ continue;
5422
+ if (!CSHARP_HEADER_RECEIVER_RE.test(getNodeText(target)))
5423
+ continue;
5424
+ const subscript = left.childForFieldName("subscript") ?? left.namedChild(1);
5425
+ const rhsText2 = getNodeText(right2);
5426
+ calls.push({
5427
+ method_name: "AddHeader",
5428
+ receiver: getNodeText(target),
5429
+ receiver_type: null,
5430
+ receiver_type_fqn: null,
5431
+ arguments: [
5432
+ {
5433
+ position: 0,
5434
+ expression: subscript ? getNodeText(subscript) : "",
5435
+ variable: null,
5436
+ literal: subscript ? getNodeText(subscript) : null,
5437
+ value: null
5438
+ },
5439
+ {
5440
+ position: 1,
5441
+ expression: rhsText2,
5442
+ variable: right2.type === "identifier" ? rhsText2 : null,
5443
+ literal: right2.type === "string_literal" ? rhsText2 : null,
5444
+ value: null
5445
+ }
5446
+ ],
5447
+ location: { line: asn.startPosition.row + 1, column: asn.startPosition.column },
5448
+ in_method: findEnclosingMethod(asn)
5449
+ });
5450
+ continue;
5451
+ }
5390
5452
  if (left?.type !== "member_access_expression")
5391
5453
  continue;
5392
5454
  const nameNode = left.childForFieldName("name");
@@ -12474,6 +12536,17 @@ var DEFAULT_SINKS = [
12474
12536
  { method: "ExecuteSqlRawAsync", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
12475
12537
  { method: "FromSqlRaw", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
12476
12538
  { method: "FromSqlRawAsync", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
12539
+ { method: "Query", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
12540
+ { method: "QueryAsync", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
12541
+ { method: "QueryFirst", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
12542
+ { method: "QueryFirstAsync", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
12543
+ { method: "QueryFirstOrDefault", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
12544
+ { method: "QueryFirstOrDefaultAsync", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
12545
+ { method: "QuerySingle", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
12546
+ { method: "QuerySingleOrDefault", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
12547
+ { method: "QueryMultiple", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
12548
+ { method: "Execute", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
12549
+ { method: "ExecuteAsync", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
12477
12550
  { method: "CommandText", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
12478
12551
  { method: "Filter", type: "ldap_injection", cwe: "CWE-90", severity: "high", arg_positions: [0], languages: ["csharp"] },
12479
12552
  { method: "ExecuteScalar", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
@@ -12515,12 +12588,16 @@ var DEFAULT_SINKS = [
12515
12588
  { method: "DeleteAsync", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
12516
12589
  { method: "GetStringAsync", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
12517
12590
  { method: "GetByteArrayAsync", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
12591
+ { method: "GetJsonAsync", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
12592
+ { method: "PatchAsync", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
12593
+ { method: "HeadAsync", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
12518
12594
  { method: "GetStreamAsync", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
12519
12595
  { method: "SendAsync", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
12520
12596
  { method: "DownloadString", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
12521
12597
  { method: "DownloadData", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
12522
12598
  { method: "DownloadFile", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [1], languages: ["csharp"] },
12523
12599
  { method: "Create", class: "WebRequest", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
12600
+ { method: "RestClient", class: "constructor", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
12524
12601
  { method: "EvaluateAsync", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
12525
12602
  { method: "RunAsync", class: "CSharpScript", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
12526
12603
  { method: "Load", class: "Assembly", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
@@ -12538,6 +12615,7 @@ var DEFAULT_SINKS = [
12538
12615
  { method: "Write", class: "Response", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["csharp"] },
12539
12616
  { method: "HtmlString", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["csharp"] },
12540
12617
  { method: "MarkupString", class: "constructor", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["csharp"] },
12618
+ { method: "Content", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["csharp"] },
12541
12619
  { method: "Format", class: "String", type: "format_string", cwe: "CWE-134", severity: "medium", arg_positions: [0], languages: ["csharp"] },
12542
12620
  { method: "DirectorySearcher", type: "ldap_injection", cwe: "CWE-90", severity: "high", arg_positions: [0], languages: ["csharp"] },
12543
12621
  { method: "DirectoryEntry", type: "ldap_injection", cwe: "CWE-90", severity: "high", arg_positions: [0], languages: ["csharp"] },
@@ -12546,12 +12624,18 @@ var DEFAULT_SINKS = [
12546
12624
  { method: "AddHeader", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [1], languages: ["csharp"] },
12547
12625
  { method: "AppendHeader", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [1], languages: ["csharp"] },
12548
12626
  { method: "Parse", class: "BsonDocument", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["csharp"] },
12627
+ { method: "BsonJavaScript", class: "constructor", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["csharp"] },
12549
12628
  { method: "LogInformation", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0], languages: ["csharp"] },
12550
12629
  { method: "LogWarning", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0], languages: ["csharp"] },
12551
12630
  { method: "LogError", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0], languages: ["csharp"] },
12552
12631
  { method: "LogDebug", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0], languages: ["csharp"] },
12553
12632
  { method: "LogCritical", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0], languages: ["csharp"] },
12554
12633
  { method: "LogTrace", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0], languages: ["csharp"] },
12634
+ { method: "IsMatch", class: "Regex", type: "redos", cwe: "CWE-1333", severity: "medium", arg_positions: [1], languages: ["csharp"] },
12635
+ { method: "Match", class: "Regex", type: "redos", cwe: "CWE-1333", severity: "medium", arg_positions: [1], languages: ["csharp"] },
12636
+ { method: "Matches", class: "Regex", type: "redos", cwe: "CWE-1333", severity: "medium", arg_positions: [1], languages: ["csharp"] },
12637
+ { method: "Replace", class: "Regex", type: "redos", cwe: "CWE-1333", severity: "medium", arg_positions: [1], languages: ["csharp"] },
12638
+ { method: "Regex", class: "constructor", type: "redos", cwe: "CWE-1333", severity: "medium", arg_positions: [0], languages: ["csharp"] },
12555
12639
  { method: "Log", class: "ILogger", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [1], languages: ["csharp"] },
12556
12640
  { method: "SelectSingleNode", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0], languages: ["csharp"] },
12557
12641
  { method: "SelectNodes", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0], languages: ["csharp"] },
@@ -12691,6 +12775,8 @@ var DEFAULT_SANITIZERS = [
12691
12775
  { method: "encodeToString", removes: ["sql_injection", "command_injection", "xss", "path_traversal", "code_injection"] },
12692
12776
  { method: "encodeHexString", removes: ["sql_injection", "command_injection", "xss", "path_traversal", "code_injection"] },
12693
12777
  { method: "encodeForURL", removes: ["xss", "ssrf", "open_redirect"] },
12778
+ { method: "IsLocalUrl", removes: ["open_redirect"] },
12779
+ { method: "LocalRedirect", removes: ["open_redirect"] },
12694
12780
  { method: "encodeURL", removes: ["xss", "ssrf", "open_redirect"] },
12695
12781
  { method: "urlEncode", removes: ["xss", "ssrf", "open_redirect"] },
12696
12782
  { method: "escapeUrl", removes: ["xss", "ssrf", "open_redirect"] },
@@ -13106,6 +13192,22 @@ var PYTHON_TAINTED_PATTERNS = [
13106
13192
  { pattern: /\brequest\.path_params\b/, sourceType: "http_param" },
13107
13193
  { pattern: /\binput\s*\(/, sourceType: "io_input" }
13108
13194
  ];
13195
+ function dropCSharpObjectCarriedWaypoints(sinks, calls) {
13196
+ const zeroArgCtors = new Set;
13197
+ for (const call of calls) {
13198
+ if (call.is_constructor && call.arguments.length === 0) {
13199
+ zeroArgCtors.add(`${call.location.line}:${call.method_name}`);
13200
+ }
13201
+ }
13202
+ return sinks.filter((sink) => {
13203
+ if (sink.type !== "sql_injection")
13204
+ return true;
13205
+ if (sink.method && zeroArgCtors.has(`${sink.line}:${sink.method}`)) {
13206
+ return false;
13207
+ }
13208
+ return true;
13209
+ });
13210
+ }
13109
13211
  function analyzeTaint(calls, types, config = getDefaultConfig(), typeHierarchy, language, code) {
13110
13212
  const sourceLines = code !== undefined ? code.split(`
13111
13213
  `) : undefined;
@@ -13113,8 +13215,9 @@ function analyzeTaint(calls, types, config = getDefaultConfig(), typeHierarchy,
13113
13215
  let sinkPatterns = expandPromisifyAliases(config.sinks, sourceLines, language);
13114
13216
  sinkPatterns = expandIndirectEvalAliases(sinkPatterns, sourceLines, language);
13115
13217
  const sinks = findSinks(calls, sinkPatterns, typeHierarchy, language, sourceLines, types);
13218
+ const gatedSinks = language === "csharp" ? dropCSharpObjectCarriedWaypoints(sinks, calls) : sinks;
13116
13219
  const sanitizers = findSanitizers(calls, types, config.sanitizers, sourceLines);
13117
- return { sources, sinks, sanitizers };
13220
+ return { sources, sinks: gatedSinks, sanitizers };
13118
13221
  }
13119
13222
  function sinkPatternAppliesTo(pattern, language) {
13120
13223
  if (language === undefined)
@@ -13399,7 +13502,7 @@ function findSources(calls, types, patterns, sourceLines, language) {
13399
13502
  }
13400
13503
  const sourceMap = new Map;
13401
13504
  for (const source of sources) {
13402
- const key = `${source.line}:${source.type}`;
13505
+ const key = source.type === "interprocedural_param" && source.variable ? `${source.line}:${source.type}:${source.variable}` : `${source.line}:${source.type}`;
13403
13506
  const existing = sourceMap.get(key);
13404
13507
  if (!existing || source.confidence > existing.confidence) {
13405
13508
  sourceMap.set(key, source);
@@ -14077,6 +14180,19 @@ var CWE_78_RECEIVER_ALLOWLIST = new Set([
14077
14180
  "ProcessExecutor",
14078
14181
  "RuntimeUtil"
14079
14182
  ]);
14183
+ function isRegexReceiver(receiver, sourceLines) {
14184
+ const r = (receiver ?? "").trim();
14185
+ if (r.length === 0)
14186
+ return false;
14187
+ if (r.startsWith("/") && /\/[a-z]*$/.test(r))
14188
+ return true;
14189
+ if (/^(?:new\s+)?RegExp\s*\(/.test(r))
14190
+ return true;
14191
+ if (!/^[A-Za-z_$][\w$]*$/.test(r) || !sourceLines)
14192
+ return false;
14193
+ const decl = new RegExp(`(?:const|let|var)\\s+${escapeRe(r)}\\s*=\\s*(?:/|(?:new\\s+)?RegExp\\s*\\()`);
14194
+ return sourceLines.some((l) => decl.test(l));
14195
+ }
14080
14196
  function isFunctionCallbackArgument(arg) {
14081
14197
  if (arg.literal !== null && arg.literal !== undefined)
14082
14198
  return false;
@@ -14284,6 +14400,12 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types)
14284
14400
  continue;
14285
14401
  }
14286
14402
  }
14403
+ if (pattern.type === "command_injection" && call.method_name === "exec" && (language === "javascript" || language === "typescript") && isRegexReceiver(call.receiver, sourceLines)) {
14404
+ continue;
14405
+ }
14406
+ if (pattern.type === "code_injection" && language === "python" && call.method_name === "compile" && call.receiver && call.receiver !== "builtins") {
14407
+ continue;
14408
+ }
14287
14409
  if (isSafeGoJsonUnmarshalCall(call, pattern, language, sourceLines)) {
14288
14410
  continue;
14289
14411
  }
@@ -15388,12 +15510,12 @@ function walkBackwardDefs(startDefId, chainsByToDef, defById, options = {}) {
15388
15510
  // node_modules/circle-ir/dist/analysis/findings.js
15389
15511
  function canSourceReachSink(sourceType, sinkType) {
15390
15512
  const sourceToSinkMapping = {
15391
- http_param: ["sql_injection", "command_injection", "path_traversal", "xss", "xpath_injection", "ldap_injection", "ssrf", "mybatis_mapper_call", "code_injection", "crlf", "mass_assignment", "open_redirect", "trust_boundary", "deserialization", "log_injection", "format_string", "nosql_injection", "prompt_injection"],
15513
+ http_param: ["sql_injection", "command_injection", "path_traversal", "xss", "xpath_injection", "ldap_injection", "ssrf", "mybatis_mapper_call", "code_injection", "crlf", "mass_assignment", "open_redirect", "trust_boundary", "deserialization", "xxe", "log_injection", "format_string", "nosql_injection", "prompt_injection"],
15392
15514
  http_body: ["sql_injection", "command_injection", "deserialization", "xxe", "xss", "code_injection", "mybatis_mapper_call", "crlf", "mass_assignment", "open_redirect", "trust_boundary", "log_injection", "format_string", "nosql_injection", "prompt_injection"],
15393
15515
  http_header: ["sql_injection", "xss", "ssrf", "mybatis_mapper_call", "code_injection", "crlf", "open_redirect", "trust_boundary", "log_injection", "format_string", "nosql_injection", "prompt_injection"],
15394
15516
  http_cookie: ["sql_injection", "xss", "mybatis_mapper_call", "code_injection", "crlf", "open_redirect", "trust_boundary", "log_injection", "format_string", "nosql_injection", "prompt_injection"],
15395
15517
  http_path: ["path_traversal", "sql_injection", "ssrf", "mybatis_mapper_call", "open_redirect", "trust_boundary", "xss", "log_injection", "format_string", "prompt_injection"],
15396
- http_query: ["sql_injection", "command_injection", "xss", "ssrf", "mybatis_mapper_call", "code_injection", "crlf", "mass_assignment", "open_redirect", "trust_boundary", "deserialization", "log_injection", "format_string", "nosql_injection", "prompt_injection"],
15518
+ http_query: ["sql_injection", "command_injection", "xss", "ssrf", "mybatis_mapper_call", "code_injection", "crlf", "mass_assignment", "open_redirect", "trust_boundary", "deserialization", "xxe", "log_injection", "format_string", "nosql_injection", "prompt_injection"],
15397
15519
  io_input: ["command_injection", "path_traversal", "deserialization", "xxe", "code_injection", "xss", "ssrf", "log_injection", "format_string", "prompt_injection"],
15398
15520
  env_input: ["command_injection", "path_traversal"],
15399
15521
  db_input: ["xss", "sql_injection", "log_injection"],
@@ -17801,6 +17923,9 @@ function findInitialTaint(sources, callsByLine, defsByLine) {
17801
17923
  for (const source of sources) {
17802
17924
  const defsOnLine = defsByLine.get(source.line) ?? [];
17803
17925
  for (const def of defsOnLine) {
17926
+ if (source.type === "interprocedural_param" && source.variable && def.kind === "param" && def.variable !== source.variable) {
17927
+ continue;
17928
+ }
17804
17929
  tainted.push({
17805
17930
  variable: def.variable,
17806
17931
  defId: def.id,
@@ -23170,13 +23295,6 @@ class RustPlugin extends BaseLanguagePlugin {
23170
23295
  severity: "high",
23171
23296
  argPositions: [0]
23172
23297
  },
23173
- {
23174
- method: "format!",
23175
- type: "format_string",
23176
- cwe: "CWE-134",
23177
- severity: "medium",
23178
- argPositions: [0]
23179
- },
23180
23298
  {
23181
23299
  method: "from_raw_parts",
23182
23300
  type: "unsafe_memory",
@@ -26134,12 +26252,21 @@ function findCSharpRequestSources(sourceCode, language) {
26134
26252
  const requestReadRe = /\bRequest\s*\.\s*(?:Query|Form|Headers|Cookies|QueryString|RouteValues|Body|Files|Params)\b/;
26135
26253
  const consoleReadRe = /\bConsole\s*\.\s*ReadLine\s*\(/;
26136
26254
  const envReadRe = /\bEnvironment\s*\.\s*GetEnvironmentVariables?\s*(?:\(|\[)/;
26255
+ const formFileParams = new Set;
26256
+ const formFileDeclRe = /\bIFormFile\s+([A-Za-z_]\w*)/g;
26257
+ for (const line of lines) {
26258
+ const re = new RegExp(formFileDeclRe.source, "g");
26259
+ let d;
26260
+ while ((d = re.exec(line)) !== null)
26261
+ formFileParams.add(d[1]);
26262
+ }
26263
+ const formFileReadRe = formFileParams.size > 0 ? new RegExp(`\\b(?:${[...formFileParams].join("|")})\\s*\\.\\s*(?:FileName|ContentType)\\b`) : null;
26137
26264
  for (let i2 = 0;i2 < lines.length; i2++) {
26138
26265
  const m = assignRe.exec(lines[i2]);
26139
26266
  if (!m)
26140
26267
  continue;
26141
26268
  const [, varName, rhs] = m;
26142
- const type = requestReadRe.test(rhs) ? "http_param" : consoleReadRe.test(rhs) ? "io_input" : envReadRe.test(rhs) ? "env_input" : null;
26269
+ const type = requestReadRe.test(rhs) ? "http_param" : formFileReadRe?.test(rhs) ? "http_param" : consoleReadRe.test(rhs) ? "io_input" : envReadRe.test(rhs) ? "env_input" : null;
26143
26270
  if (!type)
26144
26271
  continue;
26145
26272
  const lineNumber = i2 + 1;
@@ -35729,7 +35856,7 @@ class TaintPropagationPass {
35729
35856
  for (const f of paramFlows) {
35730
35857
  pushIfNew(f);
35731
35858
  }
35732
- const exprScanFlows = detectExpressionScanFlows(calls, sources, sinks, sanitizers, constProp.unreachableLines, constProp.tainted, ctx.code, ctx.language) ?? [];
35859
+ const exprScanFlows = detectExpressionScanFlows(calls, sources, sinks, sanitizers, constProp.unreachableLines, constProp.tainted, ctx.code, ctx.language, types) ?? [];
35733
35860
  for (const f of exprScanFlows) {
35734
35861
  if (flowKeys.has(flowKey(f)))
35735
35862
  continue;
@@ -35893,6 +36020,35 @@ class TaintPropagationPass {
35893
36020
  return bestByKey.get(key) === f;
35894
36021
  });
35895
36022
  }
36023
+ if (ctx.language === "csharp" && finalFlows.length > 1) {
36024
+ const CSHARP_ADO_EXECUTE = /^Execute(?:Reader|NonQuery|Scalar)(?:Async)?$/;
36025
+ const commandTextReceiverByLine = new Map;
36026
+ const execLinesByReceiver = new Map;
36027
+ for (const call of calls) {
36028
+ const receiver = (call.receiver ?? "").trim();
36029
+ if (!receiver)
36030
+ continue;
36031
+ if (call.method_name === "CommandText") {
36032
+ commandTextReceiverByLine.set(call.location.line, receiver);
36033
+ } else if (CSHARP_ADO_EXECUTE.test(call.method_name)) {
36034
+ const lines = execLinesByReceiver.get(receiver) ?? [];
36035
+ lines.push(call.location.line);
36036
+ execLinesByReceiver.set(receiver, lines);
36037
+ }
36038
+ }
36039
+ if (commandTextReceiverByLine.size > 0) {
36040
+ const reportedSqlSinkLines = new Set(finalFlows.filter((f) => f.sink_type === "sql_injection").map((f) => f.sink_line));
36041
+ finalFlows = finalFlows.filter((f) => {
36042
+ if (f.sink_type !== "sql_injection")
36043
+ return true;
36044
+ const receiver = commandTextReceiverByLine.get(f.sink_line);
36045
+ if (receiver === undefined)
36046
+ return true;
36047
+ const execLines = execLinesByReceiver.get(receiver) ?? [];
36048
+ return !execLines.some((line) => line > f.sink_line && reportedSqlSinkLines.has(line));
36049
+ });
36050
+ }
36051
+ }
35896
36052
  return { flows: finalFlows };
35897
36053
  }
35898
36054
  }
@@ -36359,7 +36515,7 @@ function isReassignedToLiteralBetween(code, variable, srcLine, sinkLine) {
36359
36515
  }
36360
36516
  return false;
36361
36517
  }
36362
- function detectExpressionScanFlows(calls, sources, sinks, sanitizers, unreachableLines, tainted, code, language) {
36518
+ function detectExpressionScanFlows(calls, sources, sinks, sanitizers, unreachableLines, tainted, code, language, types) {
36363
36519
  const flows = [];
36364
36520
  const sourcesWithVar = sources.filter((s) => typeof s.variable === "string" && s.variable.length > 0);
36365
36521
  const aliasSanitizedFor = new Map;
@@ -36642,13 +36798,37 @@ function detectExpressionScanFlows(calls, sources, sinks, sanitizers, unreachabl
36642
36798
  if (s.line < anchor.line)
36643
36799
  anchor = s;
36644
36800
  }
36801
+ const methodAt = (line) => {
36802
+ for (const t of types ?? []) {
36803
+ for (const m of t.methods) {
36804
+ if (line >= m.start_line && line <= m.end_line)
36805
+ return m.name;
36806
+ }
36807
+ }
36808
+ return null;
36809
+ };
36645
36810
  const existingVars = new Set(sourcesWithVar.map((s) => s.variable));
36646
- for (const [varName] of derived) {
36811
+ for (const [varName, derivedLine] of derived) {
36647
36812
  if (!varName || existingVars.has(varName))
36648
36813
  continue;
36814
+ const owner = methodAt(derivedLine);
36815
+ let scopedAnchor = anchor;
36816
+ if (owner) {
36817
+ let best;
36818
+ for (const s of sourcesWithVar) {
36819
+ const sOwner = s.in_method ?? methodAt(s.line);
36820
+ if (sOwner !== owner)
36821
+ continue;
36822
+ if (!best || s.line < best.line)
36823
+ best = s;
36824
+ }
36825
+ if (best)
36826
+ scopedAnchor = best;
36827
+ }
36649
36828
  sourcesWithVar.push({
36650
- ...anchor,
36651
- variable: varName
36829
+ ...scopedAnchor,
36830
+ variable: varName,
36831
+ ...owner ? { in_method: owner } : {}
36652
36832
  });
36653
36833
  existingVars.add(varName);
36654
36834
  }
@@ -48398,6 +48578,13 @@ function mergeMavenInheritance(modules, scanRoot) {
48398
48578
  byBuildFile.set(normalize(m.buildFile), m);
48399
48579
  }
48400
48580
  }
48581
+ const ownSignals = new Map;
48582
+ for (const [buildFile, m] of byBuildFile) {
48583
+ ownSignals.set(buildFile, {
48584
+ urls: [...m.signals.distributionUrls],
48585
+ plugins: [...m.signals.plugins]
48586
+ });
48587
+ }
48401
48588
  for (const child of modules) {
48402
48589
  if (child.buildSystem !== "maven")
48403
48590
  continue;
@@ -48405,7 +48592,7 @@ function mergeMavenInheritance(modules, scanRoot) {
48405
48592
  continue;
48406
48593
  const inheritedUrls = new Set;
48407
48594
  const inheritedPlugins = new Set;
48408
- walkParents(child, byBuildFile, normalizedScanRoot, inheritedUrls, inheritedPlugins);
48595
+ walkParents(child, byBuildFile, ownSignals, normalizedScanRoot, inheritedUrls, inheritedPlugins);
48409
48596
  if (inheritedUrls.size === 0 && inheritedPlugins.size === 0)
48410
48597
  continue;
48411
48598
  const existingUrls = new Set(child.signals.distributionUrls);
@@ -48420,7 +48607,7 @@ function mergeMavenInheritance(modules, scanRoot) {
48420
48607
  }
48421
48608
  }
48422
48609
  }
48423
- function walkParents(start2, byBuildFile, scanRoot, outUrls, outPlugins) {
48610
+ function walkParents(start2, byBuildFile, ownSignals, scanRoot, outUrls, outPlugins) {
48424
48611
  const visited = new Set([normalize(start2.buildFile)]);
48425
48612
  let current = start2;
48426
48613
  for (let depth = 0;depth < MAX_DEPTH; depth++) {
@@ -48442,10 +48629,13 @@ function walkParents(start2, byBuildFile, scanRoot, outUrls, outPlugins) {
48442
48629
  const parent = byBuildFile.get(parentBuildFile);
48443
48630
  if (!parent)
48444
48631
  return;
48445
- for (const u of parent.signals.distributionUrls)
48446
- outUrls.add(u);
48447
- for (const p of parent.signals.plugins)
48448
- outPlugins.add(p);
48632
+ const parentOwn = ownSignals.get(parentBuildFile);
48633
+ if (parentOwn) {
48634
+ for (const u of parentOwn.urls)
48635
+ outUrls.add(u);
48636
+ for (const p of parentOwn.plugins)
48637
+ outPlugins.add(p);
48638
+ }
48449
48639
  current = parent;
48450
48640
  }
48451
48641
  }
@@ -48821,7 +49011,7 @@ var colors = {
48821
49011
  };
48822
49012
 
48823
49013
  // src/version.ts
48824
- var version = "4.9.10";
49014
+ var version = "4.9.12";
48825
49015
 
48826
49016
  // src/formatters.ts
48827
49017
  var LIBRARY_API_SURFACE_TAG2 = "library-api-surface:caller-responsibility";
@@ -49374,8 +49564,8 @@ function generateSarifResults(results, crossFileData) {
49374
49564
 
49375
49565
  // src/build-info.ts
49376
49566
  var buildInfo = {
49377
- gitSha: "132eaf6",
49378
- builtAt: "2026-09-02T06:21:26.855Z"
49567
+ gitSha: "344dd1f",
49568
+ builtAt: "2026-09-10T17:10:54.335Z"
49379
49569
  };
49380
49570
 
49381
49571
  // src/utils/args.ts
@@ -49719,6 +49909,22 @@ var LANG_MAP = {
49719
49909
  ".html": "html",
49720
49910
  ".htm": "html"
49721
49911
  };
49912
+ var VULN_SEVERITY_RANK = { critical: 3, high: 2, medium: 1, low: 0 };
49913
+ function dedupeVulnerabilities(vulns) {
49914
+ const best = new Map;
49915
+ const order = [];
49916
+ for (const v of vulns) {
49917
+ const key = `${v.type}:${v.line}`;
49918
+ const current = best.get(key);
49919
+ if (current === undefined) {
49920
+ best.set(key, v);
49921
+ order.push(key);
49922
+ } else if ((VULN_SEVERITY_RANK[v.severity] ?? -1) > (VULN_SEVERITY_RANK[current.severity] ?? -1)) {
49923
+ best.set(key, v);
49924
+ }
49925
+ }
49926
+ return order.map((k) => best.get(k));
49927
+ }
49722
49928
  function detectLanguage7(filePath) {
49723
49929
  const ext = extname(filePath).toLowerCase();
49724
49930
  return LANG_MAP[ext] || null;
@@ -49813,7 +50019,7 @@ async function scanFile(filePath, language, analyzeOpts) {
49813
50019
  ...finding.tags && finding.tags.length > 0 ? { tags: [...finding.tags] } : {}
49814
50020
  });
49815
50021
  }
49816
- return { file: filePath, vulnerabilities };
50022
+ return { file: filePath, vulnerabilities: dedupeVulnerabilities(vulnerabilities) };
49817
50023
  } catch (error) {
49818
50024
  return {
49819
50025
  file: filePath,
@@ -49856,7 +50062,7 @@ async function scanProject(files, language, analyzeOpts, crossFileBudgetMs) {
49856
50062
  ...finding.tags && finding.tags.length > 0 ? { tags: [...finding.tags] } : {}
49857
50063
  });
49858
50064
  }
49859
- return { file, vulnerabilities };
50065
+ return { file, vulnerabilities: dedupeVulnerabilities(vulnerabilities) };
49860
50066
  });
49861
50067
  return {
49862
50068
  results,
@@ -50756,6 +50962,7 @@ export {
50756
50962
  loadConfig,
50757
50963
  isTestFile2 as isTestFile,
50758
50964
  detectLanguage7 as detectLanguage,
50965
+ dedupeVulnerabilities,
50759
50966
  convertConfigToPassOptions,
50760
50967
  applySuppressionsToResults,
50761
50968
  PASS_REGISTRY
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cognium-dev",
3
- "version": "4.9.10",
3
+ "version": "4.9.12",
4
4
  "description": "Static Application Security Testing CLI for detecting security vulnerabilities via taint tracking",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -14,7 +14,7 @@
14
14
  "test": "bun test",
15
15
  "clean": "rm -rf dist cognium-dev cognium-dev-* *.bun-build wasm",
16
16
  "typecheck": "tsc --noEmit",
17
- "version": "node -e \"const v = require('./package.json').version; require('fs').writeFileSync('src/version.ts', '/**\\n * Version information\\n *\\n * Kept in sync with package.json via the \\`version\\` npm lifecycle script.\\n * Do not edit manually use \\`npm version patch|minor|major\\` instead.\\n */\\nexport const version = \\x27' + v + '\\x27;\\n')\" && git add src/version.ts",
17
+ "version": "node -e \"const v = require('./package.json').version; require('fs').writeFileSync('src/version.ts', '/**\\n * Version information\\n *\\n * Kept in sync with package.json via the \\`version\\` npm lifecycle script.\\n * Do not edit manually \u2014 use \\`npm version patch|minor|major\\` instead.\\n */\\nexport const version = \\x27' + v + '\\x27;\\n')\" && git add src/version.ts",
18
18
  "dogfood": "bun run src/cli.ts scan src/ -q",
19
19
  "prepublishOnly": "bun run build"
20
20
  },
@@ -65,8 +65,8 @@
65
65
  "registry": "https://registry.npmjs.org/"
66
66
  },
67
67
  "dependencies": {
68
- "@cognium/project-profile-detect": "1.1.0",
69
- "circle-ir": "4.9.10"
68
+ "@cognium/project-profile-detect": "1.1.1",
69
+ "circle-ir": "4.9.12"
70
70
  },
71
71
  "devDependencies": {
72
72
  "@types/node": "^25.5.0",