circle-ir 4.14.1 → 4.15.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/config-loader.d.ts.map +1 -1
- package/dist/analysis/config-loader.js +9 -2
- package/dist/analysis/config-loader.js.map +1 -1
- package/dist/analysis/const-table-lookup.d.ts +29 -0
- package/dist/analysis/const-table-lookup.d.ts.map +1 -0
- package/dist/analysis/const-table-lookup.js +145 -0
- package/dist/analysis/const-table-lookup.js.map +1 -0
- package/dist/analysis/findings.d.ts.map +1 -1
- package/dist/analysis/findings.js +15 -4
- package/dist/analysis/findings.js.map +1 -1
- package/dist/analysis/go-strings-replacer.d.ts +21 -0
- package/dist/analysis/go-strings-replacer.d.ts.map +1 -0
- package/dist/analysis/go-strings-replacer.js +145 -0
- package/dist/analysis/go-strings-replacer.js.map +1 -0
- package/dist/analysis/interprocedural.d.ts.map +1 -1
- package/dist/analysis/interprocedural.js +53 -0
- package/dist/analysis/interprocedural.js.map +1 -1
- package/dist/analysis/java-canonical-prefix.d.ts +42 -0
- package/dist/analysis/java-canonical-prefix.d.ts.map +1 -0
- package/dist/analysis/java-canonical-prefix.js +145 -0
- package/dist/analysis/java-canonical-prefix.js.map +1 -0
- package/dist/analysis/java-crlf-strip.d.ts +30 -0
- package/dist/analysis/java-crlf-strip.d.ts.map +1 -0
- package/dist/analysis/java-crlf-strip.js +195 -0
- package/dist/analysis/java-crlf-strip.js.map +1 -0
- package/dist/analysis/passes/cleartext-credential-transport-pass.d.ts +2 -0
- package/dist/analysis/passes/cleartext-credential-transport-pass.d.ts.map +1 -1
- package/dist/analysis/passes/cleartext-credential-transport-pass.js +64 -12
- package/dist/analysis/passes/cleartext-credential-transport-pass.js.map +1 -1
- package/dist/analysis/passes/cross-file-pass.d.ts.map +1 -1
- package/dist/analysis/passes/cross-file-pass.js +43 -0
- package/dist/analysis/passes/cross-file-pass.js.map +1 -1
- package/dist/analysis/passes/csharp-sanitizer-credit.d.ts.map +1 -1
- package/dist/analysis/passes/csharp-sanitizer-credit.js +18 -0
- package/dist/analysis/passes/csharp-sanitizer-credit.js.map +1 -1
- package/dist/analysis/passes/insecure-cookie-pass.d.ts +11 -4
- package/dist/analysis/passes/insecure-cookie-pass.d.ts.map +1 -1
- package/dist/analysis/passes/insecure-cookie-pass.js +30 -15
- package/dist/analysis/passes/insecure-cookie-pass.js.map +1 -1
- package/dist/analysis/passes/interprocedural-pass.d.ts +28 -0
- package/dist/analysis/passes/interprocedural-pass.d.ts.map +1 -1
- package/dist/analysis/passes/interprocedural-pass.js +31 -3
- package/dist/analysis/passes/interprocedural-pass.js.map +1 -1
- package/dist/analysis/passes/language-sources-pass.d.ts +41 -2
- package/dist/analysis/passes/language-sources-pass.d.ts.map +1 -1
- package/dist/analysis/passes/language-sources-pass.js +894 -10
- package/dist/analysis/passes/language-sources-pass.js.map +1 -1
- package/dist/analysis/passes/param-source-flow-gate-pass.d.ts +8 -0
- package/dist/analysis/passes/param-source-flow-gate-pass.d.ts.map +1 -1
- package/dist/analysis/passes/param-source-flow-gate-pass.js +162 -6
- package/dist/analysis/passes/param-source-flow-gate-pass.js.map +1 -1
- package/dist/analysis/passes/scan-secrets-pass.d.ts.map +1 -1
- package/dist/analysis/passes/scan-secrets-pass.js +24 -3
- package/dist/analysis/passes/scan-secrets-pass.js.map +1 -1
- package/dist/analysis/passes/sink-filter-pass.d.ts +1 -1
- package/dist/analysis/passes/sink-filter-pass.d.ts.map +1 -1
- package/dist/analysis/passes/sink-filter-pass.js +255 -25
- package/dist/analysis/passes/sink-filter-pass.js.map +1 -1
- package/dist/analysis/passes/taint-propagation-pass.d.ts.map +1 -1
- package/dist/analysis/passes/taint-propagation-pass.js +268 -5
- package/dist/analysis/passes/taint-propagation-pass.js.map +1 -1
- package/dist/analysis/scalar-derivation.d.ts +25 -0
- package/dist/analysis/scalar-derivation.d.ts.map +1 -0
- package/dist/analysis/scalar-derivation.js +196 -0
- package/dist/analysis/scalar-derivation.js.map +1 -0
- package/dist/analysis/taint-matcher.d.ts.map +1 -1
- package/dist/analysis/taint-matcher.js +172 -8
- package/dist/analysis/taint-matcher.js.map +1 -1
- package/dist/analysis/taint-propagation.d.ts +9 -0
- package/dist/analysis/taint-propagation.d.ts.map +1 -1
- package/dist/analysis/taint-propagation.js +260 -11
- package/dist/analysis/taint-propagation.js.map +1 -1
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +8 -1
- package/dist/analyzer.js.map +1 -1
- package/dist/browser/circle-ir.js +3403 -1225
- package/dist/core/circle-ir-core.cjs +598 -26
- package/dist/core/circle-ir-core.js +598 -26
- package/dist/core/extractors/calls.js +19 -0
- package/dist/core/extractors/calls.js.map +1 -1
- package/dist/core/extractors/dfg.js +240 -6
- package/dist/core/extractors/dfg.js.map +1 -1
- package/dist/graph/code-graph.d.ts +9 -0
- package/dist/graph/code-graph.d.ts.map +1 -1
- package/dist/graph/code-graph.js +11 -0
- package/dist/graph/code-graph.js.map +1 -1
- package/dist/languages/plugins/go.d.ts.map +1 -1
- package/dist/languages/plugins/go.js +19 -0
- package/dist/languages/plugins/go.js.map +1 -1
- package/dist/resolution/cross-file.d.ts +7 -0
- package/dist/resolution/cross-file.d.ts.map +1 -1
- package/dist/resolution/cross-file.js +10 -1
- package/dist/resolution/cross-file.js.map +1 -1
- package/dist/wasm/web-tree-sitter.wasm +0 -0
- package/package.json +3 -3
|
@@ -8014,6 +8014,17 @@ function extractGoCallInfo(node) {
|
|
|
8014
8014
|
if (operand.type === "identifier") {
|
|
8015
8015
|
const resolved = resolveGoLocalReceiverType(opText, node);
|
|
8016
8016
|
receiver = resolved !== null ? resolved : opText;
|
|
8017
|
+
} else if (operand.type === "selector_expression" && methodName === "Get") {
|
|
8018
|
+
const fieldNode = operand.childForFieldName("field");
|
|
8019
|
+
const inner = operand.childForFieldName("operand");
|
|
8020
|
+
const fieldName = fieldNode ? getNodeText(fieldNode) : "";
|
|
8021
|
+
const innerName = inner && inner.type === "identifier" ? getNodeText(inner) : null;
|
|
8022
|
+
const innerType = innerName !== null ? resolveGoLocalReceiverType(innerName, node) : null;
|
|
8023
|
+
if (innerType === "Request" && (fieldName === "PostForm" || fieldName === "Form")) {
|
|
8024
|
+
receiver = fieldName === "PostForm" ? "RequestPostForm" : "RequestForm";
|
|
8025
|
+
} else {
|
|
8026
|
+
receiver = opText;
|
|
8027
|
+
}
|
|
8017
8028
|
} else {
|
|
8018
8029
|
receiver = opText;
|
|
8019
8030
|
}
|
|
@@ -9830,6 +9841,7 @@ function processStatement2(stmt, startDefId, startUseId, scopeStack, isJavaScrip
|
|
|
9830
9841
|
};
|
|
9831
9842
|
defs.push(def);
|
|
9832
9843
|
currentScope(scopeStack).set(name2, def.id);
|
|
9844
|
+
markDeclared(currentScope(scopeStack), name2);
|
|
9833
9845
|
} else if (isJavaScript) {
|
|
9834
9846
|
const destructDefs = extractDestructuringDefs(nameNode, defId);
|
|
9835
9847
|
for (const def of destructDefs) {
|
|
@@ -9876,21 +9888,31 @@ function processStatement2(stmt, startDefId, startUseId, scopeStack, isJavaScrip
|
|
|
9876
9888
|
useId = condUses.nextId;
|
|
9877
9889
|
}
|
|
9878
9890
|
const consequence = stmt.childForFieldName("consequence");
|
|
9891
|
+
const alternative = stmt.childForFieldName("alternative");
|
|
9892
|
+
const topAlts = alternateDefs.get(currentScope(scopeStack));
|
|
9893
|
+
const before = /* @__PURE__ */ new Map();
|
|
9894
|
+
if (topAlts) for (const [name2, ids] of topAlts) before.set(name2, [...ids]);
|
|
9895
|
+
let armA = null;
|
|
9896
|
+
let armB = null;
|
|
9879
9897
|
if (consequence) {
|
|
9880
|
-
const result =
|
|
9898
|
+
const result = processArm(consequence, defId, useId, scopeStack, isJavaScript);
|
|
9899
|
+
armA = result.popped;
|
|
9881
9900
|
defs.push(...result.defs);
|
|
9882
9901
|
uses.push(...result.uses);
|
|
9883
9902
|
defId = result.nextDefId;
|
|
9884
9903
|
useId = result.nextUseId;
|
|
9885
9904
|
}
|
|
9886
|
-
const alternative = stmt.childForFieldName("alternative");
|
|
9887
9905
|
if (alternative) {
|
|
9888
|
-
const result =
|
|
9906
|
+
const result = processArm(alternative, defId, useId, scopeStack, isJavaScript);
|
|
9907
|
+
armB = result.popped;
|
|
9889
9908
|
defs.push(...result.defs);
|
|
9890
9909
|
uses.push(...result.uses);
|
|
9891
9910
|
defId = result.nextDefId;
|
|
9892
9911
|
useId = result.nextUseId;
|
|
9893
9912
|
}
|
|
9913
|
+
if (armA && armB) {
|
|
9914
|
+
mergeIfElseDefs(armA, armB, before, scopeStack);
|
|
9915
|
+
}
|
|
9894
9916
|
break;
|
|
9895
9917
|
}
|
|
9896
9918
|
case "for_statement":
|
|
@@ -9961,7 +9983,7 @@ function processStatement2(stmt, startDefId, startUseId, scopeStack, isJavaScrip
|
|
|
9961
9983
|
}
|
|
9962
9984
|
const body2 = stmt.childForFieldName("body");
|
|
9963
9985
|
if (body2) {
|
|
9964
|
-
const result =
|
|
9986
|
+
const result = processArm(body2, defId, useId, scopeStack, isJavaScript);
|
|
9965
9987
|
defs.push(...result.defs);
|
|
9966
9988
|
uses.push(...result.uses);
|
|
9967
9989
|
defId = result.nextDefId;
|
|
@@ -9989,7 +10011,8 @@ function processStatement2(stmt, startDefId, startUseId, scopeStack, isJavaScrip
|
|
|
9989
10011
|
defId = result.nextDefId;
|
|
9990
10012
|
useId = result.nextUseId;
|
|
9991
10013
|
}
|
|
9992
|
-
scopeStack.pop();
|
|
10014
|
+
const poppedSwitch = scopeStack.pop();
|
|
10015
|
+
if (!isJavaScript) carryBranchDefsOut(poppedSwitch, scopeStack, false, true);
|
|
9993
10016
|
}
|
|
9994
10017
|
break;
|
|
9995
10018
|
}
|
|
@@ -10001,7 +10024,10 @@ function processStatement2(stmt, startDefId, startUseId, scopeStack, isJavaScrip
|
|
|
10001
10024
|
uses.push(...result.uses);
|
|
10002
10025
|
defId = result.nextDefId;
|
|
10003
10026
|
useId = result.nextUseId;
|
|
10004
|
-
scopeStack.pop();
|
|
10027
|
+
const popped = scopeStack.pop();
|
|
10028
|
+
const alwaysRuns = stmt.parent?.type === "block" || stmt.parent?.type === "synchronized_statement";
|
|
10029
|
+
lastPoppedBlock = popped;
|
|
10030
|
+
if (!isJavaScript) carryBranchDefsOut(popped, scopeStack, alwaysRuns);
|
|
10005
10031
|
break;
|
|
10006
10032
|
}
|
|
10007
10033
|
default: {
|
|
@@ -10043,6 +10069,7 @@ function processExpression(expr, startDefId, startUseId, scopeStack, isJavaScrip
|
|
|
10043
10069
|
};
|
|
10044
10070
|
defs.push(def);
|
|
10045
10071
|
currentScope(scopeStack).set(name2, def.id);
|
|
10072
|
+
alternateDefs.get(currentScope(scopeStack))?.delete(name2);
|
|
10046
10073
|
} else if (left) {
|
|
10047
10074
|
const leftUses = extractUses(left, useId, scopeStack, isJavaScript);
|
|
10048
10075
|
uses.push(...leftUses.uses);
|
|
@@ -10126,6 +10153,10 @@ function extractUses(node, startId, scopeStack, isJavaScript) {
|
|
|
10126
10153
|
line: n.startPosition.row + 1,
|
|
10127
10154
|
def_id: reachingDef
|
|
10128
10155
|
});
|
|
10156
|
+
for (const alt of findAlternateDefs(name2, scopeStack)) {
|
|
10157
|
+
if (alt === reachingDef) continue;
|
|
10158
|
+
uses.push({ id: id++, variable: name2, line: n.startPosition.row + 1, def_id: alt });
|
|
10159
|
+
}
|
|
10129
10160
|
}
|
|
10130
10161
|
});
|
|
10131
10162
|
return { uses, nextId: id };
|
|
@@ -10378,6 +10409,134 @@ function extractDestructuringDefs(node, startId) {
|
|
|
10378
10409
|
function currentScope(scopeStack) {
|
|
10379
10410
|
return scopeStack[scopeStack.length - 1];
|
|
10380
10411
|
}
|
|
10412
|
+
var lastPoppedBlock = null;
|
|
10413
|
+
function inAlwaysRunBlock(arm) {
|
|
10414
|
+
let n = arm.parent;
|
|
10415
|
+
while (n && (n.type === "if_statement" || n.type === "for_statement" || n.type === "enhanced_for_statement" || n.type === "while_statement" || n.type === "do_statement")) n = n.parent;
|
|
10416
|
+
return !!n && n.type === "block" && !!n.parent && (n.parent.type === "block" || n.parent.type === "synchronized_statement");
|
|
10417
|
+
}
|
|
10418
|
+
function inNestedBlock(arm) {
|
|
10419
|
+
let n = arm.parent;
|
|
10420
|
+
while (n && (n.type === "if_statement" || n.type === "for_statement" || n.type === "enhanced_for_statement" || n.type === "while_statement" || n.type === "do_statement")) n = n.parent;
|
|
10421
|
+
if (!n || n.type !== "block" || !n.parent) return false;
|
|
10422
|
+
switch (n.parent.type) {
|
|
10423
|
+
case "block":
|
|
10424
|
+
case "synchronized_statement":
|
|
10425
|
+
case "if_statement":
|
|
10426
|
+
case "for_statement":
|
|
10427
|
+
case "enhanced_for_statement":
|
|
10428
|
+
case "while_statement":
|
|
10429
|
+
case "do_statement":
|
|
10430
|
+
case "try_statement":
|
|
10431
|
+
case "try_with_resources_statement":
|
|
10432
|
+
case "catch_clause":
|
|
10433
|
+
case "finally_clause":
|
|
10434
|
+
case "labeled_statement":
|
|
10435
|
+
return true;
|
|
10436
|
+
default:
|
|
10437
|
+
return false;
|
|
10438
|
+
}
|
|
10439
|
+
}
|
|
10440
|
+
function processArm(node, defId, useId, scopeStack, isJavaScript) {
|
|
10441
|
+
const isBlock = node.type === "block" || node.type === "statement_block";
|
|
10442
|
+
if (isJavaScript || (isBlock ? !inAlwaysRunBlock(node) : !inNestedBlock(node))) {
|
|
10443
|
+
lastPoppedBlock = null;
|
|
10444
|
+
const result2 = processStatement2(node, defId, useId, scopeStack, isJavaScript);
|
|
10445
|
+
return { ...result2, popped: null };
|
|
10446
|
+
}
|
|
10447
|
+
if (isBlock) {
|
|
10448
|
+
lastPoppedBlock = null;
|
|
10449
|
+
const result2 = processStatement2(node, defId, useId, scopeStack, isJavaScript);
|
|
10450
|
+
return { ...result2, popped: lastPoppedBlock };
|
|
10451
|
+
}
|
|
10452
|
+
scopeStack.push(/* @__PURE__ */ new Map());
|
|
10453
|
+
const result = processStatement2(node, defId, useId, scopeStack, isJavaScript);
|
|
10454
|
+
const popped = scopeStack.pop();
|
|
10455
|
+
carryBranchDefsOut(popped, scopeStack, false);
|
|
10456
|
+
return { ...result, popped };
|
|
10457
|
+
}
|
|
10458
|
+
function mergeIfElseDefs(armA, armB, before, scopeStack) {
|
|
10459
|
+
const top = scopeStack[scopeStack.length - 1];
|
|
10460
|
+
if (!top) return;
|
|
10461
|
+
const alts = alternateDefs.get(top);
|
|
10462
|
+
for (const [name2, idA] of armA) {
|
|
10463
|
+
const idB = armB.get(name2);
|
|
10464
|
+
if (idB === void 0) continue;
|
|
10465
|
+
if (declaredNames.get(armA)?.has(name2) || declaredNames.get(armB)?.has(name2)) continue;
|
|
10466
|
+
if (!scopeStack.some((scope) => scope.has(name2))) continue;
|
|
10467
|
+
const prior = new Set(before.get(name2) ?? []);
|
|
10468
|
+
const arms = (alts?.get(name2) ?? []).filter((id) => !prior.has(id) && id !== idB);
|
|
10469
|
+
if (!arms.includes(idA)) arms.push(idA);
|
|
10470
|
+
top.set(name2, idB);
|
|
10471
|
+
let map = alts;
|
|
10472
|
+
if (!map) {
|
|
10473
|
+
map = /* @__PURE__ */ new Map();
|
|
10474
|
+
alternateDefs.set(top, map);
|
|
10475
|
+
}
|
|
10476
|
+
map.set(name2, arms);
|
|
10477
|
+
}
|
|
10478
|
+
}
|
|
10479
|
+
var declaredNames = /* @__PURE__ */ new WeakMap();
|
|
10480
|
+
var alternateDefs = /* @__PURE__ */ new WeakMap();
|
|
10481
|
+
function markDeclared(scope, name2) {
|
|
10482
|
+
let set = declaredNames.get(scope);
|
|
10483
|
+
if (!set) {
|
|
10484
|
+
set = /* @__PURE__ */ new Set();
|
|
10485
|
+
declaredNames.set(scope, set);
|
|
10486
|
+
}
|
|
10487
|
+
set.add(name2);
|
|
10488
|
+
alternateDefs.get(scope)?.delete(name2);
|
|
10489
|
+
}
|
|
10490
|
+
function carryBranchDefsOut(popped, scopeStack, alwaysRuns = false, altsOnly = false) {
|
|
10491
|
+
const declared = declaredNames.get(popped);
|
|
10492
|
+
const poppedAlts = alternateDefs.get(popped);
|
|
10493
|
+
const top = scopeStack[scopeStack.length - 1];
|
|
10494
|
+
if (!top) return;
|
|
10495
|
+
if (alwaysRuns) {
|
|
10496
|
+
for (const [name2, id] of popped) {
|
|
10497
|
+
if (declared?.has(name2)) continue;
|
|
10498
|
+
if (!scopeStack.some((scope) => scope.has(name2))) continue;
|
|
10499
|
+
top.set(name2, id);
|
|
10500
|
+
let alts = alternateDefs.get(top);
|
|
10501
|
+
if (!alts) {
|
|
10502
|
+
alts = /* @__PURE__ */ new Map();
|
|
10503
|
+
alternateDefs.set(top, alts);
|
|
10504
|
+
}
|
|
10505
|
+
alts.delete(name2);
|
|
10506
|
+
const kept = poppedAlts?.get(name2);
|
|
10507
|
+
if (kept && kept.length > 0) alts.set(name2, [...kept]);
|
|
10508
|
+
}
|
|
10509
|
+
if (poppedAlts) {
|
|
10510
|
+
for (const [name2, ids] of poppedAlts) if (!popped.has(name2)) carry(name2, ids);
|
|
10511
|
+
}
|
|
10512
|
+
return;
|
|
10513
|
+
}
|
|
10514
|
+
function carry(name2, ids) {
|
|
10515
|
+
if (declared?.has(name2)) return;
|
|
10516
|
+
if (!scopeStack.some((scope) => scope.has(name2))) return;
|
|
10517
|
+
let alts = alternateDefs.get(top);
|
|
10518
|
+
if (!alts) {
|
|
10519
|
+
alts = /* @__PURE__ */ new Map();
|
|
10520
|
+
alternateDefs.set(top, alts);
|
|
10521
|
+
}
|
|
10522
|
+
const list = alts.get(name2) ?? [];
|
|
10523
|
+
for (const id of ids) if (!list.includes(id)) list.push(id);
|
|
10524
|
+
alts.set(name2, list);
|
|
10525
|
+
}
|
|
10526
|
+
if (!altsOnly) for (const [name2, id] of popped) carry(name2, [id]);
|
|
10527
|
+
if (poppedAlts) for (const [name2, ids] of poppedAlts) carry(name2, ids);
|
|
10528
|
+
}
|
|
10529
|
+
function findAlternateDefs(name2, scopeStack) {
|
|
10530
|
+
const out2 = [];
|
|
10531
|
+
for (let i2 = scopeStack.length - 1; i2 >= 0; i2--) {
|
|
10532
|
+
const alts = alternateDefs.get(scopeStack[i2])?.get(name2);
|
|
10533
|
+
if (alts) {
|
|
10534
|
+
for (const id of alts) if (!out2.includes(id)) out2.push(id);
|
|
10535
|
+
}
|
|
10536
|
+
if (scopeStack[i2].has(name2)) break;
|
|
10537
|
+
}
|
|
10538
|
+
return out2;
|
|
10539
|
+
}
|
|
10381
10540
|
function findReachingDef(name2, scopeStack) {
|
|
10382
10541
|
for (let i2 = scopeStack.length - 1; i2 >= 0; i2--) {
|
|
10383
10542
|
const defId = scopeStack[i2].get(name2);
|
|
@@ -12382,8 +12541,15 @@ var DEFAULT_SINKS = [
|
|
|
12382
12541
|
{ method: "setExpireTimeSeconds", class: "StaticFilesConfiguration", type: "path_traversal", cwe: "CWE-22", severity: "medium", arg_positions: [0] },
|
|
12383
12542
|
{ method: "configureJarCase", class: "StaticFilesConfiguration", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
12384
12543
|
{ method: "toString", class: "StringUtils", type: "path_traversal", cwe: "CWE-22", severity: "medium", arg_positions: [0] },
|
|
12385
|
-
// Wildfly/Undertow servlet resource manager
|
|
12386
|
-
|
|
12544
|
+
// Wildfly/Undertow servlet resource manager.
|
|
12545
|
+
// ResourceManager / PathResourceManager / FileResourceManager /
|
|
12546
|
+
// ClassPathResourceManager are the interface and implementations WildFly
|
|
12547
|
+
// calls through (cognium-dev#622, CVE-2018-1047). Class-qualified only.
|
|
12548
|
+
{ method: "getResource", class: "ServletResourceManager", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["java"] },
|
|
12549
|
+
{ method: "getResource", class: "ResourceManager", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["java"] },
|
|
12550
|
+
{ method: "getResource", class: "PathResourceManager", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["java"] },
|
|
12551
|
+
{ method: "getResource", class: "FileResourceManager", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["java"] },
|
|
12552
|
+
{ method: "getResource", class: "ClassPathResourceManager", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["java"] },
|
|
12387
12553
|
// Yamcs file system bucket
|
|
12388
12554
|
{ method: "deleteObject", class: "FileSystemBucket", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
12389
12555
|
// RocketMQ validators
|
|
@@ -14727,6 +14893,157 @@ function getDefaultConfig() {
|
|
|
14727
14893
|
};
|
|
14728
14894
|
}
|
|
14729
14895
|
|
|
14896
|
+
// src/analysis/java-crlf-strip.ts
|
|
14897
|
+
function decodeJavaLiteral(raw) {
|
|
14898
|
+
return raw.replace(/\\(?:u+([0-9a-fA-F]{4})|([0-3][0-7]{0,2}|[4-7][0-7]?)|([btnfrs"'\\]))/g, (_m, hex, oct, simple) => {
|
|
14899
|
+
if (hex !== void 0) return String.fromCharCode(parseInt(hex, 16));
|
|
14900
|
+
if (oct !== void 0) return String.fromCharCode(parseInt(oct, 8));
|
|
14901
|
+
switch (simple) {
|
|
14902
|
+
case "b":
|
|
14903
|
+
return "\b";
|
|
14904
|
+
case "t":
|
|
14905
|
+
return " ";
|
|
14906
|
+
case "n":
|
|
14907
|
+
return "\n";
|
|
14908
|
+
case "f":
|
|
14909
|
+
return "\f";
|
|
14910
|
+
case "r":
|
|
14911
|
+
return "\r";
|
|
14912
|
+
case "s":
|
|
14913
|
+
return " ";
|
|
14914
|
+
default:
|
|
14915
|
+
return simple;
|
|
14916
|
+
}
|
|
14917
|
+
});
|
|
14918
|
+
}
|
|
14919
|
+
function readAtom(s, i2) {
|
|
14920
|
+
const c = s[i2];
|
|
14921
|
+
if (c === "\\" && i2 + 1 < s.length) {
|
|
14922
|
+
const n = s[i2 + 1];
|
|
14923
|
+
return { atom: n === "r" ? "cr" : n === "n" ? "lf" : "other", end: i2 + 2 };
|
|
14924
|
+
}
|
|
14925
|
+
return { atom: c === "\r" ? "cr" : c === "\n" ? "lf" : "other", end: i2 + 1 };
|
|
14926
|
+
}
|
|
14927
|
+
function quantifierAllowsOne(q) {
|
|
14928
|
+
if (q === "") return true;
|
|
14929
|
+
const m = /^(?:(\*)|(\+)|(\?)|\{(\d+)(?:(,)(\d*))?\})[+?]?$/.exec(q);
|
|
14930
|
+
if (!m) return false;
|
|
14931
|
+
if (m[1] || m[2] || m[3]) return true;
|
|
14932
|
+
const min = Number(m[4]);
|
|
14933
|
+
const max = m[5] ? m[6] === "" ? Infinity : Number(m[6]) : min;
|
|
14934
|
+
return min <= 1 && max >= 1;
|
|
14935
|
+
}
|
|
14936
|
+
var NONE = { cr: false, lf: false };
|
|
14937
|
+
function alternativeCoverage(alt) {
|
|
14938
|
+
if (alt === "") return NONE;
|
|
14939
|
+
let cr = false;
|
|
14940
|
+
let lf = false;
|
|
14941
|
+
let i2;
|
|
14942
|
+
if (alt[0] === "[") {
|
|
14943
|
+
if (alt[1] === "^") return NONE;
|
|
14944
|
+
i2 = 1;
|
|
14945
|
+
let closed = false;
|
|
14946
|
+
while (i2 < alt.length) {
|
|
14947
|
+
if (alt[i2] === "]" && i2 > 1) {
|
|
14948
|
+
closed = true;
|
|
14949
|
+
i2++;
|
|
14950
|
+
break;
|
|
14951
|
+
}
|
|
14952
|
+
if (alt[i2] === "[" || alt[i2] === "&" && alt[i2 + 1] === "&") return NONE;
|
|
14953
|
+
const { atom, end } = readAtom(alt, i2);
|
|
14954
|
+
if (atom === "cr") cr = true;
|
|
14955
|
+
else if (atom === "lf") lf = true;
|
|
14956
|
+
i2 = end;
|
|
14957
|
+
}
|
|
14958
|
+
if (!closed) return NONE;
|
|
14959
|
+
} else {
|
|
14960
|
+
const { atom, end } = readAtom(alt, 0);
|
|
14961
|
+
if (atom === "other") return NONE;
|
|
14962
|
+
cr = atom === "cr";
|
|
14963
|
+
lf = atom === "lf";
|
|
14964
|
+
i2 = end;
|
|
14965
|
+
}
|
|
14966
|
+
return quantifierAllowsOne(alt.slice(i2)) ? { cr, lf } : NONE;
|
|
14967
|
+
}
|
|
14968
|
+
function splitAlternatives(s) {
|
|
14969
|
+
const alts = [];
|
|
14970
|
+
let depth = 0;
|
|
14971
|
+
let inClass = false;
|
|
14972
|
+
let cur = "";
|
|
14973
|
+
for (let i2 = 0; i2 < s.length; i2++) {
|
|
14974
|
+
const c = s[i2];
|
|
14975
|
+
if (c === "\\") {
|
|
14976
|
+
cur += c + (s[i2 + 1] ?? "");
|
|
14977
|
+
i2++;
|
|
14978
|
+
continue;
|
|
14979
|
+
}
|
|
14980
|
+
if (inClass) {
|
|
14981
|
+
if (c === "]") inClass = false;
|
|
14982
|
+
cur += c;
|
|
14983
|
+
continue;
|
|
14984
|
+
}
|
|
14985
|
+
if (c === "[") {
|
|
14986
|
+
inClass = true;
|
|
14987
|
+
cur += c;
|
|
14988
|
+
continue;
|
|
14989
|
+
}
|
|
14990
|
+
if (c === "(") depth++;
|
|
14991
|
+
else if (c === ")") depth--;
|
|
14992
|
+
if (c === "|" && depth === 0) {
|
|
14993
|
+
alts.push(cur);
|
|
14994
|
+
cur = "";
|
|
14995
|
+
continue;
|
|
14996
|
+
}
|
|
14997
|
+
cur += c;
|
|
14998
|
+
}
|
|
14999
|
+
alts.push(cur);
|
|
15000
|
+
return alts;
|
|
15001
|
+
}
|
|
15002
|
+
function unwrapGroup(s) {
|
|
15003
|
+
const open = /^\((?:\?:)?/.exec(s);
|
|
15004
|
+
if (!open) return null;
|
|
15005
|
+
let depth = 0;
|
|
15006
|
+
let inClass = false;
|
|
15007
|
+
for (let i2 = 0; i2 < s.length; i2++) {
|
|
15008
|
+
const c = s[i2];
|
|
15009
|
+
if (c === "\\") {
|
|
15010
|
+
i2++;
|
|
15011
|
+
continue;
|
|
15012
|
+
}
|
|
15013
|
+
if (inClass) {
|
|
15014
|
+
if (c === "]") inClass = false;
|
|
15015
|
+
continue;
|
|
15016
|
+
}
|
|
15017
|
+
if (c === "[") {
|
|
15018
|
+
inClass = true;
|
|
15019
|
+
continue;
|
|
15020
|
+
}
|
|
15021
|
+
if (c === "(") depth++;
|
|
15022
|
+
else if (c === ")" && --depth === 0) {
|
|
15023
|
+
return { body: s.slice(open[0].length, i2), quantifier: s.slice(i2 + 1) };
|
|
15024
|
+
}
|
|
15025
|
+
}
|
|
15026
|
+
return null;
|
|
15027
|
+
}
|
|
15028
|
+
function javaReplaceCrLfCoverage(method, rawFrom) {
|
|
15029
|
+
const value = decodeJavaLiteral(rawFrom);
|
|
15030
|
+
if (method === "replace") return { cr: value === "\r", lf: value === "\n" };
|
|
15031
|
+
let body2 = value;
|
|
15032
|
+
const group = unwrapGroup(value);
|
|
15033
|
+
if (group) {
|
|
15034
|
+
if (!quantifierAllowsOne(group.quantifier)) return NONE;
|
|
15035
|
+
body2 = group.body;
|
|
15036
|
+
}
|
|
15037
|
+
let cr = false;
|
|
15038
|
+
let lf = false;
|
|
15039
|
+
for (const alt of splitAlternatives(body2)) {
|
|
15040
|
+
const c = alternativeCoverage(alt);
|
|
15041
|
+
if (c.cr) cr = true;
|
|
15042
|
+
if (c.lf) lf = true;
|
|
15043
|
+
}
|
|
15044
|
+
return { cr, lf };
|
|
15045
|
+
}
|
|
15046
|
+
|
|
14730
15047
|
// src/analysis/arg-type-resolver.ts
|
|
14731
15048
|
function findEnclosingType(callInMethod, types) {
|
|
14732
15049
|
if (!callInMethod) return null;
|
|
@@ -14827,7 +15144,7 @@ function analyzeTaint(calls, types, config = getDefaultConfig(), typeHierarchy,
|
|
|
14827
15144
|
sinkPatterns = expandIndirectEvalAliases(sinkPatterns, sourceLines, language);
|
|
14828
15145
|
const sinks = findSinks(calls, sinkPatterns, typeHierarchy, language, sourceLines, types);
|
|
14829
15146
|
const gatedSinks = language === "csharp" ? dropCSharpObjectCarriedWaypoints(sinks, calls) : sinks;
|
|
14830
|
-
const sanitizers = findSanitizers(calls, types, config.sanitizers, sourceLines);
|
|
15147
|
+
const sanitizers = findSanitizers(calls, types, config.sanitizers, sourceLines, language);
|
|
14831
15148
|
return { sources, sinks: gatedSinks, sanitizers };
|
|
14832
15149
|
}
|
|
14833
15150
|
function sinkPatternAppliesTo(pattern, language) {
|
|
@@ -15254,7 +15571,8 @@ function isInterproceduralTaintableType(typeName, language) {
|
|
|
15254
15571
|
}
|
|
15255
15572
|
function isParameterizedQueryCall(call, pattern) {
|
|
15256
15573
|
if (pattern.type !== "sql_injection") return false;
|
|
15257
|
-
const
|
|
15574
|
+
const positions = pattern.arg_positions ?? [];
|
|
15575
|
+
const queryPos = positions.length > 0 ? Math.min(...positions) : 0;
|
|
15258
15576
|
const queryArg = call.arguments.find((a) => a.position === queryPos);
|
|
15259
15577
|
if (queryArg) {
|
|
15260
15578
|
const queryText = queryArg.literal ?? queryArg.expression ?? "";
|
|
@@ -15845,6 +16163,25 @@ function isGoFprintToNonResponseWriter(call, pattern, language, sourceLines) {
|
|
|
15845
16163
|
}
|
|
15846
16164
|
return true;
|
|
15847
16165
|
}
|
|
16166
|
+
var JAVA_OUTBOUND_STREAM_OWNERS = "HttpURLConnection|HttpsURLConnection|URLConnection|JarURLConnection|Socket|SSLSocket|Process";
|
|
16167
|
+
function isJavaOutboundOutputStreamWrite(call, pattern, language, sourceLines) {
|
|
16168
|
+
if (language !== "java" || pattern.type !== "xss") return false;
|
|
16169
|
+
const receiver = (call.receiver ?? "").replace(/\s+/g, "");
|
|
16170
|
+
if (!receiver.endsWith(".getOutputStream()")) return false;
|
|
16171
|
+
if (/\.openConnection\(\)\)?\.getOutputStream\(\)$/.test(receiver)) return true;
|
|
16172
|
+
const m = receiver.match(/^\(?([A-Za-z_]\w*)\)?\.getOutputStream\(\)$/);
|
|
16173
|
+
if (!m || !sourceLines) return false;
|
|
16174
|
+
const declRe = new RegExp(
|
|
16175
|
+
`\\b(?:[\\w.]+\\.)?(?:${JAVA_OUTBOUND_STREAM_OWNERS})\\s+${m[1]}\\s*[=;,)]`
|
|
16176
|
+
);
|
|
16177
|
+
const anyDeclRe = new RegExp(`\\b(?:[A-Z][\\w.<>,\\[\\]]*|var)\\s+${m[1]}\\s*[=;,)]`);
|
|
16178
|
+
for (let i2 = call.location.line - 1; i2 >= 0; i2--) {
|
|
16179
|
+
const line = (sourceLines[i2] ?? "").replace(/"(?:[^"\\]|\\.)*"/g, '""').replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/.*$/, "");
|
|
16180
|
+
if (declRe.test(line)) return true;
|
|
16181
|
+
if (anyDeclRe.test(line)) return false;
|
|
16182
|
+
}
|
|
16183
|
+
return false;
|
|
16184
|
+
}
|
|
15848
16185
|
function isSafeGoJsonUnmarshalCall(call, pattern, language, sourceLines) {
|
|
15849
16186
|
if (language !== "go") return false;
|
|
15850
16187
|
if (pattern.type !== "deserialization") return false;
|
|
@@ -15994,6 +16331,9 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types)
|
|
|
15994
16331
|
if (isGoFprintToNonResponseWriter(call, pattern, language, sourceLines)) {
|
|
15995
16332
|
continue;
|
|
15996
16333
|
}
|
|
16334
|
+
if (isJavaOutboundOutputStreamWrite(call, pattern, language, sourceLines)) {
|
|
16335
|
+
continue;
|
|
16336
|
+
}
|
|
15997
16337
|
if (isSafeJinjaRenderCall(call, pattern, language, sourceLines)) {
|
|
15998
16338
|
continue;
|
|
15999
16339
|
}
|
|
@@ -16401,6 +16741,13 @@ function receiverMightBeClassImpl(receiver, className) {
|
|
|
16401
16741
|
if (receiver === className) {
|
|
16402
16742
|
return true;
|
|
16403
16743
|
}
|
|
16744
|
+
const inlineRequire = receiver.match(/^require\(\s*['"`](?:node:)?([\w./-]+)['"`]\s*\)$/);
|
|
16745
|
+
if (inlineRequire && inlineRequire[1] === className) {
|
|
16746
|
+
return true;
|
|
16747
|
+
}
|
|
16748
|
+
if (className === "ClassLoader" && /(?:^|\.)(?:getContextClassLoader|getClassLoader|getSystemClassLoader)\(\s*\)$/.test(receiver.replace(/\s+/g, ""))) {
|
|
16749
|
+
return true;
|
|
16750
|
+
}
|
|
16404
16751
|
if (receiver.endsWith(")")) {
|
|
16405
16752
|
const ctorMatch = receiver.match(/^(\w+)\(/);
|
|
16406
16753
|
if (ctorMatch) {
|
|
@@ -16689,8 +17036,9 @@ function calculateSinkConfidence(call, pattern) {
|
|
|
16689
17036
|
}
|
|
16690
17037
|
return Math.min(confidence, 1);
|
|
16691
17038
|
}
|
|
16692
|
-
function findSanitizers(calls, types, patterns, sourceLines) {
|
|
17039
|
+
function findSanitizers(calls, types, patterns, sourceLines, language) {
|
|
16693
17040
|
const sanitizers = [];
|
|
17041
|
+
const javaReplace = (call) => language === "java" && sourceLines && (call.method_name === "replace" || call.method_name === "replaceAll") ? javaReplaceChainCredit(javaChainTextAt(sourceLines, call.location.line, call.location.column)) : null;
|
|
16694
17042
|
const sanitizerMethods = /* @__PURE__ */ new Set();
|
|
16695
17043
|
for (const type of types) {
|
|
16696
17044
|
for (const method of type.methods) {
|
|
@@ -16715,10 +17063,15 @@ function findSanitizers(calls, types, patterns, sourceLines) {
|
|
|
16715
17063
|
if (inside.length !== 1) continue;
|
|
16716
17064
|
const innerCall = inside[0];
|
|
16717
17065
|
let matched;
|
|
16718
|
-
|
|
16719
|
-
|
|
16720
|
-
|
|
16721
|
-
|
|
17066
|
+
const replaceCredit = javaReplace(innerCall);
|
|
17067
|
+
if (replaceCredit) {
|
|
17068
|
+
matched = replaceCredit.length > 0 ? { method: innerCall.method_name, removes: replaceCredit } : void 0;
|
|
17069
|
+
} else {
|
|
17070
|
+
for (const pattern of patterns) {
|
|
17071
|
+
if (matchesSanitizerPattern(innerCall, pattern)) {
|
|
17072
|
+
matched = pattern;
|
|
17073
|
+
break;
|
|
17074
|
+
}
|
|
16722
17075
|
}
|
|
16723
17076
|
}
|
|
16724
17077
|
if (!matched || !matched.removes || matched.removes.length === 0) continue;
|
|
@@ -16770,6 +17123,18 @@ function findSanitizers(calls, types, patterns, sourceLines) {
|
|
|
16770
17123
|
});
|
|
16771
17124
|
continue;
|
|
16772
17125
|
}
|
|
17126
|
+
const replaceCredit = javaReplace(call);
|
|
17127
|
+
if (replaceCredit) {
|
|
17128
|
+
if (replaceCredit.length > 0) {
|
|
17129
|
+
sanitizers.push({
|
|
17130
|
+
type: "method_call",
|
|
17131
|
+
method: formatSanitizerMethod(call),
|
|
17132
|
+
line: call.location.line,
|
|
17133
|
+
sanitizes: replaceCredit
|
|
17134
|
+
});
|
|
17135
|
+
}
|
|
17136
|
+
continue;
|
|
17137
|
+
}
|
|
16773
17138
|
for (const pattern of patterns) {
|
|
16774
17139
|
if (matchesSanitizerPattern(call, pattern)) {
|
|
16775
17140
|
sanitizers.push({
|
|
@@ -16799,6 +17164,52 @@ function findSanitizers(calls, types, patterns, sourceLines) {
|
|
|
16799
17164
|
}
|
|
16800
17165
|
return sanitizers;
|
|
16801
17166
|
}
|
|
17167
|
+
var JAVA_LIT = String.raw`(?:"((?:\\.|[^"\\])*)"|'((?:\\.|[^'\\])*)')`;
|
|
17168
|
+
var JAVA_REPLACE_ARGS_RE = new RegExp(String.raw`\.(replace|replaceAll)\(\s*${JAVA_LIT}\s*,\s*${JAVA_LIT}\s*\)`, "g");
|
|
17169
|
+
function javaChainTextAt(sourceLines, line, column) {
|
|
17170
|
+
const text = [(sourceLines[line - 1] ?? "").slice(column), ...sourceLines.slice(line, line + 7)].join("\n");
|
|
17171
|
+
let depth = 0;
|
|
17172
|
+
let i2 = 0;
|
|
17173
|
+
for (; i2 < text.length; i2++) {
|
|
17174
|
+
const c = text[i2];
|
|
17175
|
+
if (c === '"' || c === "'") {
|
|
17176
|
+
for (i2++; i2 < text.length && text[i2] !== c; i2++) if (text[i2] === "\\") i2++;
|
|
17177
|
+
continue;
|
|
17178
|
+
}
|
|
17179
|
+
if (c === "(" || c === "[") {
|
|
17180
|
+
depth++;
|
|
17181
|
+
continue;
|
|
17182
|
+
}
|
|
17183
|
+
if (c === ")" || c === "]") {
|
|
17184
|
+
if (depth === 0) break;
|
|
17185
|
+
depth--;
|
|
17186
|
+
continue;
|
|
17187
|
+
}
|
|
17188
|
+
if (depth === 0 && !/[\w$.\s]/.test(c)) break;
|
|
17189
|
+
}
|
|
17190
|
+
return text.slice(0, i2);
|
|
17191
|
+
}
|
|
17192
|
+
function javaReplaceChainCredit(chainText) {
|
|
17193
|
+
let cr = false;
|
|
17194
|
+
let lf = false;
|
|
17195
|
+
let lt = false;
|
|
17196
|
+
let gt = false;
|
|
17197
|
+
for (const m of chainText.matchAll(JAVA_REPLACE_ARGS_RE)) {
|
|
17198
|
+
const kind = m[1];
|
|
17199
|
+
const rawFrom = m[2] ?? m[3] ?? "";
|
|
17200
|
+
const from = rawFrom.replace(/\\\\/g, "\\");
|
|
17201
|
+
const to = m[4] ?? m[5] ?? "";
|
|
17202
|
+
const cov = javaReplaceCrLfCoverage(kind, rawFrom);
|
|
17203
|
+
if (cov.cr) cr = true;
|
|
17204
|
+
if (cov.lf) lf = true;
|
|
17205
|
+
if (from === "<" && to.startsWith("&")) lt = true;
|
|
17206
|
+
if (from === ">" && to.startsWith("&")) gt = true;
|
|
17207
|
+
}
|
|
17208
|
+
const out2 = [];
|
|
17209
|
+
if (cr && lf) out2.push("log_injection");
|
|
17210
|
+
if (lt && gt) out2.push("xss");
|
|
17211
|
+
return out2;
|
|
17212
|
+
}
|
|
16802
17213
|
function matchesSanitizerPattern(call, pattern) {
|
|
16803
17214
|
if (pattern.method) {
|
|
16804
17215
|
if (call.method_name !== pattern.method) {
|
|
@@ -16839,6 +17250,15 @@ function formatSanitizerMethod(call) {
|
|
|
16839
17250
|
// src/graph/code-graph.ts
|
|
16840
17251
|
var CodeGraph = class {
|
|
16841
17252
|
ir;
|
|
17253
|
+
/**
|
|
17254
|
+
* Defs whose value cannot carry the content of the values they read, so
|
|
17255
|
+
* taint does not flow into them. Set by `analyze()`; empty for a bare graph.
|
|
17256
|
+
* - an int or bool measure or test (`len(x)`, `x.length`,
|
|
17257
|
+
* `re.MatchString(x)`) (#568)
|
|
17258
|
+
* - a lookup in a constant table the file never writes (`T[req.query.k]`)
|
|
17259
|
+
* (#667)
|
|
17260
|
+
*/
|
|
17261
|
+
taintBarrierDefIds = /* @__PURE__ */ new Set();
|
|
16842
17262
|
constructor(ir) {
|
|
16843
17263
|
this.ir = ir;
|
|
16844
17264
|
}
|
|
@@ -17093,6 +17513,7 @@ var CodeGraph = class {
|
|
|
17093
17513
|
for (const [fromDef, chains] of this.chainsByFromDef) {
|
|
17094
17514
|
if (!result.has(fromDef)) continue;
|
|
17095
17515
|
for (const chain of chains) {
|
|
17516
|
+
if (this.taintBarrierDefIds.has(chain.to_def)) continue;
|
|
17096
17517
|
if (!result.has(chain.to_def)) {
|
|
17097
17518
|
result.add(chain.to_def);
|
|
17098
17519
|
changed = true;
|
|
@@ -17178,6 +17599,10 @@ function buildSanitizersByLine(sanitizers) {
|
|
|
17178
17599
|
}
|
|
17179
17600
|
return out2;
|
|
17180
17601
|
}
|
|
17602
|
+
function templateInterpolationCount(expression) {
|
|
17603
|
+
if (!expression.trimStart().startsWith("`")) return 0;
|
|
17604
|
+
return (expression.match(/\$\{/g) ?? []).length;
|
|
17605
|
+
}
|
|
17181
17606
|
function concatOutsideStrings(expression) {
|
|
17182
17607
|
return expression.replace(/"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'/g, " ").includes("+");
|
|
17183
17608
|
}
|
|
@@ -17213,31 +17638,38 @@ function propagateTaint(graphOrDfg, callsOrSources, sourcesOrSinks, sinksOrSanit
|
|
|
17213
17638
|
const taintedVars = [];
|
|
17214
17639
|
const flows = [];
|
|
17215
17640
|
const javaConcat = graph.ir.meta.language === "java";
|
|
17641
|
+
const jsLike = graph.ir.meta.language === "javascript" || graph.ir.meta.language === "typescript";
|
|
17216
17642
|
const reachableSinks = /* @__PURE__ */ new Map();
|
|
17217
17643
|
const defsByLine = graph.defsByLine;
|
|
17218
17644
|
const usesByLine = graph.usesByLine;
|
|
17219
17645
|
const callsByLine = graph.callsByLine;
|
|
17220
17646
|
const sanitizersByLine = sanitizers.length > 0 ? buildSanitizersByLine(sanitizers) : graph.sanitizersByLine;
|
|
17221
17647
|
const defById = graph.defById;
|
|
17222
|
-
const rawInitialTaint = findInitialTaint(sources, callsByLine, defsByLine);
|
|
17223
|
-
const
|
|
17648
|
+
const rawInitialTaint = findInitialTaint(sources, callsByLine, defsByLine, graph.chainsByFromDef, jsLike);
|
|
17649
|
+
const filteredInitialTaint = rawInitialTaint.filter((tv) => {
|
|
17224
17650
|
if (tv.line === tv.sourceLine) return true;
|
|
17651
|
+
if (graph.taintBarrierDefIds.has(tv.defId)) return false;
|
|
17225
17652
|
const sanCheck = checkSanitized(tv.sourceLine, tv.line, tv.sourceType, sanitizersByLine);
|
|
17226
17653
|
return !sanCheck.sanitized;
|
|
17227
17654
|
});
|
|
17655
|
+
const initialTaint = [
|
|
17656
|
+
...filteredInitialTaint.filter((tv) => tv.sourceType !== "interprocedural_param"),
|
|
17657
|
+
...filteredInitialTaint.filter((tv) => tv.sourceType === "interprocedural_param")
|
|
17658
|
+
];
|
|
17228
17659
|
taintedVars.push(...initialTaint);
|
|
17229
17660
|
const propagatedTaint = propagateThroughChains(
|
|
17230
17661
|
initialTaint,
|
|
17231
17662
|
graph.chainsByFromDef,
|
|
17232
17663
|
defById,
|
|
17233
|
-
sanitizersByLine
|
|
17664
|
+
sanitizersByLine,
|
|
17665
|
+
graph.taintBarrierDefIds
|
|
17234
17666
|
);
|
|
17235
17667
|
taintedVars.push(...propagatedTaint);
|
|
17236
17668
|
const allTaintedDefIds = /* @__PURE__ */ new Set();
|
|
17237
17669
|
const taintByDefId = /* @__PURE__ */ new Map();
|
|
17238
17670
|
for (const tv of taintedVars) {
|
|
17239
17671
|
allTaintedDefIds.add(tv.defId);
|
|
17240
|
-
taintByDefId.set(tv.defId, tv);
|
|
17672
|
+
if (!yieldsToRealSource(taintByDefId.get(tv.defId), tv)) taintByDefId.set(tv.defId, tv);
|
|
17241
17673
|
}
|
|
17242
17674
|
for (const sink of sinks) {
|
|
17243
17675
|
const usesAtSink = usesByLine.get(sink.line) ?? [];
|
|
@@ -17250,7 +17682,7 @@ function propagateTaint(graphOrDfg, callsOrSources, sourcesOrSinks, sinksOrSanit
|
|
|
17250
17682
|
}
|
|
17251
17683
|
}
|
|
17252
17684
|
const expr = typeof arg.expression === "string" ? arg.expression : "";
|
|
17253
|
-
const compoundConcat =
|
|
17685
|
+
const compoundConcat = !!arg.variable && expr !== arg.variable && (javaConcat && concatOutsideStrings(expr) || jsLike && templateInterpolationCount(expr) > 1);
|
|
17254
17686
|
const candidateUses = arg.variable && !compoundConcat ? usesAtSink.filter((u) => u.variable === arg.variable) : usesAtSink;
|
|
17255
17687
|
{
|
|
17256
17688
|
for (const use of candidateUses) {
|
|
@@ -17263,11 +17695,26 @@ function propagateTaint(graphOrDfg, callsOrSources, sourcesOrSinks, sinksOrSanit
|
|
|
17263
17695
|
}
|
|
17264
17696
|
const taintInfo = taintByDefId.get(use.def_id);
|
|
17265
17697
|
if (taintInfo) {
|
|
17698
|
+
let sanMap = sanitizersByLine;
|
|
17699
|
+
if (identifierOutsideSanitizerCalls(
|
|
17700
|
+
use.variable,
|
|
17701
|
+
callsAtSink,
|
|
17702
|
+
sanitizersByLine.get(sink.line) ?? [],
|
|
17703
|
+
sink.type
|
|
17704
|
+
)) {
|
|
17705
|
+
const stripped = new Map(sanitizersByLine);
|
|
17706
|
+
const kept = (sanitizersByLine.get(sink.line) ?? []).filter(
|
|
17707
|
+
(s) => !sanitizerCoversSink(s, sink.type)
|
|
17708
|
+
);
|
|
17709
|
+
if (kept.length === 0) stripped.delete(sink.line);
|
|
17710
|
+
else stripped.set(sink.line, kept);
|
|
17711
|
+
sanMap = stripped;
|
|
17712
|
+
}
|
|
17266
17713
|
const isSanitized = checkSanitized(
|
|
17267
17714
|
taintInfo.line,
|
|
17268
17715
|
sink.line,
|
|
17269
17716
|
sink.type,
|
|
17270
|
-
|
|
17717
|
+
sanMap,
|
|
17271
17718
|
{
|
|
17272
17719
|
startDefId: use.def_id,
|
|
17273
17720
|
chainsByToDef: graph.chainsByToDef,
|
|
@@ -17308,12 +17755,46 @@ function propagateTaint(graphOrDfg, callsOrSources, sourcesOrSinks, sinksOrSanit
|
|
|
17308
17755
|
}
|
|
17309
17756
|
return { taintedVars, flows: deduped, reachableSinks };
|
|
17310
17757
|
}
|
|
17311
|
-
|
|
17758
|
+
var PARAM_SCOPED_SOURCE_RE = /^(?:GraphQL \w+\.\w+ resolver args|Express handler destructured request) /;
|
|
17759
|
+
function isCompleteConditionHeader(code) {
|
|
17760
|
+
if (!code) return false;
|
|
17761
|
+
const m = /^(?:\}\s*)?(?:else\s+)?(?:if|while)\s*\(/.exec(code);
|
|
17762
|
+
if (!m) return false;
|
|
17763
|
+
const text = code.slice(m[0].length - 1).replace(/"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|`(?:[^`\\]|\\.)*`/g, '""');
|
|
17764
|
+
let depth = 0;
|
|
17765
|
+
for (const ch of text) {
|
|
17766
|
+
if (ch === "(") depth++;
|
|
17767
|
+
else if (ch === ")" && --depth === 0) return true;
|
|
17768
|
+
}
|
|
17769
|
+
return false;
|
|
17770
|
+
}
|
|
17771
|
+
function hasSecondStatement(code) {
|
|
17772
|
+
if (!code || /^for\b/.test(code)) return false;
|
|
17773
|
+
const text = code.replace(/"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|`(?:[^`\\]|\\.)*`/g, '""');
|
|
17774
|
+
return /;\s*(?!\/\/|\/\*|#|\}|\)|$)\S/.test(text);
|
|
17775
|
+
}
|
|
17776
|
+
function findInitialTaint(sources, callsByLine, defsByLine, chainsByFromDef, jsLike = false) {
|
|
17312
17777
|
const tainted = [];
|
|
17313
17778
|
for (const source of sources) {
|
|
17314
17779
|
const defsOnLine = defsByLine.get(source.line) ?? [];
|
|
17780
|
+
let seedable = null;
|
|
17781
|
+
if (source.variable && hasSecondStatement(source.code)) {
|
|
17782
|
+
const own = defsOnLine.filter((d) => d.variable === source.variable);
|
|
17783
|
+
if (own.length > 0) {
|
|
17784
|
+
seedable = new Set(own.map((d) => d.id));
|
|
17785
|
+
for (const d of own) {
|
|
17786
|
+
for (const c of chainsByFromDef.get(d.id) ?? []) {
|
|
17787
|
+
if (defsOnLine.some((o) => o.id === c.to_def)) seedable.add(c.to_def);
|
|
17788
|
+
}
|
|
17789
|
+
}
|
|
17790
|
+
}
|
|
17791
|
+
}
|
|
17315
17792
|
for (const def of defsOnLine) {
|
|
17316
|
-
if (
|
|
17793
|
+
if (seedable && !seedable.has(def.id)) continue;
|
|
17794
|
+
if ((source.type === "interprocedural_param" || PARAM_SCOPED_SOURCE_RE.test(source.location ?? "")) && source.variable && def.kind === "param" && def.variable !== source.variable) {
|
|
17795
|
+
continue;
|
|
17796
|
+
}
|
|
17797
|
+
if (source.type === "interprocedural_param" && def.kind !== "param") {
|
|
17317
17798
|
continue;
|
|
17318
17799
|
}
|
|
17319
17800
|
tainted.push({
|
|
@@ -17325,7 +17806,8 @@ function findInitialTaint(sources, callsByLine, defsByLine) {
|
|
|
17325
17806
|
confidence: source.confidence
|
|
17326
17807
|
});
|
|
17327
17808
|
}
|
|
17328
|
-
const
|
|
17809
|
+
const closesOwnStatement = jsLike && defsOnLine.length > 0 && /;\s*(?:\/\/.*)?$/.test(source.code ?? "");
|
|
17810
|
+
const defsNextLine = isCompleteConditionHeader(source.code) ? [] : (defsByLine.get(source.line + 1) ?? []).filter((d) => !(closesOwnStatement && d.kind === "local"));
|
|
17329
17811
|
for (const def of defsNextLine) {
|
|
17330
17812
|
const callsOnSourceLine = callsByLine.get(source.line) ?? [];
|
|
17331
17813
|
if (callsOnSourceLine.length > 0) {
|
|
@@ -17359,12 +17841,15 @@ function findInitialTaint(sources, callsByLine, defsByLine) {
|
|
|
17359
17841
|
}
|
|
17360
17842
|
return tainted;
|
|
17361
17843
|
}
|
|
17362
|
-
function
|
|
17844
|
+
function yieldsToRealSource(existing, next) {
|
|
17845
|
+
return existing !== void 0 && next.sourceType === "interprocedural_param" && existing.sourceType !== "interprocedural_param";
|
|
17846
|
+
}
|
|
17847
|
+
function propagateThroughChains(initialTaint, chainsByFromDef, defById, sanitizersByLine, taintBarrierDefIds) {
|
|
17363
17848
|
const propagated = [];
|
|
17364
17849
|
const taintedDefIds = new Set(initialTaint.map((t) => t.defId));
|
|
17365
17850
|
const taintInfoByDefId = /* @__PURE__ */ new Map();
|
|
17366
17851
|
for (const t of initialTaint) {
|
|
17367
|
-
taintInfoByDefId.set(t.defId, t);
|
|
17852
|
+
if (!yieldsToRealSource(taintInfoByDefId.get(t.defId), t)) taintInfoByDefId.set(t.defId, t);
|
|
17368
17853
|
}
|
|
17369
17854
|
const queue = [...initialTaint.map((t) => t.defId)];
|
|
17370
17855
|
const visited = new Set(queue);
|
|
@@ -17375,6 +17860,7 @@ function propagateThroughChains(initialTaint, chainsByFromDef, defById, sanitize
|
|
|
17375
17860
|
const outgoingChains = chainsByFromDef.get(currentDefId) ?? [];
|
|
17376
17861
|
for (const chain of outgoingChains) {
|
|
17377
17862
|
if (visited.has(chain.to_def)) continue;
|
|
17863
|
+
if (taintBarrierDefIds.has(chain.to_def)) continue;
|
|
17378
17864
|
const targetDef = defById.get(chain.to_def);
|
|
17379
17865
|
if (!targetDef) continue;
|
|
17380
17866
|
const sanitizeCheck = checkSanitized(
|
|
@@ -17421,6 +17907,92 @@ var KNOWN_SINK_TYPES = /* @__PURE__ */ new Set([
|
|
|
17421
17907
|
"crlf",
|
|
17422
17908
|
"mass_assignment"
|
|
17423
17909
|
]);
|
|
17910
|
+
function readBalancedArg(text, openParen) {
|
|
17911
|
+
let depth = 0;
|
|
17912
|
+
let quote = null;
|
|
17913
|
+
let escaped = false;
|
|
17914
|
+
for (let i2 = openParen; i2 < text.length; i2++) {
|
|
17915
|
+
const c = text[i2];
|
|
17916
|
+
if (quote) {
|
|
17917
|
+
if (escaped) {
|
|
17918
|
+
escaped = false;
|
|
17919
|
+
continue;
|
|
17920
|
+
}
|
|
17921
|
+
if (c === "\\") {
|
|
17922
|
+
escaped = true;
|
|
17923
|
+
continue;
|
|
17924
|
+
}
|
|
17925
|
+
if (c === quote) quote = null;
|
|
17926
|
+
continue;
|
|
17927
|
+
}
|
|
17928
|
+
if (c === '"' || c === "'") {
|
|
17929
|
+
quote = c;
|
|
17930
|
+
continue;
|
|
17931
|
+
}
|
|
17932
|
+
if (c === "(") depth++;
|
|
17933
|
+
else if (c === ")") {
|
|
17934
|
+
depth--;
|
|
17935
|
+
if (depth === 0) return text.slice(openParen + 1, i2);
|
|
17936
|
+
}
|
|
17937
|
+
}
|
|
17938
|
+
return null;
|
|
17939
|
+
}
|
|
17940
|
+
function sanitizerCallRefs(sanitizers, sinkType) {
|
|
17941
|
+
const refs = [];
|
|
17942
|
+
for (const san of sanitizers) {
|
|
17943
|
+
if (!sanitizerCoversSink(san, sinkType)) continue;
|
|
17944
|
+
const m = /(?:(\w+)\.)?(\w+)\(\)/.exec(san.method);
|
|
17945
|
+
if (!m?.[2]) continue;
|
|
17946
|
+
refs.push({ receiver: m[1], methodName: m[2] });
|
|
17947
|
+
}
|
|
17948
|
+
return refs;
|
|
17949
|
+
}
|
|
17950
|
+
function maskSanitizerCalls(expr, refs) {
|
|
17951
|
+
let out2 = expr;
|
|
17952
|
+
for (const ref of refs) {
|
|
17953
|
+
const needles = ref.receiver ? [`${ref.receiver}.${ref.methodName}`, ref.methodName] : [ref.methodName];
|
|
17954
|
+
for (const needle of needles) {
|
|
17955
|
+
const re = new RegExp(`\\b${needle.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\s*\\(`);
|
|
17956
|
+
for (let guard = 0; guard < 8; guard++) {
|
|
17957
|
+
const m = re.exec(out2);
|
|
17958
|
+
if (!m) break;
|
|
17959
|
+
const open = m.index + m[0].length - 1;
|
|
17960
|
+
const arg = readBalancedArg(out2, open);
|
|
17961
|
+
if (arg === null) break;
|
|
17962
|
+
out2 = out2.slice(0, m.index) + " " + out2.slice(open + arg.length + 2);
|
|
17963
|
+
}
|
|
17964
|
+
}
|
|
17965
|
+
}
|
|
17966
|
+
return out2;
|
|
17967
|
+
}
|
|
17968
|
+
function maskLengthOf(expr) {
|
|
17969
|
+
return expr.replace(/\b(?:len|strlen|count)\s*\(\s*[\w$.]+\s*\)/g, " 0 ").replace(/[\w$.]+\s*\.\s*(?:length|size|count)\s*(?:\(\s*\))?(?![\w$(])/g, " 0 ");
|
|
17970
|
+
}
|
|
17971
|
+
function blankStringLiterals(expr) {
|
|
17972
|
+
return expr.replace(/"(?:[^"\\]|\\.)*"/g, '""').replace(/'(?:[^'\\]|\\.)*'/g, "''");
|
|
17973
|
+
}
|
|
17974
|
+
function identifierOutsideSanitizerCalls(variable, calls, sanitizers, sinkType) {
|
|
17975
|
+
if (!/^[A-Za-z_$][\w$]*$/.test(variable)) return false;
|
|
17976
|
+
const refs = sanitizerCallRefs(sanitizers, sinkType);
|
|
17977
|
+
if (refs.length === 0) return false;
|
|
17978
|
+
const re = new RegExp(
|
|
17979
|
+
`(?:^|[^A-Za-z0-9_$])${variable.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:[^A-Za-z0-9_$]|$)`
|
|
17980
|
+
);
|
|
17981
|
+
for (const call of calls) {
|
|
17982
|
+
if (refs.some((r) => r.methodName === call.method_name)) continue;
|
|
17983
|
+
const masked = call.arguments.map((arg) => {
|
|
17984
|
+
const expr = arg.expression ?? "";
|
|
17985
|
+
return { expr, masked: expr ? maskSanitizerCalls(expr, refs) : expr };
|
|
17986
|
+
});
|
|
17987
|
+
if (!masked.some((m) => m.masked !== m.expr)) continue;
|
|
17988
|
+
for (const m of masked) {
|
|
17989
|
+
if (!m.masked) continue;
|
|
17990
|
+
const text = m.masked === m.expr ? maskLengthOf(m.masked) : m.masked;
|
|
17991
|
+
if (re.test(blankStringLiterals(text))) return true;
|
|
17992
|
+
}
|
|
17993
|
+
}
|
|
17994
|
+
return false;
|
|
17995
|
+
}
|
|
17424
17996
|
function checkSanitized(fromLine, toLine, sinkType, sanitizersByLine, ctx) {
|
|
17425
17997
|
const isKnownSinkType = KNOWN_SINK_TYPES.has(sinkType);
|
|
17426
17998
|
const sanitizersAtTarget = sanitizersByLine.get(toLine);
|