cognium-dev 3.181.0 → 3.185.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 (2) hide show
  1. package/dist/cli.js +376 -11
  2. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -9440,6 +9440,10 @@ function processGoBlock(node, defs, uses, scopeStack, counters) {
9440
9440
  } else if (child.type === "for_statement") {
9441
9441
  const rangeClause = findChildByTypeGo(child, "range_clause");
9442
9442
  if (rangeClause) {
9443
+ const right = rangeClause.childForFieldName("right");
9444
+ if (right) {
9445
+ extractGoUses(right, uses, scopeStack);
9446
+ }
9443
9447
  const left = rangeClause.childForFieldName("left");
9444
9448
  if (left) {
9445
9449
  extractGoLhsDefs(left, defs, scopeStack, child.startPosition.row + 1);
@@ -9447,6 +9451,7 @@ function processGoBlock(node, defs, uses, scopeStack, counters) {
9447
9451
  }
9448
9452
  } else if (child.type === "call_expression") {
9449
9453
  extractGoUses(child, uses, scopeStack);
9454
+ recordGoOpaqueCodecDestDef(child, defs, scopeStack);
9450
9455
  } else if (child.type === "return_statement") {
9451
9456
  for (let i2 = 0;i2 < child.childCount; i2++) {
9452
9457
  const expr = child.child(i2);
@@ -9546,6 +9551,70 @@ function findChildByTypeGo(node, type) {
9546
9551
  }
9547
9552
  return null;
9548
9553
  }
9554
+ var GO_OPAQUE_CODEC_METHODS = new Set([
9555
+ "Unmarshal",
9556
+ "Decode",
9557
+ "NewDecoder",
9558
+ "UnmarshalYAML",
9559
+ "UnmarshalJSON",
9560
+ "UnmarshalText",
9561
+ "UnmarshalBinary"
9562
+ ]);
9563
+ function recordGoOpaqueCodecDestDef(call, defs, scopeStack) {
9564
+ const fn = call.childForFieldName("function");
9565
+ if (!fn || fn.type !== "selector_expression")
9566
+ return;
9567
+ const fieldNode = fn.childForFieldName("field");
9568
+ if (!fieldNode)
9569
+ return;
9570
+ const method = getNodeText(fieldNode);
9571
+ if (!GO_OPAQUE_CODEC_METHODS.has(method))
9572
+ return;
9573
+ const argsNode = call.childForFieldName("arguments");
9574
+ if (!argsNode)
9575
+ return;
9576
+ const args2 = [];
9577
+ for (let i2 = 0;i2 < argsNode.childCount; i2++) {
9578
+ const c = argsNode.child(i2);
9579
+ if (!c)
9580
+ continue;
9581
+ if (c.type === "," || c.type === "(" || c.type === ")")
9582
+ continue;
9583
+ args2.push(c);
9584
+ }
9585
+ if (args2.length === 0)
9586
+ return;
9587
+ const destArg = args2.length >= 2 ? args2[1] : args2[0];
9588
+ const destName = extractGoAddressableVarName(destArg);
9589
+ if (!destName || destName === "_")
9590
+ return;
9591
+ const line = call.startPosition.row + 1;
9592
+ const def = {
9593
+ id: defs.length + 1,
9594
+ variable: destName,
9595
+ kind: "local",
9596
+ line
9597
+ };
9598
+ defs.push(def);
9599
+ currentScope(scopeStack).set(destName, def.id);
9600
+ }
9601
+ function extractGoAddressableVarName(node) {
9602
+ if (node.type === "unary_expression") {
9603
+ const operand = node.childForFieldName("operand") ?? node.child(1) ?? null;
9604
+ if (operand)
9605
+ return extractGoAddressableVarName(operand);
9606
+ return null;
9607
+ }
9608
+ if (node.type === "identifier") {
9609
+ return getNodeText(node);
9610
+ }
9611
+ if (node.type === "selector_expression") {
9612
+ const field = node.childForFieldName("field");
9613
+ if (field)
9614
+ return getNodeText(field);
9615
+ }
9616
+ return null;
9617
+ }
9549
9618
  // ../circle-ir/dist/core/extractors/runtime-registrations.js
9550
9619
  var HTTP_VERB_METHODS = new Set([
9551
9620
  "get",
@@ -10622,6 +10691,19 @@ var DEFAULT_SOURCES = [
10622
10691
  { method: "getMap", class: "Metadata", type: "http_header", severity: "high", return_tainted: true, languages: ["javascript", "typescript"] },
10623
10692
  { method: "FromIncomingContext", class: "metadata", type: "http_header", severity: "high", return_tainted: true, languages: ["go"] },
10624
10693
  { method: "get", class: "Metadata", type: "http_header", severity: "high", return_tainted: true, languages: ["java"] },
10694
+ { method: "receive_text", class: "WebSocket", type: "network_input", severity: "high", return_tainted: true, languages: ["python"] },
10695
+ { method: "receive_bytes", class: "WebSocket", type: "network_input", severity: "high", return_tainted: true, languages: ["python"] },
10696
+ { method: "receive_json", class: "WebSocket", type: "http_body", severity: "high", return_tainted: true, languages: ["python"] },
10697
+ { method: "receive", class: "WebSocket", type: "network_input", severity: "high", return_tainted: true, languages: ["python"] },
10698
+ { method: "receive_json", type: "http_body", severity: "high", return_tainted: true, languages: ["python"] },
10699
+ { method: "receive_text", type: "network_input", severity: "high", return_tainted: true, languages: ["python"] },
10700
+ { method: "receive_bytes", type: "network_input", severity: "high", return_tainted: true, languages: ["python"] },
10701
+ { method: "ReadMessage", class: "Conn", type: "network_input", severity: "high", return_tainted: true, languages: ["go"] },
10702
+ { method: "NextReader", class: "Conn", type: "network_input", severity: "high", return_tainted: true, languages: ["go"] },
10703
+ { method: "Read", class: "Conn", type: "network_input", severity: "high", return_tainted: true, languages: ["go"] },
10704
+ { annotation: "OnMessage", type: "network_input", severity: "high", param_tainted: true, languages: ["java"] },
10705
+ { annotation: "MessageMapping", type: "http_body", severity: "high", param_tainted: true, languages: ["java"] },
10706
+ { annotation: "SubscribeMapping", type: "http_body", severity: "high", param_tainted: true, languages: ["java"] },
10625
10707
  { method: "get", class: "Redis", type: "db_input", severity: "medium", return_tainted: true, languages: ["python"] },
10626
10708
  { method: "hget", class: "Redis", type: "db_input", severity: "medium", return_tainted: true, languages: ["python"] },
10627
10709
  { method: "mget", class: "Redis", type: "db_input", severity: "medium", return_tainted: true, languages: ["python"] },
@@ -11913,6 +11995,26 @@ var DEFAULT_SANITIZERS = [
11913
11995
  { method: "resolve", class: "Path", removes: ["path_traversal"] },
11914
11996
  { method: "int", removes: ["sql_injection", "command_injection", "xss"] },
11915
11997
  { method: "float", removes: ["sql_injection", "command_injection"] },
11998
+ { method: "quote", class: "urllib.parse", removes: ["ssrf", "open_redirect", "xss", "path_traversal"] },
11999
+ { method: "quote_plus", class: "urllib.parse", removes: ["ssrf", "open_redirect", "xss", "path_traversal"] },
12000
+ { method: "urlencode", class: "urllib.parse", removes: ["ssrf", "open_redirect", "xss"] },
12001
+ { method: "quote_plus", removes: ["ssrf", "open_redirect", "xss", "path_traversal"] },
12002
+ { method: "urlencode", removes: ["ssrf", "open_redirect", "xss"] },
12003
+ { method: "linkify", class: "bleach", removes: ["xss"] },
12004
+ { method: "linkify", removes: ["xss"] },
12005
+ { method: "escape", class: "django.utils.html", removes: ["xss"] },
12006
+ { method: "strip_tags", class: "django.utils.html", removes: ["xss"] },
12007
+ { method: "escape", class: "jinja2", removes: ["xss"] },
12008
+ { method: "escape", class: "flask", removes: ["xss"] },
12009
+ { method: "escape", class: "saxutils", removes: ["xss"] },
12010
+ { method: "escape", class: "xml.sax.saxutils", removes: ["xss"] },
12011
+ { method: "quoteattr", class: "saxutils", removes: ["xss"] },
12012
+ { method: "quoteattr", class: "xml.sax.saxutils", removes: ["xss"] },
12013
+ { method: "escape", class: "re", removes: ["redos", "code_injection"] },
12014
+ { method: "bindparams", removes: ["sql_injection"] },
12015
+ { method: "Identifier", class: "sql", removes: ["sql_injection"] },
12016
+ { method: "Literal", class: "sql", removes: ["sql_injection"] },
12017
+ { method: "Placeholder", class: "sql", removes: ["sql_injection"] },
11916
12018
  { method: "query!", removes: ["sql_injection"] },
11917
12019
  { method: "query_as!", removes: ["sql_injection"] },
11918
12020
  { method: "query_scalar!", removes: ["sql_injection"] },
@@ -13836,7 +13938,13 @@ function matchesSanitizerPattern(call, pattern) {
13836
13938
  return false;
13837
13939
  }
13838
13940
  if (pattern.class) {
13839
- if (!call.receiver || !receiverMightBeClass(call.receiver, pattern.class)) {
13941
+ if (!call.receiver) {
13942
+ const target = call.resolution?.target;
13943
+ const expectedTail = `${pattern.class}.${pattern.method}`;
13944
+ if (target && (target === expectedTail || target.endsWith("." + expectedTail))) {} else {
13945
+ return false;
13946
+ }
13947
+ } else if (!receiverMightBeClass(call.receiver, pattern.class)) {
13840
13948
  return false;
13841
13949
  }
13842
13950
  }
@@ -16432,15 +16540,23 @@ function propagateTaint(graphOrDfg, callsOrSources, sourcesOrSinks, sinksOrSanit
16432
16540
  const callsAtSink = callsByLine.get(sink.line) ?? [];
16433
16541
  for (const call of callsAtSink) {
16434
16542
  for (const arg of call.arguments) {
16435
- if (arg.variable) {
16436
- if (sink.argPositions && sink.argPositions.length > 0) {
16437
- if (!sink.argPositions.includes(arg.position)) {
16438
- continue;
16439
- }
16543
+ if (sink.argPositions && sink.argPositions.length > 0) {
16544
+ if (!sink.argPositions.includes(arg.position)) {
16545
+ continue;
16440
16546
  }
16441
- for (const use of usesAtSink) {
16442
- if (use.variable === arg.variable && use.def_id !== null) {
16547
+ }
16548
+ const candidateUses = arg.variable ? usesAtSink.filter((u) => u.variable === arg.variable) : usesAtSink;
16549
+ {
16550
+ for (const use of candidateUses) {
16551
+ if (use.def_id !== null) {
16443
16552
  if (allTaintedDefIds.has(use.def_id)) {
16553
+ if (!arg.variable) {
16554
+ if (typeof arg.expression !== "string" || arg.expression.length === 0)
16555
+ continue;
16556
+ const re = new RegExp(`(?:^|[^A-Za-z0-9_$])${use.variable.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:[^A-Za-z0-9_$]|$)`);
16557
+ if (!re.test(arg.expression))
16558
+ continue;
16559
+ }
16444
16560
  const taintInfo = taintByDefId.get(use.def_id);
16445
16561
  if (taintInfo) {
16446
16562
  const isSanitized = checkSanitized(taintInfo.line, sink.line, sink.type, sanitizersByLine, {
@@ -16468,7 +16584,16 @@ function propagateTaint(graphOrDfg, callsOrSources, sourcesOrSinks, sinksOrSanit
16468
16584
  }
16469
16585
  }
16470
16586
  }
16471
- return { taintedVars, flows, reachableSinks };
16587
+ const seen = new Set;
16588
+ const deduped = [];
16589
+ for (const f of flows) {
16590
+ const key = `${f.source.line}|${f.sink.line}|${f.sink.type}|${f.path.map((p) => p.variable).join(">")}`;
16591
+ if (seen.has(key))
16592
+ continue;
16593
+ seen.add(key);
16594
+ deduped.push(f);
16595
+ }
16596
+ return { taintedVars, flows: deduped, reachableSinks };
16472
16597
  }
16473
16598
  function findInitialTaint(sources, callsByLine, defsByLine) {
16474
16599
  const tainted = [];
@@ -24602,7 +24727,10 @@ var PYTHON_TAINTED_PATTERNS2 = [
24602
24727
  { pattern: /\bget_query_parameter\s*\(/, type: "http_param" },
24603
24728
  { pattern: /\bget_header_value\s*\(/, type: "http_header" },
24604
24729
  { pattern: /\bget_cookie_value\s*\(/, type: "http_cookie" },
24605
- { pattern: /\binput\s*\(/, type: "io_input" }
24730
+ { pattern: /\binput\s*\(/, type: "io_input" },
24731
+ { pattern: /\.receive_text\s*\(/, type: "network_input" },
24732
+ { pattern: /\.receive_bytes\s*\(/, type: "network_input" },
24733
+ { pattern: /\.receive_json\s*\(/, type: "http_body" }
24606
24734
  ];
24607
24735
 
24608
24736
  class LanguageSourcesPass {
@@ -25246,6 +25374,36 @@ function findJavaScriptAssignmentSources(sourceCode, language) {
25246
25374
  }
25247
25375
  }
25248
25376
  }
25377
+ sources.push(...findJavaScriptCallbackParamSources(sourceCode));
25378
+ return sources;
25379
+ }
25380
+ function findJavaScriptCallbackParamSources(sourceCode) {
25381
+ const sources = [];
25382
+ const CB_EVENT_RE = /\.on\s*\(\s*['"](?:message|text|binary)['"]\s*,\s*(?:async\s+)?(?:function\s*\*?\s*(?:\w+\s*)?\(\s*(\w+)|\(\s*(\w+)\s*(?:[,):]|$)|(\w+)\s*=>)/;
25383
+ const lines = sourceCode.split(`
25384
+ `);
25385
+ for (let i2 = 0;i2 < lines.length; i2++) {
25386
+ const line = lines[i2];
25387
+ const trimmed = line.trimStart();
25388
+ if (trimmed.startsWith("//") || trimmed.startsWith("*"))
25389
+ continue;
25390
+ const m = line.match(CB_EVENT_RE);
25391
+ if (!m)
25392
+ continue;
25393
+ const paramName = m[1] ?? m[2] ?? m[3];
25394
+ if (!paramName)
25395
+ continue;
25396
+ if (paramName === "err" || paramName === "error")
25397
+ continue;
25398
+ sources.push({
25399
+ type: "network_input",
25400
+ location: `WebSocket .on(...) callback param '${paramName}' at line ${i2 + 1}`,
25401
+ severity: "high",
25402
+ line: i2 + 1,
25403
+ confidence: 0.95,
25404
+ variable: paramName
25405
+ });
25406
+ }
25249
25407
  return sources;
25250
25408
  }
25251
25409
  function findPythonAssignmentSources(sourceCode, language) {
@@ -25578,6 +25736,16 @@ function buildJavaScriptTaintedVars(sourceCode, language) {
25578
25736
  const tainted = new Map;
25579
25737
  const lines = sourceCode.split(`
25580
25738
  `);
25739
+ const CB_EVENT_RE = /\.on\s*\(\s*['"](?:message|text|binary)['"]\s*,\s*(?:async\s+)?(?:function\s*\*?\s*(?:\w+\s*)?\(\s*(\w+)|\(\s*(\w+)\s*(?:[,):]|$)|(\w+)\s*=>)/;
25740
+ for (let i2 = 0;i2 < lines.length; i2++) {
25741
+ const m = lines[i2].match(CB_EVENT_RE);
25742
+ if (!m)
25743
+ continue;
25744
+ const paramName = m[1] ?? m[2] ?? m[3];
25745
+ if (!paramName || paramName === "err" || paramName === "error")
25746
+ continue;
25747
+ tainted.set(paramName, i2 + 1);
25748
+ }
25581
25749
  for (let i2 = 0;i2 < lines.length; i2++) {
25582
25750
  const line = lines[i2];
25583
25751
  const trimmed = line.trimStart();
@@ -25784,6 +25952,90 @@ function findBashTaintSources(sourceCode, dfg) {
25784
25952
  });
25785
25953
  }
25786
25954
  }
25955
+ const readMatch = /^read\s*\(/.test(trimmed) ? null : trimmed.match(/^read\b([^#(]*)$/);
25956
+ if (readMatch) {
25957
+ const argStr = readMatch[1].trim();
25958
+ const noRedirect = argStr.replace(/\s*<[^<].*$/, "").trim();
25959
+ const tokens = noRedirect.split(/\s+/);
25960
+ const varNames = [];
25961
+ for (let ti = 0;ti < tokens.length; ti++) {
25962
+ const tok = tokens[ti];
25963
+ if (!tok)
25964
+ continue;
25965
+ if (tok.startsWith("-")) {
25966
+ if (/^-[antpidu]$|^-N$/.test(tok))
25967
+ ti++;
25968
+ continue;
25969
+ }
25970
+ if (/^[A-Za-z_][\w]*$/.test(tok))
25971
+ varNames.push(tok);
25972
+ }
25973
+ if (varNames.length === 0)
25974
+ varNames.push("REPLY");
25975
+ for (const v of varNames) {
25976
+ const already = sources.some((s) => s.line === lineNumber && s.variable === v);
25977
+ if (already)
25978
+ continue;
25979
+ sources.push({
25980
+ type: "io_input",
25981
+ location: `read → $${v} (stdin)`,
25982
+ severity: "high",
25983
+ line: lineNumber,
25984
+ confidence: 0.9,
25985
+ variable: v
25986
+ });
25987
+ }
25988
+ }
25989
+ const mapfileMatch = /^(?:mapfile|readarray)\s*\(/.test(trimmed) ? null : trimmed.match(/^(?:mapfile|readarray)\b([^#(]*)$/);
25990
+ if (mapfileMatch) {
25991
+ const argStr = mapfileMatch[1].trim();
25992
+ const tokens = argStr.split(/\s+/);
25993
+ const varNames = [];
25994
+ for (let ti = 0;ti < tokens.length; ti++) {
25995
+ const tok = tokens[ti];
25996
+ if (!tok)
25997
+ continue;
25998
+ if (tok.startsWith("-")) {
25999
+ if (/^-[cCnOsud]$/.test(tok))
26000
+ ti++;
26001
+ continue;
26002
+ }
26003
+ if (/^[A-Za-z_][\w]*$/.test(tok))
26004
+ varNames.push(tok);
26005
+ }
26006
+ if (varNames.length === 0)
26007
+ varNames.push("MAPFILE");
26008
+ for (const v of varNames) {
26009
+ const already = sources.some((s) => s.line === lineNumber && s.variable === v);
26010
+ if (already)
26011
+ continue;
26012
+ sources.push({
26013
+ type: "io_input",
26014
+ location: `mapfile → $${v} (stdin array)`,
26015
+ severity: "high",
26016
+ line: lineNumber,
26017
+ confidence: 0.9,
26018
+ variable: v
26019
+ });
26020
+ }
26021
+ }
26022
+ const getoptsMatch = trimmed.match(/\bgetopts\s+["'][^"']+["']\s+(\w+)/);
26023
+ if (getoptsMatch) {
26024
+ const flagVar = getoptsMatch[1];
26025
+ for (const v of [flagVar, "OPTARG"]) {
26026
+ const already = sources.some((s) => s.line === lineNumber && s.variable === v);
26027
+ if (already)
26028
+ continue;
26029
+ sources.push({
26030
+ type: "io_input",
26031
+ location: `getopts → $${v} (CLI arg)`,
26032
+ severity: "high",
26033
+ line: lineNumber,
26034
+ confidence: 0.9,
26035
+ variable: v
26036
+ });
26037
+ }
26038
+ }
25787
26039
  const envRe = /\$([A-Z][A-Z0-9_]{2,})|\$\{([A-Z][A-Z0-9_]{2,})\}/g;
25788
26040
  let em;
25789
26041
  while ((em = envRe.exec(line)) !== null) {
@@ -33393,6 +33645,12 @@ class TaintPropagationPass {
33393
33645
  continue;
33394
33646
  pushIfNew(f);
33395
33647
  }
33648
+ if (ctx.language === "go" && typeof ctx.code === "string") {
33649
+ const goGlobalFlows = detectGoPackageGlobalFlows(ctx.code, calls, sources, sinks, constProp.unreachableLines) ?? [];
33650
+ for (const f of goGlobalFlows) {
33651
+ pushIfNew(f);
33652
+ }
33653
+ }
33396
33654
  const sanitizedNames = constProp.sanitizedVars;
33397
33655
  let finalFlows = sanitizedNames.size === 0 ? flows : flows.filter((f) => {
33398
33656
  if (f.path.length === 0)
@@ -33649,6 +33907,113 @@ function isInJavaSanitizedMethod(code, types, sinkLine, sinkType) {
33649
33907
  }
33650
33908
  return false;
33651
33909
  }
33910
+ function detectGoPackageGlobalFlows(code, _calls, sources, sinks, unreachableLines) {
33911
+ const flows = [];
33912
+ if (!code || sinks.length === 0)
33913
+ return flows;
33914
+ const lines = code.split(`
33915
+ `);
33916
+ const packageVars = new Set;
33917
+ let inVarBlock = false;
33918
+ for (const line of lines) {
33919
+ const stripped = line.replace(/\s+$/, "");
33920
+ if (!stripped)
33921
+ continue;
33922
+ if (/^[ \t]/.test(line)) {
33923
+ if (inVarBlock) {
33924
+ const m = stripped.match(/^\s*([A-Za-z_][A-Za-z0-9_]*)\s+/);
33925
+ if (m)
33926
+ packageVars.add(m[1]);
33927
+ if (/^\s*\)/.test(stripped))
33928
+ inVarBlock = false;
33929
+ }
33930
+ continue;
33931
+ }
33932
+ if (/^var\s*\(/.test(stripped)) {
33933
+ inVarBlock = true;
33934
+ continue;
33935
+ }
33936
+ if (inVarBlock && /^\)/.test(stripped)) {
33937
+ inVarBlock = false;
33938
+ continue;
33939
+ }
33940
+ const varOne = stripped.match(/^var\s+([A-Za-z_][A-Za-z0-9_]*)\b/);
33941
+ if (varOne)
33942
+ packageVars.add(varOne[1]);
33943
+ }
33944
+ if (packageVars.size === 0)
33945
+ return flows;
33946
+ const sourceVarNames = new Set;
33947
+ for (const s of sources) {
33948
+ if (typeof s.variable === "string" && s.variable.length > 0) {
33949
+ sourceVarNames.add(s.variable);
33950
+ }
33951
+ }
33952
+ const GO_SOURCE_RE = /\b(r\.(?:URL\.Query|Form|PostForm|Header|Cookie|Body)|mux\.Vars|c\.(?:Query|Param|PostForm|GetHeader|Cookie)|ctx\.(?:Query|Param|PostForm|GetHeader|Cookie))\b/;
33953
+ const writes = [];
33954
+ for (let i2 = 0;i2 < lines.length; i2++) {
33955
+ const line = lines[i2];
33956
+ if (unreachableLines.has(i2 + 1))
33957
+ continue;
33958
+ if (/:=/.test(line))
33959
+ continue;
33960
+ const m = line.match(/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.+)$/);
33961
+ if (!m)
33962
+ continue;
33963
+ const [, lhs, rhs] = m;
33964
+ if (!packageVars.has(lhs))
33965
+ continue;
33966
+ let taintedBy = null;
33967
+ if (GO_SOURCE_RE.test(rhs)) {
33968
+ const src = sources.find((s) => s.line === i2 + 1);
33969
+ taintedBy = src ? { line: src.line, type: src.type } : { line: i2 + 1, type: "http_param" };
33970
+ } else {
33971
+ for (const v of sourceVarNames) {
33972
+ const re = new RegExp(`(?<![A-Za-z0-9_$])${v.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?![A-Za-z0-9_$])`);
33973
+ if (re.test(rhs)) {
33974
+ const src = sources.find((s) => s.variable === v);
33975
+ if (src) {
33976
+ taintedBy = { line: src.line, type: src.type };
33977
+ break;
33978
+ }
33979
+ }
33980
+ }
33981
+ }
33982
+ if (!taintedBy)
33983
+ continue;
33984
+ writes.push({ varName: lhs, line: i2 + 1, sourceLine: taintedBy.line, sourceType: taintedBy.type });
33985
+ }
33986
+ if (writes.length === 0)
33987
+ return flows;
33988
+ for (const sink of sinks) {
33989
+ if (unreachableLines.has(sink.line))
33990
+ continue;
33991
+ const sinkLineText = lines[sink.line - 1] ?? "";
33992
+ if (!sinkLineText)
33993
+ continue;
33994
+ for (const w of writes) {
33995
+ if (w.line >= sink.line)
33996
+ continue;
33997
+ const re = new RegExp(`(?<![A-Za-z0-9_$])${w.varName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?![A-Za-z0-9_$])`);
33998
+ if (!re.test(sinkLineText))
33999
+ continue;
34000
+ flows.push({
34001
+ source_line: w.sourceLine,
34002
+ sink_line: sink.line,
34003
+ source_type: w.sourceType,
34004
+ sink_type: sink.type,
34005
+ path: [
34006
+ { variable: w.varName, line: w.sourceLine, type: "source" },
34007
+ { variable: w.varName, line: w.line, type: "assignment" },
34008
+ { variable: w.varName, line: sink.line, type: "sink" }
34009
+ ],
34010
+ confidence: 0.9,
34011
+ sanitized: false
34012
+ });
34013
+ }
34014
+ }
34015
+ return flows;
34016
+ }
33652
34017
  function detectCollectionFlows(calls, sources, sinks, taintedVars, unreachableLines, code, types) {
33653
34018
  const flows = [];
33654
34019
  const callsByLine = new Map;
@@ -45451,7 +45816,7 @@ var colors = {
45451
45816
  };
45452
45817
 
45453
45818
  // src/version.ts
45454
- var version = "3.181.0";
45819
+ var version = "3.185.0";
45455
45820
 
45456
45821
  // src/formatters.ts
45457
45822
  var LIBRARY_API_SURFACE_TAG2 = "library-api-surface:caller-responsibility";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cognium-dev",
3
- "version": "3.181.0",
3
+ "version": "3.185.0",
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",
@@ -66,7 +66,7 @@
66
66
  },
67
67
  "dependencies": {
68
68
  "@cognium/project-profile-detect": "^1.1.0",
69
- "circle-ir": "^3.181.0"
69
+ "circle-ir": "^3.185.0"
70
70
  },
71
71
  "devDependencies": {
72
72
  "@types/node": "^25.5.0",