circle-ir 4.14.2 → 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/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/interprocedural-pass.d.ts +28 -0
- package/dist/analysis/passes/interprocedural-pass.d.ts.map +1 -1
- package/dist/analysis/passes/interprocedural-pass.js +20 -1
- 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 +842 -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/sink-filter-pass.d.ts.map +1 -1
- package/dist/analysis/passes/sink-filter-pass.js +207 -1
- 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 +257 -2
- 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 +167 -6
- package/dist/analysis/taint-matcher.js.map +1 -1
- package/dist/analysis/taint-propagation.d.ts.map +1 -1
- package/dist/analysis/taint-propagation.js +149 -19
- package/dist/analysis/taint-propagation.js.map +1 -1
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +7 -0
- package/dist/analyzer.js.map +1 -1
- package/dist/browser/circle-ir.js +3140 -1186
- package/dist/core/circle-ir-core.cjs +485 -27
- package/dist/core/circle-ir-core.js +485 -27
- 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/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
|
@@ -9907,6 +9907,7 @@ function processStatement2(stmt, startDefId, startUseId, scopeStack, isJavaScrip
|
|
|
9907
9907
|
};
|
|
9908
9908
|
defs.push(def);
|
|
9909
9909
|
currentScope(scopeStack).set(name2, def.id);
|
|
9910
|
+
markDeclared(currentScope(scopeStack), name2);
|
|
9910
9911
|
} else if (isJavaScript) {
|
|
9911
9912
|
const destructDefs = extractDestructuringDefs(nameNode, defId);
|
|
9912
9913
|
for (const def of destructDefs) {
|
|
@@ -9953,21 +9954,31 @@ function processStatement2(stmt, startDefId, startUseId, scopeStack, isJavaScrip
|
|
|
9953
9954
|
useId = condUses.nextId;
|
|
9954
9955
|
}
|
|
9955
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;
|
|
9956
9963
|
if (consequence) {
|
|
9957
|
-
const result =
|
|
9964
|
+
const result = processArm(consequence, defId, useId, scopeStack, isJavaScript);
|
|
9965
|
+
armA = result.popped;
|
|
9958
9966
|
defs.push(...result.defs);
|
|
9959
9967
|
uses.push(...result.uses);
|
|
9960
9968
|
defId = result.nextDefId;
|
|
9961
9969
|
useId = result.nextUseId;
|
|
9962
9970
|
}
|
|
9963
|
-
const alternative = stmt.childForFieldName("alternative");
|
|
9964
9971
|
if (alternative) {
|
|
9965
|
-
const result =
|
|
9972
|
+
const result = processArm(alternative, defId, useId, scopeStack, isJavaScript);
|
|
9973
|
+
armB = result.popped;
|
|
9966
9974
|
defs.push(...result.defs);
|
|
9967
9975
|
uses.push(...result.uses);
|
|
9968
9976
|
defId = result.nextDefId;
|
|
9969
9977
|
useId = result.nextUseId;
|
|
9970
9978
|
}
|
|
9979
|
+
if (armA && armB) {
|
|
9980
|
+
mergeIfElseDefs(armA, armB, before, scopeStack);
|
|
9981
|
+
}
|
|
9971
9982
|
break;
|
|
9972
9983
|
}
|
|
9973
9984
|
case "for_statement":
|
|
@@ -10038,7 +10049,7 @@ function processStatement2(stmt, startDefId, startUseId, scopeStack, isJavaScrip
|
|
|
10038
10049
|
}
|
|
10039
10050
|
const body2 = stmt.childForFieldName("body");
|
|
10040
10051
|
if (body2) {
|
|
10041
|
-
const result =
|
|
10052
|
+
const result = processArm(body2, defId, useId, scopeStack, isJavaScript);
|
|
10042
10053
|
defs.push(...result.defs);
|
|
10043
10054
|
uses.push(...result.uses);
|
|
10044
10055
|
defId = result.nextDefId;
|
|
@@ -10066,7 +10077,8 @@ function processStatement2(stmt, startDefId, startUseId, scopeStack, isJavaScrip
|
|
|
10066
10077
|
defId = result.nextDefId;
|
|
10067
10078
|
useId = result.nextUseId;
|
|
10068
10079
|
}
|
|
10069
|
-
scopeStack.pop();
|
|
10080
|
+
const poppedSwitch = scopeStack.pop();
|
|
10081
|
+
if (!isJavaScript) carryBranchDefsOut(poppedSwitch, scopeStack, false, true);
|
|
10070
10082
|
}
|
|
10071
10083
|
break;
|
|
10072
10084
|
}
|
|
@@ -10078,7 +10090,10 @@ function processStatement2(stmt, startDefId, startUseId, scopeStack, isJavaScrip
|
|
|
10078
10090
|
uses.push(...result.uses);
|
|
10079
10091
|
defId = result.nextDefId;
|
|
10080
10092
|
useId = result.nextUseId;
|
|
10081
|
-
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);
|
|
10082
10097
|
break;
|
|
10083
10098
|
}
|
|
10084
10099
|
default: {
|
|
@@ -10120,6 +10135,7 @@ function processExpression(expr, startDefId, startUseId, scopeStack, isJavaScrip
|
|
|
10120
10135
|
};
|
|
10121
10136
|
defs.push(def);
|
|
10122
10137
|
currentScope(scopeStack).set(name2, def.id);
|
|
10138
|
+
alternateDefs.get(currentScope(scopeStack))?.delete(name2);
|
|
10123
10139
|
} else if (left) {
|
|
10124
10140
|
const leftUses = extractUses(left, useId, scopeStack, isJavaScript);
|
|
10125
10141
|
uses.push(...leftUses.uses);
|
|
@@ -10203,6 +10219,10 @@ function extractUses(node, startId, scopeStack, isJavaScript) {
|
|
|
10203
10219
|
line: n.startPosition.row + 1,
|
|
10204
10220
|
def_id: reachingDef
|
|
10205
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
|
+
}
|
|
10206
10226
|
}
|
|
10207
10227
|
});
|
|
10208
10228
|
return { uses, nextId: id };
|
|
@@ -10455,6 +10475,134 @@ function extractDestructuringDefs(node, startId) {
|
|
|
10455
10475
|
function currentScope(scopeStack) {
|
|
10456
10476
|
return scopeStack[scopeStack.length - 1];
|
|
10457
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
|
+
}
|
|
10458
10606
|
function findReachingDef(name2, scopeStack) {
|
|
10459
10607
|
for (let i2 = scopeStack.length - 1; i2 >= 0; i2--) {
|
|
10460
10608
|
const defId = scopeStack[i2].get(name2);
|
|
@@ -14811,6 +14959,157 @@ function getDefaultConfig() {
|
|
|
14811
14959
|
};
|
|
14812
14960
|
}
|
|
14813
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
|
+
|
|
14814
15113
|
// src/analysis/arg-type-resolver.ts
|
|
14815
15114
|
function findEnclosingType(callInMethod, types) {
|
|
14816
15115
|
if (!callInMethod) return null;
|
|
@@ -14911,7 +15210,7 @@ function analyzeTaint(calls, types, config = getDefaultConfig(), typeHierarchy,
|
|
|
14911
15210
|
sinkPatterns = expandIndirectEvalAliases(sinkPatterns, sourceLines, language);
|
|
14912
15211
|
const sinks = findSinks(calls, sinkPatterns, typeHierarchy, language, sourceLines, types);
|
|
14913
15212
|
const gatedSinks = language === "csharp" ? dropCSharpObjectCarriedWaypoints(sinks, calls) : sinks;
|
|
14914
|
-
const sanitizers = findSanitizers(calls, types, config.sanitizers, sourceLines);
|
|
15213
|
+
const sanitizers = findSanitizers(calls, types, config.sanitizers, sourceLines, language);
|
|
14915
15214
|
return { sources, sinks: gatedSinks, sanitizers };
|
|
14916
15215
|
}
|
|
14917
15216
|
function sinkPatternAppliesTo(pattern, language) {
|
|
@@ -15930,6 +16229,25 @@ function isGoFprintToNonResponseWriter(call, pattern, language, sourceLines) {
|
|
|
15930
16229
|
}
|
|
15931
16230
|
return true;
|
|
15932
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
|
+
}
|
|
15933
16251
|
function isSafeGoJsonUnmarshalCall(call, pattern, language, sourceLines) {
|
|
15934
16252
|
if (language !== "go") return false;
|
|
15935
16253
|
if (pattern.type !== "deserialization") return false;
|
|
@@ -16079,6 +16397,9 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types)
|
|
|
16079
16397
|
if (isGoFprintToNonResponseWriter(call, pattern, language, sourceLines)) {
|
|
16080
16398
|
continue;
|
|
16081
16399
|
}
|
|
16400
|
+
if (isJavaOutboundOutputStreamWrite(call, pattern, language, sourceLines)) {
|
|
16401
|
+
continue;
|
|
16402
|
+
}
|
|
16082
16403
|
if (isSafeJinjaRenderCall(call, pattern, language, sourceLines)) {
|
|
16083
16404
|
continue;
|
|
16084
16405
|
}
|
|
@@ -16486,6 +16807,13 @@ function receiverMightBeClassImpl(receiver, className) {
|
|
|
16486
16807
|
if (receiver === className) {
|
|
16487
16808
|
return true;
|
|
16488
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
|
+
}
|
|
16489
16817
|
if (receiver.endsWith(")")) {
|
|
16490
16818
|
const ctorMatch = receiver.match(/^(\w+)\(/);
|
|
16491
16819
|
if (ctorMatch) {
|
|
@@ -16774,8 +17102,9 @@ function calculateSinkConfidence(call, pattern) {
|
|
|
16774
17102
|
}
|
|
16775
17103
|
return Math.min(confidence, 1);
|
|
16776
17104
|
}
|
|
16777
|
-
function findSanitizers(calls, types, patterns, sourceLines) {
|
|
17105
|
+
function findSanitizers(calls, types, patterns, sourceLines, language) {
|
|
16778
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;
|
|
16779
17108
|
const sanitizerMethods = /* @__PURE__ */ new Set();
|
|
16780
17109
|
for (const type of types) {
|
|
16781
17110
|
for (const method of type.methods) {
|
|
@@ -16800,10 +17129,15 @@ function findSanitizers(calls, types, patterns, sourceLines) {
|
|
|
16800
17129
|
if (inside.length !== 1) continue;
|
|
16801
17130
|
const innerCall = inside[0];
|
|
16802
17131
|
let matched;
|
|
16803
|
-
|
|
16804
|
-
|
|
16805
|
-
|
|
16806
|
-
|
|
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
|
+
}
|
|
16807
17141
|
}
|
|
16808
17142
|
}
|
|
16809
17143
|
if (!matched || !matched.removes || matched.removes.length === 0) continue;
|
|
@@ -16855,6 +17189,18 @@ function findSanitizers(calls, types, patterns, sourceLines) {
|
|
|
16855
17189
|
});
|
|
16856
17190
|
continue;
|
|
16857
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
|
+
}
|
|
16858
17204
|
for (const pattern of patterns) {
|
|
16859
17205
|
if (matchesSanitizerPattern(call, pattern)) {
|
|
16860
17206
|
sanitizers.push({
|
|
@@ -16884,6 +17230,52 @@ function findSanitizers(calls, types, patterns, sourceLines) {
|
|
|
16884
17230
|
}
|
|
16885
17231
|
return sanitizers;
|
|
16886
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
|
+
}
|
|
16887
17279
|
function matchesSanitizerPattern(call, pattern) {
|
|
16888
17280
|
if (pattern.method) {
|
|
16889
17281
|
if (call.method_name !== pattern.method) {
|
|
@@ -16924,6 +17316,15 @@ function formatSanitizerMethod(call) {
|
|
|
16924
17316
|
// src/graph/code-graph.ts
|
|
16925
17317
|
var CodeGraph = class {
|
|
16926
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();
|
|
16927
17328
|
constructor(ir) {
|
|
16928
17329
|
this.ir = ir;
|
|
16929
17330
|
}
|
|
@@ -17178,6 +17579,7 @@ var CodeGraph = class {
|
|
|
17178
17579
|
for (const [fromDef, chains] of this.chainsByFromDef) {
|
|
17179
17580
|
if (!result.has(fromDef)) continue;
|
|
17180
17581
|
for (const chain of chains) {
|
|
17582
|
+
if (this.taintBarrierDefIds.has(chain.to_def)) continue;
|
|
17181
17583
|
if (!result.has(chain.to_def)) {
|
|
17182
17584
|
result.add(chain.to_def);
|
|
17183
17585
|
changed = true;
|
|
@@ -17263,6 +17665,10 @@ function buildSanitizersByLine(sanitizers) {
|
|
|
17263
17665
|
}
|
|
17264
17666
|
return out2;
|
|
17265
17667
|
}
|
|
17668
|
+
function templateInterpolationCount(expression) {
|
|
17669
|
+
if (!expression.trimStart().startsWith("`")) return 0;
|
|
17670
|
+
return (expression.match(/\$\{/g) ?? []).length;
|
|
17671
|
+
}
|
|
17266
17672
|
function concatOutsideStrings(expression) {
|
|
17267
17673
|
return expression.replace(/"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'/g, " ").includes("+");
|
|
17268
17674
|
}
|
|
@@ -17298,31 +17704,38 @@ function propagateTaint(graphOrDfg, callsOrSources, sourcesOrSinks, sinksOrSanit
|
|
|
17298
17704
|
const taintedVars = [];
|
|
17299
17705
|
const flows = [];
|
|
17300
17706
|
const javaConcat = graph.ir.meta.language === "java";
|
|
17707
|
+
const jsLike = graph.ir.meta.language === "javascript" || graph.ir.meta.language === "typescript";
|
|
17301
17708
|
const reachableSinks = /* @__PURE__ */ new Map();
|
|
17302
17709
|
const defsByLine = graph.defsByLine;
|
|
17303
17710
|
const usesByLine = graph.usesByLine;
|
|
17304
17711
|
const callsByLine = graph.callsByLine;
|
|
17305
17712
|
const sanitizersByLine = sanitizers.length > 0 ? buildSanitizersByLine(sanitizers) : graph.sanitizersByLine;
|
|
17306
17713
|
const defById = graph.defById;
|
|
17307
|
-
const rawInitialTaint = findInitialTaint(sources, callsByLine, defsByLine);
|
|
17308
|
-
const
|
|
17714
|
+
const rawInitialTaint = findInitialTaint(sources, callsByLine, defsByLine, graph.chainsByFromDef, jsLike);
|
|
17715
|
+
const filteredInitialTaint = rawInitialTaint.filter((tv) => {
|
|
17309
17716
|
if (tv.line === tv.sourceLine) return true;
|
|
17717
|
+
if (graph.taintBarrierDefIds.has(tv.defId)) return false;
|
|
17310
17718
|
const sanCheck = checkSanitized(tv.sourceLine, tv.line, tv.sourceType, sanitizersByLine);
|
|
17311
17719
|
return !sanCheck.sanitized;
|
|
17312
17720
|
});
|
|
17721
|
+
const initialTaint = [
|
|
17722
|
+
...filteredInitialTaint.filter((tv) => tv.sourceType !== "interprocedural_param"),
|
|
17723
|
+
...filteredInitialTaint.filter((tv) => tv.sourceType === "interprocedural_param")
|
|
17724
|
+
];
|
|
17313
17725
|
taintedVars.push(...initialTaint);
|
|
17314
17726
|
const propagatedTaint = propagateThroughChains(
|
|
17315
17727
|
initialTaint,
|
|
17316
17728
|
graph.chainsByFromDef,
|
|
17317
17729
|
defById,
|
|
17318
|
-
sanitizersByLine
|
|
17730
|
+
sanitizersByLine,
|
|
17731
|
+
graph.taintBarrierDefIds
|
|
17319
17732
|
);
|
|
17320
17733
|
taintedVars.push(...propagatedTaint);
|
|
17321
17734
|
const allTaintedDefIds = /* @__PURE__ */ new Set();
|
|
17322
17735
|
const taintByDefId = /* @__PURE__ */ new Map();
|
|
17323
17736
|
for (const tv of taintedVars) {
|
|
17324
17737
|
allTaintedDefIds.add(tv.defId);
|
|
17325
|
-
taintByDefId.set(tv.defId, tv);
|
|
17738
|
+
if (!yieldsToRealSource(taintByDefId.get(tv.defId), tv)) taintByDefId.set(tv.defId, tv);
|
|
17326
17739
|
}
|
|
17327
17740
|
for (const sink of sinks) {
|
|
17328
17741
|
const usesAtSink = usesByLine.get(sink.line) ?? [];
|
|
@@ -17335,7 +17748,7 @@ function propagateTaint(graphOrDfg, callsOrSources, sourcesOrSinks, sinksOrSanit
|
|
|
17335
17748
|
}
|
|
17336
17749
|
}
|
|
17337
17750
|
const expr = typeof arg.expression === "string" ? arg.expression : "";
|
|
17338
|
-
const compoundConcat =
|
|
17751
|
+
const compoundConcat = !!arg.variable && expr !== arg.variable && (javaConcat && concatOutsideStrings(expr) || jsLike && templateInterpolationCount(expr) > 1);
|
|
17339
17752
|
const candidateUses = arg.variable && !compoundConcat ? usesAtSink.filter((u) => u.variable === arg.variable) : usesAtSink;
|
|
17340
17753
|
{
|
|
17341
17754
|
for (const use of candidateUses) {
|
|
@@ -17408,12 +17821,46 @@ function propagateTaint(graphOrDfg, callsOrSources, sourcesOrSinks, sinksOrSanit
|
|
|
17408
17821
|
}
|
|
17409
17822
|
return { taintedVars, flows: deduped, reachableSinks };
|
|
17410
17823
|
}
|
|
17411
|
-
|
|
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) {
|
|
17412
17843
|
const tainted = [];
|
|
17413
17844
|
for (const source of sources) {
|
|
17414
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
|
+
}
|
|
17415
17858
|
for (const def of defsOnLine) {
|
|
17416
|
-
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") {
|
|
17417
17864
|
continue;
|
|
17418
17865
|
}
|
|
17419
17866
|
tainted.push({
|
|
@@ -17425,7 +17872,8 @@ function findInitialTaint(sources, callsByLine, defsByLine) {
|
|
|
17425
17872
|
confidence: source.confidence
|
|
17426
17873
|
});
|
|
17427
17874
|
}
|
|
17428
|
-
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"));
|
|
17429
17877
|
for (const def of defsNextLine) {
|
|
17430
17878
|
const callsOnSourceLine = callsByLine.get(source.line) ?? [];
|
|
17431
17879
|
if (callsOnSourceLine.length > 0) {
|
|
@@ -17459,12 +17907,15 @@ function findInitialTaint(sources, callsByLine, defsByLine) {
|
|
|
17459
17907
|
}
|
|
17460
17908
|
return tainted;
|
|
17461
17909
|
}
|
|
17462
|
-
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) {
|
|
17463
17914
|
const propagated = [];
|
|
17464
17915
|
const taintedDefIds = new Set(initialTaint.map((t) => t.defId));
|
|
17465
17916
|
const taintInfoByDefId = /* @__PURE__ */ new Map();
|
|
17466
17917
|
for (const t of initialTaint) {
|
|
17467
|
-
taintInfoByDefId.set(t.defId, t);
|
|
17918
|
+
if (!yieldsToRealSource(taintInfoByDefId.get(t.defId), t)) taintInfoByDefId.set(t.defId, t);
|
|
17468
17919
|
}
|
|
17469
17920
|
const queue = [...initialTaint.map((t) => t.defId)];
|
|
17470
17921
|
const visited = new Set(queue);
|
|
@@ -17475,6 +17926,7 @@ function propagateThroughChains(initialTaint, chainsByFromDef, defById, sanitize
|
|
|
17475
17926
|
const outgoingChains = chainsByFromDef.get(currentDefId) ?? [];
|
|
17476
17927
|
for (const chain of outgoingChains) {
|
|
17477
17928
|
if (visited.has(chain.to_def)) continue;
|
|
17929
|
+
if (taintBarrierDefIds.has(chain.to_def)) continue;
|
|
17478
17930
|
const targetDef = defById.get(chain.to_def);
|
|
17479
17931
|
if (!targetDef) continue;
|
|
17480
17932
|
const sanitizeCheck = checkSanitized(
|
|
@@ -17579,6 +18031,9 @@ function maskSanitizerCalls(expr, refs) {
|
|
|
17579
18031
|
}
|
|
17580
18032
|
return out2;
|
|
17581
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
|
+
}
|
|
17582
18037
|
function blankStringLiterals(expr) {
|
|
17583
18038
|
return expr.replace(/"(?:[^"\\]|\\.)*"/g, '""').replace(/'(?:[^'\\]|\\.)*'/g, "''");
|
|
17584
18039
|
}
|
|
@@ -17591,12 +18046,15 @@ function identifierOutsideSanitizerCalls(variable, calls, sanitizers, sinkType)
|
|
|
17591
18046
|
);
|
|
17592
18047
|
for (const call of calls) {
|
|
17593
18048
|
if (refs.some((r) => r.methodName === call.method_name)) continue;
|
|
17594
|
-
|
|
18049
|
+
const masked = call.arguments.map((arg) => {
|
|
17595
18050
|
const expr = arg.expression ?? "";
|
|
17596
|
-
|
|
17597
|
-
|
|
17598
|
-
|
|
17599
|
-
|
|
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;
|
|
17600
18058
|
}
|
|
17601
18059
|
}
|
|
17602
18060
|
return false;
|