cognium-dev 3.131.0 → 3.132.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 +309 -14
  2. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -10902,8 +10902,8 @@ var DEFAULT_SINKS = [
10902
10902
  { method: "unmarshal", class: "Unmarshaller", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0] },
10903
10903
  { method: "newTransformer", class: "TransformerFactory", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0] },
10904
10904
  { method: "transform", class: "Transformer", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0] },
10905
- { method: "sendRedirect", class: "HttpServletResponse", type: "ssrf", cwe: "CWE-601", severity: "high", arg_positions: [0] },
10906
- { method: "sendRedirect", type: "ssrf", cwe: "CWE-601", severity: "high", arg_positions: [0] },
10905
+ { method: "sendRedirect", class: "HttpServletResponse", type: "open_redirect", cwe: "CWE-601", severity: "high", arg_positions: [0] },
10906
+ { method: "sendRedirect", type: "open_redirect", cwe: "CWE-601", severity: "high", arg_positions: [0] },
10907
10907
  { method: "openConnection", class: "URL", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [] },
10908
10908
  { method: "openStream", class: "URL", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [] },
10909
10909
  { method: "execute", class: "HttpClient", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
@@ -13400,19 +13400,19 @@ function formatCallCode(call) {
13400
13400
  // ../circle-ir/dist/analysis/findings.js
13401
13401
  function canSourceReachSink(sourceType, sinkType) {
13402
13402
  const sourceToSinkMapping = {
13403
- http_param: ["sql_injection", "command_injection", "path_traversal", "xss", "xpath_injection", "ldap_injection", "ssrf", "mybatis_mapper_call", "code_injection", "crlf", "mass_assignment"],
13404
- http_body: ["sql_injection", "command_injection", "deserialization", "xxe", "xss", "code_injection", "mybatis_mapper_call", "crlf", "mass_assignment"],
13405
- http_header: ["sql_injection", "xss", "ssrf", "mybatis_mapper_call", "code_injection", "crlf"],
13406
- http_cookie: ["sql_injection", "xss", "mybatis_mapper_call", "code_injection", "crlf"],
13407
- http_path: ["path_traversal", "sql_injection", "ssrf", "mybatis_mapper_call"],
13408
- http_query: ["sql_injection", "command_injection", "xss", "ssrf", "mybatis_mapper_call", "code_injection", "crlf", "mass_assignment"],
13403
+ http_param: ["sql_injection", "command_injection", "path_traversal", "xss", "xpath_injection", "ldap_injection", "ssrf", "mybatis_mapper_call", "code_injection", "crlf", "mass_assignment", "open_redirect"],
13404
+ http_body: ["sql_injection", "command_injection", "deserialization", "xxe", "xss", "code_injection", "mybatis_mapper_call", "crlf", "mass_assignment", "open_redirect"],
13405
+ http_header: ["sql_injection", "xss", "ssrf", "mybatis_mapper_call", "code_injection", "crlf", "open_redirect"],
13406
+ http_cookie: ["sql_injection", "xss", "mybatis_mapper_call", "code_injection", "crlf", "open_redirect"],
13407
+ http_path: ["path_traversal", "sql_injection", "ssrf", "mybatis_mapper_call", "open_redirect"],
13408
+ http_query: ["sql_injection", "command_injection", "xss", "ssrf", "mybatis_mapper_call", "code_injection", "crlf", "mass_assignment", "open_redirect"],
13409
13409
  io_input: ["command_injection", "path_traversal", "deserialization", "xxe", "code_injection", "xss", "ssrf"],
13410
13410
  env_input: ["command_injection", "path_traversal"],
13411
13411
  db_input: ["xss", "sql_injection"],
13412
13412
  file_input: ["deserialization", "xxe", "path_traversal", "command_injection", "code_injection"],
13413
13413
  network_input: ["sql_injection", "command_injection", "xss", "ssrf"],
13414
13414
  config_param: ["sql_injection", "command_injection", "path_traversal", "xss", "ssrf"],
13415
- interprocedural_param: ["sql_injection", "command_injection", "path_traversal", "xss", "xpath_injection", "ldap_injection", "ssrf", "code_injection", "mybatis_mapper_call", "crlf", "mass_assignment"],
13415
+ interprocedural_param: ["sql_injection", "command_injection", "path_traversal", "xss", "xpath_injection", "ldap_injection", "ssrf", "code_injection", "mybatis_mapper_call", "crlf", "mass_assignment", "open_redirect"],
13416
13416
  plugin_param: ["sql_injection", "command_injection", "path_traversal", "xss", "code_injection"]
13417
13417
  };
13418
13418
  const validSinks = sourceToSinkMapping[sourceType];
@@ -22607,6 +22607,9 @@ class LanguageSourcesPass {
22607
22607
  for (const finding of findGoXssFindings(code, graph.ir.meta.file)) {
22608
22608
  ctx.addFinding(finding);
22609
22609
  }
22610
+ for (const finding of findGoLocationHeaderOpenRedirectFindings(code, graph.ir.meta.file)) {
22611
+ ctx.addFinding(finding);
22612
+ }
22610
22613
  }
22611
22614
  if (language === "python") {
22612
22615
  additionalSanitizers.push(...findPythonNetlocAllowlistGuardSanitizers(code));
@@ -22625,6 +22628,9 @@ class LanguageSourcesPass {
22625
22628
  for (const finding of findPythonJinjaMarkupXssFindings(code, graph.ir.meta.file)) {
22626
22629
  ctx.addFinding(finding);
22627
22630
  }
22631
+ for (const finding of findPythonHeadersSubscriptOpenRedirectFindings(code, graph.ir.meta.file)) {
22632
+ ctx.addFinding(finding);
22633
+ }
22628
22634
  }
22629
22635
  if (language === "rust") {
22630
22636
  additionalSanitizers.push(...findRustSetAllowlistGuardSanitizers(code));
@@ -22646,6 +22652,9 @@ class LanguageSourcesPass {
22646
22652
  for (const finding of findRustWeakCryptoEcbFindings(code, graph.ir.meta.file)) {
22647
22653
  ctx.addFinding(finding);
22648
22654
  }
22655
+ for (const finding of findRustAppendHeaderTupleOpenRedirectFindings(code, graph.ir.meta.file)) {
22656
+ ctx.addFinding(finding);
22657
+ }
22649
22658
  }
22650
22659
  if (language === "javascript" || language === "typescript" || language === "htmljs") {
22651
22660
  additionalSanitizers.push(...findJsSafeJsonParseSanitizers(code));
@@ -22664,6 +22673,9 @@ class LanguageSourcesPass {
22664
22673
  for (const finding of findTsAngularBypassXssFindings(code, graph.ir.meta.file)) {
22665
22674
  ctx.addFinding(finding);
22666
22675
  }
22676
+ for (const finding of findJsDomOpenRedirectFindings(code, graph.ir.meta.file)) {
22677
+ ctx.addFinding(finding);
22678
+ }
22667
22679
  }
22668
22680
  if (language === "java") {
22669
22681
  additionalSanitizers.push(...findJavaSafeJsonParseSanitizers(code));
@@ -25907,6 +25919,289 @@ function findPythonJinjaMarkupXssFindings(code, file) {
25907
25919
  }
25908
25920
  return out2;
25909
25921
  }
25922
+ function findGoLocationHeaderOpenRedirectFindings(code, file) {
25923
+ const out2 = [];
25924
+ const lines = code.split(`
25925
+ `);
25926
+ const rwNames = new Set;
25927
+ const sigRe = /\(\s*([A-Za-z_]\w*)\s+http\.ResponseWriter\b/g;
25928
+ for (const line of lines) {
25929
+ let m;
25930
+ sigRe.lastIndex = 0;
25931
+ while ((m = sigRe.exec(line)) !== null)
25932
+ rwNames.add(m[1]);
25933
+ }
25934
+ if (rwNames.size === 0)
25935
+ return out2;
25936
+ const safeWrapRe = /\b(?:net\/url|url)\.Parse\b[\s\S]{0,120}?\b(?:IsAbs|Host)\b|\bstrings\.HasPrefix\s*\(/;
25937
+ const callRe = /\b([A-Za-z_]\w*)\s*\.\s*Header\s*\(\s*\)\s*\.\s*(?:Set|Add)\s*\(\s*(['"])([^'"]+)\2\s*,\s*([\s\S]*)\)\s*(?:\/\/.*)?$/;
25938
+ for (let i2 = 0;i2 < lines.length; i2++) {
25939
+ const raw = lines[i2];
25940
+ const trimmed = raw.trim();
25941
+ if (!trimmed || trimmed.startsWith("//"))
25942
+ continue;
25943
+ const m = trimmed.match(callRe);
25944
+ if (!m)
25945
+ continue;
25946
+ const recv = m[1];
25947
+ if (!rwNames.has(recv))
25948
+ continue;
25949
+ const key = m[3];
25950
+ if (key.toLowerCase() !== "location")
25951
+ continue;
25952
+ const valExpr = m[4];
25953
+ const valTrim = valExpr.trim();
25954
+ if (/^"(?:\\.|[^"\\])*"$/.test(valTrim))
25955
+ continue;
25956
+ if (safeWrapRe.test(valExpr))
25957
+ continue;
25958
+ out2.push({
25959
+ id: `open_redirect-${file}-${i2 + 1}`,
25960
+ pass: "language-sources",
25961
+ category: "security",
25962
+ rule_id: "open_redirect",
25963
+ cwe: "CWE-601",
25964
+ severity: "high",
25965
+ level: "error",
25966
+ message: 'Open redirect: ResponseWriter.Header().Set("Location", ...) ' + "writes a user-controlled value into the Location header without " + "validating the target. Restrict to an allow-list of hosts/paths " + "or compare against a known-safe set before issuing the redirect.",
25967
+ file,
25968
+ line: i2 + 1,
25969
+ snippet: trimmed.substring(0, 100)
25970
+ });
25971
+ }
25972
+ return out2;
25973
+ }
25974
+ function findPythonHeadersSubscriptOpenRedirectFindings(code, file) {
25975
+ const out2 = [];
25976
+ const lines = code.split(`
25977
+ `);
25978
+ const requestSourceRe = /\b([A-Za-z_]\w*)\s*=\s*request\s*\.\s*(?:args|form|values|files|json|cookies|headers)(?:\s*\.\s*get\s*\(|\s*\[)/;
25979
+ const taintedVars = new Set;
25980
+ for (const line of lines) {
25981
+ const m = line.match(requestSourceRe);
25982
+ if (m)
25983
+ taintedVars.add(m[1]);
25984
+ }
25985
+ const safeWrapRe = /\b(?:urllib\.parse\.urlparse|urlparse)\s*\([\s\S]{0,120}?\bnetloc\b|\b(?:startswith)\s*\(/;
25986
+ const subscriptRe = /\b([A-Za-z_]\w*)\s*\.\s*headers\s*\[\s*(['"])([^'"]+)\2\s*\]\s*=\s*(.+)$/;
25987
+ for (let i2 = 0;i2 < lines.length; i2++) {
25988
+ const raw = lines[i2];
25989
+ const trimmed = raw.trim();
25990
+ if (!trimmed || trimmed.startsWith("#"))
25991
+ continue;
25992
+ const m = trimmed.match(subscriptRe);
25993
+ if (!m)
25994
+ continue;
25995
+ const key = m[3];
25996
+ if (key.toLowerCase() !== "location")
25997
+ continue;
25998
+ const rhs = m[4].replace(/\s*(?:#.*)?$/, "").trim();
25999
+ if (/^['"](?:\\.|[^'"\\])*['"]$/.test(rhs))
26000
+ continue;
26001
+ if (safeWrapRe.test(rhs))
26002
+ continue;
26003
+ const hasTaintedVar = [...taintedVars].some((v) => new RegExp(`\\b${v}\\b`).test(rhs));
26004
+ const inlineSource = /\brequest\s*\.\s*(?:args|form|values|files|cookies|headers|json)(?:\s*\.\s*get\s*\(|\s*\[)/.test(rhs);
26005
+ if (!hasTaintedVar && !inlineSource)
26006
+ continue;
26007
+ out2.push({
26008
+ id: `open_redirect-${file}-${i2 + 1}`,
26009
+ pass: "language-sources",
26010
+ category: "security",
26011
+ rule_id: "open_redirect",
26012
+ cwe: "CWE-601",
26013
+ severity: "high",
26014
+ level: "error",
26015
+ message: 'Open redirect: response.headers["Location"] = ... assigns a ' + "user-controlled value to the Location header. Validate the URL " + "against an allow-list of trusted hosts before sending.",
26016
+ file,
26017
+ line: i2 + 1,
26018
+ snippet: trimmed.substring(0, 100)
26019
+ });
26020
+ }
26021
+ return out2;
26022
+ }
26023
+ function findRustAppendHeaderTupleOpenRedirectFindings(code, file) {
26024
+ const out2 = [];
26025
+ const lines = code.split(`
26026
+ `);
26027
+ const extractorRe = /\b([A-Za-z_]\w*)\s*:\s*(?:web\s*::\s*)?(?:Query|Path|Form|Json)\s*<|\b([A-Za-z_]\w*)\s*:\s*&?\s*HttpRequest\b/g;
26028
+ const extractorParams = new Set;
26029
+ for (const line of lines) {
26030
+ let m;
26031
+ extractorRe.lastIndex = 0;
26032
+ while ((m = extractorRe.exec(line)) !== null) {
26033
+ const name2 = m[1] ?? m[2];
26034
+ if (name2)
26035
+ extractorParams.add(name2);
26036
+ }
26037
+ }
26038
+ if (extractorParams.size === 0)
26039
+ return out2;
26040
+ const taintedVars = new Set(extractorParams);
26041
+ const letRe = /\blet\s+(?:mut\s+)?([A-Za-z_]\w*)\s*(?::[^=]+)?=\s*([^;]+);/g;
26042
+ for (let pass = 0;pass < 4; pass++) {
26043
+ const before = taintedVars.size;
26044
+ let lm;
26045
+ letRe.lastIndex = 0;
26046
+ while ((lm = letRe.exec(code)) !== null) {
26047
+ const name2 = lm[1];
26048
+ const rhs = lm[2];
26049
+ if (taintedVars.has(name2))
26050
+ continue;
26051
+ for (const tv of taintedVars) {
26052
+ if (new RegExp(`\\b${tv}\\b`).test(rhs)) {
26053
+ taintedVars.add(name2);
26054
+ break;
26055
+ }
26056
+ }
26057
+ }
26058
+ if (taintedVars.size === before)
26059
+ break;
26060
+ }
26061
+ const safeWrapRe = /\b(?:Url\s*::\s*parse|url\s*::\s*Url\s*::\s*parse)\s*\([\s\S]{0,160}?\b(?:host_str|host|origin)\b/;
26062
+ const tupleCallRe = /\.\s*(?:append_header|insert_header)\s*\(\s*\(\s*(['"])([^'"]+)\1\s*,\s*([^)]*?)\s*\)\s*\)/g;
26063
+ for (let i2 = 0;i2 < lines.length; i2++) {
26064
+ const raw = lines[i2];
26065
+ const trimmed = raw.trim();
26066
+ if (!trimmed || trimmed.startsWith("//"))
26067
+ continue;
26068
+ let cm;
26069
+ tupleCallRe.lastIndex = 0;
26070
+ while ((cm = tupleCallRe.exec(raw)) !== null) {
26071
+ const key = cm[2];
26072
+ if (key.toLowerCase() !== "location")
26073
+ continue;
26074
+ const valExpr = cm[3];
26075
+ if (/^['"](?:\\.|[^'"\\])*['"]$/.test(valExpr.trim()))
26076
+ continue;
26077
+ if (safeWrapRe.test(valExpr))
26078
+ continue;
26079
+ const hasTainted = [...taintedVars].some((v) => new RegExp(`\\b${v}\\b`).test(valExpr));
26080
+ if (!hasTainted)
26081
+ continue;
26082
+ out2.push({
26083
+ id: `open_redirect-${file}-${i2 + 1}`,
26084
+ pass: "language-sources",
26085
+ category: "security",
26086
+ rule_id: "open_redirect",
26087
+ cwe: "CWE-601",
26088
+ severity: "high",
26089
+ level: "error",
26090
+ message: "Open redirect: HttpResponse builder " + '.append_header(("Location", ...)) sets the Location header from ' + "a request-derived value without allow-list validation. Parse " + "with url::Url::parse and compare host_str against an allow-list.",
26091
+ file,
26092
+ line: i2 + 1,
26093
+ snippet: trimmed.substring(0, 100)
26094
+ });
26095
+ break;
26096
+ }
26097
+ }
26098
+ return out2;
26099
+ }
26100
+ function findJsDomOpenRedirectFindings(code, file) {
26101
+ const out2 = [];
26102
+ const lines = code.split(`
26103
+ `);
26104
+ const domSourceRe = /\blocation\s*\.\s*(?:search|hash|href|pathname)\b|\bwindow\s*\.\s*location\s*\.\s*(?:search|hash|href|pathname)\b|\bURLSearchParams\b|\bdocument\s*\.\s*(?:referrer|URL|location\s*\.\s*(?:search|hash|href|pathname))\b|\bwindow\s*\.\s*name\b/;
26105
+ const taintedVars = new Set;
26106
+ const assignRe = /\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*([^;\n]+)/g;
26107
+ let am;
26108
+ assignRe.lastIndex = 0;
26109
+ while ((am = assignRe.exec(code)) !== null) {
26110
+ if (domSourceRe.test(am[2]))
26111
+ taintedVars.add(am[1]);
26112
+ }
26113
+ for (let pass = 0;pass < 4; pass++) {
26114
+ const before = taintedVars.size;
26115
+ assignRe.lastIndex = 0;
26116
+ while ((am = assignRe.exec(code)) !== null) {
26117
+ if (taintedVars.has(am[1]))
26118
+ continue;
26119
+ for (const tv of taintedVars) {
26120
+ if (new RegExp(`\\b${tv}\\b`).test(am[2])) {
26121
+ taintedVars.add(am[1]);
26122
+ break;
26123
+ }
26124
+ }
26125
+ }
26126
+ if (taintedVars.size === before)
26127
+ break;
26128
+ }
26129
+ const safeWrapRe = /\b(?:startsWith|includes)\s*\(\s*['"`]\/[^'"`]*['"`]\s*\)|\bnew\s+URL\s*\([\s\S]{0,80}?\)\s*\.\s*(?:origin|hostname)\s*===/;
26130
+ const containsTaint = (expr) => {
26131
+ if (domSourceRe.test(expr))
26132
+ return true;
26133
+ for (const tv of taintedVars) {
26134
+ if (new RegExp(`\\b${tv}\\b`).test(expr))
26135
+ return true;
26136
+ }
26137
+ return false;
26138
+ };
26139
+ const hrefSinkRe = /\b(?:(?:window|document|self|top|parent)\s*\.\s*)?location\s*\.\s*href\s*=\s*([^;\n]+?)\s*;?\s*(?:\/\/.*)?$/;
26140
+ const locSinkRe = /\b(?:window|document|self|top|parent)\s*\.\s*location\s*=\s*([^;\n]+?)\s*;?\s*(?:\/\/.*)?$/;
26141
+ const contentSinkRe = /\.\s*content\s*=\s*([^\n]+?)\s*$/;
26142
+ const callSinkRe = /\b(?:(?:window|document|self|top|parent)\s*\.\s*)?location\s*\.\s*(?:assign|replace)\s*\(\s*([^)]+)\)/;
26143
+ const emit = (line, msg, snippet) => {
26144
+ out2.push({
26145
+ id: `open_redirect-${file}-${line}`,
26146
+ pass: "language-sources",
26147
+ category: "security",
26148
+ rule_id: "open_redirect",
26149
+ cwe: "CWE-601",
26150
+ severity: "high",
26151
+ level: "error",
26152
+ message: msg,
26153
+ file,
26154
+ line,
26155
+ snippet: snippet.substring(0, 100)
26156
+ });
26157
+ };
26158
+ const isLiteralOnly = (expr) => /^['"`](?:\\.|[^'"`\\])*['"`]$/.test(expr.trim());
26159
+ for (let i2 = 0;i2 < lines.length; i2++) {
26160
+ const raw = lines[i2];
26161
+ const trimmed = raw.trim();
26162
+ if (!trimmed || trimmed.startsWith("//"))
26163
+ continue;
26164
+ let matched = false;
26165
+ const hm = trimmed.match(hrefSinkRe);
26166
+ if (hm) {
26167
+ const rhs = hm[1].trim();
26168
+ if (!isLiteralOnly(rhs) && containsTaint(rhs) && !safeWrapRe.test(rhs)) {
26169
+ emit(i2 + 1, "Open redirect: assignment to location.href uses a value " + "derived from a URL query/hash without allow-list validation. " + "Compare the target origin to a known-safe set before navigating.", trimmed);
26170
+ matched = true;
26171
+ }
26172
+ }
26173
+ if (!matched) {
26174
+ const lm = trimmed.match(locSinkRe);
26175
+ if (lm) {
26176
+ const rhs = lm[1].trim();
26177
+ if (!isLiteralOnly(rhs) && containsTaint(rhs) && !safeWrapRe.test(rhs)) {
26178
+ emit(i2 + 1, "Open redirect: assignment to window.location uses a value " + "derived from a URL query/hash without allow-list validation. " + "Compare the target origin to a known-safe set before navigating.", trimmed);
26179
+ matched = true;
26180
+ }
26181
+ }
26182
+ }
26183
+ if (!matched) {
26184
+ const cm = trimmed.match(contentSinkRe);
26185
+ if (cm) {
26186
+ const rhs = cm[1].replace(/\s*\/\/.*$/, "").replace(/\s*;\s*$/, "").trim();
26187
+ if (!isLiteralOnly(rhs) && /['"`][^'"`]*\burl\s*=/i.test(rhs) && containsTaint(rhs) && !safeWrapRe.test(rhs)) {
26188
+ emit(i2 + 1, "Open redirect: DOM assignment to <meta>.content with a " + "meta-refresh URL built from a URL query/hash without " + "allow-list validation. Validate origin before navigating.", trimmed);
26189
+ matched = true;
26190
+ }
26191
+ }
26192
+ }
26193
+ if (!matched) {
26194
+ const callm = trimmed.match(callSinkRe);
26195
+ if (callm) {
26196
+ const arg = callm[1].trim();
26197
+ if (!isLiteralOnly(arg) && containsTaint(arg) && !safeWrapRe.test(arg)) {
26198
+ emit(i2 + 1, "Open redirect: location.assign / location.replace invoked " + "with a value derived from a URL query/hash without allow-" + "list validation. Validate origin before navigating.", trimmed);
26199
+ }
26200
+ }
26201
+ }
26202
+ }
26203
+ return out2;
26204
+ }
25910
26205
 
25911
26206
  // ../circle-ir/dist/analysis/passes/sink-filter-pass.js
25912
26207
  var JS_XSS_SANITIZERS = [
@@ -27371,13 +27666,13 @@ class TaintPropagationPass {
27371
27666
  }));
27372
27667
  const arrayFlows = detectArrayElementFlows(calls, sources, sinks, constProp.taintedArrayElements, constProp.unreachableLines, types) ?? [];
27373
27668
  for (const f of arrayFlows) {
27374
- if (!flows.some((x) => x.source_line === f.source_line && x.sink_line === f.sink_line)) {
27669
+ if (!flows.some((x) => x.source_line === f.source_line && x.sink_line === f.sink_line && x.sink_type === f.sink_type)) {
27375
27670
  flows.push(f);
27376
27671
  }
27377
27672
  }
27378
27673
  const collectionFlows = detectCollectionFlows(calls, sources, sinks, constProp.tainted, constProp.unreachableLines, ctx.code, types) ?? [];
27379
27674
  for (const f of collectionFlows) {
27380
- if (flows.some((x) => x.source_line === f.source_line && x.sink_line === f.sink_line))
27675
+ if (flows.some((x) => x.source_line === f.source_line && x.sink_line === f.sink_line && x.sink_type === f.sink_type))
27381
27676
  continue;
27382
27677
  const flowForCheck = {
27383
27678
  source: { line: f.source_line },
@@ -27399,7 +27694,7 @@ class TaintPropagationPass {
27399
27694
  }
27400
27695
  const paramFlows = detectParameterSinkFlows(types, calls, sources, sinks, constProp.unreachableLines, constProp.tainted, ctx.code) ?? [];
27401
27696
  for (const f of paramFlows) {
27402
- if (!flows.some((x) => x.source_line === f.source_line && x.sink_line === f.sink_line)) {
27697
+ if (!flows.some((x) => x.source_line === f.source_line && x.sink_line === f.sink_line && x.sink_type === f.sink_type)) {
27403
27698
  flows.push(f);
27404
27699
  }
27405
27700
  }
@@ -27837,7 +28132,7 @@ function detectParameterSinkFlows(types, calls, sources, sinks, unreachableLines
27837
28132
  }
27838
28133
  const paramSource = methodParamSources.get(arg.variable);
27839
28134
  if (paramSource) {
27840
- const exists = flows.some((f) => f.source_line === paramSource.line && f.sink_line === sink.line);
28135
+ const exists = flows.some((f) => f.source_line === paramSource.line && f.sink_line === sink.line && f.sink_type === sink.type);
27841
28136
  if (!exists) {
27842
28137
  if (typeof code === "string" && isReassignedToLiteralBetween(code, arg.variable, paramSource.line, sink.line)) {
27843
28138
  continue;
@@ -39034,7 +39329,7 @@ var colors = {
39034
39329
  };
39035
39330
 
39036
39331
  // src/version.ts
39037
- var version = "3.131.0";
39332
+ var version = "3.132.0";
39038
39333
 
39039
39334
  // src/formatters.ts
39040
39335
  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.131.0",
3
+ "version": "3.132.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.131.0"
69
+ "circle-ir": "^3.132.0"
70
70
  },
71
71
  "devDependencies": {
72
72
  "@types/node": "^25.5.0",