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
|
@@ -8080,6 +8080,17 @@ function extractGoCallInfo(node) {
|
|
|
8080
8080
|
if (operand.type === "identifier") {
|
|
8081
8081
|
const resolved = resolveGoLocalReceiverType(opText, node);
|
|
8082
8082
|
receiver = resolved !== null ? resolved : opText;
|
|
8083
|
+
} else if (operand.type === "selector_expression" && methodName === "Get") {
|
|
8084
|
+
const fieldNode = operand.childForFieldName("field");
|
|
8085
|
+
const inner = operand.childForFieldName("operand");
|
|
8086
|
+
const fieldName = fieldNode ? getNodeText(fieldNode) : "";
|
|
8087
|
+
const innerName = inner && inner.type === "identifier" ? getNodeText(inner) : null;
|
|
8088
|
+
const innerType = innerName !== null ? resolveGoLocalReceiverType(innerName, node) : null;
|
|
8089
|
+
if (innerType === "Request" && (fieldName === "PostForm" || fieldName === "Form")) {
|
|
8090
|
+
receiver = fieldName === "PostForm" ? "RequestPostForm" : "RequestForm";
|
|
8091
|
+
} else {
|
|
8092
|
+
receiver = opText;
|
|
8093
|
+
}
|
|
8083
8094
|
} else {
|
|
8084
8095
|
receiver = opText;
|
|
8085
8096
|
}
|
|
@@ -9896,6 +9907,7 @@ function processStatement2(stmt, startDefId, startUseId, scopeStack, isJavaScrip
|
|
|
9896
9907
|
};
|
|
9897
9908
|
defs.push(def);
|
|
9898
9909
|
currentScope(scopeStack).set(name2, def.id);
|
|
9910
|
+
markDeclared(currentScope(scopeStack), name2);
|
|
9899
9911
|
} else if (isJavaScript) {
|
|
9900
9912
|
const destructDefs = extractDestructuringDefs(nameNode, defId);
|
|
9901
9913
|
for (const def of destructDefs) {
|
|
@@ -9942,21 +9954,31 @@ function processStatement2(stmt, startDefId, startUseId, scopeStack, isJavaScrip
|
|
|
9942
9954
|
useId = condUses.nextId;
|
|
9943
9955
|
}
|
|
9944
9956
|
const consequence = stmt.childForFieldName("consequence");
|
|
9957
|
+
const alternative = stmt.childForFieldName("alternative");
|
|
9958
|
+
const topAlts = alternateDefs.get(currentScope(scopeStack));
|
|
9959
|
+
const before = /* @__PURE__ */ new Map();
|
|
9960
|
+
if (topAlts) for (const [name2, ids] of topAlts) before.set(name2, [...ids]);
|
|
9961
|
+
let armA = null;
|
|
9962
|
+
let armB = null;
|
|
9945
9963
|
if (consequence) {
|
|
9946
|
-
const result =
|
|
9964
|
+
const result = processArm(consequence, defId, useId, scopeStack, isJavaScript);
|
|
9965
|
+
armA = result.popped;
|
|
9947
9966
|
defs.push(...result.defs);
|
|
9948
9967
|
uses.push(...result.uses);
|
|
9949
9968
|
defId = result.nextDefId;
|
|
9950
9969
|
useId = result.nextUseId;
|
|
9951
9970
|
}
|
|
9952
|
-
const alternative = stmt.childForFieldName("alternative");
|
|
9953
9971
|
if (alternative) {
|
|
9954
|
-
const result =
|
|
9972
|
+
const result = processArm(alternative, defId, useId, scopeStack, isJavaScript);
|
|
9973
|
+
armB = result.popped;
|
|
9955
9974
|
defs.push(...result.defs);
|
|
9956
9975
|
uses.push(...result.uses);
|
|
9957
9976
|
defId = result.nextDefId;
|
|
9958
9977
|
useId = result.nextUseId;
|
|
9959
9978
|
}
|
|
9979
|
+
if (armA && armB) {
|
|
9980
|
+
mergeIfElseDefs(armA, armB, before, scopeStack);
|
|
9981
|
+
}
|
|
9960
9982
|
break;
|
|
9961
9983
|
}
|
|
9962
9984
|
case "for_statement":
|
|
@@ -10027,7 +10049,7 @@ function processStatement2(stmt, startDefId, startUseId, scopeStack, isJavaScrip
|
|
|
10027
10049
|
}
|
|
10028
10050
|
const body2 = stmt.childForFieldName("body");
|
|
10029
10051
|
if (body2) {
|
|
10030
|
-
const result =
|
|
10052
|
+
const result = processArm(body2, defId, useId, scopeStack, isJavaScript);
|
|
10031
10053
|
defs.push(...result.defs);
|
|
10032
10054
|
uses.push(...result.uses);
|
|
10033
10055
|
defId = result.nextDefId;
|
|
@@ -10055,7 +10077,8 @@ function processStatement2(stmt, startDefId, startUseId, scopeStack, isJavaScrip
|
|
|
10055
10077
|
defId = result.nextDefId;
|
|
10056
10078
|
useId = result.nextUseId;
|
|
10057
10079
|
}
|
|
10058
|
-
scopeStack.pop();
|
|
10080
|
+
const poppedSwitch = scopeStack.pop();
|
|
10081
|
+
if (!isJavaScript) carryBranchDefsOut(poppedSwitch, scopeStack, false, true);
|
|
10059
10082
|
}
|
|
10060
10083
|
break;
|
|
10061
10084
|
}
|
|
@@ -10067,7 +10090,10 @@ function processStatement2(stmt, startDefId, startUseId, scopeStack, isJavaScrip
|
|
|
10067
10090
|
uses.push(...result.uses);
|
|
10068
10091
|
defId = result.nextDefId;
|
|
10069
10092
|
useId = result.nextUseId;
|
|
10070
|
-
scopeStack.pop();
|
|
10093
|
+
const popped = scopeStack.pop();
|
|
10094
|
+
const alwaysRuns = stmt.parent?.type === "block" || stmt.parent?.type === "synchronized_statement";
|
|
10095
|
+
lastPoppedBlock = popped;
|
|
10096
|
+
if (!isJavaScript) carryBranchDefsOut(popped, scopeStack, alwaysRuns);
|
|
10071
10097
|
break;
|
|
10072
10098
|
}
|
|
10073
10099
|
default: {
|
|
@@ -10109,6 +10135,7 @@ function processExpression(expr, startDefId, startUseId, scopeStack, isJavaScrip
|
|
|
10109
10135
|
};
|
|
10110
10136
|
defs.push(def);
|
|
10111
10137
|
currentScope(scopeStack).set(name2, def.id);
|
|
10138
|
+
alternateDefs.get(currentScope(scopeStack))?.delete(name2);
|
|
10112
10139
|
} else if (left) {
|
|
10113
10140
|
const leftUses = extractUses(left, useId, scopeStack, isJavaScript);
|
|
10114
10141
|
uses.push(...leftUses.uses);
|
|
@@ -10192,6 +10219,10 @@ function extractUses(node, startId, scopeStack, isJavaScript) {
|
|
|
10192
10219
|
line: n.startPosition.row + 1,
|
|
10193
10220
|
def_id: reachingDef
|
|
10194
10221
|
});
|
|
10222
|
+
for (const alt of findAlternateDefs(name2, scopeStack)) {
|
|
10223
|
+
if (alt === reachingDef) continue;
|
|
10224
|
+
uses.push({ id: id++, variable: name2, line: n.startPosition.row + 1, def_id: alt });
|
|
10225
|
+
}
|
|
10195
10226
|
}
|
|
10196
10227
|
});
|
|
10197
10228
|
return { uses, nextId: id };
|
|
@@ -10444,6 +10475,134 @@ function extractDestructuringDefs(node, startId) {
|
|
|
10444
10475
|
function currentScope(scopeStack) {
|
|
10445
10476
|
return scopeStack[scopeStack.length - 1];
|
|
10446
10477
|
}
|
|
10478
|
+
var lastPoppedBlock = null;
|
|
10479
|
+
function inAlwaysRunBlock(arm) {
|
|
10480
|
+
let n = arm.parent;
|
|
10481
|
+
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;
|
|
10482
|
+
return !!n && n.type === "block" && !!n.parent && (n.parent.type === "block" || n.parent.type === "synchronized_statement");
|
|
10483
|
+
}
|
|
10484
|
+
function inNestedBlock(arm) {
|
|
10485
|
+
let n = arm.parent;
|
|
10486
|
+
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;
|
|
10487
|
+
if (!n || n.type !== "block" || !n.parent) return false;
|
|
10488
|
+
switch (n.parent.type) {
|
|
10489
|
+
case "block":
|
|
10490
|
+
case "synchronized_statement":
|
|
10491
|
+
case "if_statement":
|
|
10492
|
+
case "for_statement":
|
|
10493
|
+
case "enhanced_for_statement":
|
|
10494
|
+
case "while_statement":
|
|
10495
|
+
case "do_statement":
|
|
10496
|
+
case "try_statement":
|
|
10497
|
+
case "try_with_resources_statement":
|
|
10498
|
+
case "catch_clause":
|
|
10499
|
+
case "finally_clause":
|
|
10500
|
+
case "labeled_statement":
|
|
10501
|
+
return true;
|
|
10502
|
+
default:
|
|
10503
|
+
return false;
|
|
10504
|
+
}
|
|
10505
|
+
}
|
|
10506
|
+
function processArm(node, defId, useId, scopeStack, isJavaScript) {
|
|
10507
|
+
const isBlock = node.type === "block" || node.type === "statement_block";
|
|
10508
|
+
if (isJavaScript || (isBlock ? !inAlwaysRunBlock(node) : !inNestedBlock(node))) {
|
|
10509
|
+
lastPoppedBlock = null;
|
|
10510
|
+
const result2 = processStatement2(node, defId, useId, scopeStack, isJavaScript);
|
|
10511
|
+
return { ...result2, popped: null };
|
|
10512
|
+
}
|
|
10513
|
+
if (isBlock) {
|
|
10514
|
+
lastPoppedBlock = null;
|
|
10515
|
+
const result2 = processStatement2(node, defId, useId, scopeStack, isJavaScript);
|
|
10516
|
+
return { ...result2, popped: lastPoppedBlock };
|
|
10517
|
+
}
|
|
10518
|
+
scopeStack.push(/* @__PURE__ */ new Map());
|
|
10519
|
+
const result = processStatement2(node, defId, useId, scopeStack, isJavaScript);
|
|
10520
|
+
const popped = scopeStack.pop();
|
|
10521
|
+
carryBranchDefsOut(popped, scopeStack, false);
|
|
10522
|
+
return { ...result, popped };
|
|
10523
|
+
}
|
|
10524
|
+
function mergeIfElseDefs(armA, armB, before, scopeStack) {
|
|
10525
|
+
const top = scopeStack[scopeStack.length - 1];
|
|
10526
|
+
if (!top) return;
|
|
10527
|
+
const alts = alternateDefs.get(top);
|
|
10528
|
+
for (const [name2, idA] of armA) {
|
|
10529
|
+
const idB = armB.get(name2);
|
|
10530
|
+
if (idB === void 0) continue;
|
|
10531
|
+
if (declaredNames.get(armA)?.has(name2) || declaredNames.get(armB)?.has(name2)) continue;
|
|
10532
|
+
if (!scopeStack.some((scope) => scope.has(name2))) continue;
|
|
10533
|
+
const prior = new Set(before.get(name2) ?? []);
|
|
10534
|
+
const arms = (alts?.get(name2) ?? []).filter((id) => !prior.has(id) && id !== idB);
|
|
10535
|
+
if (!arms.includes(idA)) arms.push(idA);
|
|
10536
|
+
top.set(name2, idB);
|
|
10537
|
+
let map = alts;
|
|
10538
|
+
if (!map) {
|
|
10539
|
+
map = /* @__PURE__ */ new Map();
|
|
10540
|
+
alternateDefs.set(top, map);
|
|
10541
|
+
}
|
|
10542
|
+
map.set(name2, arms);
|
|
10543
|
+
}
|
|
10544
|
+
}
|
|
10545
|
+
var declaredNames = /* @__PURE__ */ new WeakMap();
|
|
10546
|
+
var alternateDefs = /* @__PURE__ */ new WeakMap();
|
|
10547
|
+
function markDeclared(scope, name2) {
|
|
10548
|
+
let set = declaredNames.get(scope);
|
|
10549
|
+
if (!set) {
|
|
10550
|
+
set = /* @__PURE__ */ new Set();
|
|
10551
|
+
declaredNames.set(scope, set);
|
|
10552
|
+
}
|
|
10553
|
+
set.add(name2);
|
|
10554
|
+
alternateDefs.get(scope)?.delete(name2);
|
|
10555
|
+
}
|
|
10556
|
+
function carryBranchDefsOut(popped, scopeStack, alwaysRuns = false, altsOnly = false) {
|
|
10557
|
+
const declared = declaredNames.get(popped);
|
|
10558
|
+
const poppedAlts = alternateDefs.get(popped);
|
|
10559
|
+
const top = scopeStack[scopeStack.length - 1];
|
|
10560
|
+
if (!top) return;
|
|
10561
|
+
if (alwaysRuns) {
|
|
10562
|
+
for (const [name2, id] of popped) {
|
|
10563
|
+
if (declared?.has(name2)) continue;
|
|
10564
|
+
if (!scopeStack.some((scope) => scope.has(name2))) continue;
|
|
10565
|
+
top.set(name2, id);
|
|
10566
|
+
let alts = alternateDefs.get(top);
|
|
10567
|
+
if (!alts) {
|
|
10568
|
+
alts = /* @__PURE__ */ new Map();
|
|
10569
|
+
alternateDefs.set(top, alts);
|
|
10570
|
+
}
|
|
10571
|
+
alts.delete(name2);
|
|
10572
|
+
const kept = poppedAlts?.get(name2);
|
|
10573
|
+
if (kept && kept.length > 0) alts.set(name2, [...kept]);
|
|
10574
|
+
}
|
|
10575
|
+
if (poppedAlts) {
|
|
10576
|
+
for (const [name2, ids] of poppedAlts) if (!popped.has(name2)) carry(name2, ids);
|
|
10577
|
+
}
|
|
10578
|
+
return;
|
|
10579
|
+
}
|
|
10580
|
+
function carry(name2, ids) {
|
|
10581
|
+
if (declared?.has(name2)) return;
|
|
10582
|
+
if (!scopeStack.some((scope) => scope.has(name2))) return;
|
|
10583
|
+
let alts = alternateDefs.get(top);
|
|
10584
|
+
if (!alts) {
|
|
10585
|
+
alts = /* @__PURE__ */ new Map();
|
|
10586
|
+
alternateDefs.set(top, alts);
|
|
10587
|
+
}
|
|
10588
|
+
const list = alts.get(name2) ?? [];
|
|
10589
|
+
for (const id of ids) if (!list.includes(id)) list.push(id);
|
|
10590
|
+
alts.set(name2, list);
|
|
10591
|
+
}
|
|
10592
|
+
if (!altsOnly) for (const [name2, id] of popped) carry(name2, [id]);
|
|
10593
|
+
if (poppedAlts) for (const [name2, ids] of poppedAlts) carry(name2, ids);
|
|
10594
|
+
}
|
|
10595
|
+
function findAlternateDefs(name2, scopeStack) {
|
|
10596
|
+
const out2 = [];
|
|
10597
|
+
for (let i2 = scopeStack.length - 1; i2 >= 0; i2--) {
|
|
10598
|
+
const alts = alternateDefs.get(scopeStack[i2])?.get(name2);
|
|
10599
|
+
if (alts) {
|
|
10600
|
+
for (const id of alts) if (!out2.includes(id)) out2.push(id);
|
|
10601
|
+
}
|
|
10602
|
+
if (scopeStack[i2].has(name2)) break;
|
|
10603
|
+
}
|
|
10604
|
+
return out2;
|
|
10605
|
+
}
|
|
10447
10606
|
function findReachingDef(name2, scopeStack) {
|
|
10448
10607
|
for (let i2 = scopeStack.length - 1; i2 >= 0; i2--) {
|
|
10449
10608
|
const defId = scopeStack[i2].get(name2);
|
|
@@ -12448,8 +12607,15 @@ var DEFAULT_SINKS = [
|
|
|
12448
12607
|
{ method: "setExpireTimeSeconds", class: "StaticFilesConfiguration", type: "path_traversal", cwe: "CWE-22", severity: "medium", arg_positions: [0] },
|
|
12449
12608
|
{ method: "configureJarCase", class: "StaticFilesConfiguration", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
12450
12609
|
{ method: "toString", class: "StringUtils", type: "path_traversal", cwe: "CWE-22", severity: "medium", arg_positions: [0] },
|
|
12451
|
-
// Wildfly/Undertow servlet resource manager
|
|
12452
|
-
|
|
12610
|
+
// Wildfly/Undertow servlet resource manager.
|
|
12611
|
+
// ResourceManager / PathResourceManager / FileResourceManager /
|
|
12612
|
+
// ClassPathResourceManager are the interface and implementations WildFly
|
|
12613
|
+
// calls through (cognium-dev#622, CVE-2018-1047). Class-qualified only.
|
|
12614
|
+
{ method: "getResource", class: "ServletResourceManager", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["java"] },
|
|
12615
|
+
{ method: "getResource", class: "ResourceManager", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["java"] },
|
|
12616
|
+
{ method: "getResource", class: "PathResourceManager", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["java"] },
|
|
12617
|
+
{ method: "getResource", class: "FileResourceManager", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["java"] },
|
|
12618
|
+
{ method: "getResource", class: "ClassPathResourceManager", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["java"] },
|
|
12453
12619
|
// Yamcs file system bucket
|
|
12454
12620
|
{ method: "deleteObject", class: "FileSystemBucket", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
12455
12621
|
// RocketMQ validators
|
|
@@ -14793,6 +14959,157 @@ function getDefaultConfig() {
|
|
|
14793
14959
|
};
|
|
14794
14960
|
}
|
|
14795
14961
|
|
|
14962
|
+
// src/analysis/java-crlf-strip.ts
|
|
14963
|
+
function decodeJavaLiteral(raw) {
|
|
14964
|
+
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) => {
|
|
14965
|
+
if (hex !== void 0) return String.fromCharCode(parseInt(hex, 16));
|
|
14966
|
+
if (oct !== void 0) return String.fromCharCode(parseInt(oct, 8));
|
|
14967
|
+
switch (simple) {
|
|
14968
|
+
case "b":
|
|
14969
|
+
return "\b";
|
|
14970
|
+
case "t":
|
|
14971
|
+
return " ";
|
|
14972
|
+
case "n":
|
|
14973
|
+
return "\n";
|
|
14974
|
+
case "f":
|
|
14975
|
+
return "\f";
|
|
14976
|
+
case "r":
|
|
14977
|
+
return "\r";
|
|
14978
|
+
case "s":
|
|
14979
|
+
return " ";
|
|
14980
|
+
default:
|
|
14981
|
+
return simple;
|
|
14982
|
+
}
|
|
14983
|
+
});
|
|
14984
|
+
}
|
|
14985
|
+
function readAtom(s, i2) {
|
|
14986
|
+
const c = s[i2];
|
|
14987
|
+
if (c === "\\" && i2 + 1 < s.length) {
|
|
14988
|
+
const n = s[i2 + 1];
|
|
14989
|
+
return { atom: n === "r" ? "cr" : n === "n" ? "lf" : "other", end: i2 + 2 };
|
|
14990
|
+
}
|
|
14991
|
+
return { atom: c === "\r" ? "cr" : c === "\n" ? "lf" : "other", end: i2 + 1 };
|
|
14992
|
+
}
|
|
14993
|
+
function quantifierAllowsOne(q) {
|
|
14994
|
+
if (q === "") return true;
|
|
14995
|
+
const m = /^(?:(\*)|(\+)|(\?)|\{(\d+)(?:(,)(\d*))?\})[+?]?$/.exec(q);
|
|
14996
|
+
if (!m) return false;
|
|
14997
|
+
if (m[1] || m[2] || m[3]) return true;
|
|
14998
|
+
const min = Number(m[4]);
|
|
14999
|
+
const max = m[5] ? m[6] === "" ? Infinity : Number(m[6]) : min;
|
|
15000
|
+
return min <= 1 && max >= 1;
|
|
15001
|
+
}
|
|
15002
|
+
var NONE = { cr: false, lf: false };
|
|
15003
|
+
function alternativeCoverage(alt) {
|
|
15004
|
+
if (alt === "") return NONE;
|
|
15005
|
+
let cr = false;
|
|
15006
|
+
let lf = false;
|
|
15007
|
+
let i2;
|
|
15008
|
+
if (alt[0] === "[") {
|
|
15009
|
+
if (alt[1] === "^") return NONE;
|
|
15010
|
+
i2 = 1;
|
|
15011
|
+
let closed = false;
|
|
15012
|
+
while (i2 < alt.length) {
|
|
15013
|
+
if (alt[i2] === "]" && i2 > 1) {
|
|
15014
|
+
closed = true;
|
|
15015
|
+
i2++;
|
|
15016
|
+
break;
|
|
15017
|
+
}
|
|
15018
|
+
if (alt[i2] === "[" || alt[i2] === "&" && alt[i2 + 1] === "&") return NONE;
|
|
15019
|
+
const { atom, end } = readAtom(alt, i2);
|
|
15020
|
+
if (atom === "cr") cr = true;
|
|
15021
|
+
else if (atom === "lf") lf = true;
|
|
15022
|
+
i2 = end;
|
|
15023
|
+
}
|
|
15024
|
+
if (!closed) return NONE;
|
|
15025
|
+
} else {
|
|
15026
|
+
const { atom, end } = readAtom(alt, 0);
|
|
15027
|
+
if (atom === "other") return NONE;
|
|
15028
|
+
cr = atom === "cr";
|
|
15029
|
+
lf = atom === "lf";
|
|
15030
|
+
i2 = end;
|
|
15031
|
+
}
|
|
15032
|
+
return quantifierAllowsOne(alt.slice(i2)) ? { cr, lf } : NONE;
|
|
15033
|
+
}
|
|
15034
|
+
function splitAlternatives(s) {
|
|
15035
|
+
const alts = [];
|
|
15036
|
+
let depth = 0;
|
|
15037
|
+
let inClass = false;
|
|
15038
|
+
let cur = "";
|
|
15039
|
+
for (let i2 = 0; i2 < s.length; i2++) {
|
|
15040
|
+
const c = s[i2];
|
|
15041
|
+
if (c === "\\") {
|
|
15042
|
+
cur += c + (s[i2 + 1] ?? "");
|
|
15043
|
+
i2++;
|
|
15044
|
+
continue;
|
|
15045
|
+
}
|
|
15046
|
+
if (inClass) {
|
|
15047
|
+
if (c === "]") inClass = false;
|
|
15048
|
+
cur += c;
|
|
15049
|
+
continue;
|
|
15050
|
+
}
|
|
15051
|
+
if (c === "[") {
|
|
15052
|
+
inClass = true;
|
|
15053
|
+
cur += c;
|
|
15054
|
+
continue;
|
|
15055
|
+
}
|
|
15056
|
+
if (c === "(") depth++;
|
|
15057
|
+
else if (c === ")") depth--;
|
|
15058
|
+
if (c === "|" && depth === 0) {
|
|
15059
|
+
alts.push(cur);
|
|
15060
|
+
cur = "";
|
|
15061
|
+
continue;
|
|
15062
|
+
}
|
|
15063
|
+
cur += c;
|
|
15064
|
+
}
|
|
15065
|
+
alts.push(cur);
|
|
15066
|
+
return alts;
|
|
15067
|
+
}
|
|
15068
|
+
function unwrapGroup(s) {
|
|
15069
|
+
const open = /^\((?:\?:)?/.exec(s);
|
|
15070
|
+
if (!open) return null;
|
|
15071
|
+
let depth = 0;
|
|
15072
|
+
let inClass = false;
|
|
15073
|
+
for (let i2 = 0; i2 < s.length; i2++) {
|
|
15074
|
+
const c = s[i2];
|
|
15075
|
+
if (c === "\\") {
|
|
15076
|
+
i2++;
|
|
15077
|
+
continue;
|
|
15078
|
+
}
|
|
15079
|
+
if (inClass) {
|
|
15080
|
+
if (c === "]") inClass = false;
|
|
15081
|
+
continue;
|
|
15082
|
+
}
|
|
15083
|
+
if (c === "[") {
|
|
15084
|
+
inClass = true;
|
|
15085
|
+
continue;
|
|
15086
|
+
}
|
|
15087
|
+
if (c === "(") depth++;
|
|
15088
|
+
else if (c === ")" && --depth === 0) {
|
|
15089
|
+
return { body: s.slice(open[0].length, i2), quantifier: s.slice(i2 + 1) };
|
|
15090
|
+
}
|
|
15091
|
+
}
|
|
15092
|
+
return null;
|
|
15093
|
+
}
|
|
15094
|
+
function javaReplaceCrLfCoverage(method, rawFrom) {
|
|
15095
|
+
const value = decodeJavaLiteral(rawFrom);
|
|
15096
|
+
if (method === "replace") return { cr: value === "\r", lf: value === "\n" };
|
|
15097
|
+
let body2 = value;
|
|
15098
|
+
const group = unwrapGroup(value);
|
|
15099
|
+
if (group) {
|
|
15100
|
+
if (!quantifierAllowsOne(group.quantifier)) return NONE;
|
|
15101
|
+
body2 = group.body;
|
|
15102
|
+
}
|
|
15103
|
+
let cr = false;
|
|
15104
|
+
let lf = false;
|
|
15105
|
+
for (const alt of splitAlternatives(body2)) {
|
|
15106
|
+
const c = alternativeCoverage(alt);
|
|
15107
|
+
if (c.cr) cr = true;
|
|
15108
|
+
if (c.lf) lf = true;
|
|
15109
|
+
}
|
|
15110
|
+
return { cr, lf };
|
|
15111
|
+
}
|
|
15112
|
+
|
|
14796
15113
|
// src/analysis/arg-type-resolver.ts
|
|
14797
15114
|
function findEnclosingType(callInMethod, types) {
|
|
14798
15115
|
if (!callInMethod) return null;
|
|
@@ -14893,7 +15210,7 @@ function analyzeTaint(calls, types, config = getDefaultConfig(), typeHierarchy,
|
|
|
14893
15210
|
sinkPatterns = expandIndirectEvalAliases(sinkPatterns, sourceLines, language);
|
|
14894
15211
|
const sinks = findSinks(calls, sinkPatterns, typeHierarchy, language, sourceLines, types);
|
|
14895
15212
|
const gatedSinks = language === "csharp" ? dropCSharpObjectCarriedWaypoints(sinks, calls) : sinks;
|
|
14896
|
-
const sanitizers = findSanitizers(calls, types, config.sanitizers, sourceLines);
|
|
15213
|
+
const sanitizers = findSanitizers(calls, types, config.sanitizers, sourceLines, language);
|
|
14897
15214
|
return { sources, sinks: gatedSinks, sanitizers };
|
|
14898
15215
|
}
|
|
14899
15216
|
function sinkPatternAppliesTo(pattern, language) {
|
|
@@ -15320,7 +15637,8 @@ function isInterproceduralTaintableType(typeName, language) {
|
|
|
15320
15637
|
}
|
|
15321
15638
|
function isParameterizedQueryCall(call, pattern) {
|
|
15322
15639
|
if (pattern.type !== "sql_injection") return false;
|
|
15323
|
-
const
|
|
15640
|
+
const positions = pattern.arg_positions ?? [];
|
|
15641
|
+
const queryPos = positions.length > 0 ? Math.min(...positions) : 0;
|
|
15324
15642
|
const queryArg = call.arguments.find((a) => a.position === queryPos);
|
|
15325
15643
|
if (queryArg) {
|
|
15326
15644
|
const queryText = queryArg.literal ?? queryArg.expression ?? "";
|
|
@@ -15911,6 +16229,25 @@ function isGoFprintToNonResponseWriter(call, pattern, language, sourceLines) {
|
|
|
15911
16229
|
}
|
|
15912
16230
|
return true;
|
|
15913
16231
|
}
|
|
16232
|
+
var JAVA_OUTBOUND_STREAM_OWNERS = "HttpURLConnection|HttpsURLConnection|URLConnection|JarURLConnection|Socket|SSLSocket|Process";
|
|
16233
|
+
function isJavaOutboundOutputStreamWrite(call, pattern, language, sourceLines) {
|
|
16234
|
+
if (language !== "java" || pattern.type !== "xss") return false;
|
|
16235
|
+
const receiver = (call.receiver ?? "").replace(/\s+/g, "");
|
|
16236
|
+
if (!receiver.endsWith(".getOutputStream()")) return false;
|
|
16237
|
+
if (/\.openConnection\(\)\)?\.getOutputStream\(\)$/.test(receiver)) return true;
|
|
16238
|
+
const m = receiver.match(/^\(?([A-Za-z_]\w*)\)?\.getOutputStream\(\)$/);
|
|
16239
|
+
if (!m || !sourceLines) return false;
|
|
16240
|
+
const declRe = new RegExp(
|
|
16241
|
+
`\\b(?:[\\w.]+\\.)?(?:${JAVA_OUTBOUND_STREAM_OWNERS})\\s+${m[1]}\\s*[=;,)]`
|
|
16242
|
+
);
|
|
16243
|
+
const anyDeclRe = new RegExp(`\\b(?:[A-Z][\\w.<>,\\[\\]]*|var)\\s+${m[1]}\\s*[=;,)]`);
|
|
16244
|
+
for (let i2 = call.location.line - 1; i2 >= 0; i2--) {
|
|
16245
|
+
const line = (sourceLines[i2] ?? "").replace(/"(?:[^"\\]|\\.)*"/g, '""').replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/.*$/, "");
|
|
16246
|
+
if (declRe.test(line)) return true;
|
|
16247
|
+
if (anyDeclRe.test(line)) return false;
|
|
16248
|
+
}
|
|
16249
|
+
return false;
|
|
16250
|
+
}
|
|
15914
16251
|
function isSafeGoJsonUnmarshalCall(call, pattern, language, sourceLines) {
|
|
15915
16252
|
if (language !== "go") return false;
|
|
15916
16253
|
if (pattern.type !== "deserialization") return false;
|
|
@@ -16060,6 +16397,9 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types)
|
|
|
16060
16397
|
if (isGoFprintToNonResponseWriter(call, pattern, language, sourceLines)) {
|
|
16061
16398
|
continue;
|
|
16062
16399
|
}
|
|
16400
|
+
if (isJavaOutboundOutputStreamWrite(call, pattern, language, sourceLines)) {
|
|
16401
|
+
continue;
|
|
16402
|
+
}
|
|
16063
16403
|
if (isSafeJinjaRenderCall(call, pattern, language, sourceLines)) {
|
|
16064
16404
|
continue;
|
|
16065
16405
|
}
|
|
@@ -16467,6 +16807,13 @@ function receiverMightBeClassImpl(receiver, className) {
|
|
|
16467
16807
|
if (receiver === className) {
|
|
16468
16808
|
return true;
|
|
16469
16809
|
}
|
|
16810
|
+
const inlineRequire = receiver.match(/^require\(\s*['"`](?:node:)?([\w./-]+)['"`]\s*\)$/);
|
|
16811
|
+
if (inlineRequire && inlineRequire[1] === className) {
|
|
16812
|
+
return true;
|
|
16813
|
+
}
|
|
16814
|
+
if (className === "ClassLoader" && /(?:^|\.)(?:getContextClassLoader|getClassLoader|getSystemClassLoader)\(\s*\)$/.test(receiver.replace(/\s+/g, ""))) {
|
|
16815
|
+
return true;
|
|
16816
|
+
}
|
|
16470
16817
|
if (receiver.endsWith(")")) {
|
|
16471
16818
|
const ctorMatch = receiver.match(/^(\w+)\(/);
|
|
16472
16819
|
if (ctorMatch) {
|
|
@@ -16755,8 +17102,9 @@ function calculateSinkConfidence(call, pattern) {
|
|
|
16755
17102
|
}
|
|
16756
17103
|
return Math.min(confidence, 1);
|
|
16757
17104
|
}
|
|
16758
|
-
function findSanitizers(calls, types, patterns, sourceLines) {
|
|
17105
|
+
function findSanitizers(calls, types, patterns, sourceLines, language) {
|
|
16759
17106
|
const sanitizers = [];
|
|
17107
|
+
const javaReplace = (call) => language === "java" && sourceLines && (call.method_name === "replace" || call.method_name === "replaceAll") ? javaReplaceChainCredit(javaChainTextAt(sourceLines, call.location.line, call.location.column)) : null;
|
|
16760
17108
|
const sanitizerMethods = /* @__PURE__ */ new Set();
|
|
16761
17109
|
for (const type of types) {
|
|
16762
17110
|
for (const method of type.methods) {
|
|
@@ -16781,10 +17129,15 @@ function findSanitizers(calls, types, patterns, sourceLines) {
|
|
|
16781
17129
|
if (inside.length !== 1) continue;
|
|
16782
17130
|
const innerCall = inside[0];
|
|
16783
17131
|
let matched;
|
|
16784
|
-
|
|
16785
|
-
|
|
16786
|
-
|
|
16787
|
-
|
|
17132
|
+
const replaceCredit = javaReplace(innerCall);
|
|
17133
|
+
if (replaceCredit) {
|
|
17134
|
+
matched = replaceCredit.length > 0 ? { method: innerCall.method_name, removes: replaceCredit } : void 0;
|
|
17135
|
+
} else {
|
|
17136
|
+
for (const pattern of patterns) {
|
|
17137
|
+
if (matchesSanitizerPattern(innerCall, pattern)) {
|
|
17138
|
+
matched = pattern;
|
|
17139
|
+
break;
|
|
17140
|
+
}
|
|
16788
17141
|
}
|
|
16789
17142
|
}
|
|
16790
17143
|
if (!matched || !matched.removes || matched.removes.length === 0) continue;
|
|
@@ -16836,6 +17189,18 @@ function findSanitizers(calls, types, patterns, sourceLines) {
|
|
|
16836
17189
|
});
|
|
16837
17190
|
continue;
|
|
16838
17191
|
}
|
|
17192
|
+
const replaceCredit = javaReplace(call);
|
|
17193
|
+
if (replaceCredit) {
|
|
17194
|
+
if (replaceCredit.length > 0) {
|
|
17195
|
+
sanitizers.push({
|
|
17196
|
+
type: "method_call",
|
|
17197
|
+
method: formatSanitizerMethod(call),
|
|
17198
|
+
line: call.location.line,
|
|
17199
|
+
sanitizes: replaceCredit
|
|
17200
|
+
});
|
|
17201
|
+
}
|
|
17202
|
+
continue;
|
|
17203
|
+
}
|
|
16839
17204
|
for (const pattern of patterns) {
|
|
16840
17205
|
if (matchesSanitizerPattern(call, pattern)) {
|
|
16841
17206
|
sanitizers.push({
|
|
@@ -16865,6 +17230,52 @@ function findSanitizers(calls, types, patterns, sourceLines) {
|
|
|
16865
17230
|
}
|
|
16866
17231
|
return sanitizers;
|
|
16867
17232
|
}
|
|
17233
|
+
var JAVA_LIT = String.raw`(?:"((?:\\.|[^"\\])*)"|'((?:\\.|[^'\\])*)')`;
|
|
17234
|
+
var JAVA_REPLACE_ARGS_RE = new RegExp(String.raw`\.(replace|replaceAll)\(\s*${JAVA_LIT}\s*,\s*${JAVA_LIT}\s*\)`, "g");
|
|
17235
|
+
function javaChainTextAt(sourceLines, line, column) {
|
|
17236
|
+
const text = [(sourceLines[line - 1] ?? "").slice(column), ...sourceLines.slice(line, line + 7)].join("\n");
|
|
17237
|
+
let depth = 0;
|
|
17238
|
+
let i2 = 0;
|
|
17239
|
+
for (; i2 < text.length; i2++) {
|
|
17240
|
+
const c = text[i2];
|
|
17241
|
+
if (c === '"' || c === "'") {
|
|
17242
|
+
for (i2++; i2 < text.length && text[i2] !== c; i2++) if (text[i2] === "\\") i2++;
|
|
17243
|
+
continue;
|
|
17244
|
+
}
|
|
17245
|
+
if (c === "(" || c === "[") {
|
|
17246
|
+
depth++;
|
|
17247
|
+
continue;
|
|
17248
|
+
}
|
|
17249
|
+
if (c === ")" || c === "]") {
|
|
17250
|
+
if (depth === 0) break;
|
|
17251
|
+
depth--;
|
|
17252
|
+
continue;
|
|
17253
|
+
}
|
|
17254
|
+
if (depth === 0 && !/[\w$.\s]/.test(c)) break;
|
|
17255
|
+
}
|
|
17256
|
+
return text.slice(0, i2);
|
|
17257
|
+
}
|
|
17258
|
+
function javaReplaceChainCredit(chainText) {
|
|
17259
|
+
let cr = false;
|
|
17260
|
+
let lf = false;
|
|
17261
|
+
let lt = false;
|
|
17262
|
+
let gt = false;
|
|
17263
|
+
for (const m of chainText.matchAll(JAVA_REPLACE_ARGS_RE)) {
|
|
17264
|
+
const kind = m[1];
|
|
17265
|
+
const rawFrom = m[2] ?? m[3] ?? "";
|
|
17266
|
+
const from = rawFrom.replace(/\\\\/g, "\\");
|
|
17267
|
+
const to = m[4] ?? m[5] ?? "";
|
|
17268
|
+
const cov = javaReplaceCrLfCoverage(kind, rawFrom);
|
|
17269
|
+
if (cov.cr) cr = true;
|
|
17270
|
+
if (cov.lf) lf = true;
|
|
17271
|
+
if (from === "<" && to.startsWith("&")) lt = true;
|
|
17272
|
+
if (from === ">" && to.startsWith("&")) gt = true;
|
|
17273
|
+
}
|
|
17274
|
+
const out2 = [];
|
|
17275
|
+
if (cr && lf) out2.push("log_injection");
|
|
17276
|
+
if (lt && gt) out2.push("xss");
|
|
17277
|
+
return out2;
|
|
17278
|
+
}
|
|
16868
17279
|
function matchesSanitizerPattern(call, pattern) {
|
|
16869
17280
|
if (pattern.method) {
|
|
16870
17281
|
if (call.method_name !== pattern.method) {
|
|
@@ -16905,6 +17316,15 @@ function formatSanitizerMethod(call) {
|
|
|
16905
17316
|
// src/graph/code-graph.ts
|
|
16906
17317
|
var CodeGraph = class {
|
|
16907
17318
|
ir;
|
|
17319
|
+
/**
|
|
17320
|
+
* Defs whose value cannot carry the content of the values they read, so
|
|
17321
|
+
* taint does not flow into them. Set by `analyze()`; empty for a bare graph.
|
|
17322
|
+
* - an int or bool measure or test (`len(x)`, `x.length`,
|
|
17323
|
+
* `re.MatchString(x)`) (#568)
|
|
17324
|
+
* - a lookup in a constant table the file never writes (`T[req.query.k]`)
|
|
17325
|
+
* (#667)
|
|
17326
|
+
*/
|
|
17327
|
+
taintBarrierDefIds = /* @__PURE__ */ new Set();
|
|
16908
17328
|
constructor(ir) {
|
|
16909
17329
|
this.ir = ir;
|
|
16910
17330
|
}
|
|
@@ -17159,6 +17579,7 @@ var CodeGraph = class {
|
|
|
17159
17579
|
for (const [fromDef, chains] of this.chainsByFromDef) {
|
|
17160
17580
|
if (!result.has(fromDef)) continue;
|
|
17161
17581
|
for (const chain of chains) {
|
|
17582
|
+
if (this.taintBarrierDefIds.has(chain.to_def)) continue;
|
|
17162
17583
|
if (!result.has(chain.to_def)) {
|
|
17163
17584
|
result.add(chain.to_def);
|
|
17164
17585
|
changed = true;
|
|
@@ -17244,6 +17665,10 @@ function buildSanitizersByLine(sanitizers) {
|
|
|
17244
17665
|
}
|
|
17245
17666
|
return out2;
|
|
17246
17667
|
}
|
|
17668
|
+
function templateInterpolationCount(expression) {
|
|
17669
|
+
if (!expression.trimStart().startsWith("`")) return 0;
|
|
17670
|
+
return (expression.match(/\$\{/g) ?? []).length;
|
|
17671
|
+
}
|
|
17247
17672
|
function concatOutsideStrings(expression) {
|
|
17248
17673
|
return expression.replace(/"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'/g, " ").includes("+");
|
|
17249
17674
|
}
|
|
@@ -17279,31 +17704,38 @@ function propagateTaint(graphOrDfg, callsOrSources, sourcesOrSinks, sinksOrSanit
|
|
|
17279
17704
|
const taintedVars = [];
|
|
17280
17705
|
const flows = [];
|
|
17281
17706
|
const javaConcat = graph.ir.meta.language === "java";
|
|
17707
|
+
const jsLike = graph.ir.meta.language === "javascript" || graph.ir.meta.language === "typescript";
|
|
17282
17708
|
const reachableSinks = /* @__PURE__ */ new Map();
|
|
17283
17709
|
const defsByLine = graph.defsByLine;
|
|
17284
17710
|
const usesByLine = graph.usesByLine;
|
|
17285
17711
|
const callsByLine = graph.callsByLine;
|
|
17286
17712
|
const sanitizersByLine = sanitizers.length > 0 ? buildSanitizersByLine(sanitizers) : graph.sanitizersByLine;
|
|
17287
17713
|
const defById = graph.defById;
|
|
17288
|
-
const rawInitialTaint = findInitialTaint(sources, callsByLine, defsByLine);
|
|
17289
|
-
const
|
|
17714
|
+
const rawInitialTaint = findInitialTaint(sources, callsByLine, defsByLine, graph.chainsByFromDef, jsLike);
|
|
17715
|
+
const filteredInitialTaint = rawInitialTaint.filter((tv) => {
|
|
17290
17716
|
if (tv.line === tv.sourceLine) return true;
|
|
17717
|
+
if (graph.taintBarrierDefIds.has(tv.defId)) return false;
|
|
17291
17718
|
const sanCheck = checkSanitized(tv.sourceLine, tv.line, tv.sourceType, sanitizersByLine);
|
|
17292
17719
|
return !sanCheck.sanitized;
|
|
17293
17720
|
});
|
|
17721
|
+
const initialTaint = [
|
|
17722
|
+
...filteredInitialTaint.filter((tv) => tv.sourceType !== "interprocedural_param"),
|
|
17723
|
+
...filteredInitialTaint.filter((tv) => tv.sourceType === "interprocedural_param")
|
|
17724
|
+
];
|
|
17294
17725
|
taintedVars.push(...initialTaint);
|
|
17295
17726
|
const propagatedTaint = propagateThroughChains(
|
|
17296
17727
|
initialTaint,
|
|
17297
17728
|
graph.chainsByFromDef,
|
|
17298
17729
|
defById,
|
|
17299
|
-
sanitizersByLine
|
|
17730
|
+
sanitizersByLine,
|
|
17731
|
+
graph.taintBarrierDefIds
|
|
17300
17732
|
);
|
|
17301
17733
|
taintedVars.push(...propagatedTaint);
|
|
17302
17734
|
const allTaintedDefIds = /* @__PURE__ */ new Set();
|
|
17303
17735
|
const taintByDefId = /* @__PURE__ */ new Map();
|
|
17304
17736
|
for (const tv of taintedVars) {
|
|
17305
17737
|
allTaintedDefIds.add(tv.defId);
|
|
17306
|
-
taintByDefId.set(tv.defId, tv);
|
|
17738
|
+
if (!yieldsToRealSource(taintByDefId.get(tv.defId), tv)) taintByDefId.set(tv.defId, tv);
|
|
17307
17739
|
}
|
|
17308
17740
|
for (const sink of sinks) {
|
|
17309
17741
|
const usesAtSink = usesByLine.get(sink.line) ?? [];
|
|
@@ -17316,7 +17748,7 @@ function propagateTaint(graphOrDfg, callsOrSources, sourcesOrSinks, sinksOrSanit
|
|
|
17316
17748
|
}
|
|
17317
17749
|
}
|
|
17318
17750
|
const expr = typeof arg.expression === "string" ? arg.expression : "";
|
|
17319
|
-
const compoundConcat =
|
|
17751
|
+
const compoundConcat = !!arg.variable && expr !== arg.variable && (javaConcat && concatOutsideStrings(expr) || jsLike && templateInterpolationCount(expr) > 1);
|
|
17320
17752
|
const candidateUses = arg.variable && !compoundConcat ? usesAtSink.filter((u) => u.variable === arg.variable) : usesAtSink;
|
|
17321
17753
|
{
|
|
17322
17754
|
for (const use of candidateUses) {
|
|
@@ -17329,11 +17761,26 @@ function propagateTaint(graphOrDfg, callsOrSources, sourcesOrSinks, sinksOrSanit
|
|
|
17329
17761
|
}
|
|
17330
17762
|
const taintInfo = taintByDefId.get(use.def_id);
|
|
17331
17763
|
if (taintInfo) {
|
|
17764
|
+
let sanMap = sanitizersByLine;
|
|
17765
|
+
if (identifierOutsideSanitizerCalls(
|
|
17766
|
+
use.variable,
|
|
17767
|
+
callsAtSink,
|
|
17768
|
+
sanitizersByLine.get(sink.line) ?? [],
|
|
17769
|
+
sink.type
|
|
17770
|
+
)) {
|
|
17771
|
+
const stripped = new Map(sanitizersByLine);
|
|
17772
|
+
const kept = (sanitizersByLine.get(sink.line) ?? []).filter(
|
|
17773
|
+
(s) => !sanitizerCoversSink(s, sink.type)
|
|
17774
|
+
);
|
|
17775
|
+
if (kept.length === 0) stripped.delete(sink.line);
|
|
17776
|
+
else stripped.set(sink.line, kept);
|
|
17777
|
+
sanMap = stripped;
|
|
17778
|
+
}
|
|
17332
17779
|
const isSanitized = checkSanitized(
|
|
17333
17780
|
taintInfo.line,
|
|
17334
17781
|
sink.line,
|
|
17335
17782
|
sink.type,
|
|
17336
|
-
|
|
17783
|
+
sanMap,
|
|
17337
17784
|
{
|
|
17338
17785
|
startDefId: use.def_id,
|
|
17339
17786
|
chainsByToDef: graph.chainsByToDef,
|
|
@@ -17374,12 +17821,46 @@ function propagateTaint(graphOrDfg, callsOrSources, sourcesOrSinks, sinksOrSanit
|
|
|
17374
17821
|
}
|
|
17375
17822
|
return { taintedVars, flows: deduped, reachableSinks };
|
|
17376
17823
|
}
|
|
17377
|
-
|
|
17824
|
+
var PARAM_SCOPED_SOURCE_RE = /^(?:GraphQL \w+\.\w+ resolver args|Express handler destructured request) /;
|
|
17825
|
+
function isCompleteConditionHeader(code) {
|
|
17826
|
+
if (!code) return false;
|
|
17827
|
+
const m = /^(?:\}\s*)?(?:else\s+)?(?:if|while)\s*\(/.exec(code);
|
|
17828
|
+
if (!m) return false;
|
|
17829
|
+
const text = code.slice(m[0].length - 1).replace(/"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|`(?:[^`\\]|\\.)*`/g, '""');
|
|
17830
|
+
let depth = 0;
|
|
17831
|
+
for (const ch of text) {
|
|
17832
|
+
if (ch === "(") depth++;
|
|
17833
|
+
else if (ch === ")" && --depth === 0) return true;
|
|
17834
|
+
}
|
|
17835
|
+
return false;
|
|
17836
|
+
}
|
|
17837
|
+
function hasSecondStatement(code) {
|
|
17838
|
+
if (!code || /^for\b/.test(code)) return false;
|
|
17839
|
+
const text = code.replace(/"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|`(?:[^`\\]|\\.)*`/g, '""');
|
|
17840
|
+
return /;\s*(?!\/\/|\/\*|#|\}|\)|$)\S/.test(text);
|
|
17841
|
+
}
|
|
17842
|
+
function findInitialTaint(sources, callsByLine, defsByLine, chainsByFromDef, jsLike = false) {
|
|
17378
17843
|
const tainted = [];
|
|
17379
17844
|
for (const source of sources) {
|
|
17380
17845
|
const defsOnLine = defsByLine.get(source.line) ?? [];
|
|
17846
|
+
let seedable = null;
|
|
17847
|
+
if (source.variable && hasSecondStatement(source.code)) {
|
|
17848
|
+
const own = defsOnLine.filter((d) => d.variable === source.variable);
|
|
17849
|
+
if (own.length > 0) {
|
|
17850
|
+
seedable = new Set(own.map((d) => d.id));
|
|
17851
|
+
for (const d of own) {
|
|
17852
|
+
for (const c of chainsByFromDef.get(d.id) ?? []) {
|
|
17853
|
+
if (defsOnLine.some((o) => o.id === c.to_def)) seedable.add(c.to_def);
|
|
17854
|
+
}
|
|
17855
|
+
}
|
|
17856
|
+
}
|
|
17857
|
+
}
|
|
17381
17858
|
for (const def of defsOnLine) {
|
|
17382
|
-
if (
|
|
17859
|
+
if (seedable && !seedable.has(def.id)) continue;
|
|
17860
|
+
if ((source.type === "interprocedural_param" || PARAM_SCOPED_SOURCE_RE.test(source.location ?? "")) && source.variable && def.kind === "param" && def.variable !== source.variable) {
|
|
17861
|
+
continue;
|
|
17862
|
+
}
|
|
17863
|
+
if (source.type === "interprocedural_param" && def.kind !== "param") {
|
|
17383
17864
|
continue;
|
|
17384
17865
|
}
|
|
17385
17866
|
tainted.push({
|
|
@@ -17391,7 +17872,8 @@ function findInitialTaint(sources, callsByLine, defsByLine) {
|
|
|
17391
17872
|
confidence: source.confidence
|
|
17392
17873
|
});
|
|
17393
17874
|
}
|
|
17394
|
-
const
|
|
17875
|
+
const closesOwnStatement = jsLike && defsOnLine.length > 0 && /;\s*(?:\/\/.*)?$/.test(source.code ?? "");
|
|
17876
|
+
const defsNextLine = isCompleteConditionHeader(source.code) ? [] : (defsByLine.get(source.line + 1) ?? []).filter((d) => !(closesOwnStatement && d.kind === "local"));
|
|
17395
17877
|
for (const def of defsNextLine) {
|
|
17396
17878
|
const callsOnSourceLine = callsByLine.get(source.line) ?? [];
|
|
17397
17879
|
if (callsOnSourceLine.length > 0) {
|
|
@@ -17425,12 +17907,15 @@ function findInitialTaint(sources, callsByLine, defsByLine) {
|
|
|
17425
17907
|
}
|
|
17426
17908
|
return tainted;
|
|
17427
17909
|
}
|
|
17428
|
-
function
|
|
17910
|
+
function yieldsToRealSource(existing, next) {
|
|
17911
|
+
return existing !== void 0 && next.sourceType === "interprocedural_param" && existing.sourceType !== "interprocedural_param";
|
|
17912
|
+
}
|
|
17913
|
+
function propagateThroughChains(initialTaint, chainsByFromDef, defById, sanitizersByLine, taintBarrierDefIds) {
|
|
17429
17914
|
const propagated = [];
|
|
17430
17915
|
const taintedDefIds = new Set(initialTaint.map((t) => t.defId));
|
|
17431
17916
|
const taintInfoByDefId = /* @__PURE__ */ new Map();
|
|
17432
17917
|
for (const t of initialTaint) {
|
|
17433
|
-
taintInfoByDefId.set(t.defId, t);
|
|
17918
|
+
if (!yieldsToRealSource(taintInfoByDefId.get(t.defId), t)) taintInfoByDefId.set(t.defId, t);
|
|
17434
17919
|
}
|
|
17435
17920
|
const queue = [...initialTaint.map((t) => t.defId)];
|
|
17436
17921
|
const visited = new Set(queue);
|
|
@@ -17441,6 +17926,7 @@ function propagateThroughChains(initialTaint, chainsByFromDef, defById, sanitize
|
|
|
17441
17926
|
const outgoingChains = chainsByFromDef.get(currentDefId) ?? [];
|
|
17442
17927
|
for (const chain of outgoingChains) {
|
|
17443
17928
|
if (visited.has(chain.to_def)) continue;
|
|
17929
|
+
if (taintBarrierDefIds.has(chain.to_def)) continue;
|
|
17444
17930
|
const targetDef = defById.get(chain.to_def);
|
|
17445
17931
|
if (!targetDef) continue;
|
|
17446
17932
|
const sanitizeCheck = checkSanitized(
|
|
@@ -17487,6 +17973,92 @@ var KNOWN_SINK_TYPES = /* @__PURE__ */ new Set([
|
|
|
17487
17973
|
"crlf",
|
|
17488
17974
|
"mass_assignment"
|
|
17489
17975
|
]);
|
|
17976
|
+
function readBalancedArg(text, openParen) {
|
|
17977
|
+
let depth = 0;
|
|
17978
|
+
let quote = null;
|
|
17979
|
+
let escaped = false;
|
|
17980
|
+
for (let i2 = openParen; i2 < text.length; i2++) {
|
|
17981
|
+
const c = text[i2];
|
|
17982
|
+
if (quote) {
|
|
17983
|
+
if (escaped) {
|
|
17984
|
+
escaped = false;
|
|
17985
|
+
continue;
|
|
17986
|
+
}
|
|
17987
|
+
if (c === "\\") {
|
|
17988
|
+
escaped = true;
|
|
17989
|
+
continue;
|
|
17990
|
+
}
|
|
17991
|
+
if (c === quote) quote = null;
|
|
17992
|
+
continue;
|
|
17993
|
+
}
|
|
17994
|
+
if (c === '"' || c === "'") {
|
|
17995
|
+
quote = c;
|
|
17996
|
+
continue;
|
|
17997
|
+
}
|
|
17998
|
+
if (c === "(") depth++;
|
|
17999
|
+
else if (c === ")") {
|
|
18000
|
+
depth--;
|
|
18001
|
+
if (depth === 0) return text.slice(openParen + 1, i2);
|
|
18002
|
+
}
|
|
18003
|
+
}
|
|
18004
|
+
return null;
|
|
18005
|
+
}
|
|
18006
|
+
function sanitizerCallRefs(sanitizers, sinkType) {
|
|
18007
|
+
const refs = [];
|
|
18008
|
+
for (const san of sanitizers) {
|
|
18009
|
+
if (!sanitizerCoversSink(san, sinkType)) continue;
|
|
18010
|
+
const m = /(?:(\w+)\.)?(\w+)\(\)/.exec(san.method);
|
|
18011
|
+
if (!m?.[2]) continue;
|
|
18012
|
+
refs.push({ receiver: m[1], methodName: m[2] });
|
|
18013
|
+
}
|
|
18014
|
+
return refs;
|
|
18015
|
+
}
|
|
18016
|
+
function maskSanitizerCalls(expr, refs) {
|
|
18017
|
+
let out2 = expr;
|
|
18018
|
+
for (const ref of refs) {
|
|
18019
|
+
const needles = ref.receiver ? [`${ref.receiver}.${ref.methodName}`, ref.methodName] : [ref.methodName];
|
|
18020
|
+
for (const needle of needles) {
|
|
18021
|
+
const re = new RegExp(`\\b${needle.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\s*\\(`);
|
|
18022
|
+
for (let guard = 0; guard < 8; guard++) {
|
|
18023
|
+
const m = re.exec(out2);
|
|
18024
|
+
if (!m) break;
|
|
18025
|
+
const open = m.index + m[0].length - 1;
|
|
18026
|
+
const arg = readBalancedArg(out2, open);
|
|
18027
|
+
if (arg === null) break;
|
|
18028
|
+
out2 = out2.slice(0, m.index) + " " + out2.slice(open + arg.length + 2);
|
|
18029
|
+
}
|
|
18030
|
+
}
|
|
18031
|
+
}
|
|
18032
|
+
return out2;
|
|
18033
|
+
}
|
|
18034
|
+
function maskLengthOf(expr) {
|
|
18035
|
+
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 ");
|
|
18036
|
+
}
|
|
18037
|
+
function blankStringLiterals(expr) {
|
|
18038
|
+
return expr.replace(/"(?:[^"\\]|\\.)*"/g, '""').replace(/'(?:[^'\\]|\\.)*'/g, "''");
|
|
18039
|
+
}
|
|
18040
|
+
function identifierOutsideSanitizerCalls(variable, calls, sanitizers, sinkType) {
|
|
18041
|
+
if (!/^[A-Za-z_$][\w$]*$/.test(variable)) return false;
|
|
18042
|
+
const refs = sanitizerCallRefs(sanitizers, sinkType);
|
|
18043
|
+
if (refs.length === 0) return false;
|
|
18044
|
+
const re = new RegExp(
|
|
18045
|
+
`(?:^|[^A-Za-z0-9_$])${variable.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:[^A-Za-z0-9_$]|$)`
|
|
18046
|
+
);
|
|
18047
|
+
for (const call of calls) {
|
|
18048
|
+
if (refs.some((r) => r.methodName === call.method_name)) continue;
|
|
18049
|
+
const masked = call.arguments.map((arg) => {
|
|
18050
|
+
const expr = arg.expression ?? "";
|
|
18051
|
+
return { expr, masked: expr ? maskSanitizerCalls(expr, refs) : expr };
|
|
18052
|
+
});
|
|
18053
|
+
if (!masked.some((m) => m.masked !== m.expr)) continue;
|
|
18054
|
+
for (const m of masked) {
|
|
18055
|
+
if (!m.masked) continue;
|
|
18056
|
+
const text = m.masked === m.expr ? maskLengthOf(m.masked) : m.masked;
|
|
18057
|
+
if (re.test(blankStringLiterals(text))) return true;
|
|
18058
|
+
}
|
|
18059
|
+
}
|
|
18060
|
+
return false;
|
|
18061
|
+
}
|
|
17490
18062
|
function checkSanitized(fromLine, toLine, sinkType, sanitizersByLine, ctx) {
|
|
17491
18063
|
const isKnownSinkType = KNOWN_SINK_TYPES.has(sinkType);
|
|
17492
18064
|
const sanitizersAtTarget = sanitizersByLine.get(toLine);
|