circle-ir 4.16.0 → 4.17.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/constant-propagation/index.d.ts +2 -0
- package/dist/analysis/constant-propagation/index.d.ts.map +1 -1
- package/dist/analysis/constant-propagation/index.js +10 -0
- package/dist/analysis/constant-propagation/index.js.map +1 -1
- package/dist/analysis/constant-propagation/propagator.d.ts +11 -0
- package/dist/analysis/constant-propagation/propagator.d.ts.map +1 -1
- package/dist/analysis/constant-propagation/propagator.js +125 -20
- package/dist/analysis/constant-propagation/propagator.js.map +1 -1
- package/dist/analysis/constant-propagation/types.d.ts +2 -0
- package/dist/analysis/constant-propagation/types.d.ts.map +1 -1
- package/dist/analysis/constant-propagation.d.ts +1 -1
- package/dist/analysis/constant-propagation.d.ts.map +1 -1
- package/dist/analysis/constant-propagation.js +1 -1
- package/dist/analysis/constant-propagation.js.map +1 -1
- package/dist/analysis/interprocedural.d.ts.map +1 -1
- package/dist/analysis/interprocedural.js +37 -8
- package/dist/analysis/interprocedural.js.map +1 -1
- package/dist/analysis/passes/cross-file-pass.d.ts.map +1 -1
- package/dist/analysis/passes/cross-file-pass.js +3 -1
- package/dist/analysis/passes/cross-file-pass.js.map +1 -1
- package/dist/analysis/passes/csharp-sanitizer-credit.d.ts +13 -0
- package/dist/analysis/passes/csharp-sanitizer-credit.d.ts.map +1 -1
- package/dist/analysis/passes/csharp-sanitizer-credit.js +48 -0
- package/dist/analysis/passes/csharp-sanitizer-credit.js.map +1 -1
- package/dist/analysis/passes/language-sources-pass.d.ts.map +1 -1
- package/dist/analysis/passes/language-sources-pass.js +80 -8
- package/dist/analysis/passes/language-sources-pass.js.map +1 -1
- package/dist/analysis/passes/param-source-flow-gate-pass.d.ts.map +1 -1
- package/dist/analysis/passes/param-source-flow-gate-pass.js +17 -1
- 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 +19 -3
- 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 +205 -22
- package/dist/analysis/passes/taint-propagation-pass.js.map +1 -1
- package/dist/analysis/rust-literal-guard.d.ts.map +1 -1
- package/dist/analysis/rust-literal-guard.js +100 -1
- package/dist/analysis/rust-literal-guard.js.map +1 -1
- package/dist/analysis/taint-matcher.d.ts.map +1 -1
- package/dist/analysis/taint-matcher.js +1 -0
- package/dist/analysis/taint-matcher.js.map +1 -1
- package/dist/browser/circle-ir.js +561 -41
- package/dist/core/circle-ir-core.cjs +149 -17
- package/dist/core/circle-ir-core.js +149 -17
- package/dist/core/extractors/types.js +53 -9
- package/dist/core/extractors/types.js.map +1 -1
- package/dist/navigation/navigation-index.d.ts +40 -1
- package/dist/navigation/navigation-index.d.ts.map +1 -1
- package/dist/navigation/navigation-index.js +300 -45
- package/dist/navigation/navigation-index.js.map +1 -1
- package/dist/resolution/cross-file.d.ts +9 -0
- package/dist/resolution/cross-file.d.ts.map +1 -1
- package/dist/resolution/cross-file.js +134 -0
- package/dist/resolution/cross-file.js.map +1 -1
- package/dist/types/index.d.ts +18 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -4663,14 +4663,43 @@ function extractJavaTypes(tree, cache, options) {
|
|
|
4663
4663
|
);
|
|
4664
4664
|
const path = node ? enclosingTypePath(node) : [];
|
|
4665
4665
|
if (path.length > 0) t.enclosing_type = path.join(".");
|
|
4666
|
-
const
|
|
4667
|
-
if (
|
|
4668
|
-
|
|
4669
|
-
|
|
4666
|
+
const tp = typeParameters(node);
|
|
4667
|
+
if (tp.names.length > 0) t.type_parameters = tp.names;
|
|
4668
|
+
if (Object.keys(tp.bounds).length > 0) t.type_parameter_bounds = tp.bounds;
|
|
4669
|
+
}
|
|
4670
|
+
const allMethods = /* @__PURE__ */ new Map();
|
|
4671
|
+
for (const t of types) {
|
|
4672
|
+
for (const m of t.methods) allMethods.set(`${m.start_line}:${m.name}`, m);
|
|
4673
|
+
}
|
|
4674
|
+
for (const kind of ["method_declaration", "constructor_declaration"]) {
|
|
4675
|
+
for (const node of getNodesFromCache(tree.rootNode, kind, cache)) {
|
|
4676
|
+
const name2 = getIdentifier(node, "name");
|
|
4677
|
+
const info2 = name2 ? allMethods.get(`${node.startPosition.row + 1}:${name2}`) : void 0;
|
|
4678
|
+
if (!info2) continue;
|
|
4679
|
+
const tp = typeParameters(node);
|
|
4680
|
+
if (tp.names.length > 0) info2.type_parameters = tp.names;
|
|
4681
|
+
if (Object.keys(tp.bounds).length > 0) info2.type_parameter_bounds = tp.bounds;
|
|
4670
4682
|
}
|
|
4671
4683
|
}
|
|
4672
4684
|
return types;
|
|
4673
4685
|
}
|
|
4686
|
+
function typeParameters(node) {
|
|
4687
|
+
const names = [];
|
|
4688
|
+
const bounds = {};
|
|
4689
|
+
const list = node ? node.childForFieldName("type_parameters") ?? node.namedChildren.find((c) => c?.type === "type_parameters") ?? null : null;
|
|
4690
|
+
if (!list) return { names, bounds };
|
|
4691
|
+
for (const c of list.namedChildren) {
|
|
4692
|
+
if (c?.type !== "type_parameter") continue;
|
|
4693
|
+
const id = c.namedChildren.find((x) => x?.type === "type_identifier" || x?.type === "identifier");
|
|
4694
|
+
const name2 = id ? getNodeText(id) : "";
|
|
4695
|
+
if (!name2) continue;
|
|
4696
|
+
names.push(name2);
|
|
4697
|
+
const bound = c.namedChildren.find((x) => x?.type === "type_bound");
|
|
4698
|
+
const first = bound?.namedChildren.find((x) => x !== null && x.type !== "annotation" && x.type !== "marker_annotation");
|
|
4699
|
+
if (first) bounds[name2] = getNodeText(first);
|
|
4700
|
+
}
|
|
4701
|
+
return { names, bounds };
|
|
4702
|
+
}
|
|
4674
4703
|
function recordComponents(node) {
|
|
4675
4704
|
const out2 = [];
|
|
4676
4705
|
const params = node.childForFieldName("parameters");
|
|
@@ -16735,6 +16764,85 @@ function passesMutParam(line, n) {
|
|
|
16735
16764
|
if (!new RegExp(String.raw`(?<![\w.])(?:[A-Za-z_]\w*(?:\s*::\s*[A-Za-z_]\w*)*)\s*\((?:[^)]*,\s*)?${n}\b`).test(line)) return false;
|
|
16736
16765
|
return !new RegExp(String.raw`\b${n}\s*\.\s*\w+\s*\(`).test(line) || new RegExp(String.raw`(?<![\w.])\w+\s*\(\s*${n}\b`).test(line);
|
|
16737
16766
|
}
|
|
16767
|
+
function findRustAllowlistTernarySanitizers(code) {
|
|
16768
|
+
const masked = maskRust(code);
|
|
16769
|
+
const origLines = code.split("\n");
|
|
16770
|
+
const maskedLines = masked.split("\n");
|
|
16771
|
+
const out2 = [];
|
|
16772
|
+
const allowlistVar = (cond) => {
|
|
16773
|
+
const c = cond.trim();
|
|
16774
|
+
let m = new RegExp(String.raw`^!?(\w+)\s*\.\s*contains\s*\(\s*&\s*(\w+)(?:\s*\.\s*as_str\s*\(\s*\))?\s*\)$`).exec(c);
|
|
16775
|
+
if (m) return m[2];
|
|
16776
|
+
if (m = new RegExp(String.raw`^!\s*\[\s*${STR}(?:\s*,\s*${STR})*\s*,?\s*\]\s*\.\s*contains\s*\(\s*&\s*${VAR}\s*\)$`).exec(c)) {
|
|
16777
|
+
return m[1];
|
|
16778
|
+
}
|
|
16779
|
+
return guardedVariable(c);
|
|
16780
|
+
};
|
|
16781
|
+
const LITERAL_CHAIN = /* @__PURE__ */ new Set(["to_string", "into", "to_owned", "clone"]);
|
|
16782
|
+
const literalOnly = (block) => {
|
|
16783
|
+
const maskedB = maskRust(block.trim());
|
|
16784
|
+
if (!maskedB.trim()) return false;
|
|
16785
|
+
const re = /[A-Za-z_]\w*/g;
|
|
16786
|
+
let m;
|
|
16787
|
+
while ((m = re.exec(maskedB)) !== null) {
|
|
16788
|
+
const id = m[0];
|
|
16789
|
+
if (KEYWORDS.has(id) || /[A-Z]/.test(id[0]) || LITERAL_CHAIN.has(id)) continue;
|
|
16790
|
+
return false;
|
|
16791
|
+
}
|
|
16792
|
+
return true;
|
|
16793
|
+
};
|
|
16794
|
+
const usesOnlyVar = (block, name2) => {
|
|
16795
|
+
const b = block.trim();
|
|
16796
|
+
if (!b) return false;
|
|
16797
|
+
const maskedB = maskRust(b);
|
|
16798
|
+
const re = new RegExp(String.raw`(?<![\w.])${name2}\b`);
|
|
16799
|
+
if (!re.test(maskedB)) return false;
|
|
16800
|
+
return !readsOtherValue(b, maskedB, name2);
|
|
16801
|
+
};
|
|
16802
|
+
for (const m of masked.matchAll(/\blet\s+(\w+)\s*=\s*if\b/g)) {
|
|
16803
|
+
const bound = m[1];
|
|
16804
|
+
const at = m.index + m[0].length - 2;
|
|
16805
|
+
let depth = 0;
|
|
16806
|
+
let open = -1;
|
|
16807
|
+
for (let i2 = at + 2; i2 < masked.length; i2++) {
|
|
16808
|
+
const ch = masked[i2];
|
|
16809
|
+
if (ch === "(" || ch === "[") depth++;
|
|
16810
|
+
else if (ch === ")" || ch === "]") depth--;
|
|
16811
|
+
else if (ch === "{" && depth === 0) {
|
|
16812
|
+
open = i2;
|
|
16813
|
+
break;
|
|
16814
|
+
} else if (ch === ";" || ch === "}") break;
|
|
16815
|
+
}
|
|
16816
|
+
if (open < 0) continue;
|
|
16817
|
+
const cond = code.slice(at + 2, open).trim();
|
|
16818
|
+
const guarded = allowlistVar(cond);
|
|
16819
|
+
if (!guarded) continue;
|
|
16820
|
+
const thenClose = matchBrace(masked, open);
|
|
16821
|
+
if (thenClose < 0) continue;
|
|
16822
|
+
const thenBody = code.slice(open + 1, thenClose);
|
|
16823
|
+
if (!/^\s*else\b/.test(masked.slice(thenClose + 1, thenClose + 10))) continue;
|
|
16824
|
+
const elseOpen = masked.indexOf("{", thenClose + 1);
|
|
16825
|
+
if (elseOpen < 0) continue;
|
|
16826
|
+
const elseClose = matchBrace(masked, elseOpen);
|
|
16827
|
+
if (elseClose < 0) continue;
|
|
16828
|
+
const elseBody = code.slice(elseOpen + 1, elseClose);
|
|
16829
|
+
const inverted = /^\s*!/.test(cond);
|
|
16830
|
+
const ok = inverted ? literalOnly(thenBody) && usesOnlyVar(elseBody, guarded) : usesOnlyVar(thenBody, guarded) && literalOnly(elseBody);
|
|
16831
|
+
if (!ok) continue;
|
|
16832
|
+
const assignLine = masked.slice(0, at).split("\n").length;
|
|
16833
|
+
const mention = new RegExp(String.raw`(?<![\w.])${bound}\b`);
|
|
16834
|
+
for (let l = assignLine; l < origLines.length; l++) {
|
|
16835
|
+
if (!mention.test(maskedLines[l])) continue;
|
|
16836
|
+
out2.push({
|
|
16837
|
+
type: "rust_allowlist_ternary",
|
|
16838
|
+
method: "guard",
|
|
16839
|
+
line: l + 1,
|
|
16840
|
+
sanitizes: CREDITED_TYPES
|
|
16841
|
+
});
|
|
16842
|
+
}
|
|
16843
|
+
}
|
|
16844
|
+
return out2;
|
|
16845
|
+
}
|
|
16738
16846
|
function findRustLiteralEqualityGuardSanitizers(code) {
|
|
16739
16847
|
const masked = maskRust(code);
|
|
16740
16848
|
const origLines = code.split("\n");
|
|
@@ -16855,7 +16963,7 @@ function findRustLiteralEqualityGuardSanitizers(code) {
|
|
|
16855
16963
|
});
|
|
16856
16964
|
}
|
|
16857
16965
|
}
|
|
16858
|
-
return out2;
|
|
16966
|
+
return [...out2, ...findRustAllowlistTernarySanitizers(code)];
|
|
16859
16967
|
}
|
|
16860
16968
|
function statementSpan(code, masked, lineStart, endIdx) {
|
|
16861
16969
|
let start2 = lineStart;
|
|
@@ -18233,6 +18341,7 @@ function findCSharpRequestSources(sourceCode, language) {
|
|
|
18233
18341
|
const assignRe = /^\s*(?:await\s+)?(?:using\s*\(?\s*)?(?:var\s+|[A-Za-z_][\w.<>\[\]]*\s+)?([A-Za-z_]\w*)\s*=\s*(.+?);?\s*$/;
|
|
18234
18342
|
const arrayElementAssignRe = /^\s*([A-Za-z_]\w*)\s*\[[^\]]+\]\s*=\s*(.+?);?\s*$/;
|
|
18235
18343
|
const memberFieldAssignRe = /^\s*([A-Za-z_]\w*)\.([A-Za-z_]\w*)\s*=\s*(.+?);?\s*$/;
|
|
18344
|
+
const collectionAddRe = /^\s*([A-Za-z_]\w*)\s*\.\s*Add\s*\(\s*(.+?)\s*\)\s*;?\s*$/;
|
|
18236
18345
|
const consoleReadRe = /\bConsole\s*\.\s*ReadLine\s*\(/;
|
|
18237
18346
|
const envReadRe = /\bEnvironment\s*\.\s*GetEnvironmentVariables?\s*(?:\(|\[)/;
|
|
18238
18347
|
const formFileParams = /* @__PURE__ */ new Set();
|
|
@@ -18326,6 +18435,25 @@ function findCSharpRequestSources(sourceCode, language) {
|
|
|
18326
18435
|
continue;
|
|
18327
18436
|
}
|
|
18328
18437
|
}
|
|
18438
|
+
const addEl = collectionAddRe.exec(stmt);
|
|
18439
|
+
if (addEl) {
|
|
18440
|
+
const [, baseName, rhs2] = addEl;
|
|
18441
|
+
const type2 = classify(rhs2);
|
|
18442
|
+
if (type2 && baseName) {
|
|
18443
|
+
const lineNumber2 = i2 + 1;
|
|
18444
|
+
if (!sources.some((s) => s.line === lineNumber2 && s.variable === baseName)) {
|
|
18445
|
+
sources.push({
|
|
18446
|
+
type: type2,
|
|
18447
|
+
location: `${baseName}.Add(${rhs2.trim().substring(0, 50)}${rhs2.length > 50 ? "..." : ""})`,
|
|
18448
|
+
severity: type2 === "db_input" ? "medium" : "high",
|
|
18449
|
+
line: lineNumber2,
|
|
18450
|
+
confidence: 1,
|
|
18451
|
+
variable: baseName
|
|
18452
|
+
});
|
|
18453
|
+
}
|
|
18454
|
+
continue;
|
|
18455
|
+
}
|
|
18456
|
+
}
|
|
18329
18457
|
const m = assignRe.exec(stmt);
|
|
18330
18458
|
const rhsCrossesBlock = m !== null && m[2].includes("{");
|
|
18331
18459
|
if (!m || rhsCrossesBlock) {
|
|
@@ -21865,14 +21993,17 @@ function findCSharpEarlyReturnGuardSanitizers(code, language) {
|
|
|
21865
21993
|
const sanitizers = [];
|
|
21866
21994
|
const lines = code.split("\n");
|
|
21867
21995
|
const terminatorRe = /\b(throw|return)\b/;
|
|
21996
|
+
const literalArrays = collectCSharpLiteralArrayFields(code);
|
|
21997
|
+
const indexOfRe = /\bif\s*\(\s*(?:System\s*\.\s*)?Array\s*\.\s*IndexOf\s*\(\s*([A-Za-z_]\w*)\s*,\s*([A-Za-z_][\w.]*)\s*\)\s*(?:<\s*0|==\s*-\s*1)\s*\)/;
|
|
21868
21998
|
const allowlistRes = [
|
|
21869
|
-
/\bif\s*\(\s*Array\s*\.\s*IndexOf\s*\([^,]+,\s*([A-Za-z_][\w.]*)\s*\)\s*<\s*0\s*\)/,
|
|
21870
21999
|
/\bif\s*\(\s*!\s*[A-Za-z_][\w.]*\s*\.\s*Contains\s*\(\s*([A-Za-z_][\w.]*)\s*\)\s*\)/
|
|
21871
22000
|
];
|
|
21872
22001
|
const charClassRe = /\bif\s*\(\s*!\s*([A-Za-z_][\w.]*)\s*\.\s*All\s*\(\s*char\s*\.\s*Is[A-Za-z]+\s*\)\s*\)/;
|
|
21873
22002
|
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
21874
22003
|
let guardedExpr = null;
|
|
21875
|
-
|
|
22004
|
+
const im = indexOfRe.exec(lines[i2]);
|
|
22005
|
+
if (im && literalArrays.has(im[1])) guardedExpr = im[2];
|
|
22006
|
+
if (!guardedExpr) for (const re of allowlistRes) {
|
|
21876
22007
|
const m = re.exec(lines[i2]);
|
|
21877
22008
|
if (m) {
|
|
21878
22009
|
guardedExpr = m[1];
|
|
@@ -21900,6 +22031,7 @@ function findCSharpEarlyReturnGuardSanitizers(code, language) {
|
|
|
21900
22031
|
for (let l = i2 + 1; l < lines.length; l++) {
|
|
21901
22032
|
const a = assignRe.exec(lines[l]);
|
|
21902
22033
|
if (a && mentions2(a[2])) guarded.add(a[1]);
|
|
22034
|
+
else if (a) guarded.delete(a[1]);
|
|
21903
22035
|
if (mentions2(lines[l])) {
|
|
21904
22036
|
sanitizers.push({
|
|
21905
22037
|
type: "csharp_early_return_guard",
|
|
@@ -21920,6 +22052,25 @@ function findCSharpEarlyReturnGuardSanitizers(code, language) {
|
|
|
21920
22052
|
}
|
|
21921
22053
|
return sanitizers;
|
|
21922
22054
|
}
|
|
22055
|
+
function collectCSharpLiteralArrayFields(code) {
|
|
22056
|
+
const names = /* @__PURE__ */ new Set();
|
|
22057
|
+
const lit = String.raw`(?:@"(?:[^"]|"")*"|"(?:[^"\\\n]|\\.)*")`;
|
|
22058
|
+
const elems = new RegExp(String.raw`^\s*${lit}(?:\s*,\s*${lit})*\s*,?\s*$`);
|
|
22059
|
+
const declRe = /((?:\b(?:public|private|protected|internal|static|readonly)\s+)+)string\s*\[\s*\]\s+([A-Za-z_]\w*)\s*=\s*(?:new\s+(?:string\s*)?\[\s*\]\s*)?\{([^{}]*)\}\s*;/g;
|
|
22060
|
+
for (const m of code.matchAll(declRe)) {
|
|
22061
|
+
const mods = m[1];
|
|
22062
|
+
const name2 = m[2];
|
|
22063
|
+
if (!/\bstatic\b/.test(mods) || !/\breadonly\b/.test(mods)) continue;
|
|
22064
|
+
if (!elems.test(m[3])) continue;
|
|
22065
|
+
const rest = code.slice(0, m.index) + code.slice(m.index + m[0].length);
|
|
22066
|
+
const written = new RegExp(
|
|
22067
|
+
String.raw`\b${name2}\s*(?:\[[^\]]*\]\s*)?(?:[-+*/%&|^]|\?\?)?=(?!=)` + String.raw`|\b(?:ref|out)\s+${name2}\b` + String.raw`|\b${name2}\s*\.\s*SetValue\s*\(` + String.raw`|\bArray\s*\.\s*(?:Clear|Copy|ConstrainedCopy|Fill|Resize|Reverse|Sort)\s*\([^;]*\b${name2}\b`
|
|
22068
|
+
);
|
|
22069
|
+
if (written.test(rest)) continue;
|
|
22070
|
+
names.add(name2);
|
|
22071
|
+
}
|
|
22072
|
+
return names;
|
|
22073
|
+
}
|
|
21923
22074
|
function findCSharpFullPathStartsWithGuardSanitizers(code) {
|
|
21924
22075
|
const sanitizers = [];
|
|
21925
22076
|
const lines = code.split("\n");
|
|
@@ -26381,6 +26532,9 @@ function isInterproceduralTaintableType(typeName, language) {
|
|
|
26381
26532
|
"IEnumerable",
|
|
26382
26533
|
"ICollection",
|
|
26383
26534
|
"IList",
|
|
26535
|
+
"List",
|
|
26536
|
+
"Hashtable",
|
|
26537
|
+
"ArrayList",
|
|
26384
26538
|
"Dictionary",
|
|
26385
26539
|
"IDictionary",
|
|
26386
26540
|
"IReadOnlyList",
|
|
@@ -30968,10 +31122,12 @@ function isJavaStaticFormatterCall(call, declaredTypeNames, imports, calls) {
|
|
|
30968
31122
|
if (other === call || other.method_name === "format") continue;
|
|
30969
31123
|
if (other.arguments.some((a) => a.expression.includes(needle))) return false;
|
|
30970
31124
|
}
|
|
30971
|
-
|
|
31125
|
+
return isJdkClassReceiver(call.receiver, JAVA_FORMATTER_CLASSES, declaredTypeNames, imports);
|
|
31126
|
+
}
|
|
31127
|
+
function isJdkClassReceiver(receiver, classes, declaredTypeNames, imports) {
|
|
30972
31128
|
const dot = receiver.lastIndexOf(".");
|
|
30973
31129
|
const simple = dot < 0 ? receiver : receiver.slice(dot + 1);
|
|
30974
|
-
const pkg =
|
|
31130
|
+
const pkg = classes.get(simple);
|
|
30975
31131
|
if (pkg === void 0) return false;
|
|
30976
31132
|
if (dot >= 0) return receiver === `${pkg}.${simple}`;
|
|
30977
31133
|
if (declaredTypeNames.has(simple)) return false;
|
|
@@ -30982,8 +31138,41 @@ function isJavaStaticFormatterCall(call, declaredTypeNames, imports, calls) {
|
|
|
30982
31138
|
if (pkg === "java.lang") return true;
|
|
30983
31139
|
return imports.some((imp) => imp.from_package === pkg && (imp.imported_name === simple || imp.is_wildcard));
|
|
30984
31140
|
}
|
|
31141
|
+
var JAVA_CHARACTER_CLASS = /* @__PURE__ */ new Map([["Character", "java.lang"]]);
|
|
31142
|
+
var JAVA_CHARACTER_PREDICATES = /* @__PURE__ */ new Set([
|
|
31143
|
+
"isLetter",
|
|
31144
|
+
"isDigit",
|
|
31145
|
+
"isLetterOrDigit",
|
|
31146
|
+
"isAlphabetic",
|
|
31147
|
+
"isWhitespace",
|
|
31148
|
+
"isSpaceChar",
|
|
31149
|
+
"isUpperCase",
|
|
31150
|
+
"isLowerCase",
|
|
31151
|
+
"isTitleCase",
|
|
31152
|
+
"isISOControl",
|
|
31153
|
+
"isDefined",
|
|
31154
|
+
"isIdeographic",
|
|
31155
|
+
"isMirrored",
|
|
31156
|
+
"isJavaIdentifierStart",
|
|
31157
|
+
"isJavaIdentifierPart",
|
|
31158
|
+
"isUnicodeIdentifierStart",
|
|
31159
|
+
"isUnicodeIdentifierPart",
|
|
31160
|
+
"isIdentifierIgnorable",
|
|
31161
|
+
"isHighSurrogate",
|
|
31162
|
+
"isLowSurrogate",
|
|
31163
|
+
"isSurrogate",
|
|
31164
|
+
"isSurrogatePair",
|
|
31165
|
+
"isValidCodePoint",
|
|
31166
|
+
"isBmpCodePoint",
|
|
31167
|
+
"isSupplementaryCodePoint"
|
|
31168
|
+
]);
|
|
31169
|
+
function isJavaCharacterPredicateCall(call, declaredTypeNames, imports) {
|
|
31170
|
+
if (!call.receiver || !JAVA_CHARACTER_PREDICATES.has(call.method_name)) return false;
|
|
31171
|
+
return isJdkClassReceiver(call.receiver, JAVA_CHARACTER_CLASS, declaredTypeNames, imports);
|
|
31172
|
+
}
|
|
30985
31173
|
function isJavaNonEscapeCall(call, declaredTypeNames, imports, calls) {
|
|
30986
31174
|
if (isJavaStaticFormatterCall(call, declaredTypeNames, imports, calls)) return true;
|
|
31175
|
+
if (isJavaCharacterPredicateCall(call, declaredTypeNames, imports)) return true;
|
|
30987
31176
|
if (call.receiver) return false;
|
|
30988
31177
|
const isCtor = call.is_constructor === true || (call.resolution?.target ?? "").endsWith(".<init>");
|
|
30989
31178
|
if (!isCtor) return false;
|
|
@@ -31969,6 +32158,11 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
31969
32158
|
taintedArrayElements = /* @__PURE__ */ new Map();
|
|
31970
32159
|
// Track current method name for scoping local variables
|
|
31971
32160
|
currentMethod = null;
|
|
32161
|
+
/** Start lines of nested `{ ... }` blocks — disambiguate same-named locals (#476 Collections10). */
|
|
32162
|
+
blockScopeStack = [];
|
|
32163
|
+
lineBlockScopes = /* @__PURE__ */ new Map();
|
|
32164
|
+
/** Skip block-scope push on the method/constructor body `{ ... }` (#476). */
|
|
32165
|
+
skipMethodBodyBlockScope = false;
|
|
31972
32166
|
// Track conditional taints: which variables were tainted under which conditions
|
|
31973
32167
|
// Maps condition expression string → set of variables tainted under that condition
|
|
31974
32168
|
conditionalTaints = /* @__PURE__ */ new Map();
|
|
@@ -31982,6 +32176,8 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
31982
32176
|
inSynchronizedBlock = false;
|
|
31983
32177
|
// Track iterator sources: iterator variable name → collection name it was created from
|
|
31984
32178
|
iteratorSources = /* @__PURE__ */ new Map();
|
|
32179
|
+
/** Vars assigned from `iter.next()` — do not retro-clear on later iterator rebind (#476). */
|
|
32180
|
+
taintedFromIteratorNext = /* @__PURE__ */ new Set();
|
|
31985
32181
|
// Track class field names (declared at class level, not local variables)
|
|
31986
32182
|
classFields = /* @__PURE__ */ new Set();
|
|
31987
32183
|
// Track tainted method parameters for inter-procedural analysis
|
|
@@ -32039,12 +32235,16 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
32039
32235
|
this.sanitizedVars.clear();
|
|
32040
32236
|
this.sanitizedVarUncovered.clear();
|
|
32041
32237
|
this.currentMethod = null;
|
|
32238
|
+
this.blockScopeStack = [];
|
|
32239
|
+
this.lineBlockScopes.clear();
|
|
32240
|
+
this.skipMethodBodyBlockScope = false;
|
|
32042
32241
|
this.conditionalTaints.clear();
|
|
32043
32242
|
this.conditionStack = [];
|
|
32044
32243
|
this.lineConditions.clear();
|
|
32045
32244
|
this.synchronizedLines.clear();
|
|
32046
32245
|
this.inSynchronizedBlock = false;
|
|
32047
32246
|
this.iteratorSources.clear();
|
|
32247
|
+
this.taintedFromIteratorNext.clear();
|
|
32048
32248
|
this.classFields.clear();
|
|
32049
32249
|
this.instanceFieldTaint.clear();
|
|
32050
32250
|
this.currentClassName = null;
|
|
@@ -32069,6 +32269,9 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
32069
32269
|
const resultSymbols = new Map(this.symbols);
|
|
32070
32270
|
for (const name2 of this.tainted) {
|
|
32071
32271
|
if (name2.includes(":")) {
|
|
32272
|
+
if (/^[^:]+:b\d+:/.test(name2)) {
|
|
32273
|
+
continue;
|
|
32274
|
+
}
|
|
32072
32275
|
const unscoped = name2.substring(name2.indexOf(":") + 1);
|
|
32073
32276
|
resultTainted.add(unscoped);
|
|
32074
32277
|
}
|
|
@@ -32083,6 +32286,9 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
32083
32286
|
for (const [name2, value] of this.symbols) {
|
|
32084
32287
|
if (this.goScopedNames.has(name2)) continue;
|
|
32085
32288
|
if (name2.includes(":")) {
|
|
32289
|
+
if (/^[^:]+:b\d+:/.test(name2)) {
|
|
32290
|
+
continue;
|
|
32291
|
+
}
|
|
32086
32292
|
const unscoped = name2.substring(name2.indexOf(":") + 1);
|
|
32087
32293
|
if (!resultSymbols.has(unscoped)) {
|
|
32088
32294
|
resultSymbols.set(unscoped, value);
|
|
@@ -32092,6 +32298,7 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
32092
32298
|
return {
|
|
32093
32299
|
symbols: resultSymbols,
|
|
32094
32300
|
tainted: resultTainted,
|
|
32301
|
+
lineBlockScopes: new Map(this.lineBlockScopes),
|
|
32095
32302
|
unreachableLines: new Set(this.unreachableLines),
|
|
32096
32303
|
taintedCollections: new Map(this.taintedCollections),
|
|
32097
32304
|
taintedArrayElements: new Map(this.taintedArrayElements),
|
|
@@ -32635,6 +32842,18 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
32635
32842
|
// ===========================================================================
|
|
32636
32843
|
// Taint Refinement
|
|
32637
32844
|
// ===========================================================================
|
|
32845
|
+
/** True when `node` is `iter.next()` (possibly under a cast). */
|
|
32846
|
+
isIteratorNextExpression(node) {
|
|
32847
|
+
const text = getNodeText2(node, this.source);
|
|
32848
|
+
if (/\.\s*next\s*\(/.test(text)) return true;
|
|
32849
|
+
let current = node;
|
|
32850
|
+
if (current.type === "cast_expression" || current.type === "parenthesized_expression") {
|
|
32851
|
+
current = current.childForFieldName("operand") ?? current.childForFieldName("expression");
|
|
32852
|
+
}
|
|
32853
|
+
if (!current || current.type !== "method_invocation") return false;
|
|
32854
|
+
const nameNode = current.childForFieldName("name");
|
|
32855
|
+
return nameNode ? getNodeText2(nameNode, this.source) === "next" : false;
|
|
32856
|
+
}
|
|
32638
32857
|
refineTaintFromConstants() {
|
|
32639
32858
|
let changed = true;
|
|
32640
32859
|
let iterations = 0;
|
|
@@ -32644,6 +32863,9 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
32644
32863
|
iterations++;
|
|
32645
32864
|
const toRemove = [];
|
|
32646
32865
|
for (const varName of this.tainted) {
|
|
32866
|
+
if (this.taintedFromIteratorNext.has(varName)) {
|
|
32867
|
+
continue;
|
|
32868
|
+
}
|
|
32647
32869
|
const symbol = this.symbols.get(varName);
|
|
32648
32870
|
if (symbol && symbol.type !== "unknown") {
|
|
32649
32871
|
toRemove.push(varName);
|
|
@@ -32692,6 +32914,9 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
32692
32914
|
if (this.unreachableLines.has(line)) {
|
|
32693
32915
|
return true;
|
|
32694
32916
|
}
|
|
32917
|
+
if (this.blockScopeStack.length > 0) {
|
|
32918
|
+
this.lineBlockScopes.set(line, this.blockScopeStack[this.blockScopeStack.length - 1]);
|
|
32919
|
+
}
|
|
32695
32920
|
if (this.conditionStack.length > 0 && !this.lineConditions.has(line)) {
|
|
32696
32921
|
this.lineConditions.set(line, this.conditionStack[this.conditionStack.length - 1]);
|
|
32697
32922
|
}
|
|
@@ -32723,6 +32948,15 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
32723
32948
|
case "if_statement":
|
|
32724
32949
|
this.handleIfStatement(node);
|
|
32725
32950
|
return true;
|
|
32951
|
+
case "block": {
|
|
32952
|
+
if (this.skipMethodBodyBlockScope) {
|
|
32953
|
+
this.skipMethodBodyBlockScope = false;
|
|
32954
|
+
}
|
|
32955
|
+
for (const child of node.children) {
|
|
32956
|
+
this.visit(child);
|
|
32957
|
+
}
|
|
32958
|
+
return true;
|
|
32959
|
+
}
|
|
32726
32960
|
case "switch_expression":
|
|
32727
32961
|
case "switch_statement":
|
|
32728
32962
|
this.handleSwitch(node);
|
|
@@ -32808,7 +33042,9 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
32808
33042
|
}
|
|
32809
33043
|
const body2 = node.childForFieldName("body");
|
|
32810
33044
|
if (body2) {
|
|
33045
|
+
this.skipMethodBodyBlockScope = true;
|
|
32811
33046
|
this.visit(body2);
|
|
33047
|
+
this.skipMethodBodyBlockScope = false;
|
|
32812
33048
|
}
|
|
32813
33049
|
this.currentMethod = prevMethod;
|
|
32814
33050
|
this.inConstructor = prevInConstructor;
|
|
@@ -32822,10 +33058,31 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
32822
33058
|
if (varName.includes(".") || varName.includes(":")) {
|
|
32823
33059
|
return varName;
|
|
32824
33060
|
}
|
|
33061
|
+
const blockId = this.blockScopeStack.length > 0 ? this.blockScopeStack[this.blockScopeStack.length - 1] : void 0;
|
|
32825
33062
|
if (this.currentMethod) {
|
|
32826
|
-
return `${this.currentMethod}:${varName}`;
|
|
33063
|
+
return blockId !== void 0 ? `${this.currentMethod}:b${blockId}:${varName}` : `${this.currentMethod}:${varName}`;
|
|
32827
33064
|
}
|
|
32828
|
-
return varName;
|
|
33065
|
+
return blockId !== void 0 ? `b${blockId}:${varName}` : varName;
|
|
33066
|
+
}
|
|
33067
|
+
/** Innermost block → method → unscoped, for reads that may bind to an outer declaration. */
|
|
33068
|
+
getScopedNameCandidates(varName) {
|
|
33069
|
+
if (varName.includes(".") || varName.includes(":")) {
|
|
33070
|
+
return [varName];
|
|
33071
|
+
}
|
|
33072
|
+
const out2 = [];
|
|
33073
|
+
for (let i2 = this.blockScopeStack.length - 1; i2 >= 0; i2--) {
|
|
33074
|
+
const blockId = this.blockScopeStack[i2];
|
|
33075
|
+
if (this.currentMethod) {
|
|
33076
|
+
out2.push(`${this.currentMethod}:b${blockId}:${varName}`);
|
|
33077
|
+
} else {
|
|
33078
|
+
out2.push(`b${blockId}:${varName}`);
|
|
33079
|
+
}
|
|
33080
|
+
}
|
|
33081
|
+
if (this.currentMethod) {
|
|
33082
|
+
out2.push(`${this.currentMethod}:${varName}`);
|
|
33083
|
+
}
|
|
33084
|
+
out2.push(varName);
|
|
33085
|
+
return out2;
|
|
32829
33086
|
}
|
|
32830
33087
|
/**
|
|
32831
33088
|
* Look up a variable value, checking both scoped and unscoped names.
|
|
@@ -32833,11 +33090,12 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
32833
33090
|
* either local (scoped) or global (unscoped, like class fields).
|
|
32834
33091
|
*/
|
|
32835
33092
|
lookupSymbol(varName) {
|
|
32836
|
-
if (
|
|
32837
|
-
const scopedName
|
|
32838
|
-
|
|
32839
|
-
|
|
32840
|
-
|
|
33093
|
+
if (!varName.includes(".") && !varName.includes(":")) {
|
|
33094
|
+
for (const scopedName of this.getScopedNameCandidates(varName)) {
|
|
33095
|
+
const scopedValue = this.symbols.get(scopedName);
|
|
33096
|
+
if (scopedValue) {
|
|
33097
|
+
return scopedValue;
|
|
33098
|
+
}
|
|
32841
33099
|
}
|
|
32842
33100
|
}
|
|
32843
33101
|
return this.symbols.get(varName);
|
|
@@ -32889,8 +33147,22 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
32889
33147
|
this.trackIteratorsInNode(initNode);
|
|
32890
33148
|
}
|
|
32891
33149
|
}
|
|
32892
|
-
|
|
32893
|
-
|
|
33150
|
+
if (node.type === "while_statement" || node.type === "do_statement") {
|
|
33151
|
+
const body2 = node.childForFieldName("body");
|
|
33152
|
+
for (const child of node.children) {
|
|
33153
|
+
if (child !== body2) this.visit(child);
|
|
33154
|
+
}
|
|
33155
|
+
if (body2?.type === "block") {
|
|
33156
|
+
this.blockScopeStack.push(getNodeLine(body2));
|
|
33157
|
+
this.visit(body2);
|
|
33158
|
+
this.blockScopeStack.pop();
|
|
33159
|
+
} else if (body2) {
|
|
33160
|
+
this.visit(body2);
|
|
33161
|
+
}
|
|
33162
|
+
} else {
|
|
33163
|
+
for (const child of node.children) {
|
|
33164
|
+
this.visit(child);
|
|
33165
|
+
}
|
|
32894
33166
|
}
|
|
32895
33167
|
for (const varName of loopVarNames) {
|
|
32896
33168
|
this.symbols.set(varName, createUnknown(getNodeLine(node)));
|
|
@@ -33032,6 +33304,10 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
33032
33304
|
this.sanitizedVars.delete(scopedName);
|
|
33033
33305
|
this.sanitizedVarUncovered.delete(scopedName);
|
|
33034
33306
|
this.definitionNodes.set(scopedName, valueNode);
|
|
33307
|
+
if (this.isIteratorNextExpression(valueNode)) {
|
|
33308
|
+
this.taintedFromIteratorNext.add(scopedName);
|
|
33309
|
+
this.taintedFromIteratorNext.add(varName);
|
|
33310
|
+
}
|
|
33035
33311
|
this.symbols.set(scopedName, createUnknown(line));
|
|
33036
33312
|
} else {
|
|
33037
33313
|
if (this.inConditionalBranch) {
|
|
@@ -33159,6 +33435,12 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
33159
33435
|
this.sanitizedVars.delete(scopedName);
|
|
33160
33436
|
this.sanitizedVarUncovered.delete(scopedName);
|
|
33161
33437
|
this.definitionNodes.set(scopedName, right);
|
|
33438
|
+
if (this.isIteratorNextExpression(right)) {
|
|
33439
|
+
this.taintedFromIteratorNext.add(scopedName);
|
|
33440
|
+
if (!varName.includes(".")) {
|
|
33441
|
+
this.taintedFromIteratorNext.add(varName);
|
|
33442
|
+
}
|
|
33443
|
+
}
|
|
33162
33444
|
this.symbols.set(scopedName, createUnknown(line));
|
|
33163
33445
|
if (this.inConstructor && varName.startsWith("this.")) {
|
|
33164
33446
|
const fieldName = varName.substring(5);
|
|
@@ -34149,10 +34431,17 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
34149
34431
|
}
|
|
34150
34432
|
}
|
|
34151
34433
|
if (node.type === "identifier") {
|
|
34152
|
-
|
|
34153
|
-
|
|
34154
|
-
|
|
34155
|
-
if (this.
|
|
34434
|
+
let isTainted = false;
|
|
34435
|
+
let boundName = null;
|
|
34436
|
+
for (const candidate of this.getScopedNameCandidates(text)) {
|
|
34437
|
+
if (this.tainted.has(candidate)) {
|
|
34438
|
+
isTainted = true;
|
|
34439
|
+
boundName = candidate;
|
|
34440
|
+
break;
|
|
34441
|
+
}
|
|
34442
|
+
}
|
|
34443
|
+
if (isTainted && boundName) {
|
|
34444
|
+
if (this.isExcludedByCondition(boundName) || this.isExcludedByCondition(text)) {
|
|
34156
34445
|
return false;
|
|
34157
34446
|
}
|
|
34158
34447
|
}
|
|
@@ -34359,6 +34648,14 @@ function analyzeConstantPropagation(tree, sourceCode, options = {}) {
|
|
|
34359
34648
|
options.taintedParameters ?? []
|
|
34360
34649
|
);
|
|
34361
34650
|
}
|
|
34651
|
+
function constPropScopedVar(method, varName, line, result) {
|
|
34652
|
+
if (!method) return varName;
|
|
34653
|
+
const blockId = result.lineBlockScopes?.get(line);
|
|
34654
|
+
if (blockId !== void 0) {
|
|
34655
|
+
return `${method}:b${blockId}:${varName}`;
|
|
34656
|
+
}
|
|
34657
|
+
return `${method}:${varName}`;
|
|
34658
|
+
}
|
|
34362
34659
|
function isFalsePositive(result, sinkLine, taintedVar) {
|
|
34363
34660
|
if (result.unreachableLines.has(sinkLine)) {
|
|
34364
34661
|
return { isFalsePositive: true, reason: "sink_in_dead_code" };
|
|
@@ -38627,6 +38924,11 @@ function bareIdentifiers(text) {
|
|
|
38627
38924
|
for (const m of stripped.matchAll(/\b([A-Za-z_]\w*)\b/g)) out2.push(m[1]);
|
|
38628
38925
|
return out2;
|
|
38629
38926
|
}
|
|
38927
|
+
function isLiteralValue(rhs) {
|
|
38928
|
+
if (/\$"/.test(rhs)) return false;
|
|
38929
|
+
const ids = bareIdentifiers(rhs);
|
|
38930
|
+
return ids.every((id) => id === "null" || id === "true" || id === "false" || id === "default") || /^\s*(?:string|String)\s*\.\s*Empty\s*$/.test(rhs);
|
|
38931
|
+
}
|
|
38630
38932
|
function insideParens(text, open) {
|
|
38631
38933
|
let depth = 0;
|
|
38632
38934
|
for (let i2 = open; i2 < text.length; i2++) {
|
|
@@ -38807,6 +39109,36 @@ var CSharpCredit = class {
|
|
|
38807
39109
|
sanitizedVarsAt(type, sinkLine) {
|
|
38808
39110
|
return this.sanitizedVarsIn(type, this.methodAt(sinkLine)?.start_line ?? 1, sinkLine, true);
|
|
38809
39111
|
}
|
|
39112
|
+
/**
|
|
39113
|
+
* Class fields judged across every method that writes them (cognium-dev#808).
|
|
39114
|
+
* A field is read in one method and written in others, so each write is
|
|
39115
|
+
* judged in its own method with the same in-method credit as a local, and
|
|
39116
|
+
* the field is safe for `type` only when every write is: `sanitized` holds
|
|
39117
|
+
* the fields with at least one credited write and no uncredited one,
|
|
39118
|
+
* `unsanitized` the fields with an uncredited write. A literal write
|
|
39119
|
+
* (`_v = "";`, `_v = null;`) is neutral.
|
|
39120
|
+
*/
|
|
39121
|
+
fieldWrites(type, fields) {
|
|
39122
|
+
const credited = /* @__PURE__ */ new Set();
|
|
39123
|
+
const unsanitized = /* @__PURE__ */ new Set();
|
|
39124
|
+
for (const m of this.methods) {
|
|
39125
|
+
const sanitized = /* @__PURE__ */ new Set();
|
|
39126
|
+
for (const st of this.statements(m.start_line, m.end_line + 1)) {
|
|
39127
|
+
if (this.methodAt(st.line) !== m) continue;
|
|
39128
|
+
for (const op of this.creditOpsInStatement(st.text)) {
|
|
39129
|
+
const lhs = op.lhs;
|
|
39130
|
+
const keeps = op.kind === "append" ? sanitized.has(lhs) && this.rhsKeepsSanitizerCredit(type, op.rhs, sanitized, true, true) : this.rhsKeepsSanitizerCredit(type, op.rhs, sanitized, true, false);
|
|
39131
|
+
if (keeps) sanitized.add(lhs);
|
|
39132
|
+
else sanitized.delete(lhs);
|
|
39133
|
+
if (!fields.has(lhs) || isLiteralValue(op.rhs)) continue;
|
|
39134
|
+
if (keeps) credited.add(lhs);
|
|
39135
|
+
else unsanitized.add(lhs);
|
|
39136
|
+
}
|
|
39137
|
+
}
|
|
39138
|
+
}
|
|
39139
|
+
for (const f of unsanitized) credited.delete(f);
|
|
39140
|
+
return { sanitized: credited, unsanitized };
|
|
39141
|
+
}
|
|
38810
39142
|
/**
|
|
38811
39143
|
* True when the sink on `sinkLine` also receives a tainted value that no
|
|
38812
39144
|
* sanitizer covers. In `Write(HtmlEncode(a) + b)` the encoder covers `a`
|
|
@@ -41832,6 +42164,15 @@ var SinkFilterPass = class {
|
|
|
41832
42164
|
const csFields = new Set(ctx.graph.ir.types.flatMap((t) => (t.fields ?? []).map((f) => f.name)));
|
|
41833
42165
|
const credit = new CSharpCredit(ctx.code, ctx.graph.ir.types.flatMap((t) => t.methods));
|
|
41834
42166
|
const csSeeds = mergedSources.flatMap((s) => s.variable ? [{ variable: s.variable, line: s.line }] : []);
|
|
42167
|
+
const fieldWritesByType = /* @__PURE__ */ new Map();
|
|
42168
|
+
const fieldWritesFor = (type) => {
|
|
42169
|
+
let w = fieldWritesByType.get(type);
|
|
42170
|
+
if (!w) {
|
|
42171
|
+
w = credit.fieldWrites(type, csFields);
|
|
42172
|
+
fieldWritesByType.set(type, w);
|
|
42173
|
+
}
|
|
42174
|
+
return w;
|
|
42175
|
+
};
|
|
41835
42176
|
const xxeHardened = /\bXmlResolver\s*=\s*null\b/.test(ctx.code) || /\bDtdProcessing\s*=\s*(?:DtdProcessing\s*\.\s*)?(?:Prohibit|Ignore)\b/.test(ctx.code);
|
|
41836
42177
|
filtered = filtered.filter((sink) => {
|
|
41837
42178
|
if (sink.type === "xxe" && xxeHardened) return false;
|
|
@@ -41841,9 +42182,13 @@ var SinkFilterPass = class {
|
|
|
41841
42182
|
const scoped = credit.sanitizedVarsAt(sink.type, sink.line);
|
|
41842
42183
|
const fileWide = sanitizedByType.get(sink.type);
|
|
41843
42184
|
const sanitizedVars = new Set(scoped);
|
|
42185
|
+
const fieldWrites = fieldWritesFor(sink.type);
|
|
41844
42186
|
if (fileWide) {
|
|
41845
|
-
for (const v of fileWide)
|
|
42187
|
+
for (const v of fileWide) {
|
|
42188
|
+
if (csFields.has(v) && !fieldWrites.unsanitized.has(v)) sanitizedVars.add(v);
|
|
42189
|
+
}
|
|
41846
42190
|
}
|
|
42191
|
+
for (const v of fieldWrites.sanitized) sanitizedVars.add(v);
|
|
41847
42192
|
for (const v of credit.guardedVarsAt(sink.type, sink.line)) sanitizedVars.add(v);
|
|
41848
42193
|
for (const v of credit.safeBuilders(sanitizedVars, sink.line)) sanitizedVars.add(v);
|
|
41849
42194
|
const received = credit.sinkText(sink.line);
|
|
@@ -43190,6 +43535,15 @@ function isReactHtmlComponent(m, f, lines) {
|
|
|
43190
43535
|
const sig = lines[m.start_line - 1] ?? "";
|
|
43191
43536
|
return /\bfunction\s+[A-Z][A-Za-z0-9]*\s*\(\s*\{/.test(sig);
|
|
43192
43537
|
}
|
|
43538
|
+
function isPythonYamlLoadParamDeser(f, lines) {
|
|
43539
|
+
if (f.source_type !== "interprocedural_param" || f.sink_type !== "deserialization") return false;
|
|
43540
|
+
const head = f.path[0]?.variable;
|
|
43541
|
+
if (!head || f.path.length < 2) return false;
|
|
43542
|
+
const sinkLine = lines[f.sink_line - 1] ?? "";
|
|
43543
|
+
if (!/\byaml\.load\s*\(/.test(sinkLine) || /\bLoader\s*=\s*yaml\.SafeLoader/.test(sinkLine)) return false;
|
|
43544
|
+
const esc = head.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
43545
|
+
return new RegExp(`\\byaml\\.load\\s*\\(\\s*${esc}\\b`).test(sinkLine);
|
|
43546
|
+
}
|
|
43193
43547
|
var FIELD_CARRIED_SOURCE = /constructor-injected field|returns tainted field/;
|
|
43194
43548
|
var EXECUTOR_CALLEES = /* @__PURE__ */ new Map([
|
|
43195
43549
|
["submit", 0],
|
|
@@ -43329,7 +43683,7 @@ var ParamSourceFlowGatePass = class {
|
|
|
43329
43683
|
let keep = true;
|
|
43330
43684
|
if (f.source_type === "interprocedural_param" && !realSourceLines.has(f.source_line)) {
|
|
43331
43685
|
const m = enclosingMethod(methods, f.source_line);
|
|
43332
|
-
keep = m !== void 0 && (isHandler(m) || fedByRealSource(f, m) || isReactHtmlComponent(m, f, lines));
|
|
43686
|
+
keep = m !== void 0 && (isHandler(m) || fedByRealSource(f, m) || isReactHtmlComponent(m, f, lines) || ctx.language === "python" && isPythonYamlLoadParamDeser(f, lines));
|
|
43333
43687
|
}
|
|
43334
43688
|
if (keep) flows[w++] = f;
|
|
43335
43689
|
else {
|
|
@@ -43954,21 +44308,56 @@ function resolveJavaDerivedAnchor(varName, derived, realSourceByVar, codeLines,
|
|
|
43954
44308
|
}
|
|
43955
44309
|
return best;
|
|
43956
44310
|
}
|
|
43957
|
-
function javaReceiverChainDerivedVar(
|
|
44311
|
+
function javaReceiverChainDerivedVar(rhsByLocal, localVar, seedVar) {
|
|
43958
44312
|
if (!seedVar || localVar === seedVar) return false;
|
|
44313
|
+
const rhsList = rhsByLocal.get(localVar);
|
|
44314
|
+
if (!rhsList) return false;
|
|
43959
44315
|
const esc = seedVar.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
43960
44316
|
const receiverOnRhs = new RegExp(`(?:^|[^\\w.$])${esc}\\s*\\.`, "u");
|
|
44317
|
+
return rhsList.some((rhs) => receiverOnRhs.test(rhs));
|
|
44318
|
+
}
|
|
44319
|
+
function defLineForUse(graph, varName, useLine) {
|
|
44320
|
+
const own = (graph.defsByLine.get(useLine) ?? []).find((d2) => d2.variable === varName);
|
|
44321
|
+
if (own) return own.line;
|
|
44322
|
+
const use = (graph.usesByLine.get(useLine) ?? []).find((u) => u.variable === varName && u.def_id != null);
|
|
44323
|
+
const d = use ? graph.defById.get(use.def_id) : void 0;
|
|
44324
|
+
return d?.line ?? useLine;
|
|
44325
|
+
}
|
|
44326
|
+
function constPropFlowLookup(language, method, varName, blockLine, constProp, preferMethodScoped = false) {
|
|
44327
|
+
if (language === "go") {
|
|
44328
|
+
return constPropScopedVar(method, varName, blockLine, constProp);
|
|
44329
|
+
}
|
|
44330
|
+
if (!method || varName.includes(":")) return varName;
|
|
44331
|
+
const blockScoped = constPropScopedVar(method, varName, blockLine, constProp);
|
|
44332
|
+
const hasTaintBinding = (name2) => constProp.tainted.has(name2) || constProp.sanitizedVars.has(name2);
|
|
44333
|
+
if (blockScoped.includes(":b") && hasTaintBinding(blockScoped)) {
|
|
44334
|
+
return blockScoped;
|
|
44335
|
+
}
|
|
44336
|
+
const methodScoped = `${method}:${varName}`;
|
|
44337
|
+
if (hasTaintBinding(methodScoped)) {
|
|
44338
|
+
return methodScoped;
|
|
44339
|
+
}
|
|
44340
|
+
if (preferMethodScoped && method) {
|
|
44341
|
+
return methodScoped;
|
|
44342
|
+
}
|
|
44343
|
+
return constProp.lineBlockScopes?.has(blockLine) ? blockScoped : varName;
|
|
44344
|
+
}
|
|
44345
|
+
function indexJavaLocalAssignments(code) {
|
|
43961
44346
|
const declRe = /^\s*(?:public|private|protected|static|final|volatile|transient|\s)*\s*(?:[A-Za-z_][\w.]*(?:\s*<[^>]*>)?(?:\s*\[\s*\])*)\s+([A-Za-z_]\w*)\s*=\s*(.+?);\s*$/;
|
|
43962
44347
|
const assignRe = /^\s*([A-Za-z_]\w*)\s*=\s*(.+?);\s*$/;
|
|
44348
|
+
const index = /* @__PURE__ */ new Map();
|
|
43963
44349
|
for (const line of code.split("\n")) {
|
|
43964
44350
|
const trimmed = line.trimStart();
|
|
43965
44351
|
if (trimmed.startsWith("//") || trimmed.startsWith("*")) continue;
|
|
44352
|
+
if (!line.includes("=") || !line.includes(";")) continue;
|
|
43966
44353
|
const m = declRe.exec(line) ?? assignRe.exec(line);
|
|
43967
|
-
if (!m
|
|
44354
|
+
if (!m) continue;
|
|
44355
|
+
const list = index.get(m[1]);
|
|
43968
44356
|
const rhs = stripJavaTrailingComment(m[2]);
|
|
43969
|
-
if (
|
|
44357
|
+
if (list) list.push(rhs);
|
|
44358
|
+
else index.set(m[1], [rhs]);
|
|
43970
44359
|
}
|
|
43971
|
-
return
|
|
44360
|
+
return index;
|
|
43972
44361
|
}
|
|
43973
44362
|
function makeConstPropVeto(constProp, graph, sources, code, language, sanitizers) {
|
|
43974
44363
|
const lines = typeof code === "string" ? code.split("\n") : [];
|
|
@@ -43998,10 +44387,24 @@ function makeConstPropVeto(constProp, graph, sources, code, language, sanitizers
|
|
|
43998
44387
|
};
|
|
43999
44388
|
return (step, sourceLine, sinkLine, sinkType) => {
|
|
44000
44389
|
const method = methodAtLine(graph, step.line);
|
|
44001
|
-
const
|
|
44002
|
-
const
|
|
44390
|
+
const blockLine = defLineForUse(graph, step.variable, step.line);
|
|
44391
|
+
const fpLookup = constPropFlowLookup(
|
|
44392
|
+
language,
|
|
44393
|
+
method,
|
|
44394
|
+
step.variable,
|
|
44395
|
+
blockLine,
|
|
44396
|
+
constProp
|
|
44397
|
+
);
|
|
44398
|
+
const r = isFalsePositive(constProp, step.line, fpLookup);
|
|
44003
44399
|
if (!r.isFalsePositive) return false;
|
|
44004
44400
|
if (r.reason !== "variable_not_tainted") return true;
|
|
44401
|
+
if (r.reason === "variable_not_tainted" && language === "java" && method && fpLookup === step.variable && !step.variable.includes(".")) {
|
|
44402
|
+
const v = step.variable;
|
|
44403
|
+
if (constProp.tainted.has(`${method}:${v}`)) return false;
|
|
44404
|
+
for (const t of constProp.tainted) {
|
|
44405
|
+
if (t.startsWith(`${method}:b`) && t.endsWith(`:${v}`)) return false;
|
|
44406
|
+
}
|
|
44407
|
+
}
|
|
44005
44408
|
if (sinkType !== void 0 && constProp.sanitizedVarUncovered) {
|
|
44006
44409
|
const unc = constProp.sanitizedVarUncovered;
|
|
44007
44410
|
if (step.variable.includes(":")) {
|
|
@@ -44009,7 +44412,9 @@ function makeConstPropVeto(constProp, graph, sources, code, language, sanitizers
|
|
|
44009
44412
|
} else {
|
|
44010
44413
|
const method2 = methodAtLine(graph, step.line);
|
|
44011
44414
|
if (method2) {
|
|
44012
|
-
if (unc.get(
|
|
44415
|
+
if (unc.get(fpLookup)?.has(sinkType)) {
|
|
44416
|
+
return false;
|
|
44417
|
+
}
|
|
44013
44418
|
} else if (unc.get(step.variable)?.has(sinkType)) {
|
|
44014
44419
|
return false;
|
|
44015
44420
|
}
|
|
@@ -44047,7 +44452,7 @@ function makeConstPropVeto(constProp, graph, sources, code, language, sanitizers
|
|
|
44047
44452
|
}
|
|
44048
44453
|
if (!d) return true;
|
|
44049
44454
|
if (d.line === sourceLine && sources.some((s) => s.line === sourceLine && !CONTAINER_READ_SOURCES.has(s.type))) {
|
|
44050
|
-
if (step.line === sinkLine && !constProp.tainted.has(
|
|
44455
|
+
if (step.line === sinkLine && !constProp.tainted.has(fpLookup)) {
|
|
44051
44456
|
for (let ln = sourceLine; ln <= sinkLine; ln++) {
|
|
44052
44457
|
const calleeMatch = (lines[ln - 1] ?? "").match(/:=\s*(\w+)\s*\(/);
|
|
44053
44458
|
if (calleeMatch && constProp.constantReturningMethods?.has(calleeMatch[1])) {
|
|
@@ -44071,13 +44476,17 @@ var TaintPropagationPass = class {
|
|
|
44071
44476
|
const sinkFilter = ctx.getResult("sink-filter");
|
|
44072
44477
|
const { sources, sinks, sanitizers } = sinkFilter;
|
|
44073
44478
|
if (ctx.language === "java" && typeof ctx.code === "string") {
|
|
44479
|
+
const rhsByLocal = indexJavaLocalAssignments(ctx.code);
|
|
44480
|
+
const seen = /* @__PURE__ */ new Set();
|
|
44074
44481
|
for (const s of sources) {
|
|
44075
|
-
if (!s.variable) continue;
|
|
44076
|
-
|
|
44077
|
-
|
|
44078
|
-
|
|
44079
|
-
|
|
44080
|
-
|
|
44482
|
+
if (!s.variable || seen.has(s.variable)) continue;
|
|
44483
|
+
seen.add(s.variable);
|
|
44484
|
+
const seedVar = s.variable;
|
|
44485
|
+
const candidates = [...rhsByLocal.keys()].filter((v) => !constProp.tainted.has(v) && javaReceiverChainDerivedVar(rhsByLocal, v, seedVar));
|
|
44486
|
+
if (candidates.length === 0) continue;
|
|
44487
|
+
const derived = buildJavaTaintedVars(ctx.code, /* @__PURE__ */ new Set([seedVar]));
|
|
44488
|
+
for (const v of candidates) {
|
|
44489
|
+
if (derived.has(v)) constProp.tainted.add(v);
|
|
44081
44490
|
}
|
|
44082
44491
|
}
|
|
44083
44492
|
}
|
|
@@ -44222,7 +44631,15 @@ var TaintPropagationPass = class {
|
|
|
44222
44631
|
for (const f of arrayFlows) {
|
|
44223
44632
|
pushIfNew(f);
|
|
44224
44633
|
}
|
|
44225
|
-
const collectionFlows = detectCollectionFlows(
|
|
44634
|
+
const collectionFlows = detectCollectionFlows(
|
|
44635
|
+
calls,
|
|
44636
|
+
sources,
|
|
44637
|
+
sinks,
|
|
44638
|
+
constProp,
|
|
44639
|
+
ctx.code,
|
|
44640
|
+
types,
|
|
44641
|
+
graph
|
|
44642
|
+
) ?? [];
|
|
44226
44643
|
for (const f of collectionFlows) {
|
|
44227
44644
|
if (flowKeys.has(flowKey(f))) continue;
|
|
44228
44645
|
const flowForCheck = {
|
|
@@ -44276,6 +44693,17 @@ var TaintPropagationPass = class {
|
|
|
44276
44693
|
pushIfNew(f);
|
|
44277
44694
|
}
|
|
44278
44695
|
}
|
|
44696
|
+
if ((ctx.language === "javascript" || ctx.language === "typescript") && typeof ctx.code === "string") {
|
|
44697
|
+
const jsModuleFlows = detectJsModuleScopeFlows(
|
|
44698
|
+
ctx.code,
|
|
44699
|
+
sources,
|
|
44700
|
+
sinks,
|
|
44701
|
+
constProp.unreachableLines
|
|
44702
|
+
) ?? [];
|
|
44703
|
+
for (const f of jsModuleFlows) {
|
|
44704
|
+
pushIfNew(f);
|
|
44705
|
+
}
|
|
44706
|
+
}
|
|
44279
44707
|
const sanitizedNames = constProp.sanitizedVars;
|
|
44280
44708
|
const sanitizedUncovered = constProp.sanitizedVarUncovered;
|
|
44281
44709
|
let finalFlows = sanitizedNames.size === 0 ? flows : flows.filter((f) => {
|
|
@@ -44689,6 +45117,85 @@ function detectGoPackageGlobalFlows(code, _calls, sources, sinks, unreachableLin
|
|
|
44689
45117
|
}
|
|
44690
45118
|
return flows;
|
|
44691
45119
|
}
|
|
45120
|
+
function detectJsModuleScopeFlows(code, sources, sinks, unreachableLines) {
|
|
45121
|
+
const flows = [];
|
|
45122
|
+
if (!code || sinks.length === 0) return flows;
|
|
45123
|
+
const lines = code.split("\n");
|
|
45124
|
+
const moduleVars = /* @__PURE__ */ new Set();
|
|
45125
|
+
for (const line of lines) {
|
|
45126
|
+
const stripped = line.replace(/\s+$/, "");
|
|
45127
|
+
if (!stripped || /^[ \t]/.test(line)) continue;
|
|
45128
|
+
const m = stripped.match(/^(?:let|var|const)\s+([A-Za-z_$][\w$]*)\b/);
|
|
45129
|
+
if (m) moduleVars.add(m[1]);
|
|
45130
|
+
}
|
|
45131
|
+
if (moduleVars.size === 0) return flows;
|
|
45132
|
+
const sourceVarNames = /* @__PURE__ */ new Set();
|
|
45133
|
+
for (const s of sources) {
|
|
45134
|
+
if (typeof s.variable === "string" && s.variable.length > 0) {
|
|
45135
|
+
sourceVarNames.add(s.variable);
|
|
45136
|
+
}
|
|
45137
|
+
}
|
|
45138
|
+
const JS_HTTP_SOURCE_RE = /\b(?:req|request)\.(?:body|query|params|headers|cookies|get)\b|\breq\.(?:param|query|body)\(/;
|
|
45139
|
+
const writes = [];
|
|
45140
|
+
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
45141
|
+
const line = lines[i2];
|
|
45142
|
+
if (unreachableLines.has(i2 + 1)) continue;
|
|
45143
|
+
for (const lhs of moduleVars) {
|
|
45144
|
+
const declRe = new RegExp(`\\b(?:let|const|var)\\s+${lhs.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`);
|
|
45145
|
+
if (declRe.test(line)) continue;
|
|
45146
|
+
const m = line.match(
|
|
45147
|
+
new RegExp(`\\b${lhs.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\s*=\\s*([^;]+)`)
|
|
45148
|
+
);
|
|
45149
|
+
if (!m) continue;
|
|
45150
|
+
const rhs = m[1];
|
|
45151
|
+
let taintedBy = null;
|
|
45152
|
+
const onLine = sources.filter((s) => s.line === i2 + 1);
|
|
45153
|
+
if (onLine.length > 0) {
|
|
45154
|
+
taintedBy = { line: onLine[0].line, type: onLine[0].type };
|
|
45155
|
+
} else if (JS_HTTP_SOURCE_RE.test(rhs)) {
|
|
45156
|
+
taintedBy = { line: i2 + 1, type: "http_body" };
|
|
45157
|
+
} else {
|
|
45158
|
+
for (const v of sourceVarNames) {
|
|
45159
|
+
const re = new RegExp(`(?<![A-Za-z0-9_$])${v.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?![A-Za-z0-9_$])`);
|
|
45160
|
+
if (re.test(rhs)) {
|
|
45161
|
+
const src = sources.find((s) => s.variable === v);
|
|
45162
|
+
if (src) {
|
|
45163
|
+
taintedBy = { line: src.line, type: src.type };
|
|
45164
|
+
break;
|
|
45165
|
+
}
|
|
45166
|
+
}
|
|
45167
|
+
}
|
|
45168
|
+
}
|
|
45169
|
+
if (!taintedBy) continue;
|
|
45170
|
+
writes.push({ varName: lhs, line: i2 + 1, sourceLine: taintedBy.line, sourceType: taintedBy.type });
|
|
45171
|
+
}
|
|
45172
|
+
}
|
|
45173
|
+
if (writes.length === 0) return flows;
|
|
45174
|
+
for (const sink of sinks) {
|
|
45175
|
+
if (unreachableLines.has(sink.line)) continue;
|
|
45176
|
+
const sinkLineText = lines[sink.line - 1] ?? "";
|
|
45177
|
+
if (!sinkLineText) continue;
|
|
45178
|
+
for (const w of writes) {
|
|
45179
|
+
if (w.line >= sink.line) continue;
|
|
45180
|
+
const re = new RegExp(`(?<![A-Za-z0-9_$])${w.varName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?![A-Za-z0-9_$])`);
|
|
45181
|
+
if (!re.test(sinkLineText)) continue;
|
|
45182
|
+
flows.push({
|
|
45183
|
+
source_line: w.sourceLine,
|
|
45184
|
+
sink_line: sink.line,
|
|
45185
|
+
source_type: w.sourceType,
|
|
45186
|
+
sink_type: sink.type,
|
|
45187
|
+
path: [
|
|
45188
|
+
{ variable: w.varName, line: w.sourceLine, type: "source" },
|
|
45189
|
+
{ variable: w.varName, line: w.line, type: "assignment" },
|
|
45190
|
+
{ variable: w.varName, line: sink.line, type: "sink" }
|
|
45191
|
+
],
|
|
45192
|
+
confidence: 0.9,
|
|
45193
|
+
sanitized: false
|
|
45194
|
+
});
|
|
45195
|
+
}
|
|
45196
|
+
}
|
|
45197
|
+
return flows;
|
|
45198
|
+
}
|
|
44692
45199
|
function methodHasLocalDecl(code, methodName, varName, atLine) {
|
|
44693
45200
|
if (!code || !methodName) return false;
|
|
44694
45201
|
const esc = methodName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -44756,7 +45263,9 @@ function isTaintedInMethod(taintedVars, varName, scopedName, fieldNames, isField
|
|
|
44756
45263
|
}
|
|
44757
45264
|
return true;
|
|
44758
45265
|
}
|
|
44759
|
-
function detectCollectionFlows(calls, sources, sinks,
|
|
45266
|
+
function detectCollectionFlows(calls, sources, sinks, constProp, code, types, graph) {
|
|
45267
|
+
const taintedVars = constProp.tainted;
|
|
45268
|
+
const unreachableLines = constProp.unreachableLines;
|
|
44760
45269
|
const flows = [];
|
|
44761
45270
|
const fieldNames = /* @__PURE__ */ new Set();
|
|
44762
45271
|
for (const t of types ?? []) for (const f of t.fields ?? []) fieldNames.add(f.name);
|
|
@@ -44776,7 +45285,18 @@ function detectCollectionFlows(calls, sources, sinks, taintedVars, unreachableLi
|
|
|
44776
45285
|
}
|
|
44777
45286
|
if (arg.variable) {
|
|
44778
45287
|
const varName = arg.variable;
|
|
44779
|
-
|
|
45288
|
+
let scopedName = varName;
|
|
45289
|
+
if (call.in_method) {
|
|
45290
|
+
const blockLine = graph ? defLineForUse(graph, varName, call.location.line) : call.location.line;
|
|
45291
|
+
scopedName = constPropFlowLookup(
|
|
45292
|
+
"java",
|
|
45293
|
+
call.in_method,
|
|
45294
|
+
varName,
|
|
45295
|
+
blockLine,
|
|
45296
|
+
constProp,
|
|
45297
|
+
true
|
|
45298
|
+
);
|
|
45299
|
+
}
|
|
44780
45300
|
const expr = arg.expression ?? "";
|
|
44781
45301
|
const explicitField = isFieldUseExpression(
|
|
44782
45302
|
expr,
|