cognium-dev 3.129.0 → 3.131.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 +464 -1
  2. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -22604,6 +22604,9 @@ 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
+ }
22607
22610
  }
22608
22611
  if (language === "python") {
22609
22612
  additionalSanitizers.push(...findPythonNetlocAllowlistGuardSanitizers(code));
@@ -22616,6 +22619,12 @@ class LanguageSourcesPass {
22616
22619
  for (const finding of pyMisconfigFindings) {
22617
22620
  ctx.addFinding(finding);
22618
22621
  }
22622
+ for (const finding of findPythonFlaskStringConcatXssFindings(code, graph.ir.meta.file)) {
22623
+ ctx.addFinding(finding);
22624
+ }
22625
+ for (const finding of findPythonJinjaMarkupXssFindings(code, graph.ir.meta.file)) {
22626
+ ctx.addFinding(finding);
22627
+ }
22619
22628
  }
22620
22629
  if (language === "rust") {
22621
22630
  additionalSanitizers.push(...findRustSetAllowlistGuardSanitizers(code));
@@ -22644,9 +22653,17 @@ class LanguageSourcesPass {
22644
22653
  additionalSanitizers.push(...findJsCsvFormulaPrefixSanitizers(code));
22645
22654
  additionalSanitizers.push(...findJsWrapperFunctionSanitizers(code));
22646
22655
  additionalSanitizers.push(...findJsSsrfAllowlistGuardSanitizers(code));
22656
+ additionalSanitizers.push(...findJsArgvFormExecSanitizers(code));
22657
+ additionalSanitizers.push(...findJsParameterizedSqlSanitizers(code));
22647
22658
  for (const finding of findJsPatternFindings(code, graph.ir.meta.file)) {
22648
22659
  ctx.addFinding(finding);
22649
22660
  }
22661
+ for (const finding of findJsVueVHtmlXssFindings(code, graph.ir.meta.file)) {
22662
+ ctx.addFinding(finding);
22663
+ }
22664
+ for (const finding of findTsAngularBypassXssFindings(code, graph.ir.meta.file)) {
22665
+ ctx.addFinding(finding);
22666
+ }
22650
22667
  }
22651
22668
  if (language === "java") {
22652
22669
  additionalSanitizers.push(...findJavaSafeJsonParseSanitizers(code));
@@ -22656,6 +22673,9 @@ class LanguageSourcesPass {
22656
22673
  for (const finding of findJavaPatternFindings(code, graph.ir.meta.file)) {
22657
22674
  ctx.addFinding(finding);
22658
22675
  }
22676
+ for (const finding of findJavaResponseWriterXssFindings(code, graph.ir.meta.file)) {
22677
+ ctx.addFinding(finding);
22678
+ }
22659
22679
  }
22660
22680
  if (language === "python" || language === "javascript" || language === "typescript" || language === "go") {
22661
22681
  const exfilFindings = findExternalSecretExfiltrationFindings(code, graph.ir.meta.file, language);
@@ -24607,6 +24627,64 @@ function findJsSsrfAllowlistGuardSanitizers(code) {
24607
24627
  }
24608
24628
  return sanitizers;
24609
24629
  }
24630
+ function findJsArgvFormExecSanitizers(code) {
24631
+ const sanitizers = [];
24632
+ const lines = code.split(`
24633
+ `);
24634
+ const argvExecRe = /\b(?:execFile|spawn)(?:Sync)?\s*\(\s*(?:'[^']*'|"[^"]*"|`[^`]*`)\s*,\s*\[/;
24635
+ const shellArgvRe = /\b(?:execFile|spawn)(?:Sync)?\s*\(\s*['"`](?:[\w./-]*\/)?(?:sh|bash|zsh|ksh|dash|cmd(?:\.exe)?|powershell|pwsh)['"`]\s*,\s*\[\s*['"`]-c['"`]/;
24636
+ for (let i2 = 0;i2 < lines.length; i2++) {
24637
+ const text = lines[i2];
24638
+ if (!argvExecRe.test(text))
24639
+ continue;
24640
+ if (shellArgvRe.test(text))
24641
+ continue;
24642
+ sanitizers.push({
24643
+ type: "js_argv_form_exec",
24644
+ method: "execFile",
24645
+ line: i2 + 1,
24646
+ sanitizes: ["command_injection", "external_taint_escape"]
24647
+ });
24648
+ }
24649
+ return sanitizers;
24650
+ }
24651
+ function findJsParameterizedSqlSanitizers(code) {
24652
+ const sanitizers = [];
24653
+ const lines = code.split(`
24654
+ `);
24655
+ const singleRe = /\.\s*(?:query|execute)\s*\(\s*'([^'\\]*(?:\\.[^'\\]*)*)'\s*,\s*\[/;
24656
+ const doubleRe = /\.\s*(?:query|execute)\s*\(\s*"([^"\\]*(?:\\.[^"\\]*)*)"\s*,\s*\[/;
24657
+ const tickRe = /\.\s*(?:query|execute)\s*\(\s*`([^`]*)`\s*,\s*\[/;
24658
+ const placeholderRe = /(?:\$\d+|\?)/;
24659
+ for (let i2 = 0;i2 < lines.length; i2++) {
24660
+ const text = lines[i2];
24661
+ let sql = null;
24662
+ const s = singleRe.exec(text);
24663
+ if (s)
24664
+ sql = s[1];
24665
+ if (sql == null) {
24666
+ const d = doubleRe.exec(text);
24667
+ if (d)
24668
+ sql = d[1];
24669
+ }
24670
+ if (sql == null) {
24671
+ const t = tickRe.exec(text);
24672
+ if (t && !/\$\{/.test(t[1]))
24673
+ sql = t[1];
24674
+ }
24675
+ if (sql == null)
24676
+ continue;
24677
+ if (!placeholderRe.test(sql))
24678
+ continue;
24679
+ sanitizers.push({
24680
+ type: "js_parameterized_sql",
24681
+ method: "query",
24682
+ line: i2 + 1,
24683
+ sanitizes: ["sql_injection", "external_taint_escape"]
24684
+ });
24685
+ }
24686
+ return sanitizers;
24687
+ }
24610
24688
  function findJavaPathNormalizeStartsWithGuardSanitizers(code) {
24611
24689
  const sanitizers = [];
24612
24690
  const lines = code.split(`
@@ -25444,6 +25522,391 @@ function findJsPatternFindings(code, file) {
25444
25522
  }
25445
25523
  return out2;
25446
25524
  }
25525
+ function findGoXssFindings(code, file) {
25526
+ const out2 = [];
25527
+ const lines = code.split(`
25528
+ `);
25529
+ const rwNames = new Set;
25530
+ const sigRe = /\(\s*([A-Za-z_]\w*)\s+http\.ResponseWriter\b/g;
25531
+ for (const line of lines) {
25532
+ let m;
25533
+ sigRe.lastIndex = 0;
25534
+ while ((m = sigRe.exec(line)) !== null)
25535
+ rwNames.add(m[1]);
25536
+ }
25537
+ if (rwNames.size === 0)
25538
+ return out2;
25539
+ const escaperRe = /\b(?:html|template)\.(?:EscapeString|HTMLEscapeString|HTMLEscaper|JSEscapeString|URLQueryEscaper)\s*\(/;
25540
+ for (let i2 = 0;i2 < lines.length; i2++) {
25541
+ const raw = lines[i2];
25542
+ const trimmed = raw.trim();
25543
+ if (!trimmed || trimmed.startsWith("//"))
25544
+ continue;
25545
+ const callRe = /\bfmt\.Fprint(?:f|ln)?\s*\(\s*([A-Za-z_]\w*)\s*,\s*([\s\S]*)\)\s*(?:\/\/.*)?$/;
25546
+ const m = trimmed.match(callRe);
25547
+ if (!m)
25548
+ continue;
25549
+ if (!rwNames.has(m[1]))
25550
+ continue;
25551
+ const tail = m[2];
25552
+ if (escaperRe.test(tail))
25553
+ continue;
25554
+ if (!/[A-Za-z_]\w*/.test(tail.replace(/"[^"]*"|`[^`]*`/g, "")))
25555
+ continue;
25556
+ out2.push({
25557
+ id: `xss-${file}-${i2 + 1}`,
25558
+ pass: "language-sources",
25559
+ category: "security",
25560
+ rule_id: "xss",
25561
+ cwe: "CWE-79",
25562
+ severity: "high",
25563
+ level: "error",
25564
+ message: "Reflected XSS: fmt.Fprint writes data to http.ResponseWriter " + "without HTML escaping. Wrap user-controlled args with " + "html.EscapeString / template.HTMLEscapeString.",
25565
+ file,
25566
+ line: i2 + 1,
25567
+ snippet: trimmed.substring(0, 100)
25568
+ });
25569
+ }
25570
+ return out2;
25571
+ }
25572
+ function findJavaResponseWriterXssFindings(code, file) {
25573
+ const out2 = [];
25574
+ const lines = code.split(`
25575
+ `);
25576
+ const respNames = new Set;
25577
+ const sigRe = /\bHttpServletResponse\s+([A-Za-z_]\w*)\b/g;
25578
+ for (const line of lines) {
25579
+ let m;
25580
+ sigRe.lastIndex = 0;
25581
+ while ((m = sigRe.exec(line)) !== null)
25582
+ respNames.add(m[1]);
25583
+ }
25584
+ if (respNames.size === 0)
25585
+ return out2;
25586
+ 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*\(/;
25587
+ for (let i2 = 0;i2 < lines.length; i2++) {
25588
+ const raw = lines[i2];
25589
+ const trimmed = raw.trim();
25590
+ if (!trimmed || trimmed.startsWith("//") || trimmed.startsWith("*"))
25591
+ continue;
25592
+ 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*(?:\/\/.*)?$/;
25593
+ const m = trimmed.match(chainRe);
25594
+ if (!m)
25595
+ continue;
25596
+ const recv = m[1];
25597
+ if (!respNames.has(recv))
25598
+ continue;
25599
+ const args2 = m[3];
25600
+ if (safeWrapRe.test(args2))
25601
+ continue;
25602
+ const argsTrim = args2.trim();
25603
+ if (/^"(?:\\.|[^"\\])*"$/.test(argsTrim))
25604
+ continue;
25605
+ if (!/[A-Za-z_]\w*/.test(argsTrim))
25606
+ continue;
25607
+ out2.push({
25608
+ id: `xss-${file}-${i2 + 1}`,
25609
+ pass: "language-sources",
25610
+ category: "security",
25611
+ rule_id: "xss",
25612
+ cwe: "CWE-79",
25613
+ severity: "high",
25614
+ level: "error",
25615
+ 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.",
25616
+ file,
25617
+ line: i2 + 1,
25618
+ snippet: trimmed.substring(0, 100)
25619
+ });
25620
+ }
25621
+ return out2;
25622
+ }
25623
+ function findJsVueVHtmlXssFindings(code, file) {
25624
+ const out2 = [];
25625
+ const lines = code.split(`
25626
+ `);
25627
+ 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/;
25628
+ const tplRe = /\btemplate\s*:\s*(['"`])([\s\S]*?)\1/g;
25629
+ let tm;
25630
+ const boundVars = new Set;
25631
+ while ((tm = tplRe.exec(code)) !== null) {
25632
+ const tpl = tm[2];
25633
+ const vhRe = /\bv-html\s*=\s*"([^"]+)"/g;
25634
+ let vm;
25635
+ while ((vm = vhRe.exec(tpl)) !== null) {
25636
+ const expr = vm[1].trim();
25637
+ const idMatch = expr.match(/^([A-Za-z_$][\w$]*)/);
25638
+ if (idMatch)
25639
+ boundVars.add(idMatch[1]);
25640
+ }
25641
+ }
25642
+ if (boundVars.size === 0)
25643
+ return out2;
25644
+ const taintedSet = new Set;
25645
+ const assignRe = /\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*([^;\n]+)/g;
25646
+ let am;
25647
+ assignRe.lastIndex = 0;
25648
+ while ((am = assignRe.exec(code)) !== null) {
25649
+ if (sourceRe.test(am[2]))
25650
+ taintedSet.add(am[1]);
25651
+ }
25652
+ for (let pass = 0;pass < 3; pass++) {
25653
+ assignRe.lastIndex = 0;
25654
+ const before = taintedSet.size;
25655
+ while ((am = assignRe.exec(code)) !== null) {
25656
+ if (taintedSet.has(am[1]))
25657
+ continue;
25658
+ for (const tv of taintedSet) {
25659
+ if (new RegExp(`\\b${tv}\\b`).test(am[2])) {
25660
+ taintedSet.add(am[1]);
25661
+ break;
25662
+ }
25663
+ }
25664
+ }
25665
+ if (taintedSet.size === before)
25666
+ break;
25667
+ }
25668
+ const taintedBindings = new Set;
25669
+ for (const v of boundVars) {
25670
+ const bindRe = new RegExp(`\\b${v}\\s*[:=]\\s*([^,;\\n]+)`);
25671
+ for (const line of lines) {
25672
+ const m = line.match(bindRe);
25673
+ if (!m)
25674
+ continue;
25675
+ const val = m[1];
25676
+ if (sourceRe.test(val)) {
25677
+ taintedBindings.add(v);
25678
+ break;
25679
+ }
25680
+ let found = false;
25681
+ for (const tv of taintedSet) {
25682
+ if (new RegExp(`\\b${tv}\\b`).test(val)) {
25683
+ taintedBindings.add(v);
25684
+ found = true;
25685
+ break;
25686
+ }
25687
+ }
25688
+ if (found)
25689
+ break;
25690
+ }
25691
+ const propsRe = new RegExp(`\\bprops\\s*:\\s*\\[[^\\]]*['"\`]${v}['"\`][^\\]]*\\]`);
25692
+ if (propsRe.test(code))
25693
+ taintedBindings.add(v);
25694
+ }
25695
+ if (taintedBindings.size === 0)
25696
+ return out2;
25697
+ for (let i2 = 0;i2 < lines.length; i2++) {
25698
+ const raw = lines[i2];
25699
+ if (!/v-html\s*=/.test(raw))
25700
+ continue;
25701
+ const vm = raw.match(/v-html\s*=\s*"([^"]+)"/);
25702
+ if (!vm)
25703
+ continue;
25704
+ const idMatch = vm[1].trim().match(/^([A-Za-z_$][\w$]*)/);
25705
+ if (!idMatch || !taintedBindings.has(idMatch[1]))
25706
+ continue;
25707
+ out2.push({
25708
+ id: `xss-${file}-${i2 + 1}`,
25709
+ pass: "language-sources",
25710
+ category: "security",
25711
+ rule_id: "xss",
25712
+ cwe: "CWE-79",
25713
+ severity: "high",
25714
+ level: "error",
25715
+ 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.",
25716
+ file,
25717
+ line: i2 + 1,
25718
+ snippet: raw.trim().substring(0, 100)
25719
+ });
25720
+ }
25721
+ return out2;
25722
+ }
25723
+ function findTsAngularBypassXssFindings(code, file) {
25724
+ const out2 = [];
25725
+ const lines = code.split(`
25726
+ `);
25727
+ const sanitizerNames = new Set;
25728
+ const declRe = /\b(?:public|private|protected|readonly|\s)?\s*([A-Za-z_$][\w$]*)\s*:\s*DomSanitizer\b/g;
25729
+ let m;
25730
+ declRe.lastIndex = 0;
25731
+ while ((m = declRe.exec(code)) !== null)
25732
+ sanitizerNames.add(m[1]);
25733
+ if (sanitizerNames.size === 0)
25734
+ return out2;
25735
+ const assignRe = /\bthis\s*\.\s*([A-Za-z_$][\w$]*)\s*=\s*([A-Za-z_$][\w$]*)\b/g;
25736
+ let am;
25737
+ assignRe.lastIndex = 0;
25738
+ while ((am = assignRe.exec(code)) !== null) {
25739
+ if (sanitizerNames.has(am[2]))
25740
+ sanitizerNames.add(am[1]);
25741
+ }
25742
+ for (let i2 = 0;i2 < lines.length; i2++) {
25743
+ const raw = lines[i2];
25744
+ const trimmed = raw.trim();
25745
+ if (!trimmed || trimmed.startsWith("//") || trimmed.startsWith("*"))
25746
+ continue;
25747
+ const callRe = /\b(?:this\s*\.\s*)?([A-Za-z_$][\w$]*)\s*\.\s*bypassSecurityTrust(Html|Script|Url|ResourceUrl|Style)\s*\(([\s\S]*?)\)/;
25748
+ const cm = trimmed.match(callRe);
25749
+ if (!cm)
25750
+ continue;
25751
+ const recv = cm[1];
25752
+ if (!sanitizerNames.has(recv))
25753
+ continue;
25754
+ const arg = cm[3].trim();
25755
+ if (/^(['"`])[^'"`]*\1$/.test(arg))
25756
+ continue;
25757
+ out2.push({
25758
+ id: `xss-${file}-${i2 + 1}`,
25759
+ pass: "language-sources",
25760
+ category: "security",
25761
+ rule_id: "xss",
25762
+ cwe: "CWE-79",
25763
+ severity: "high",
25764
+ level: "error",
25765
+ 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.",
25766
+ file,
25767
+ line: i2 + 1,
25768
+ snippet: trimmed.substring(0, 100)
25769
+ });
25770
+ }
25771
+ return out2;
25772
+ }
25773
+ function findPythonFlaskStringConcatXssFindings(code, file) {
25774
+ const out2 = [];
25775
+ const lines = code.split(`
25776
+ `);
25777
+ const fns = [];
25778
+ const defRe = /^(\s*)def\s+([A-Za-z_]\w*)\s*\(/;
25779
+ for (let i2 = 0;i2 < lines.length; i2++) {
25780
+ const raw = lines[i2];
25781
+ const dm = raw.match(defRe);
25782
+ if (!dm)
25783
+ continue;
25784
+ const indent = dm[1].length;
25785
+ let hasRoute = false;
25786
+ for (let j = i2 - 1;j >= 0; j--) {
25787
+ const prev = lines[j].trim();
25788
+ if (prev === "" || prev.startsWith("#"))
25789
+ continue;
25790
+ if (!prev.startsWith("@"))
25791
+ break;
25792
+ if (/@\s*[A-Za-z_]\w*\s*\.\s*route\s*\(/.test(prev) || /@\s*route\s*\(/.test(prev)) {
25793
+ hasRoute = true;
25794
+ }
25795
+ }
25796
+ if (!hasRoute)
25797
+ continue;
25798
+ let end = lines.length;
25799
+ for (let k = i2 + 1;k < lines.length; k++) {
25800
+ const ln = lines[k];
25801
+ if (!ln.trim())
25802
+ continue;
25803
+ const ind = ln.match(/^(\s*)/)?.[1].length ?? 0;
25804
+ if (ind <= indent) {
25805
+ end = k;
25806
+ break;
25807
+ }
25808
+ }
25809
+ fns.push({ name: dm[2], startLine: i2 + 1, endLine: end });
25810
+ }
25811
+ if (fns.length === 0)
25812
+ return out2;
25813
+ const requestSourceRe = /\b([A-Za-z_]\w*)\s*=\s*request\s*\.\s*(?:args|form|values|files|json|cookies|headers)(?:\s*\.\s*get\s*\(|\s*\[)/;
25814
+ const escapeWrapRe = /\b(?:html\.escape|markupsafe\.escape|escape|markupsafe\.Markup\s*\.\s*escape|werkzeug\.utils\.escape)\s*\(/;
25815
+ for (const fn of fns) {
25816
+ const taintedVars = new Set;
25817
+ for (let li = fn.startLine;li < fn.endLine; li++) {
25818
+ const raw = lines[li];
25819
+ const rm = raw.match(requestSourceRe);
25820
+ if (rm)
25821
+ taintedVars.add(rm[1]);
25822
+ }
25823
+ for (let li = fn.startLine;li < fn.endLine; li++) {
25824
+ const raw = lines[li];
25825
+ const trimmed = raw.trim();
25826
+ if (!trimmed || trimmed.startsWith("#"))
25827
+ continue;
25828
+ const retMatch = trimmed.match(/^(?:return|yield)\s+(.+)$/);
25829
+ if (!retMatch)
25830
+ continue;
25831
+ const expr = retMatch[1];
25832
+ const inlineSource = /\brequest\s*\.\s*(?:args|form|values|files|cookies|headers)(?:\s*\.\s*get\s*\(|\s*\[)/.test(expr);
25833
+ const hasTaintedVar = [...taintedVars].some((v) => new RegExp(`\\b${v}\\b`).test(expr));
25834
+ if (!hasTaintedVar && !inlineSource)
25835
+ continue;
25836
+ if (escapeWrapRe.test(expr))
25837
+ continue;
25838
+ const buildsHtml = /['"`]\s*\+/.test(expr) || /\+\s*['"`]/.test(expr) || /\bf['"`][^{\n]*\{[^}]+\}/.test(expr) || /['"`]\s*%\s*[^=]/.test(expr) || /['"`]\.\s*format\s*\(/.test(expr);
25839
+ if (!buildsHtml)
25840
+ continue;
25841
+ out2.push({
25842
+ id: `xss-${file}-${li + 1}`,
25843
+ pass: "language-sources",
25844
+ category: "security",
25845
+ rule_id: "xss",
25846
+ cwe: "CWE-79",
25847
+ severity: "high",
25848
+ level: "error",
25849
+ 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).",
25850
+ file,
25851
+ line: li + 1,
25852
+ snippet: trimmed.substring(0, 100)
25853
+ });
25854
+ break;
25855
+ }
25856
+ }
25857
+ return out2;
25858
+ }
25859
+ function findPythonJinjaMarkupXssFindings(code, file) {
25860
+ const out2 = [];
25861
+ const lines = code.split(`
25862
+ `);
25863
+ const importRe = /^\s*from\s+(?:markupsafe|flask)\s+import\s+(?:[^#\n]*\b)?Markup\b/m;
25864
+ const classDefRe = /^\s*class\s+Markup\b/m;
25865
+ if (!importRe.test(code))
25866
+ return out2;
25867
+ if (classDefRe.test(code))
25868
+ return out2;
25869
+ const requestSourceRe = /\b([A-Za-z_]\w*)\s*=\s*request\s*\.\s*(?:args|form|values|files|json|cookies|headers)(?:\s*\.\s*get\s*\(|\s*\[)/;
25870
+ const taintedVars = new Set;
25871
+ for (const line of lines) {
25872
+ const m = line.match(requestSourceRe);
25873
+ if (m)
25874
+ taintedVars.add(m[1]);
25875
+ }
25876
+ if (taintedVars.size === 0)
25877
+ return out2;
25878
+ if (!/\.\s*render\s*\(/.test(code))
25879
+ return out2;
25880
+ const markupCallRe = /\bMarkup\s*\(\s*([A-Za-z_]\w*)\s*[,)]/g;
25881
+ for (let i2 = 0;i2 < lines.length; i2++) {
25882
+ const raw = lines[i2];
25883
+ const trimmed = raw.trim();
25884
+ if (!trimmed || trimmed.startsWith("#"))
25885
+ continue;
25886
+ let mm;
25887
+ markupCallRe.lastIndex = 0;
25888
+ while ((mm = markupCallRe.exec(trimmed)) !== null) {
25889
+ const argName = mm[1];
25890
+ if (!taintedVars.has(argName))
25891
+ continue;
25892
+ out2.push({
25893
+ id: `xss-${file}-${i2 + 1}`,
25894
+ pass: "language-sources",
25895
+ category: "security",
25896
+ rule_id: "xss",
25897
+ cwe: "CWE-79",
25898
+ severity: "high",
25899
+ level: "error",
25900
+ 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.",
25901
+ file,
25902
+ line: i2 + 1,
25903
+ snippet: trimmed.substring(0, 100)
25904
+ });
25905
+ break;
25906
+ }
25907
+ }
25908
+ return out2;
25909
+ }
25447
25910
 
25448
25911
  // ../circle-ir/dist/analysis/passes/sink-filter-pass.js
25449
25912
  var JS_XSS_SANITIZERS = [
@@ -38571,7 +39034,7 @@ var colors = {
38571
39034
  };
38572
39035
 
38573
39036
  // src/version.ts
38574
- var version = "3.129.0";
39037
+ var version = "3.131.0";
38575
39038
 
38576
39039
  // src/formatters.ts
38577
39040
  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.131.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.131.0"
70
70
  },
71
71
  "devDependencies": {
72
72
  "@types/node": "^25.5.0",