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.
- package/dist/analysis/passes/language-sources-pass.d.ts +28 -0
- package/dist/analysis/passes/language-sources-pass.d.ts.map +1 -1
- package/dist/analysis/passes/language-sources-pass.js +316 -0
- package/dist/analysis/passes/language-sources-pass.js.map +1 -1
- package/dist/browser/circle-ir.js +248 -0
- package/package.json +1 -1
|
@@ -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.
|
|
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",
|