cognium-dev 3.136.0 → 3.137.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 +596 -1
  2. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -22616,6 +22616,9 @@ class LanguageSourcesPass {
22616
22616
  for (const finding of findGoMongoNosqlInjectionFindings(code, graph.ir.meta.file)) {
22617
22617
  ctx.addFinding(finding);
22618
22618
  }
22619
+ for (const finding of findGoLdapInjectionFindings(code, graph.ir.meta.file)) {
22620
+ ctx.addFinding(finding);
22621
+ }
22619
22622
  }
22620
22623
  if (language === "python") {
22621
22624
  additionalSanitizers.push(...findPythonNetlocAllowlistGuardSanitizers(code));
@@ -22676,6 +22679,12 @@ class LanguageSourcesPass {
22676
22679
  for (const finding of findRustEvalCrateCodeInjectionFindings(code, graph.ir.meta.file)) {
22677
22680
  ctx.addFinding(finding);
22678
22681
  }
22682
+ for (const finding of findRustLdapInjectionFindings(code, graph.ir.meta.file)) {
22683
+ ctx.addFinding(finding);
22684
+ }
22685
+ for (const finding of findRustLogInjectionFindings(code, graph.ir.meta.file)) {
22686
+ ctx.addFinding(finding);
22687
+ }
22679
22688
  }
22680
22689
  if (language === "javascript" || language === "typescript" || language === "htmljs") {
22681
22690
  additionalSanitizers.push(...findJsSafeJsonParseSanitizers(code));
@@ -22703,6 +22712,9 @@ class LanguageSourcesPass {
22703
22712
  for (const finding of findJsUtilFormatFormatStringFindings(code, graph.ir.meta.file)) {
22704
22713
  ctx.addFinding(finding);
22705
22714
  }
22715
+ for (const finding of findJsLdapInjectionFindings(code, graph.ir.meta.file)) {
22716
+ ctx.addFinding(finding);
22717
+ }
22706
22718
  }
22707
22719
  if (language === "java") {
22708
22720
  additionalSanitizers.push(...findJavaSafeJsonParseSanitizers(code));
@@ -27291,6 +27303,589 @@ function findPythonHeaderCrlfInjectionFindings(code, file) {
27291
27303
  }
27292
27304
  return findings;
27293
27305
  }
27306
+ function findJsLdapInjectionFindings(code, file) {
27307
+ const findings = [];
27308
+ if (typeof code !== "string" || code.length === 0)
27309
+ return findings;
27310
+ if (!/\.\s*search\s*\(/.test(code))
27311
+ return findings;
27312
+ if (!/\breq\s*\.\s*(?:query|body|params|headers|cookies)\b/.test(code)) {
27313
+ return findings;
27314
+ }
27315
+ if (!/\b(?:ldapjs|ldapts|require\(['"]ldapjs['"]\)|from\s+['"]ldapts['"])\b/.test(code)) {
27316
+ return findings;
27317
+ }
27318
+ const lines = code.split(`
27319
+ `);
27320
+ const reqExtractRe = /\breq\s*\.\s*(?:query|body|params|headers|cookies)\b/;
27321
+ const taintedVars = new Set;
27322
+ for (let pass = 0;pass < 3; pass++) {
27323
+ const before = taintedVars.size;
27324
+ for (let i2 = 0;i2 < lines.length; i2++) {
27325
+ const t = lines[i2].trim();
27326
+ if (t.startsWith("//"))
27327
+ continue;
27328
+ const m = t.match(/^(?:const|let|var)\s+(\w+)(?:\s*:\s*[^=]+)?\s*=\s*(.+?);?$/);
27329
+ if (!m)
27330
+ continue;
27331
+ const lhs = m[1];
27332
+ const rhs = m[2];
27333
+ if (taintedVars.has(lhs))
27334
+ continue;
27335
+ if (reqExtractRe.test(rhs)) {
27336
+ taintedVars.add(lhs);
27337
+ continue;
27338
+ }
27339
+ for (const v of taintedVars) {
27340
+ if (new RegExp(`\\b${v}\\b`).test(rhs)) {
27341
+ taintedVars.add(lhs);
27342
+ break;
27343
+ }
27344
+ }
27345
+ }
27346
+ if (taintedVars.size === before)
27347
+ break;
27348
+ }
27349
+ if (taintedVars.size === 0)
27350
+ return findings;
27351
+ const filterPropRe = /\bfilter\s*:\s*([^,}\n]+)/;
27352
+ const filterShorthandRe = /(?:^|[\{,])\s*filter\s*[,}]/;
27353
+ const seen = new Set;
27354
+ for (let i2 = 0;i2 < lines.length; i2++) {
27355
+ const line = lines[i2];
27356
+ const fm = line.match(filterPropRe);
27357
+ let expr = null;
27358
+ if (fm) {
27359
+ expr = fm[1].trim();
27360
+ } else if (filterShorthandRe.test(line)) {
27361
+ expr = "filter";
27362
+ } else {
27363
+ continue;
27364
+ }
27365
+ let tainted = false;
27366
+ for (const v of taintedVars) {
27367
+ if (new RegExp(`\\b${v}\\b`).test(expr)) {
27368
+ tainted = true;
27369
+ break;
27370
+ }
27371
+ }
27372
+ if (!tainted)
27373
+ continue;
27374
+ if (seen.has(i2 + 1))
27375
+ continue;
27376
+ seen.add(i2 + 1);
27377
+ findings.push({
27378
+ id: `ldap_injection-${file}-${i2 + 1}-js-ldap-filter`,
27379
+ pass: "language-sources",
27380
+ category: "security",
27381
+ rule_id: "ldap_injection",
27382
+ cwe: "CWE-90",
27383
+ severity: "critical",
27384
+ level: "error",
27385
+ message: "LDAP injection: ldapjs/ldapts `client.search(..., { filter: " + "<tainted>, ... })` lets the attacker break out of the filter " + "expression (e.g. `*)(uid=*`) and enumerate the directory. " + "Escape the user input with a tight allowlist (`[A-Za-z0-9_-]+`) " + "or use a structured filter builder.",
27386
+ file,
27387
+ line: i2 + 1,
27388
+ snippet: line.trim()
27389
+ });
27390
+ }
27391
+ return findings;
27392
+ }
27393
+ function findGoLdapInjectionFindings(code, file) {
27394
+ const findings = [];
27395
+ if (typeof code !== "string" || code.length === 0)
27396
+ return findings;
27397
+ if (!/\bldap\s*\.\s*NewSearchRequest\s*\(/.test(code))
27398
+ return findings;
27399
+ if (!/\br\s*\.\s*(?:URL\s*\.\s*Query\s*\(\s*\)|Form|PostForm|FormValue|PostFormValue|Header)/.test(code)) {
27400
+ return findings;
27401
+ }
27402
+ const lines = code.split(`
27403
+ `);
27404
+ const reqExtractRe = /\br\s*\.\s*(?:URL\s*\.\s*Query\s*\(\s*\)\s*\.\s*Get|Form\s*\.\s*Get|PostForm\s*\.\s*Get|FormValue|PostFormValue|Header\s*\.\s*Get)\s*\(/;
27405
+ const taintedVars = new Set;
27406
+ for (let pass = 0;pass < 3; pass++) {
27407
+ const before = taintedVars.size;
27408
+ for (let i2 = 0;i2 < lines.length; i2++) {
27409
+ const t = lines[i2].trim();
27410
+ if (t.startsWith("//"))
27411
+ continue;
27412
+ const m = t.match(/^(\w+)\s*(?::=|=)\s*(.+?)$/);
27413
+ if (!m)
27414
+ continue;
27415
+ const lhs = m[1];
27416
+ const rhs = m[2];
27417
+ if (taintedVars.has(lhs))
27418
+ continue;
27419
+ if (reqExtractRe.test(rhs)) {
27420
+ taintedVars.add(lhs);
27421
+ continue;
27422
+ }
27423
+ for (const v of taintedVars) {
27424
+ if (new RegExp(`\\b${v}\\b`).test(rhs)) {
27425
+ taintedVars.add(lhs);
27426
+ break;
27427
+ }
27428
+ }
27429
+ }
27430
+ if (taintedVars.size === before)
27431
+ break;
27432
+ }
27433
+ if (taintedVars.size === 0)
27434
+ return findings;
27435
+ const seen = new Set;
27436
+ for (let i2 = 0;i2 < lines.length; i2++) {
27437
+ const openIdx = lines[i2].indexOf("NewSearchRequest(");
27438
+ if (openIdx === -1)
27439
+ continue;
27440
+ let depth = 0;
27441
+ let buf = "";
27442
+ let endLine = i2;
27443
+ let started = false;
27444
+ for (let j = i2;j < Math.min(i2 + 25, lines.length); j++) {
27445
+ for (const ch of lines[j]) {
27446
+ if (ch === "(") {
27447
+ depth++;
27448
+ started = true;
27449
+ } else if (ch === ")") {
27450
+ depth--;
27451
+ }
27452
+ buf += ch;
27453
+ if (started && depth === 0) {
27454
+ endLine = j;
27455
+ break;
27456
+ }
27457
+ }
27458
+ if (started && depth === 0)
27459
+ break;
27460
+ buf += `
27461
+ `;
27462
+ }
27463
+ const argsStart = buf.indexOf("(");
27464
+ if (argsStart === -1)
27465
+ continue;
27466
+ const args2 = buf.substring(argsStart + 1, buf.length - 1);
27467
+ const parts2 = [];
27468
+ {
27469
+ let d = 0;
27470
+ let b = "";
27471
+ let inStr = null;
27472
+ for (let k = 0;k < args2.length; k++) {
27473
+ const ch = args2[k];
27474
+ if (inStr) {
27475
+ if (ch === "\\") {
27476
+ b += ch + (args2[k + 1] ?? "");
27477
+ k++;
27478
+ continue;
27479
+ }
27480
+ if (ch === inStr)
27481
+ inStr = null;
27482
+ b += ch;
27483
+ continue;
27484
+ }
27485
+ if (ch === '"' || ch === "`") {
27486
+ inStr = ch;
27487
+ b += ch;
27488
+ continue;
27489
+ }
27490
+ if (ch === "(" || ch === "[" || ch === "{")
27491
+ d++;
27492
+ else if (ch === ")" || ch === "]" || ch === "}")
27493
+ d--;
27494
+ if (ch === "," && d === 0) {
27495
+ parts2.push(b);
27496
+ b = "";
27497
+ continue;
27498
+ }
27499
+ b += ch;
27500
+ }
27501
+ if (b.trim().length > 0)
27502
+ parts2.push(b);
27503
+ }
27504
+ if (parts2.length < 7)
27505
+ continue;
27506
+ const filterExpr = parts2[6].trim();
27507
+ let tainted = false;
27508
+ for (const v of taintedVars) {
27509
+ if (new RegExp(`\\b${v}\\b`).test(filterExpr)) {
27510
+ tainted = true;
27511
+ break;
27512
+ }
27513
+ }
27514
+ if (!tainted)
27515
+ continue;
27516
+ if (seen.has(i2 + 1))
27517
+ continue;
27518
+ seen.add(i2 + 1);
27519
+ findings.push({
27520
+ id: `ldap_injection-${file}-${i2 + 1}-go-newsearchrequest`,
27521
+ pass: "language-sources",
27522
+ category: "security",
27523
+ rule_id: "ldap_injection",
27524
+ cwe: "CWE-90",
27525
+ severity: "critical",
27526
+ level: "error",
27527
+ message: "LDAP injection: go-ldap `ldap.NewSearchRequest(..., <tainted " + "filter>, ...)` lets the attacker break out of the filter " + "expression and enumerate the directory. Escape the user input " + "with `ldap.EscapeFilter(...)` or use a structured filter " + "builder.",
27528
+ file,
27529
+ line: i2 + 1,
27530
+ snippet: (lines[i2] + (endLine > i2 ? " …" : "")).trim()
27531
+ });
27532
+ }
27533
+ return findings;
27534
+ }
27535
+ function findRustLdapInjectionFindings(code, file) {
27536
+ const findings = [];
27537
+ if (typeof code !== "string" || code.length === 0)
27538
+ return findings;
27539
+ if (!/\.\s*search\s*\(/.test(code))
27540
+ return findings;
27541
+ if (!/\b(?:ldap3|LdapConn|Ldap)\b/.test(code))
27542
+ return findings;
27543
+ const lines = code.split(`
27544
+ `);
27545
+ const extractorTypeRe = /:\s*(?:String|Bytes|bytes::Bytes|axum::body::Bytes|web::Query\b|web::Path\b|web::Form\b|web::Json\b|HttpRequest\b|actix_web::HttpRequest\b)/;
27546
+ const fns = [];
27547
+ let cur = null;
27548
+ for (let i2 = 0;i2 < lines.length; i2++) {
27549
+ const t = lines[i2];
27550
+ if (/^\s*(?:pub\s+)?(?:async\s+)?fn\s+\w+\s*\(/.test(t)) {
27551
+ if (cur) {
27552
+ cur.end = i2 - 1;
27553
+ fns.push(cur);
27554
+ }
27555
+ cur = { start: i2, end: lines.length - 1, tainted: new Set };
27556
+ let headerJoined = "";
27557
+ for (let j = i2;j < Math.min(i2 + 12, lines.length); j++) {
27558
+ headerJoined += lines[j];
27559
+ if (/\{\s*$/.test(lines[j]))
27560
+ break;
27561
+ }
27562
+ const open = headerJoined.indexOf("(");
27563
+ const close = headerJoined.lastIndexOf(")");
27564
+ if (open !== -1 && close > open) {
27565
+ const params = headerJoined.substring(open + 1, close);
27566
+ let depth = 0;
27567
+ let buf = "";
27568
+ const parts2 = [];
27569
+ for (const ch of params) {
27570
+ if (ch === "<" || ch === "(")
27571
+ depth++;
27572
+ else if (ch === ">" || ch === ")")
27573
+ depth--;
27574
+ if (ch === "," && depth === 0) {
27575
+ parts2.push(buf);
27576
+ buf = "";
27577
+ continue;
27578
+ }
27579
+ buf += ch;
27580
+ }
27581
+ if (buf.trim().length > 0)
27582
+ parts2.push(buf);
27583
+ for (const p of parts2) {
27584
+ const pm = p.match(/(?:mut\s+)?(\w+)\s*:/);
27585
+ if (!pm)
27586
+ continue;
27587
+ if (extractorTypeRe.test(p))
27588
+ cur.tainted.add(pm[1]);
27589
+ }
27590
+ }
27591
+ }
27592
+ }
27593
+ if (cur)
27594
+ fns.push(cur);
27595
+ for (const fn of fns) {
27596
+ for (let pass = 0;pass < 3; pass++) {
27597
+ const before = fn.tainted.size;
27598
+ for (let i2 = fn.start;i2 <= fn.end; i2++) {
27599
+ const t = lines[i2].trim();
27600
+ const m = t.match(/^let\s+(?:mut\s+)?(\w+)\s*(?::\s*[^=]+)?=\s*(.+?);?$/);
27601
+ if (!m)
27602
+ continue;
27603
+ const lhs = m[1];
27604
+ const rhs = m[2];
27605
+ if (fn.tainted.has(lhs))
27606
+ continue;
27607
+ for (const v of fn.tainted) {
27608
+ if (new RegExp(`\\b${v}\\b`).test(rhs)) {
27609
+ fn.tainted.add(lhs);
27610
+ break;
27611
+ }
27612
+ }
27613
+ }
27614
+ if (fn.tainted.size === before)
27615
+ break;
27616
+ }
27617
+ }
27618
+ const seen = new Set;
27619
+ for (const fn of fns) {
27620
+ if (fn.tainted.size === 0)
27621
+ continue;
27622
+ for (let i2 = fn.start;i2 <= fn.end; i2++) {
27623
+ const searchIdx = lines[i2].indexOf(".search(");
27624
+ if (searchIdx === -1)
27625
+ continue;
27626
+ let depth = 0;
27627
+ let buf = "";
27628
+ let started = false;
27629
+ for (let j = i2;j < Math.min(i2 + 15, lines.length); j++) {
27630
+ const startCol = j === i2 ? searchIdx : 0;
27631
+ for (let k = startCol;k < lines[j].length; k++) {
27632
+ const ch = lines[j][k];
27633
+ if (ch === "(") {
27634
+ depth++;
27635
+ started = true;
27636
+ } else if (ch === ")") {
27637
+ depth--;
27638
+ }
27639
+ buf += ch;
27640
+ if (started && depth === 0)
27641
+ break;
27642
+ }
27643
+ if (started && depth === 0)
27644
+ break;
27645
+ buf += `
27646
+ `;
27647
+ }
27648
+ const argsStart = buf.indexOf("(");
27649
+ if (argsStart === -1)
27650
+ continue;
27651
+ const args2 = buf.substring(argsStart + 1, buf.length - 1);
27652
+ const parts2 = [];
27653
+ {
27654
+ let d = 0;
27655
+ let b = "";
27656
+ let inStr = null;
27657
+ for (let k = 0;k < args2.length; k++) {
27658
+ const ch = args2[k];
27659
+ if (inStr) {
27660
+ if (ch === "\\") {
27661
+ b += ch + (args2[k + 1] ?? "");
27662
+ k++;
27663
+ continue;
27664
+ }
27665
+ if (ch === inStr)
27666
+ inStr = null;
27667
+ b += ch;
27668
+ continue;
27669
+ }
27670
+ if (ch === '"') {
27671
+ inStr = ch;
27672
+ b += ch;
27673
+ continue;
27674
+ }
27675
+ if (ch === "(" || ch === "[" || ch === "{")
27676
+ d++;
27677
+ else if (ch === ")" || ch === "]" || ch === "}")
27678
+ d--;
27679
+ if (ch === "," && d === 0) {
27680
+ parts2.push(b);
27681
+ b = "";
27682
+ continue;
27683
+ }
27684
+ b += ch;
27685
+ }
27686
+ if (b.trim().length > 0)
27687
+ parts2.push(b);
27688
+ }
27689
+ if (parts2.length < 3)
27690
+ continue;
27691
+ const filterExpr = parts2[2].trim();
27692
+ let tainted = false;
27693
+ for (const v of fn.tainted) {
27694
+ if (new RegExp(`\\b${v}\\b`).test(filterExpr)) {
27695
+ tainted = true;
27696
+ break;
27697
+ }
27698
+ }
27699
+ if (!tainted)
27700
+ continue;
27701
+ if (seen.has(i2 + 1))
27702
+ continue;
27703
+ seen.add(i2 + 1);
27704
+ findings.push({
27705
+ id: `ldap_injection-${file}-${i2 + 1}-rust-ldap3-search`,
27706
+ pass: "language-sources",
27707
+ category: "security",
27708
+ rule_id: "ldap_injection",
27709
+ cwe: "CWE-90",
27710
+ severity: "critical",
27711
+ level: "error",
27712
+ message: "LDAP injection: ldap3 `LdapConn::search(..., &<tainted " + "filter>, ...)` lets the attacker break out of the filter " + "expression and enumerate the directory. Escape the user input " + "or use a structured filter builder.",
27713
+ file,
27714
+ line: i2 + 1,
27715
+ snippet: lines[i2].trim()
27716
+ });
27717
+ }
27718
+ }
27719
+ return findings;
27720
+ }
27721
+ function findRustLogInjectionFindings(code, file) {
27722
+ const findings = [];
27723
+ if (typeof code !== "string" || code.length === 0)
27724
+ return findings;
27725
+ if (!/\b(?:info|warn|error|debug|trace)\s*!\s*\(/.test(code))
27726
+ return findings;
27727
+ if (!/\b(?:log|tracing)\b/.test(code))
27728
+ return findings;
27729
+ const lines = code.split(`
27730
+ `);
27731
+ const extractorTypeRe = /:\s*(?:String|Bytes|bytes::Bytes|axum::body::Bytes|web::Query\b|web::Path\b|web::Form\b|web::Json\b|HttpRequest\b|actix_web::HttpRequest\b)/;
27732
+ const fns = [];
27733
+ let cur = null;
27734
+ for (let i2 = 0;i2 < lines.length; i2++) {
27735
+ const t = lines[i2];
27736
+ if (/^\s*(?:pub\s+)?(?:async\s+)?fn\s+\w+\s*\(/.test(t)) {
27737
+ if (cur) {
27738
+ cur.end = i2 - 1;
27739
+ fns.push(cur);
27740
+ }
27741
+ cur = { start: i2, end: lines.length - 1, tainted: new Set };
27742
+ let headerJoined = "";
27743
+ for (let j = i2;j < Math.min(i2 + 12, lines.length); j++) {
27744
+ headerJoined += lines[j];
27745
+ if (/\{\s*$/.test(lines[j]))
27746
+ break;
27747
+ }
27748
+ const open = headerJoined.indexOf("(");
27749
+ const close = headerJoined.lastIndexOf(")");
27750
+ if (open !== -1 && close > open) {
27751
+ const params = headerJoined.substring(open + 1, close);
27752
+ let depth = 0;
27753
+ let buf = "";
27754
+ const parts2 = [];
27755
+ for (const ch of params) {
27756
+ if (ch === "<" || ch === "(")
27757
+ depth++;
27758
+ else if (ch === ">" || ch === ")")
27759
+ depth--;
27760
+ if (ch === "," && depth === 0) {
27761
+ parts2.push(buf);
27762
+ buf = "";
27763
+ continue;
27764
+ }
27765
+ buf += ch;
27766
+ }
27767
+ if (buf.trim().length > 0)
27768
+ parts2.push(buf);
27769
+ for (const p of parts2) {
27770
+ const pm = p.match(/(?:mut\s+)?(\w+)\s*:/);
27771
+ if (!pm)
27772
+ continue;
27773
+ if (extractorTypeRe.test(p))
27774
+ cur.tainted.add(pm[1]);
27775
+ }
27776
+ }
27777
+ }
27778
+ }
27779
+ if (cur)
27780
+ fns.push(cur);
27781
+ for (const fn of fns) {
27782
+ for (let pass = 0;pass < 3; pass++) {
27783
+ const before = fn.tainted.size;
27784
+ for (let i2 = fn.start;i2 <= fn.end; i2++) {
27785
+ const t = lines[i2].trim();
27786
+ const m = t.match(/^let\s+(?:mut\s+)?(\w+)\s*(?::\s*[^=]+)?=\s*(.+?);?$/);
27787
+ if (!m)
27788
+ continue;
27789
+ const lhs = m[1];
27790
+ const rhs = m[2];
27791
+ if (fn.tainted.has(lhs))
27792
+ continue;
27793
+ for (const v of fn.tainted) {
27794
+ if (new RegExp(`\\b${v}\\b`).test(rhs)) {
27795
+ fn.tainted.add(lhs);
27796
+ break;
27797
+ }
27798
+ }
27799
+ }
27800
+ if (fn.tainted.size === before)
27801
+ break;
27802
+ }
27803
+ }
27804
+ const macroRe = /\b(info|warn|error|debug|trace)\s*!\s*\(\s*([^;]+?)\)\s*;?\s*$/;
27805
+ const seen = new Set;
27806
+ for (const fn of fns) {
27807
+ if (fn.tainted.size === 0)
27808
+ continue;
27809
+ for (let i2 = fn.start;i2 <= fn.end; i2++) {
27810
+ const line = lines[i2];
27811
+ const m = line.match(macroRe);
27812
+ if (!m)
27813
+ continue;
27814
+ const macroName = m[1];
27815
+ const argSpan = m[2];
27816
+ const parts2 = [];
27817
+ {
27818
+ let d = 0;
27819
+ let b = "";
27820
+ let inStr = null;
27821
+ for (let k = 0;k < argSpan.length; k++) {
27822
+ const ch = argSpan[k];
27823
+ if (inStr) {
27824
+ if (ch === "\\") {
27825
+ b += ch + (argSpan[k + 1] ?? "");
27826
+ k++;
27827
+ continue;
27828
+ }
27829
+ if (ch === inStr)
27830
+ inStr = null;
27831
+ b += ch;
27832
+ continue;
27833
+ }
27834
+ if (ch === '"') {
27835
+ inStr = ch;
27836
+ b += ch;
27837
+ continue;
27838
+ }
27839
+ if (ch === "(" || ch === "[" || ch === "{")
27840
+ d++;
27841
+ else if (ch === ")" || ch === "]" || ch === "}")
27842
+ d--;
27843
+ if (ch === "," && d === 0) {
27844
+ parts2.push(b);
27845
+ b = "";
27846
+ continue;
27847
+ }
27848
+ b += ch;
27849
+ }
27850
+ if (b.trim().length > 0)
27851
+ parts2.push(b);
27852
+ }
27853
+ if (parts2.length < 2)
27854
+ continue;
27855
+ let tainted = false;
27856
+ for (let p = 1;p < parts2.length; p++) {
27857
+ for (const v of fn.tainted) {
27858
+ if (new RegExp(`\\b${v}\\b`).test(parts2[p])) {
27859
+ tainted = true;
27860
+ break;
27861
+ }
27862
+ }
27863
+ if (tainted)
27864
+ break;
27865
+ }
27866
+ if (!tainted)
27867
+ continue;
27868
+ const key = `${i2 + 1}:${macroName}`;
27869
+ if (seen.has(key))
27870
+ continue;
27871
+ seen.add(key);
27872
+ findings.push({
27873
+ id: `log_injection-${file}-${i2 + 1}-rust-${macroName}`,
27874
+ pass: "language-sources",
27875
+ category: "security",
27876
+ rule_id: "log_injection",
27877
+ cwe: "CWE-117",
27878
+ severity: "medium",
27879
+ level: "warning",
27880
+ message: `Log injection: Rust \`${macroName}!(...)\` interpolates a ` + "tainted value into the log line. Unsanitized CRLF lets an " + "attacker forge log entries or split records. Strip control " + "characters or use a structured logging API.",
27881
+ file,
27882
+ line: i2 + 1,
27883
+ snippet: line.trim()
27884
+ });
27885
+ }
27886
+ }
27887
+ return findings;
27888
+ }
27294
27889
 
27295
27890
  // ../circle-ir/dist/analysis/passes/sink-filter-pass.js
27296
27891
  var JS_XSS_SANITIZERS = [
@@ -40418,7 +41013,7 @@ var colors = {
40418
41013
  };
40419
41014
 
40420
41015
  // src/version.ts
40421
- var version = "3.136.0";
41016
+ var version = "3.137.0";
40422
41017
 
40423
41018
  // src/formatters.ts
40424
41019
  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.136.0",
3
+ "version": "3.137.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.136.0"
69
+ "circle-ir": "^3.137.0"
70
70
  },
71
71
  "devDependencies": {
72
72
  "@types/node": "^25.5.0",