circle-ir 3.133.0 → 3.134.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.
@@ -23678,6 +23678,12 @@ var LanguageSourcesPass = class {
23678
23678
  )) {
23679
23679
  ctx.addFinding(finding);
23680
23680
  }
23681
+ for (const finding of findGoMongoNosqlInjectionFindings(
23682
+ code,
23683
+ graph.ir.meta.file
23684
+ )) {
23685
+ ctx.addFinding(finding);
23686
+ }
23681
23687
  }
23682
23688
  if (language === "python") {
23683
23689
  additionalSanitizers.push(...findPythonNetlocAllowlistGuardSanitizers(code));
@@ -23708,6 +23714,12 @@ var LanguageSourcesPass = class {
23708
23714
  )) {
23709
23715
  ctx.addFinding(finding);
23710
23716
  }
23717
+ for (const finding of findPythonMongoengineWhereNosqlInjectionFindings(
23718
+ code,
23719
+ graph.ir.meta.file
23720
+ )) {
23721
+ ctx.addFinding(finding);
23722
+ }
23711
23723
  }
23712
23724
  if (language === "rust") {
23713
23725
  additionalSanitizers.push(...findRustSetAllowlistGuardSanitizers(code));
@@ -23785,6 +23797,12 @@ var LanguageSourcesPass = class {
23785
23797
  for (const finding of findJavaResponseWriterXssFindings(code, graph.ir.meta.file)) {
23786
23798
  ctx.addFinding(finding);
23787
23799
  }
23800
+ for (const finding of findJavaMongoNosqlInjectionFindings(
23801
+ code,
23802
+ graph.ir.meta.file
23803
+ )) {
23804
+ ctx.addFinding(finding);
23805
+ }
23788
23806
  }
23789
23807
  if (language === "python" || language === "javascript" || language === "typescript" || language === "go") {
23790
23808
  const exfilFindings = findExternalSecretExfiltrationFindings(
@@ -27358,6 +27376,236 @@ function findRustEvalCrateCodeInjectionFindings(code, file) {
27358
27376
  }
27359
27377
  return findings;
27360
27378
  }
27379
+ function findGoMongoNosqlInjectionFindings(code, file) {
27380
+ const findings = [];
27381
+ if (typeof code !== "string" || code.length === 0) return findings;
27382
+ if (!/(\bbson\s*\.\s*[MDAE]\b|mongo-driver|\*\s*mongo\.Collection)/.test(code)) {
27383
+ return findings;
27384
+ }
27385
+ const lines = code.split("\n");
27386
+ const reqExtractRe = /\b\w+\s*\.\s*(?:FormValue|PostFormValue|URL\s*\.\s*Query\s*\(\s*\)\s*\.\s*Get|Header\s*\.\s*Get|Cookie)\s*\(/;
27387
+ const httpReqParamRe = /\*\s*http\.Request\b/;
27388
+ const opsAlt = "(?:FindOne|Find|InsertOne|InsertMany|UpdateOne|UpdateMany|DeleteOne|DeleteMany|FindOneAndUpdate|FindOneAndDelete|FindOneAndReplace|Aggregate)";
27389
+ const callHeadRe = new RegExp(`\\.\\s*(${opsAlt})\\s*\\(`);
27390
+ function extractBalanced(line, openIdx) {
27391
+ let depth = 0;
27392
+ for (let k = openIdx; k < line.length; k++) {
27393
+ const ch = line[k];
27394
+ if (ch === "(") depth++;
27395
+ else if (ch === ")") {
27396
+ depth--;
27397
+ if (depth === 0) return line.substring(openIdx + 1, k);
27398
+ }
27399
+ }
27400
+ return null;
27401
+ }
27402
+ const funcs = [];
27403
+ let cur = null;
27404
+ for (let i2 = 0; i2 < lines.length; i2++) {
27405
+ const t = lines[i2].trim();
27406
+ if (/^func\b/.test(t)) {
27407
+ if (cur) {
27408
+ cur.end = i2 - 1;
27409
+ funcs.push(cur);
27410
+ }
27411
+ cur = { start: i2, end: lines.length - 1 };
27412
+ }
27413
+ }
27414
+ if (cur) funcs.push(cur);
27415
+ for (const fn of funcs) {
27416
+ const header = lines[fn.start];
27417
+ if (!httpReqParamRe.test(header)) continue;
27418
+ const taintedVars = /* @__PURE__ */ new Set();
27419
+ for (let pass = 0; pass < 3; pass++) {
27420
+ const before = taintedVars.size;
27421
+ for (let i2 = fn.start; i2 <= fn.end; i2++) {
27422
+ const trimmed = lines[i2].trim();
27423
+ const assignMatch = trimmed.match(
27424
+ /^(\w+)\s*(?::=|=)\s*(.+?)(?:\s*\/\/.*)?$/
27425
+ );
27426
+ if (!assignMatch) continue;
27427
+ const lhs = assignMatch[1];
27428
+ const rhs = assignMatch[2];
27429
+ if (taintedVars.has(lhs)) continue;
27430
+ if (reqExtractRe.test(rhs)) {
27431
+ taintedVars.add(lhs);
27432
+ continue;
27433
+ }
27434
+ for (const v of taintedVars) {
27435
+ if (new RegExp(`\\b${v}\\b`).test(rhs)) {
27436
+ taintedVars.add(lhs);
27437
+ break;
27438
+ }
27439
+ }
27440
+ }
27441
+ if (taintedVars.size === before) break;
27442
+ }
27443
+ if (taintedVars.size === 0) continue;
27444
+ for (let i2 = fn.start; i2 <= fn.end; i2++) {
27445
+ const line = lines[i2];
27446
+ const m = callHeadRe.exec(line);
27447
+ if (!m) continue;
27448
+ const op = m[1];
27449
+ const openIdx = m.index + m[0].length - 1;
27450
+ const args2 = extractBalanced(line, openIdx);
27451
+ if (args2 === null || args2.length === 0) continue;
27452
+ let tainted = reqExtractRe.test(args2);
27453
+ if (!tainted) {
27454
+ for (const v of taintedVars) {
27455
+ if (new RegExp(`\\b${v}\\b`).test(args2)) {
27456
+ tainted = true;
27457
+ break;
27458
+ }
27459
+ }
27460
+ }
27461
+ if (!tainted) continue;
27462
+ findings.push({
27463
+ id: `nosql_injection-${file}-${i2 + 1}-go-mongo-${op.toLowerCase()}`,
27464
+ pass: "language-sources",
27465
+ category: "security",
27466
+ rule_id: "nosql_injection",
27467
+ cwe: "CWE-943",
27468
+ severity: "critical",
27469
+ level: "error",
27470
+ message: `NoSQL injection: Go MongoDB driver \`${op}(...)\` called with a filter derived from *http.Request input. Untrusted values inside a \`bson.M\` / \`bson.D\` filter can be operator objects (e.g. \`{"$ne": null}\`) and bypass authentication/intent. Validate or coerce the value to a primitive before building the filter.`,
27471
+ file,
27472
+ line: i2 + 1,
27473
+ snippet: line.trim()
27474
+ });
27475
+ }
27476
+ }
27477
+ return findings;
27478
+ }
27479
+ function findJavaMongoNosqlInjectionFindings(code, file) {
27480
+ const findings = [];
27481
+ if (typeof code !== "string" || code.length === 0) return findings;
27482
+ if (!/(MongoCollection\b|com\.mongodb\b|\bFilters\b|\bnew\s+Document\s*\()/.test(code)) {
27483
+ return findings;
27484
+ }
27485
+ const lines = code.split("\n");
27486
+ const reqExtractRe = /\b\w+\s*\.\s*(?:getParameter|getParameterValues|getHeader|getHeaders|getCookies|getReader|getQueryString|getRequestURI|getInputStream|getPart|getParts)\s*\(/;
27487
+ const opsAlt = "(?:find|findOne|findOneAndUpdate|findOneAndDelete|findOneAndReplace|insertOne|insertMany|updateOne|updateMany|deleteOne|deleteMany|replaceOne|aggregate|countDocuments|distinct)";
27488
+ const callRe = new RegExp(`\\.\\s*(${opsAlt})\\s*\\(([\\s\\S]*?)\\)`);
27489
+ const taintedVars = /* @__PURE__ */ new Set();
27490
+ for (let pass = 0; pass < 3; pass++) {
27491
+ const before = taintedVars.size;
27492
+ for (let i2 = 0; i2 < lines.length; i2++) {
27493
+ const t = lines[i2].trim();
27494
+ const a = t.match(/^(?:final\s+)?(?:[\w<>?,\[\]]+\s+)?(\w+)\s*=\s*(.+?);?$/);
27495
+ if (!a) continue;
27496
+ const lhs = a[1];
27497
+ const rhs = a[2];
27498
+ if (taintedVars.has(lhs)) continue;
27499
+ if (reqExtractRe.test(rhs)) {
27500
+ taintedVars.add(lhs);
27501
+ continue;
27502
+ }
27503
+ for (const v of taintedVars) {
27504
+ if (new RegExp(`\\b${v}\\b`).test(rhs)) {
27505
+ taintedVars.add(lhs);
27506
+ break;
27507
+ }
27508
+ }
27509
+ }
27510
+ if (taintedVars.size === before) break;
27511
+ }
27512
+ if (taintedVars.size === 0) return findings;
27513
+ for (let i2 = 0; i2 < lines.length; i2++) {
27514
+ const line = lines[i2];
27515
+ const m = line.match(callRe);
27516
+ if (!m) continue;
27517
+ const op = m[1];
27518
+ const args2 = m[2];
27519
+ if (args2.trim().length === 0) continue;
27520
+ let tainted = reqExtractRe.test(args2);
27521
+ if (!tainted) {
27522
+ for (const v of taintedVars) {
27523
+ if (new RegExp(`\\b${v}\\b`).test(args2)) {
27524
+ tainted = true;
27525
+ break;
27526
+ }
27527
+ }
27528
+ }
27529
+ if (!tainted) continue;
27530
+ findings.push({
27531
+ id: `nosql_injection-${file}-${i2 + 1}-java-mongo-${op.toLowerCase()}`,
27532
+ pass: "language-sources",
27533
+ category: "security",
27534
+ rule_id: "nosql_injection",
27535
+ cwe: "CWE-943",
27536
+ severity: "critical",
27537
+ level: "error",
27538
+ message: `NoSQL injection: Java Mongo driver \`${op}(...)\` called with a filter derived from servlet request input. Untrusted values can reach BSON operator positions and bypass intent. Validate the input type before constructing the filter (e.g. require String).`,
27539
+ file,
27540
+ line: i2 + 1,
27541
+ snippet: line.trim()
27542
+ });
27543
+ }
27544
+ return findings;
27545
+ }
27546
+ function findPythonMongoengineWhereNosqlInjectionFindings(code, file) {
27547
+ const findings = [];
27548
+ if (typeof code !== "string" || code.length === 0) return findings;
27549
+ if (!/['"]\$where['"]/.test(code)) return findings;
27550
+ const lines = code.split("\n");
27551
+ const reqExtractRe = /\b(?:request\.(?:args|form|values|json|files|cookies|headers|data)\b|flask\.request\b)/;
27552
+ const taintedVars = /* @__PURE__ */ new Set();
27553
+ for (let pass = 0; pass < 3; pass++) {
27554
+ const before = taintedVars.size;
27555
+ for (let i2 = 0; i2 < lines.length; i2++) {
27556
+ const t = lines[i2].trim();
27557
+ if (t.startsWith("#")) continue;
27558
+ const a = t.match(/^(\w+)\s*=\s*(.+?)$/);
27559
+ if (!a) continue;
27560
+ const lhs = a[1];
27561
+ const rhs = a[2];
27562
+ if (taintedVars.has(lhs)) continue;
27563
+ if (reqExtractRe.test(rhs)) {
27564
+ taintedVars.add(lhs);
27565
+ continue;
27566
+ }
27567
+ for (const v of taintedVars) {
27568
+ if (new RegExp(`\\b${v}\\b`).test(rhs)) {
27569
+ taintedVars.add(lhs);
27570
+ break;
27571
+ }
27572
+ }
27573
+ }
27574
+ if (taintedVars.size === before) break;
27575
+ }
27576
+ const whereRe = /['"]\$where['"]\s*:\s*([^,}\n]+)/;
27577
+ for (let i2 = 0; i2 < lines.length; i2++) {
27578
+ const line = lines[i2];
27579
+ const m = line.match(whereRe);
27580
+ if (!m) continue;
27581
+ const valueExpr = m[1].trim();
27582
+ if (/^"[^"]*"$/.test(valueExpr) || /^'[^']*'$/.test(valueExpr)) continue;
27583
+ let tainted = reqExtractRe.test(valueExpr);
27584
+ if (!tainted) {
27585
+ for (const v of taintedVars) {
27586
+ if (new RegExp(`\\b${v}\\b`).test(valueExpr)) {
27587
+ tainted = true;
27588
+ break;
27589
+ }
27590
+ }
27591
+ }
27592
+ if (!tainted) continue;
27593
+ findings.push({
27594
+ id: `nosql_injection-${file}-${i2 + 1}-py-mongoengine-where`,
27595
+ pass: "language-sources",
27596
+ category: "security",
27597
+ rule_id: "nosql_injection",
27598
+ cwe: "CWE-943",
27599
+ severity: "critical",
27600
+ level: "error",
27601
+ message: 'NoSQL injection: mongoengine `__raw__={"$where": ...}` payload derived from HTTP request input. The `$where` operator evaluates JavaScript on the server; tainted string concatenation lets an attacker inject arbitrary JS. Replace `$where` with field-based operators or validate the input.',
27602
+ file,
27603
+ line: i2 + 1,
27604
+ snippet: line.trim()
27605
+ });
27606
+ }
27607
+ return findings;
27608
+ }
27361
27609
 
27362
27610
  // src/analysis/passes/sink-filter-pass.ts
27363
27611
  var JS_XSS_SANITIZERS = [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "circle-ir",
3
- "version": "3.133.0",
3
+ "version": "3.134.0",
4
4
  "description": "High-performance Static Application Security Testing (SAST) library for detecting security vulnerabilities through taint analysis",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",