cognium-dev 3.129.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 +772 -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];
@@ -22604,6 +22604,12 @@ class LanguageSourcesPass {
22604
22604
  for (const finding of goMisconfigFindings) {
22605
22605
  ctx.addFinding(finding);
22606
22606
  }
22607
+ for (const finding of findGoXssFindings(code, graph.ir.meta.file)) {
22608
+ ctx.addFinding(finding);
22609
+ }
22610
+ for (const finding of findGoLocationHeaderOpenRedirectFindings(code, graph.ir.meta.file)) {
22611
+ ctx.addFinding(finding);
22612
+ }
22607
22613
  }
22608
22614
  if (language === "python") {
22609
22615
  additionalSanitizers.push(...findPythonNetlocAllowlistGuardSanitizers(code));
@@ -22616,6 +22622,15 @@ class LanguageSourcesPass {
22616
22622
  for (const finding of pyMisconfigFindings) {
22617
22623
  ctx.addFinding(finding);
22618
22624
  }
22625
+ for (const finding of findPythonFlaskStringConcatXssFindings(code, graph.ir.meta.file)) {
22626
+ ctx.addFinding(finding);
22627
+ }
22628
+ for (const finding of findPythonJinjaMarkupXssFindings(code, graph.ir.meta.file)) {
22629
+ ctx.addFinding(finding);
22630
+ }
22631
+ for (const finding of findPythonHeadersSubscriptOpenRedirectFindings(code, graph.ir.meta.file)) {
22632
+ ctx.addFinding(finding);
22633
+ }
22619
22634
  }
22620
22635
  if (language === "rust") {
22621
22636
  additionalSanitizers.push(...findRustSetAllowlistGuardSanitizers(code));
@@ -22637,6 +22652,9 @@ class LanguageSourcesPass {
22637
22652
  for (const finding of findRustWeakCryptoEcbFindings(code, graph.ir.meta.file)) {
22638
22653
  ctx.addFinding(finding);
22639
22654
  }
22655
+ for (const finding of findRustAppendHeaderTupleOpenRedirectFindings(code, graph.ir.meta.file)) {
22656
+ ctx.addFinding(finding);
22657
+ }
22640
22658
  }
22641
22659
  if (language === "javascript" || language === "typescript" || language === "htmljs") {
22642
22660
  additionalSanitizers.push(...findJsSafeJsonParseSanitizers(code));
@@ -22644,9 +22662,20 @@ class LanguageSourcesPass {
22644
22662
  additionalSanitizers.push(...findJsCsvFormulaPrefixSanitizers(code));
22645
22663
  additionalSanitizers.push(...findJsWrapperFunctionSanitizers(code));
22646
22664
  additionalSanitizers.push(...findJsSsrfAllowlistGuardSanitizers(code));
22665
+ additionalSanitizers.push(...findJsArgvFormExecSanitizers(code));
22666
+ additionalSanitizers.push(...findJsParameterizedSqlSanitizers(code));
22647
22667
  for (const finding of findJsPatternFindings(code, graph.ir.meta.file)) {
22648
22668
  ctx.addFinding(finding);
22649
22669
  }
22670
+ for (const finding of findJsVueVHtmlXssFindings(code, graph.ir.meta.file)) {
22671
+ ctx.addFinding(finding);
22672
+ }
22673
+ for (const finding of findTsAngularBypassXssFindings(code, graph.ir.meta.file)) {
22674
+ ctx.addFinding(finding);
22675
+ }
22676
+ for (const finding of findJsDomOpenRedirectFindings(code, graph.ir.meta.file)) {
22677
+ ctx.addFinding(finding);
22678
+ }
22650
22679
  }
22651
22680
  if (language === "java") {
22652
22681
  additionalSanitizers.push(...findJavaSafeJsonParseSanitizers(code));
@@ -22656,6 +22685,9 @@ class LanguageSourcesPass {
22656
22685
  for (const finding of findJavaPatternFindings(code, graph.ir.meta.file)) {
22657
22686
  ctx.addFinding(finding);
22658
22687
  }
22688
+ for (const finding of findJavaResponseWriterXssFindings(code, graph.ir.meta.file)) {
22689
+ ctx.addFinding(finding);
22690
+ }
22659
22691
  }
22660
22692
  if (language === "python" || language === "javascript" || language === "typescript" || language === "go") {
22661
22693
  const exfilFindings = findExternalSecretExfiltrationFindings(code, graph.ir.meta.file, language);
@@ -24607,6 +24639,64 @@ function findJsSsrfAllowlistGuardSanitizers(code) {
24607
24639
  }
24608
24640
  return sanitizers;
24609
24641
  }
24642
+ function findJsArgvFormExecSanitizers(code) {
24643
+ const sanitizers = [];
24644
+ const lines = code.split(`
24645
+ `);
24646
+ const argvExecRe = /\b(?:execFile|spawn)(?:Sync)?\s*\(\s*(?:'[^']*'|"[^"]*"|`[^`]*`)\s*,\s*\[/;
24647
+ const shellArgvRe = /\b(?:execFile|spawn)(?:Sync)?\s*\(\s*['"`](?:[\w./-]*\/)?(?:sh|bash|zsh|ksh|dash|cmd(?:\.exe)?|powershell|pwsh)['"`]\s*,\s*\[\s*['"`]-c['"`]/;
24648
+ for (let i2 = 0;i2 < lines.length; i2++) {
24649
+ const text = lines[i2];
24650
+ if (!argvExecRe.test(text))
24651
+ continue;
24652
+ if (shellArgvRe.test(text))
24653
+ continue;
24654
+ sanitizers.push({
24655
+ type: "js_argv_form_exec",
24656
+ method: "execFile",
24657
+ line: i2 + 1,
24658
+ sanitizes: ["command_injection", "external_taint_escape"]
24659
+ });
24660
+ }
24661
+ return sanitizers;
24662
+ }
24663
+ function findJsParameterizedSqlSanitizers(code) {
24664
+ const sanitizers = [];
24665
+ const lines = code.split(`
24666
+ `);
24667
+ const singleRe = /\.\s*(?:query|execute)\s*\(\s*'([^'\\]*(?:\\.[^'\\]*)*)'\s*,\s*\[/;
24668
+ const doubleRe = /\.\s*(?:query|execute)\s*\(\s*"([^"\\]*(?:\\.[^"\\]*)*)"\s*,\s*\[/;
24669
+ const tickRe = /\.\s*(?:query|execute)\s*\(\s*`([^`]*)`\s*,\s*\[/;
24670
+ const placeholderRe = /(?:\$\d+|\?)/;
24671
+ for (let i2 = 0;i2 < lines.length; i2++) {
24672
+ const text = lines[i2];
24673
+ let sql = null;
24674
+ const s = singleRe.exec(text);
24675
+ if (s)
24676
+ sql = s[1];
24677
+ if (sql == null) {
24678
+ const d = doubleRe.exec(text);
24679
+ if (d)
24680
+ sql = d[1];
24681
+ }
24682
+ if (sql == null) {
24683
+ const t = tickRe.exec(text);
24684
+ if (t && !/\$\{/.test(t[1]))
24685
+ sql = t[1];
24686
+ }
24687
+ if (sql == null)
24688
+ continue;
24689
+ if (!placeholderRe.test(sql))
24690
+ continue;
24691
+ sanitizers.push({
24692
+ type: "js_parameterized_sql",
24693
+ method: "query",
24694
+ line: i2 + 1,
24695
+ sanitizes: ["sql_injection", "external_taint_escape"]
24696
+ });
24697
+ }
24698
+ return sanitizers;
24699
+ }
24610
24700
  function findJavaPathNormalizeStartsWithGuardSanitizers(code) {
24611
24701
  const sanitizers = [];
24612
24702
  const lines = code.split(`
@@ -25444,6 +25534,674 @@ function findJsPatternFindings(code, file) {
25444
25534
  }
25445
25535
  return out2;
25446
25536
  }
25537
+ function findGoXssFindings(code, file) {
25538
+ const out2 = [];
25539
+ const lines = code.split(`
25540
+ `);
25541
+ const rwNames = new Set;
25542
+ const sigRe = /\(\s*([A-Za-z_]\w*)\s+http\.ResponseWriter\b/g;
25543
+ for (const line of lines) {
25544
+ let m;
25545
+ sigRe.lastIndex = 0;
25546
+ while ((m = sigRe.exec(line)) !== null)
25547
+ rwNames.add(m[1]);
25548
+ }
25549
+ if (rwNames.size === 0)
25550
+ return out2;
25551
+ const escaperRe = /\b(?:html|template)\.(?:EscapeString|HTMLEscapeString|HTMLEscaper|JSEscapeString|URLQueryEscaper)\s*\(/;
25552
+ for (let i2 = 0;i2 < lines.length; i2++) {
25553
+ const raw = lines[i2];
25554
+ const trimmed = raw.trim();
25555
+ if (!trimmed || trimmed.startsWith("//"))
25556
+ continue;
25557
+ const callRe = /\bfmt\.Fprint(?:f|ln)?\s*\(\s*([A-Za-z_]\w*)\s*,\s*([\s\S]*)\)\s*(?:\/\/.*)?$/;
25558
+ const m = trimmed.match(callRe);
25559
+ if (!m)
25560
+ continue;
25561
+ if (!rwNames.has(m[1]))
25562
+ continue;
25563
+ const tail = m[2];
25564
+ if (escaperRe.test(tail))
25565
+ continue;
25566
+ if (!/[A-Za-z_]\w*/.test(tail.replace(/"[^"]*"|`[^`]*`/g, "")))
25567
+ continue;
25568
+ out2.push({
25569
+ id: `xss-${file}-${i2 + 1}`,
25570
+ pass: "language-sources",
25571
+ category: "security",
25572
+ rule_id: "xss",
25573
+ cwe: "CWE-79",
25574
+ severity: "high",
25575
+ level: "error",
25576
+ message: "Reflected XSS: fmt.Fprint writes data to http.ResponseWriter " + "without HTML escaping. Wrap user-controlled args with " + "html.EscapeString / template.HTMLEscapeString.",
25577
+ file,
25578
+ line: i2 + 1,
25579
+ snippet: trimmed.substring(0, 100)
25580
+ });
25581
+ }
25582
+ return out2;
25583
+ }
25584
+ function findJavaResponseWriterXssFindings(code, file) {
25585
+ const out2 = [];
25586
+ const lines = code.split(`
25587
+ `);
25588
+ const respNames = new Set;
25589
+ const sigRe = /\bHttpServletResponse\s+([A-Za-z_]\w*)\b/g;
25590
+ for (const line of lines) {
25591
+ let m;
25592
+ sigRe.lastIndex = 0;
25593
+ while ((m = sigRe.exec(line)) !== null)
25594
+ respNames.add(m[1]);
25595
+ }
25596
+ if (respNames.size === 0)
25597
+ return out2;
25598
+ const safeWrapRe = /\b(?:Encode\.(?:forHtml|forHtmlAttribute|forHtmlContent|forJavaScript)|StringEscapeUtils\.escape(?:Html3|Html4|EcmaScript|Xml)|HtmlUtils\.htmlEscape(?:Decimal|Hex)?|Escaper\.escapeHtml|HtmlEscapers\.(?:escapeHtml|htmlEscaper)|Encoder\.encodeForHTML(?:Attribute)?|Jsoup\.clean)\s*\(/;
25599
+ for (let i2 = 0;i2 < lines.length; i2++) {
25600
+ const raw = lines[i2];
25601
+ const trimmed = raw.trim();
25602
+ if (!trimmed || trimmed.startsWith("//") || trimmed.startsWith("*"))
25603
+ continue;
25604
+ const chainRe = /\b([A-Za-z_]\w*)\s*\.\s*getWriter\s*\(\s*\)\s*\.\s*(print|println|write|printf|format|append)\s*\(([\s\S]*)\)\s*;?\s*(?:\/\/.*)?$/;
25605
+ const m = trimmed.match(chainRe);
25606
+ if (!m)
25607
+ continue;
25608
+ const recv = m[1];
25609
+ if (!respNames.has(recv))
25610
+ continue;
25611
+ const args2 = m[3];
25612
+ if (safeWrapRe.test(args2))
25613
+ continue;
25614
+ const argsTrim = args2.trim();
25615
+ if (/^"(?:\\.|[^"\\])*"$/.test(argsTrim))
25616
+ continue;
25617
+ if (!/[A-Za-z_]\w*/.test(argsTrim))
25618
+ continue;
25619
+ out2.push({
25620
+ id: `xss-${file}-${i2 + 1}`,
25621
+ pass: "language-sources",
25622
+ category: "security",
25623
+ rule_id: "xss",
25624
+ cwe: "CWE-79",
25625
+ severity: "high",
25626
+ level: "error",
25627
+ message: "Reflected XSS: HttpServletResponse.getWriter()." + m[2] + "(...) writes data to the response without HTML escaping. " + "Wrap user-controlled values with OWASP Encode.forHtml / " + "StringEscapeUtils.escapeHtml4 / Jsoup.clean.",
25628
+ file,
25629
+ line: i2 + 1,
25630
+ snippet: trimmed.substring(0, 100)
25631
+ });
25632
+ }
25633
+ return out2;
25634
+ }
25635
+ function findJsVueVHtmlXssFindings(code, file) {
25636
+ const out2 = [];
25637
+ const lines = code.split(`
25638
+ `);
25639
+ const sourceRe = /\b(?:URLSearchParams|location\s*\.\s*(?:search|hash|href|pathname)|window\s*\.\s*location|route\s*\.\s*(?:query|params)|router\s*\.\s*(?:currentRoute|query)|\$route\s*\.\s*(?:query|params)|fetch\s*\(|axios\s*\.\s*(?:get|post)|XMLHttpRequest|document\s*\.\s*location)\b/;
25640
+ const tplRe = /\btemplate\s*:\s*(['"`])([\s\S]*?)\1/g;
25641
+ let tm;
25642
+ const boundVars = new Set;
25643
+ while ((tm = tplRe.exec(code)) !== null) {
25644
+ const tpl = tm[2];
25645
+ const vhRe = /\bv-html\s*=\s*"([^"]+)"/g;
25646
+ let vm;
25647
+ while ((vm = vhRe.exec(tpl)) !== null) {
25648
+ const expr = vm[1].trim();
25649
+ const idMatch = expr.match(/^([A-Za-z_$][\w$]*)/);
25650
+ if (idMatch)
25651
+ boundVars.add(idMatch[1]);
25652
+ }
25653
+ }
25654
+ if (boundVars.size === 0)
25655
+ return out2;
25656
+ const taintedSet = new Set;
25657
+ const assignRe = /\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*([^;\n]+)/g;
25658
+ let am;
25659
+ assignRe.lastIndex = 0;
25660
+ while ((am = assignRe.exec(code)) !== null) {
25661
+ if (sourceRe.test(am[2]))
25662
+ taintedSet.add(am[1]);
25663
+ }
25664
+ for (let pass = 0;pass < 3; pass++) {
25665
+ assignRe.lastIndex = 0;
25666
+ const before = taintedSet.size;
25667
+ while ((am = assignRe.exec(code)) !== null) {
25668
+ if (taintedSet.has(am[1]))
25669
+ continue;
25670
+ for (const tv of taintedSet) {
25671
+ if (new RegExp(`\\b${tv}\\b`).test(am[2])) {
25672
+ taintedSet.add(am[1]);
25673
+ break;
25674
+ }
25675
+ }
25676
+ }
25677
+ if (taintedSet.size === before)
25678
+ break;
25679
+ }
25680
+ const taintedBindings = new Set;
25681
+ for (const v of boundVars) {
25682
+ const bindRe = new RegExp(`\\b${v}\\s*[:=]\\s*([^,;\\n]+)`);
25683
+ for (const line of lines) {
25684
+ const m = line.match(bindRe);
25685
+ if (!m)
25686
+ continue;
25687
+ const val = m[1];
25688
+ if (sourceRe.test(val)) {
25689
+ taintedBindings.add(v);
25690
+ break;
25691
+ }
25692
+ let found = false;
25693
+ for (const tv of taintedSet) {
25694
+ if (new RegExp(`\\b${tv}\\b`).test(val)) {
25695
+ taintedBindings.add(v);
25696
+ found = true;
25697
+ break;
25698
+ }
25699
+ }
25700
+ if (found)
25701
+ break;
25702
+ }
25703
+ const propsRe = new RegExp(`\\bprops\\s*:\\s*\\[[^\\]]*['"\`]${v}['"\`][^\\]]*\\]`);
25704
+ if (propsRe.test(code))
25705
+ taintedBindings.add(v);
25706
+ }
25707
+ if (taintedBindings.size === 0)
25708
+ return out2;
25709
+ for (let i2 = 0;i2 < lines.length; i2++) {
25710
+ const raw = lines[i2];
25711
+ if (!/v-html\s*=/.test(raw))
25712
+ continue;
25713
+ const vm = raw.match(/v-html\s*=\s*"([^"]+)"/);
25714
+ if (!vm)
25715
+ continue;
25716
+ const idMatch = vm[1].trim().match(/^([A-Za-z_$][\w$]*)/);
25717
+ if (!idMatch || !taintedBindings.has(idMatch[1]))
25718
+ continue;
25719
+ out2.push({
25720
+ id: `xss-${file}-${i2 + 1}`,
25721
+ pass: "language-sources",
25722
+ category: "security",
25723
+ rule_id: "xss",
25724
+ cwe: "CWE-79",
25725
+ severity: "high",
25726
+ level: "error",
25727
+ message: 'Vue v-html XSS: directive binds to "' + idMatch[1] + '" which is sourced from user-controlled input. Use {{ }} ' + "interpolation (auto-escapes) or sanitize the HTML with " + "DOMPurify before binding.",
25728
+ file,
25729
+ line: i2 + 1,
25730
+ snippet: raw.trim().substring(0, 100)
25731
+ });
25732
+ }
25733
+ return out2;
25734
+ }
25735
+ function findTsAngularBypassXssFindings(code, file) {
25736
+ const out2 = [];
25737
+ const lines = code.split(`
25738
+ `);
25739
+ const sanitizerNames = new Set;
25740
+ const declRe = /\b(?:public|private|protected|readonly|\s)?\s*([A-Za-z_$][\w$]*)\s*:\s*DomSanitizer\b/g;
25741
+ let m;
25742
+ declRe.lastIndex = 0;
25743
+ while ((m = declRe.exec(code)) !== null)
25744
+ sanitizerNames.add(m[1]);
25745
+ if (sanitizerNames.size === 0)
25746
+ return out2;
25747
+ const assignRe = /\bthis\s*\.\s*([A-Za-z_$][\w$]*)\s*=\s*([A-Za-z_$][\w$]*)\b/g;
25748
+ let am;
25749
+ assignRe.lastIndex = 0;
25750
+ while ((am = assignRe.exec(code)) !== null) {
25751
+ if (sanitizerNames.has(am[2]))
25752
+ sanitizerNames.add(am[1]);
25753
+ }
25754
+ for (let i2 = 0;i2 < lines.length; i2++) {
25755
+ const raw = lines[i2];
25756
+ const trimmed = raw.trim();
25757
+ if (!trimmed || trimmed.startsWith("//") || trimmed.startsWith("*"))
25758
+ continue;
25759
+ const callRe = /\b(?:this\s*\.\s*)?([A-Za-z_$][\w$]*)\s*\.\s*bypassSecurityTrust(Html|Script|Url|ResourceUrl|Style)\s*\(([\s\S]*?)\)/;
25760
+ const cm = trimmed.match(callRe);
25761
+ if (!cm)
25762
+ continue;
25763
+ const recv = cm[1];
25764
+ if (!sanitizerNames.has(recv))
25765
+ continue;
25766
+ const arg = cm[3].trim();
25767
+ if (/^(['"`])[^'"`]*\1$/.test(arg))
25768
+ continue;
25769
+ out2.push({
25770
+ id: `xss-${file}-${i2 + 1}`,
25771
+ pass: "language-sources",
25772
+ category: "security",
25773
+ rule_id: "xss",
25774
+ cwe: "CWE-79",
25775
+ severity: "high",
25776
+ level: "error",
25777
+ message: "Angular XSS: DomSanitizer.bypassSecurityTrust" + cm[2] + "() opts out of Angular's built-in sanitization for a " + "non-literal argument. Prefer leaving Angular's default " + "sanitizer in place, or sanitize the input with DOMPurify " + "before bypassing.",
25778
+ file,
25779
+ line: i2 + 1,
25780
+ snippet: trimmed.substring(0, 100)
25781
+ });
25782
+ }
25783
+ return out2;
25784
+ }
25785
+ function findPythonFlaskStringConcatXssFindings(code, file) {
25786
+ const out2 = [];
25787
+ const lines = code.split(`
25788
+ `);
25789
+ const fns = [];
25790
+ const defRe = /^(\s*)def\s+([A-Za-z_]\w*)\s*\(/;
25791
+ for (let i2 = 0;i2 < lines.length; i2++) {
25792
+ const raw = lines[i2];
25793
+ const dm = raw.match(defRe);
25794
+ if (!dm)
25795
+ continue;
25796
+ const indent = dm[1].length;
25797
+ let hasRoute = false;
25798
+ for (let j = i2 - 1;j >= 0; j--) {
25799
+ const prev = lines[j].trim();
25800
+ if (prev === "" || prev.startsWith("#"))
25801
+ continue;
25802
+ if (!prev.startsWith("@"))
25803
+ break;
25804
+ if (/@\s*[A-Za-z_]\w*\s*\.\s*route\s*\(/.test(prev) || /@\s*route\s*\(/.test(prev)) {
25805
+ hasRoute = true;
25806
+ }
25807
+ }
25808
+ if (!hasRoute)
25809
+ continue;
25810
+ let end = lines.length;
25811
+ for (let k = i2 + 1;k < lines.length; k++) {
25812
+ const ln = lines[k];
25813
+ if (!ln.trim())
25814
+ continue;
25815
+ const ind = ln.match(/^(\s*)/)?.[1].length ?? 0;
25816
+ if (ind <= indent) {
25817
+ end = k;
25818
+ break;
25819
+ }
25820
+ }
25821
+ fns.push({ name: dm[2], startLine: i2 + 1, endLine: end });
25822
+ }
25823
+ if (fns.length === 0)
25824
+ return out2;
25825
+ const requestSourceRe = /\b([A-Za-z_]\w*)\s*=\s*request\s*\.\s*(?:args|form|values|files|json|cookies|headers)(?:\s*\.\s*get\s*\(|\s*\[)/;
25826
+ const escapeWrapRe = /\b(?:html\.escape|markupsafe\.escape|escape|markupsafe\.Markup\s*\.\s*escape|werkzeug\.utils\.escape)\s*\(/;
25827
+ for (const fn of fns) {
25828
+ const taintedVars = new Set;
25829
+ for (let li = fn.startLine;li < fn.endLine; li++) {
25830
+ const raw = lines[li];
25831
+ const rm = raw.match(requestSourceRe);
25832
+ if (rm)
25833
+ taintedVars.add(rm[1]);
25834
+ }
25835
+ for (let li = fn.startLine;li < fn.endLine; li++) {
25836
+ const raw = lines[li];
25837
+ const trimmed = raw.trim();
25838
+ if (!trimmed || trimmed.startsWith("#"))
25839
+ continue;
25840
+ const retMatch = trimmed.match(/^(?:return|yield)\s+(.+)$/);
25841
+ if (!retMatch)
25842
+ continue;
25843
+ const expr = retMatch[1];
25844
+ const inlineSource = /\brequest\s*\.\s*(?:args|form|values|files|cookies|headers)(?:\s*\.\s*get\s*\(|\s*\[)/.test(expr);
25845
+ const hasTaintedVar = [...taintedVars].some((v) => new RegExp(`\\b${v}\\b`).test(expr));
25846
+ if (!hasTaintedVar && !inlineSource)
25847
+ continue;
25848
+ if (escapeWrapRe.test(expr))
25849
+ continue;
25850
+ const buildsHtml = /['"`]\s*\+/.test(expr) || /\+\s*['"`]/.test(expr) || /\bf['"`][^{\n]*\{[^}]+\}/.test(expr) || /['"`]\s*%\s*[^=]/.test(expr) || /['"`]\.\s*format\s*\(/.test(expr);
25851
+ if (!buildsHtml)
25852
+ continue;
25853
+ out2.push({
25854
+ id: `xss-${file}-${li + 1}`,
25855
+ pass: "language-sources",
25856
+ category: "security",
25857
+ rule_id: "xss",
25858
+ cwe: "CWE-79",
25859
+ severity: "high",
25860
+ level: "error",
25861
+ message: "Reflected XSS: Flask route returns HTML built from request " + "input via string concatenation / f-string / format. Wrap " + "user input with markupsafe.escape() or render via a " + "Jinja2 template (autoescape on by default).",
25862
+ file,
25863
+ line: li + 1,
25864
+ snippet: trimmed.substring(0, 100)
25865
+ });
25866
+ break;
25867
+ }
25868
+ }
25869
+ return out2;
25870
+ }
25871
+ function findPythonJinjaMarkupXssFindings(code, file) {
25872
+ const out2 = [];
25873
+ const lines = code.split(`
25874
+ `);
25875
+ const importRe = /^\s*from\s+(?:markupsafe|flask)\s+import\s+(?:[^#\n]*\b)?Markup\b/m;
25876
+ const classDefRe = /^\s*class\s+Markup\b/m;
25877
+ if (!importRe.test(code))
25878
+ return out2;
25879
+ if (classDefRe.test(code))
25880
+ return out2;
25881
+ const requestSourceRe = /\b([A-Za-z_]\w*)\s*=\s*request\s*\.\s*(?:args|form|values|files|json|cookies|headers)(?:\s*\.\s*get\s*\(|\s*\[)/;
25882
+ const taintedVars = new Set;
25883
+ for (const line of lines) {
25884
+ const m = line.match(requestSourceRe);
25885
+ if (m)
25886
+ taintedVars.add(m[1]);
25887
+ }
25888
+ if (taintedVars.size === 0)
25889
+ return out2;
25890
+ if (!/\.\s*render\s*\(/.test(code))
25891
+ return out2;
25892
+ const markupCallRe = /\bMarkup\s*\(\s*([A-Za-z_]\w*)\s*[,)]/g;
25893
+ for (let i2 = 0;i2 < lines.length; i2++) {
25894
+ const raw = lines[i2];
25895
+ const trimmed = raw.trim();
25896
+ if (!trimmed || trimmed.startsWith("#"))
25897
+ continue;
25898
+ let mm;
25899
+ markupCallRe.lastIndex = 0;
25900
+ while ((mm = markupCallRe.exec(trimmed)) !== null) {
25901
+ const argName = mm[1];
25902
+ if (!taintedVars.has(argName))
25903
+ continue;
25904
+ out2.push({
25905
+ id: `xss-${file}-${i2 + 1}`,
25906
+ pass: "language-sources",
25907
+ category: "security",
25908
+ rule_id: "xss",
25909
+ cwe: "CWE-79",
25910
+ severity: "high",
25911
+ level: "error",
25912
+ message: "Jinja2 autoescape bypass: Markup(" + argName + ") wraps a request-derived value, disabling autoescape when " + "rendered. Pass the raw value to render() and let Jinja2 " + "auto-escape, or sanitize with bleach.clean first.",
25913
+ file,
25914
+ line: i2 + 1,
25915
+ snippet: trimmed.substring(0, 100)
25916
+ });
25917
+ break;
25918
+ }
25919
+ }
25920
+ return out2;
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
+ }
25447
26205
 
25448
26206
  // ../circle-ir/dist/analysis/passes/sink-filter-pass.js
25449
26207
  var JS_XSS_SANITIZERS = [
@@ -26908,13 +27666,13 @@ class TaintPropagationPass {
26908
27666
  }));
26909
27667
  const arrayFlows = detectArrayElementFlows(calls, sources, sinks, constProp.taintedArrayElements, constProp.unreachableLines, types) ?? [];
26910
27668
  for (const f of arrayFlows) {
26911
- 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)) {
26912
27670
  flows.push(f);
26913
27671
  }
26914
27672
  }
26915
27673
  const collectionFlows = detectCollectionFlows(calls, sources, sinks, constProp.tainted, constProp.unreachableLines, ctx.code, types) ?? [];
26916
27674
  for (const f of collectionFlows) {
26917
- 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))
26918
27676
  continue;
26919
27677
  const flowForCheck = {
26920
27678
  source: { line: f.source_line },
@@ -26936,7 +27694,7 @@ class TaintPropagationPass {
26936
27694
  }
26937
27695
  const paramFlows = detectParameterSinkFlows(types, calls, sources, sinks, constProp.unreachableLines, constProp.tainted, ctx.code) ?? [];
26938
27696
  for (const f of paramFlows) {
26939
- 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)) {
26940
27698
  flows.push(f);
26941
27699
  }
26942
27700
  }
@@ -27374,7 +28132,7 @@ function detectParameterSinkFlows(types, calls, sources, sinks, unreachableLines
27374
28132
  }
27375
28133
  const paramSource = methodParamSources.get(arg.variable);
27376
28134
  if (paramSource) {
27377
- 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);
27378
28136
  if (!exists) {
27379
28137
  if (typeof code === "string" && isReassignedToLiteralBetween(code, arg.variable, paramSource.line, sink.line)) {
27380
28138
  continue;
@@ -38571,7 +39329,7 @@ var colors = {
38571
39329
  };
38572
39330
 
38573
39331
  // src/version.ts
38574
- var version = "3.129.0";
39332
+ var version = "3.132.0";
38575
39333
 
38576
39334
  // src/formatters.ts
38577
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.129.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.129.0"
69
+ "circle-ir": "^3.132.0"
70
70
  },
71
71
  "devDependencies": {
72
72
  "@types/node": "^25.5.0",