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
|
@@ -9841,6 +9841,7 @@ function processStatement2(stmt, startDefId, startUseId, scopeStack, isJavaScrip
|
|
|
9841
9841
|
};
|
|
9842
9842
|
defs.push(def);
|
|
9843
9843
|
currentScope(scopeStack).set(name2, def.id);
|
|
9844
|
+
markDeclared(currentScope(scopeStack), name2);
|
|
9844
9845
|
} else if (isJavaScript) {
|
|
9845
9846
|
const destructDefs = extractDestructuringDefs(nameNode, defId);
|
|
9846
9847
|
for (const def of destructDefs) {
|
|
@@ -9887,21 +9888,31 @@ function processStatement2(stmt, startDefId, startUseId, scopeStack, isJavaScrip
|
|
|
9887
9888
|
useId = condUses.nextId;
|
|
9888
9889
|
}
|
|
9889
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;
|
|
9890
9897
|
if (consequence) {
|
|
9891
|
-
const result =
|
|
9898
|
+
const result = processArm(consequence, defId, useId, scopeStack, isJavaScript);
|
|
9899
|
+
armA = result.popped;
|
|
9892
9900
|
defs.push(...result.defs);
|
|
9893
9901
|
uses.push(...result.uses);
|
|
9894
9902
|
defId = result.nextDefId;
|
|
9895
9903
|
useId = result.nextUseId;
|
|
9896
9904
|
}
|
|
9897
|
-
const alternative = stmt.childForFieldName("alternative");
|
|
9898
9905
|
if (alternative) {
|
|
9899
|
-
const result =
|
|
9906
|
+
const result = processArm(alternative, defId, useId, scopeStack, isJavaScript);
|
|
9907
|
+
armB = result.popped;
|
|
9900
9908
|
defs.push(...result.defs);
|
|
9901
9909
|
uses.push(...result.uses);
|
|
9902
9910
|
defId = result.nextDefId;
|
|
9903
9911
|
useId = result.nextUseId;
|
|
9904
9912
|
}
|
|
9913
|
+
if (armA && armB) {
|
|
9914
|
+
mergeIfElseDefs(armA, armB, before, scopeStack);
|
|
9915
|
+
}
|
|
9905
9916
|
break;
|
|
9906
9917
|
}
|
|
9907
9918
|
case "for_statement":
|
|
@@ -9972,7 +9983,7 @@ function processStatement2(stmt, startDefId, startUseId, scopeStack, isJavaScrip
|
|
|
9972
9983
|
}
|
|
9973
9984
|
const body2 = stmt.childForFieldName("body");
|
|
9974
9985
|
if (body2) {
|
|
9975
|
-
const result =
|
|
9986
|
+
const result = processArm(body2, defId, useId, scopeStack, isJavaScript);
|
|
9976
9987
|
defs.push(...result.defs);
|
|
9977
9988
|
uses.push(...result.uses);
|
|
9978
9989
|
defId = result.nextDefId;
|
|
@@ -10000,7 +10011,8 @@ function processStatement2(stmt, startDefId, startUseId, scopeStack, isJavaScrip
|
|
|
10000
10011
|
defId = result.nextDefId;
|
|
10001
10012
|
useId = result.nextUseId;
|
|
10002
10013
|
}
|
|
10003
|
-
scopeStack.pop();
|
|
10014
|
+
const poppedSwitch = scopeStack.pop();
|
|
10015
|
+
if (!isJavaScript) carryBranchDefsOut(poppedSwitch, scopeStack, false, true);
|
|
10004
10016
|
}
|
|
10005
10017
|
break;
|
|
10006
10018
|
}
|
|
@@ -10012,7 +10024,10 @@ function processStatement2(stmt, startDefId, startUseId, scopeStack, isJavaScrip
|
|
|
10012
10024
|
uses.push(...result.uses);
|
|
10013
10025
|
defId = result.nextDefId;
|
|
10014
10026
|
useId = result.nextUseId;
|
|
10015
|
-
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);
|
|
10016
10031
|
break;
|
|
10017
10032
|
}
|
|
10018
10033
|
default: {
|
|
@@ -10054,6 +10069,7 @@ function processExpression(expr, startDefId, startUseId, scopeStack, isJavaScrip
|
|
|
10054
10069
|
};
|
|
10055
10070
|
defs.push(def);
|
|
10056
10071
|
currentScope(scopeStack).set(name2, def.id);
|
|
10072
|
+
alternateDefs.get(currentScope(scopeStack))?.delete(name2);
|
|
10057
10073
|
} else if (left) {
|
|
10058
10074
|
const leftUses = extractUses(left, useId, scopeStack, isJavaScript);
|
|
10059
10075
|
uses.push(...leftUses.uses);
|
|
@@ -10137,6 +10153,10 @@ function extractUses(node, startId, scopeStack, isJavaScript) {
|
|
|
10137
10153
|
line: n.startPosition.row + 1,
|
|
10138
10154
|
def_id: reachingDef
|
|
10139
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
|
+
}
|
|
10140
10160
|
}
|
|
10141
10161
|
});
|
|
10142
10162
|
return { uses, nextId: id };
|
|
@@ -10389,6 +10409,134 @@ function extractDestructuringDefs(node, startId) {
|
|
|
10389
10409
|
function currentScope(scopeStack) {
|
|
10390
10410
|
return scopeStack[scopeStack.length - 1];
|
|
10391
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
|
+
}
|
|
10392
10540
|
function findReachingDef(name2, scopeStack) {
|
|
10393
10541
|
for (let i2 = scopeStack.length - 1; i2 >= 0; i2--) {
|
|
10394
10542
|
const defId = scopeStack[i2].get(name2);
|
|
@@ -14745,6 +14893,157 @@ function getDefaultConfig() {
|
|
|
14745
14893
|
};
|
|
14746
14894
|
}
|
|
14747
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
|
+
|
|
14748
15047
|
// src/analysis/arg-type-resolver.ts
|
|
14749
15048
|
function findEnclosingType(callInMethod, types) {
|
|
14750
15049
|
if (!callInMethod) return null;
|
|
@@ -14845,7 +15144,7 @@ function analyzeTaint(calls, types, config = getDefaultConfig(), typeHierarchy,
|
|
|
14845
15144
|
sinkPatterns = expandIndirectEvalAliases(sinkPatterns, sourceLines, language);
|
|
14846
15145
|
const sinks = findSinks(calls, sinkPatterns, typeHierarchy, language, sourceLines, types);
|
|
14847
15146
|
const gatedSinks = language === "csharp" ? dropCSharpObjectCarriedWaypoints(sinks, calls) : sinks;
|
|
14848
|
-
const sanitizers = findSanitizers(calls, types, config.sanitizers, sourceLines);
|
|
15147
|
+
const sanitizers = findSanitizers(calls, types, config.sanitizers, sourceLines, language);
|
|
14849
15148
|
return { sources, sinks: gatedSinks, sanitizers };
|
|
14850
15149
|
}
|
|
14851
15150
|
function sinkPatternAppliesTo(pattern, language) {
|
|
@@ -15864,6 +16163,25 @@ function isGoFprintToNonResponseWriter(call, pattern, language, sourceLines) {
|
|
|
15864
16163
|
}
|
|
15865
16164
|
return true;
|
|
15866
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
|
+
}
|
|
15867
16185
|
function isSafeGoJsonUnmarshalCall(call, pattern, language, sourceLines) {
|
|
15868
16186
|
if (language !== "go") return false;
|
|
15869
16187
|
if (pattern.type !== "deserialization") return false;
|
|
@@ -16013,6 +16331,9 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types)
|
|
|
16013
16331
|
if (isGoFprintToNonResponseWriter(call, pattern, language, sourceLines)) {
|
|
16014
16332
|
continue;
|
|
16015
16333
|
}
|
|
16334
|
+
if (isJavaOutboundOutputStreamWrite(call, pattern, language, sourceLines)) {
|
|
16335
|
+
continue;
|
|
16336
|
+
}
|
|
16016
16337
|
if (isSafeJinjaRenderCall(call, pattern, language, sourceLines)) {
|
|
16017
16338
|
continue;
|
|
16018
16339
|
}
|
|
@@ -16420,6 +16741,13 @@ function receiverMightBeClassImpl(receiver, className) {
|
|
|
16420
16741
|
if (receiver === className) {
|
|
16421
16742
|
return true;
|
|
16422
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
|
+
}
|
|
16423
16751
|
if (receiver.endsWith(")")) {
|
|
16424
16752
|
const ctorMatch = receiver.match(/^(\w+)\(/);
|
|
16425
16753
|
if (ctorMatch) {
|
|
@@ -16708,8 +17036,9 @@ function calculateSinkConfidence(call, pattern) {
|
|
|
16708
17036
|
}
|
|
16709
17037
|
return Math.min(confidence, 1);
|
|
16710
17038
|
}
|
|
16711
|
-
function findSanitizers(calls, types, patterns, sourceLines) {
|
|
17039
|
+
function findSanitizers(calls, types, patterns, sourceLines, language) {
|
|
16712
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;
|
|
16713
17042
|
const sanitizerMethods = /* @__PURE__ */ new Set();
|
|
16714
17043
|
for (const type of types) {
|
|
16715
17044
|
for (const method of type.methods) {
|
|
@@ -16734,10 +17063,15 @@ function findSanitizers(calls, types, patterns, sourceLines) {
|
|
|
16734
17063
|
if (inside.length !== 1) continue;
|
|
16735
17064
|
const innerCall = inside[0];
|
|
16736
17065
|
let matched;
|
|
16737
|
-
|
|
16738
|
-
|
|
16739
|
-
|
|
16740
|
-
|
|
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
|
+
}
|
|
16741
17075
|
}
|
|
16742
17076
|
}
|
|
16743
17077
|
if (!matched || !matched.removes || matched.removes.length === 0) continue;
|
|
@@ -16789,6 +17123,18 @@ function findSanitizers(calls, types, patterns, sourceLines) {
|
|
|
16789
17123
|
});
|
|
16790
17124
|
continue;
|
|
16791
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
|
+
}
|
|
16792
17138
|
for (const pattern of patterns) {
|
|
16793
17139
|
if (matchesSanitizerPattern(call, pattern)) {
|
|
16794
17140
|
sanitizers.push({
|
|
@@ -16818,6 +17164,52 @@ function findSanitizers(calls, types, patterns, sourceLines) {
|
|
|
16818
17164
|
}
|
|
16819
17165
|
return sanitizers;
|
|
16820
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
|
+
}
|
|
16821
17213
|
function matchesSanitizerPattern(call, pattern) {
|
|
16822
17214
|
if (pattern.method) {
|
|
16823
17215
|
if (call.method_name !== pattern.method) {
|
|
@@ -16858,6 +17250,15 @@ function formatSanitizerMethod(call) {
|
|
|
16858
17250
|
// src/graph/code-graph.ts
|
|
16859
17251
|
var CodeGraph = class {
|
|
16860
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();
|
|
16861
17262
|
constructor(ir) {
|
|
16862
17263
|
this.ir = ir;
|
|
16863
17264
|
}
|
|
@@ -17112,6 +17513,7 @@ var CodeGraph = class {
|
|
|
17112
17513
|
for (const [fromDef, chains] of this.chainsByFromDef) {
|
|
17113
17514
|
if (!result.has(fromDef)) continue;
|
|
17114
17515
|
for (const chain of chains) {
|
|
17516
|
+
if (this.taintBarrierDefIds.has(chain.to_def)) continue;
|
|
17115
17517
|
if (!result.has(chain.to_def)) {
|
|
17116
17518
|
result.add(chain.to_def);
|
|
17117
17519
|
changed = true;
|
|
@@ -17197,6 +17599,10 @@ function buildSanitizersByLine(sanitizers) {
|
|
|
17197
17599
|
}
|
|
17198
17600
|
return out2;
|
|
17199
17601
|
}
|
|
17602
|
+
function templateInterpolationCount(expression) {
|
|
17603
|
+
if (!expression.trimStart().startsWith("`")) return 0;
|
|
17604
|
+
return (expression.match(/\$\{/g) ?? []).length;
|
|
17605
|
+
}
|
|
17200
17606
|
function concatOutsideStrings(expression) {
|
|
17201
17607
|
return expression.replace(/"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'/g, " ").includes("+");
|
|
17202
17608
|
}
|
|
@@ -17232,31 +17638,38 @@ function propagateTaint(graphOrDfg, callsOrSources, sourcesOrSinks, sinksOrSanit
|
|
|
17232
17638
|
const taintedVars = [];
|
|
17233
17639
|
const flows = [];
|
|
17234
17640
|
const javaConcat = graph.ir.meta.language === "java";
|
|
17641
|
+
const jsLike = graph.ir.meta.language === "javascript" || graph.ir.meta.language === "typescript";
|
|
17235
17642
|
const reachableSinks = /* @__PURE__ */ new Map();
|
|
17236
17643
|
const defsByLine = graph.defsByLine;
|
|
17237
17644
|
const usesByLine = graph.usesByLine;
|
|
17238
17645
|
const callsByLine = graph.callsByLine;
|
|
17239
17646
|
const sanitizersByLine = sanitizers.length > 0 ? buildSanitizersByLine(sanitizers) : graph.sanitizersByLine;
|
|
17240
17647
|
const defById = graph.defById;
|
|
17241
|
-
const rawInitialTaint = findInitialTaint(sources, callsByLine, defsByLine);
|
|
17242
|
-
const
|
|
17648
|
+
const rawInitialTaint = findInitialTaint(sources, callsByLine, defsByLine, graph.chainsByFromDef, jsLike);
|
|
17649
|
+
const filteredInitialTaint = rawInitialTaint.filter((tv) => {
|
|
17243
17650
|
if (tv.line === tv.sourceLine) return true;
|
|
17651
|
+
if (graph.taintBarrierDefIds.has(tv.defId)) return false;
|
|
17244
17652
|
const sanCheck = checkSanitized(tv.sourceLine, tv.line, tv.sourceType, sanitizersByLine);
|
|
17245
17653
|
return !sanCheck.sanitized;
|
|
17246
17654
|
});
|
|
17655
|
+
const initialTaint = [
|
|
17656
|
+
...filteredInitialTaint.filter((tv) => tv.sourceType !== "interprocedural_param"),
|
|
17657
|
+
...filteredInitialTaint.filter((tv) => tv.sourceType === "interprocedural_param")
|
|
17658
|
+
];
|
|
17247
17659
|
taintedVars.push(...initialTaint);
|
|
17248
17660
|
const propagatedTaint = propagateThroughChains(
|
|
17249
17661
|
initialTaint,
|
|
17250
17662
|
graph.chainsByFromDef,
|
|
17251
17663
|
defById,
|
|
17252
|
-
sanitizersByLine
|
|
17664
|
+
sanitizersByLine,
|
|
17665
|
+
graph.taintBarrierDefIds
|
|
17253
17666
|
);
|
|
17254
17667
|
taintedVars.push(...propagatedTaint);
|
|
17255
17668
|
const allTaintedDefIds = /* @__PURE__ */ new Set();
|
|
17256
17669
|
const taintByDefId = /* @__PURE__ */ new Map();
|
|
17257
17670
|
for (const tv of taintedVars) {
|
|
17258
17671
|
allTaintedDefIds.add(tv.defId);
|
|
17259
|
-
taintByDefId.set(tv.defId, tv);
|
|
17672
|
+
if (!yieldsToRealSource(taintByDefId.get(tv.defId), tv)) taintByDefId.set(tv.defId, tv);
|
|
17260
17673
|
}
|
|
17261
17674
|
for (const sink of sinks) {
|
|
17262
17675
|
const usesAtSink = usesByLine.get(sink.line) ?? [];
|
|
@@ -17269,7 +17682,7 @@ function propagateTaint(graphOrDfg, callsOrSources, sourcesOrSinks, sinksOrSanit
|
|
|
17269
17682
|
}
|
|
17270
17683
|
}
|
|
17271
17684
|
const expr = typeof arg.expression === "string" ? arg.expression : "";
|
|
17272
|
-
const compoundConcat =
|
|
17685
|
+
const compoundConcat = !!arg.variable && expr !== arg.variable && (javaConcat && concatOutsideStrings(expr) || jsLike && templateInterpolationCount(expr) > 1);
|
|
17273
17686
|
const candidateUses = arg.variable && !compoundConcat ? usesAtSink.filter((u) => u.variable === arg.variable) : usesAtSink;
|
|
17274
17687
|
{
|
|
17275
17688
|
for (const use of candidateUses) {
|
|
@@ -17342,12 +17755,46 @@ function propagateTaint(graphOrDfg, callsOrSources, sourcesOrSinks, sinksOrSanit
|
|
|
17342
17755
|
}
|
|
17343
17756
|
return { taintedVars, flows: deduped, reachableSinks };
|
|
17344
17757
|
}
|
|
17345
|
-
|
|
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) {
|
|
17346
17777
|
const tainted = [];
|
|
17347
17778
|
for (const source of sources) {
|
|
17348
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
|
+
}
|
|
17349
17792
|
for (const def of defsOnLine) {
|
|
17350
|
-
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") {
|
|
17351
17798
|
continue;
|
|
17352
17799
|
}
|
|
17353
17800
|
tainted.push({
|
|
@@ -17359,7 +17806,8 @@ function findInitialTaint(sources, callsByLine, defsByLine) {
|
|
|
17359
17806
|
confidence: source.confidence
|
|
17360
17807
|
});
|
|
17361
17808
|
}
|
|
17362
|
-
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"));
|
|
17363
17811
|
for (const def of defsNextLine) {
|
|
17364
17812
|
const callsOnSourceLine = callsByLine.get(source.line) ?? [];
|
|
17365
17813
|
if (callsOnSourceLine.length > 0) {
|
|
@@ -17393,12 +17841,15 @@ function findInitialTaint(sources, callsByLine, defsByLine) {
|
|
|
17393
17841
|
}
|
|
17394
17842
|
return tainted;
|
|
17395
17843
|
}
|
|
17396
|
-
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) {
|
|
17397
17848
|
const propagated = [];
|
|
17398
17849
|
const taintedDefIds = new Set(initialTaint.map((t) => t.defId));
|
|
17399
17850
|
const taintInfoByDefId = /* @__PURE__ */ new Map();
|
|
17400
17851
|
for (const t of initialTaint) {
|
|
17401
|
-
taintInfoByDefId.set(t.defId, t);
|
|
17852
|
+
if (!yieldsToRealSource(taintInfoByDefId.get(t.defId), t)) taintInfoByDefId.set(t.defId, t);
|
|
17402
17853
|
}
|
|
17403
17854
|
const queue = [...initialTaint.map((t) => t.defId)];
|
|
17404
17855
|
const visited = new Set(queue);
|
|
@@ -17409,6 +17860,7 @@ function propagateThroughChains(initialTaint, chainsByFromDef, defById, sanitize
|
|
|
17409
17860
|
const outgoingChains = chainsByFromDef.get(currentDefId) ?? [];
|
|
17410
17861
|
for (const chain of outgoingChains) {
|
|
17411
17862
|
if (visited.has(chain.to_def)) continue;
|
|
17863
|
+
if (taintBarrierDefIds.has(chain.to_def)) continue;
|
|
17412
17864
|
const targetDef = defById.get(chain.to_def);
|
|
17413
17865
|
if (!targetDef) continue;
|
|
17414
17866
|
const sanitizeCheck = checkSanitized(
|
|
@@ -17513,6 +17965,9 @@ function maskSanitizerCalls(expr, refs) {
|
|
|
17513
17965
|
}
|
|
17514
17966
|
return out2;
|
|
17515
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
|
+
}
|
|
17516
17971
|
function blankStringLiterals(expr) {
|
|
17517
17972
|
return expr.replace(/"(?:[^"\\]|\\.)*"/g, '""').replace(/'(?:[^'\\]|\\.)*'/g, "''");
|
|
17518
17973
|
}
|
|
@@ -17525,12 +17980,15 @@ function identifierOutsideSanitizerCalls(variable, calls, sanitizers, sinkType)
|
|
|
17525
17980
|
);
|
|
17526
17981
|
for (const call of calls) {
|
|
17527
17982
|
if (refs.some((r) => r.methodName === call.method_name)) continue;
|
|
17528
|
-
|
|
17983
|
+
const masked = call.arguments.map((arg) => {
|
|
17529
17984
|
const expr = arg.expression ?? "";
|
|
17530
|
-
|
|
17531
|
-
|
|
17532
|
-
|
|
17533
|
-
|
|
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;
|
|
17534
17992
|
}
|
|
17535
17993
|
}
|
|
17536
17994
|
return false;
|