circle-ir 4.16.1 → 4.18.1
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/advisories.d.ts +186 -0
- package/dist/analysis/advisories.d.ts.map +1 -0
- package/dist/analysis/advisories.js +391 -0
- package/dist/analysis/advisories.js.map +1 -0
- package/dist/analysis/comment-lines.d.ts +14 -0
- package/dist/analysis/comment-lines.d.ts.map +1 -0
- package/dist/analysis/comment-lines.js +34 -0
- package/dist/analysis/comment-lines.js.map +1 -0
- package/dist/analysis/config-loader.d.ts.map +1 -1
- package/dist/analysis/config-loader.js +59 -5
- package/dist/analysis/config-loader.js.map +1 -1
- package/dist/analysis/constant-caller-gate.d.ts +2 -2
- package/dist/analysis/constant-caller-gate.d.ts.map +1 -1
- package/dist/analysis/constant-caller-gate.js +205 -29
- package/dist/analysis/constant-caller-gate.js.map +1 -1
- 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 +15 -0
- package/dist/analysis/constant-propagation/propagator.d.ts.map +1 -1
- package/dist/analysis/constant-propagation/propagator.js +224 -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/entry-point-detection.d.ts.map +1 -1
- package/dist/analysis/entry-point-detection.js +72 -0
- package/dist/analysis/entry-point-detection.js.map +1 -1
- package/dist/analysis/interprocedural.d.ts.map +1 -1
- package/dist/analysis/interprocedural.js +75 -8
- package/dist/analysis/interprocedural.js.map +1 -1
- package/dist/analysis/maven-declared.d.ts +72 -0
- package/dist/analysis/maven-declared.d.ts.map +1 -0
- package/dist/analysis/maven-declared.js +216 -0
- package/dist/analysis/maven-declared.js.map +1 -0
- package/dist/analysis/maven-version.d.ts +28 -0
- package/dist/analysis/maven-version.d.ts.map +1 -0
- package/dist/analysis/maven-version.js +172 -0
- package/dist/analysis/maven-version.js.map +1 -0
- package/dist/analysis/passes/constant-propagation-pass.d.ts.map +1 -1
- package/dist/analysis/passes/constant-propagation-pass.js +29 -1
- package/dist/analysis/passes/constant-propagation-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 +84 -2
- package/dist/analysis/passes/cross-file-pass.js.map +1 -1
- package/dist/analysis/passes/csharp-sanitizer-credit.d.ts +7 -0
- package/dist/analysis/passes/csharp-sanitizer-credit.d.ts.map +1 -1
- package/dist/analysis/passes/csharp-sanitizer-credit.js +44 -1
- package/dist/analysis/passes/csharp-sanitizer-credit.js.map +1 -1
- package/dist/analysis/passes/insecure-cookie-pass.d.ts +2 -0
- package/dist/analysis/passes/insecure-cookie-pass.d.ts.map +1 -1
- package/dist/analysis/passes/insecure-cookie-pass.js +25 -3
- package/dist/analysis/passes/insecure-cookie-pass.js.map +1 -1
- package/dist/analysis/passes/interprocedural-pass.d.ts +11 -14
- package/dist/analysis/passes/interprocedural-pass.d.ts.map +1 -1
- package/dist/analysis/passes/interprocedural-pass.js +562 -0
- package/dist/analysis/passes/interprocedural-pass.js.map +1 -1
- package/dist/analysis/passes/language-sources-pass.d.ts +18 -1
- package/dist/analysis/passes/language-sources-pass.d.ts.map +1 -1
- package/dist/analysis/passes/language-sources-pass.js +1079 -14
- 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 +82 -3
- package/dist/analysis/passes/param-source-flow-gate-pass.js.map +1 -1
- package/dist/analysis/passes/sink-filter-pass.d.ts +44 -2
- package/dist/analysis/passes/sink-filter-pass.d.ts.map +1 -1
- package/dist/analysis/passes/sink-filter-pass.js +690 -12
- 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 +298 -19
- package/dist/analysis/passes/taint-propagation-pass.js.map +1 -1
- package/dist/analysis/passes/tls-verify-disabled-pass.d.ts +18 -0
- package/dist/analysis/passes/tls-verify-disabled-pass.d.ts.map +1 -1
- package/dist/analysis/passes/tls-verify-disabled-pass.js +161 -0
- package/dist/analysis/passes/tls-verify-disabled-pass.js.map +1 -1
- package/dist/analysis/passes/weak-random-pass.d.ts +6 -0
- package/dist/analysis/passes/weak-random-pass.d.ts.map +1 -1
- package/dist/analysis/passes/weak-random-pass.js +29 -0
- package/dist/analysis/passes/weak-random-pass.js.map +1 -1
- package/dist/analysis/promote-cross-file-flows.d.ts +17 -0
- package/dist/analysis/promote-cross-file-flows.d.ts.map +1 -0
- package/dist/analysis/promote-cross-file-flows.js +32 -0
- package/dist/analysis/promote-cross-file-flows.js.map +1 -0
- 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/sbom.d.ts +100 -0
- package/dist/analysis/sbom.d.ts.map +1 -1
- package/dist/analysis/sbom.js +514 -3
- package/dist/analysis/sbom.js.map +1 -1
- package/dist/analysis/semver-version.d.ts +30 -0
- package/dist/analysis/semver-version.d.ts.map +1 -0
- package/dist/analysis/semver-version.js +76 -0
- package/dist/analysis/semver-version.js.map +1 -0
- package/dist/analysis/taint-matcher.d.ts +5 -0
- package/dist/analysis/taint-matcher.d.ts.map +1 -1
- package/dist/analysis/taint-matcher.js +226 -4
- package/dist/analysis/taint-matcher.js.map +1 -1
- package/dist/analyzer.d.ts +57 -0
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +69 -6
- package/dist/analyzer.js.map +1 -1
- package/dist/browser/circle-ir.js +3360 -314
- package/dist/core/circle-ir-core.cjs +783 -220
- package/dist/core/circle-ir-core.js +783 -220
- package/dist/core/extractors/dfg.js +205 -2
- package/dist/core/extractors/dfg.js.map +1 -1
- package/dist/core/extractors/ts-declaration-detail.d.ts +39 -0
- package/dist/core/extractors/ts-declaration-detail.d.ts.map +1 -0
- package/dist/core/extractors/ts-declaration-detail.js +266 -0
- package/dist/core/extractors/ts-declaration-detail.js.map +1 -0
- package/dist/core/extractors/types.js +53 -9
- package/dist/core/extractors/types.js.map +1 -1
- package/dist/graph/analysis-pass.d.ts +4 -1
- package/dist/graph/analysis-pass.d.ts.map +1 -1
- package/dist/graph/analysis-pass.js +4 -1
- package/dist/graph/analysis-pass.js.map +1 -1
- package/dist/graph-facts/dead-code.d.ts +62 -0
- package/dist/graph-facts/dead-code.d.ts.map +1 -0
- package/dist/graph-facts/dead-code.js +90 -0
- package/dist/graph-facts/dead-code.js.map +1 -0
- package/dist/graph-facts/dependency-calls.d.ts +86 -0
- package/dist/graph-facts/dependency-calls.d.ts.map +1 -0
- package/dist/graph-facts/dependency-calls.js +93 -0
- package/dist/graph-facts/dependency-calls.js.map +1 -0
- package/dist/graph-facts/index.d.ts +10 -0
- package/dist/graph-facts/index.d.ts.map +1 -0
- package/dist/graph-facts/index.js +10 -0
- package/dist/graph-facts/index.js.map +1 -0
- package/dist/graph-facts/maven-packages.d.ts +42 -0
- package/dist/graph-facts/maven-packages.d.ts.map +1 -0
- package/dist/graph-facts/maven-packages.js +133 -0
- package/dist/graph-facts/maven-packages.js.map +1 -0
- package/dist/graph-facts/reach.d.ts +203 -0
- package/dist/graph-facts/reach.d.ts.map +1 -0
- package/dist/graph-facts/reach.js +219 -0
- package/dist/graph-facts/reach.js.map +1 -0
- package/dist/graph-facts/removal-reach.d.ts +73 -0
- package/dist/graph-facts/removal-reach.d.ts.map +1 -0
- package/dist/graph-facts/removal-reach.js +64 -0
- package/dist/graph-facts/removal-reach.js.map +1 -0
- package/dist/index.d.ts +21 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +20 -3
- package/dist/index.js.map +1 -1
- package/dist/navigation/go-graph.d.ts +68 -0
- package/dist/navigation/go-graph.d.ts.map +1 -0
- package/dist/navigation/go-graph.js +753 -0
- package/dist/navigation/go-graph.js.map +1 -0
- package/dist/navigation/index.d.ts +7 -1
- package/dist/navigation/index.d.ts.map +1 -1
- package/dist/navigation/index.js +6 -0
- package/dist/navigation/index.js.map +1 -1
- package/dist/navigation/java-entry-tables.d.ts +43 -0
- package/dist/navigation/java-entry-tables.d.ts.map +1 -0
- package/dist/navigation/java-entry-tables.js +56 -0
- package/dist/navigation/java-entry-tables.js.map +1 -0
- package/dist/navigation/java-graph.d.ts +64 -0
- package/dist/navigation/java-graph.d.ts.map +1 -0
- package/dist/navigation/java-graph.js +466 -0
- package/dist/navigation/java-graph.js.map +1 -0
- package/dist/navigation/navigation-index.d.ts +91 -2
- package/dist/navigation/navigation-index.d.ts.map +1 -1
- package/dist/navigation/navigation-index.js +372 -45
- package/dist/navigation/navigation-index.js.map +1 -1
- package/dist/navigation/python-graph.d.ts +73 -0
- package/dist/navigation/python-graph.d.ts.map +1 -0
- package/dist/navigation/python-graph.js +867 -0
- package/dist/navigation/python-graph.js.map +1 -0
- package/dist/navigation/ts-call-edges.d.ts +193 -0
- package/dist/navigation/ts-call-edges.d.ts.map +1 -0
- package/dist/navigation/ts-call-edges.js +2089 -0
- package/dist/navigation/ts-call-edges.js.map +1 -0
- package/dist/navigation/ts-entry-tables.d.ts +88 -0
- package/dist/navigation/ts-entry-tables.d.ts.map +1 -0
- package/dist/navigation/ts-entry-tables.js +50 -0
- package/dist/navigation/ts-entry-tables.js.map +1 -0
- package/dist/path-set/branches.d.ts +52 -0
- package/dist/path-set/branches.d.ts.map +1 -0
- package/dist/path-set/branches.js +124 -0
- package/dist/path-set/branches.js.map +1 -0
- package/dist/path-set/finding-diff.d.ts +63 -0
- package/dist/path-set/finding-diff.d.ts.map +1 -0
- package/dist/path-set/finding-diff.js +150 -0
- package/dist/path-set/finding-diff.js.map +1 -0
- package/dist/path-set/index.d.ts +18 -0
- package/dist/path-set/index.d.ts.map +1 -0
- package/dist/path-set/index.js +12 -0
- package/dist/path-set/index.js.map +1 -0
- package/dist/path-set/line-diff.d.ts +19 -0
- package/dist/path-set/line-diff.d.ts.map +1 -0
- package/dist/path-set/line-diff.js +98 -0
- package/dist/path-set/line-diff.js.map +1 -0
- package/dist/path-set/path-set.d.ts +88 -0
- package/dist/path-set/path-set.d.ts.map +1 -0
- package/dist/path-set/path-set.js +716 -0
- package/dist/path-set/path-set.js.map +1 -0
- package/dist/path-set/templates.d.ts +42 -0
- package/dist/path-set/templates.d.ts.map +1 -0
- package/dist/path-set/templates.js +119 -0
- package/dist/path-set/templates.js.map +1 -0
- package/dist/path-set/types.d.ts +195 -0
- package/dist/path-set/types.d.ts.map +1 -0
- package/dist/path-set/types.js +8 -0
- package/dist/path-set/types.js.map +1 -0
- package/dist/remedy-class/index.d.ts +12 -0
- package/dist/remedy-class/index.d.ts.map +1 -0
- package/dist/remedy-class/index.js +9 -0
- package/dist/remedy-class/index.js.map +1 -0
- package/dist/remedy-class/match.d.ts +165 -0
- package/dist/remedy-class/match.d.ts.map +1 -0
- package/dist/remedy-class/match.js +705 -0
- package/dist/remedy-class/match.js.map +1 -0
- package/dist/remedy-class/output-context.d.ts +25 -0
- package/dist/remedy-class/output-context.d.ts.map +1 -0
- package/dist/remedy-class/output-context.js +150 -0
- package/dist/remedy-class/output-context.js.map +1 -0
- package/dist/remedy-class/sha256.d.ts +18 -0
- package/dist/remedy-class/sha256.d.ts.map +1 -0
- package/dist/remedy-class/sha256.js +89 -0
- package/dist/remedy-class/sha256.js.map +1 -0
- package/dist/remedy-class/table.d.ts +130 -0
- package/dist/remedy-class/table.d.ts.map +1 -0
- package/dist/remedy-class/table.js +457 -0
- package/dist/remedy-class/table.js.map +1 -0
- package/dist/resolution/cross-file.d.ts +18 -0
- package/dist/resolution/cross-file.d.ts.map +1 -1
- package/dist/resolution/cross-file.js +194 -5
- package/dist/resolution/cross-file.js.map +1 -1
- package/dist/stable-ids/builder.d.ts +20 -0
- package/dist/stable-ids/builder.d.ts.map +1 -0
- package/dist/stable-ids/builder.js +65 -0
- package/dist/stable-ids/builder.js.map +1 -0
- package/dist/stable-ids/diff.d.ts +18 -0
- package/dist/stable-ids/diff.d.ts.map +1 -0
- package/dist/stable-ids/diff.js +37 -0
- package/dist/stable-ids/diff.js.map +1 -0
- package/dist/stable-ids/go.d.ts +28 -0
- package/dist/stable-ids/go.d.ts.map +1 -0
- package/dist/stable-ids/go.js +160 -0
- package/dist/stable-ids/go.js.map +1 -0
- package/dist/stable-ids/hash.d.ts +22 -0
- package/dist/stable-ids/hash.d.ts.map +1 -0
- package/dist/stable-ids/hash.js +167 -0
- package/dist/stable-ids/hash.js.map +1 -0
- package/dist/stable-ids/index.d.ts +30 -0
- package/dist/stable-ids/index.d.ts.map +1 -0
- package/dist/stable-ids/index.js +59 -0
- package/dist/stable-ids/index.js.map +1 -0
- package/dist/stable-ids/java.d.ts +28 -0
- package/dist/stable-ids/java.d.ts.map +1 -0
- package/dist/stable-ids/java.js +224 -0
- package/dist/stable-ids/java.js.map +1 -0
- package/dist/stable-ids/moves.d.ts +87 -0
- package/dist/stable-ids/moves.d.ts.map +1 -0
- package/dist/stable-ids/moves.js +222 -0
- package/dist/stable-ids/moves.js.map +1 -0
- package/dist/stable-ids/nodes.d.ts +32 -0
- package/dist/stable-ids/nodes.d.ts.map +1 -0
- package/dist/stable-ids/nodes.js +92 -0
- package/dist/stable-ids/nodes.js.map +1 -0
- package/dist/stable-ids/normalize.d.ts +35 -0
- package/dist/stable-ids/normalize.d.ts.map +1 -0
- package/dist/stable-ids/normalize.js +174 -0
- package/dist/stable-ids/normalize.js.map +1 -0
- package/dist/stable-ids/parse-errors.d.ts +24 -0
- package/dist/stable-ids/parse-errors.d.ts.map +1 -0
- package/dist/stable-ids/parse-errors.js +46 -0
- package/dist/stable-ids/parse-errors.js.map +1 -0
- package/dist/stable-ids/python.d.ts +27 -0
- package/dist/stable-ids/python.d.ts.map +1 -0
- package/dist/stable-ids/python.js +212 -0
- package/dist/stable-ids/python.js.map +1 -0
- package/dist/stable-ids/ts-parse-errors.d.ts +27 -0
- package/dist/stable-ids/ts-parse-errors.d.ts.map +1 -0
- package/dist/stable-ids/ts-parse-errors.js +75 -0
- package/dist/stable-ids/ts-parse-errors.js.map +1 -0
- package/dist/stable-ids/types.d.ts +106 -0
- package/dist/stable-ids/types.d.ts.map +1 -0
- package/dist/stable-ids/types.js +16 -0
- package/dist/stable-ids/types.js.map +1 -0
- package/dist/stable-ids/typescript.d.ts +30 -0
- package/dist/stable-ids/typescript.d.ts.map +1 -0
- package/dist/stable-ids/typescript.js +362 -0
- package/dist/stable-ids/typescript.js.map +1 -0
- package/dist/types/config.d.ts +9 -0
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/index.d.ts +53 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js.map +1 -1
- package/package.json +3 -3
|
@@ -1405,11 +1405,11 @@ var Language = class _Language {
|
|
|
1405
1405
|
/**
|
|
1406
1406
|
* Get the node type id for a node type name.
|
|
1407
1407
|
*/
|
|
1408
|
-
idForNodeType(type,
|
|
1408
|
+
idForNodeType(type, named2) {
|
|
1409
1409
|
const typeLength = C.lengthBytesUTF8(type);
|
|
1410
1410
|
const typeAddress = C._malloc(typeLength + 1);
|
|
1411
1411
|
C.stringToUTF8(type, typeAddress, typeLength + 1);
|
|
1412
|
-
const result = C._ts_language_symbol_for_name(this[0], typeAddress, typeLength,
|
|
1412
|
+
const result = C._ts_language_symbol_for_name(this[0], typeAddress, typeLength, named2 ? 1 : 0);
|
|
1413
1413
|
C._free(typeAddress);
|
|
1414
1414
|
return result || null;
|
|
1415
1415
|
}
|
|
@@ -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");
|
|
@@ -10381,6 +10410,79 @@ function processStatement2(stmt, startDefId, startUseId, scopeStack, isJavaScrip
|
|
|
10381
10410
|
carryBranchDefsOut(popped, scopeStack, alwaysRuns);
|
|
10382
10411
|
break;
|
|
10383
10412
|
}
|
|
10413
|
+
case "try_statement":
|
|
10414
|
+
case "try_with_resources_statement": {
|
|
10415
|
+
if (isJavaScript) {
|
|
10416
|
+
const stmtUses = extractUses(stmt, useId, scopeStack, isJavaScript);
|
|
10417
|
+
uses.push(...stmtUses.uses);
|
|
10418
|
+
useId = stmtUses.nextId;
|
|
10419
|
+
break;
|
|
10420
|
+
}
|
|
10421
|
+
scopeStack.push(/* @__PURE__ */ new Map());
|
|
10422
|
+
const resources = stmt.childForFieldName("resources");
|
|
10423
|
+
for (const res of resources?.namedChildren ?? []) {
|
|
10424
|
+
if (!res || res.type !== "resource") continue;
|
|
10425
|
+
const nameNode = res.childForFieldName("name");
|
|
10426
|
+
const value = res.childForFieldName("value");
|
|
10427
|
+
if (value) {
|
|
10428
|
+
const valueUses = extractUses(value, useId, scopeStack, isJavaScript);
|
|
10429
|
+
uses.push(...valueUses.uses);
|
|
10430
|
+
useId = valueUses.nextId;
|
|
10431
|
+
}
|
|
10432
|
+
if (nameNode && nameNode.type === "identifier") {
|
|
10433
|
+
const def = { id: defId++, variable: getNodeText(nameNode), line: nameNode.startPosition.row + 1, column: nameNode.startPosition.column, kind: "local" };
|
|
10434
|
+
defs.push(def);
|
|
10435
|
+
currentScope(scopeStack).set(def.variable, def.id);
|
|
10436
|
+
markDeclared(currentScope(scopeStack), def.variable);
|
|
10437
|
+
} else if (!value) {
|
|
10438
|
+
const resUses = extractUses(res, useId, scopeStack, isJavaScript);
|
|
10439
|
+
uses.push(...resUses.uses);
|
|
10440
|
+
useId = resUses.nextId;
|
|
10441
|
+
}
|
|
10442
|
+
}
|
|
10443
|
+
for (const part of stmt.namedChildren) {
|
|
10444
|
+
if (!part) continue;
|
|
10445
|
+
if (part.type === "block") {
|
|
10446
|
+
const r = processStatement2(part, defId, useId, scopeStack, isJavaScript);
|
|
10447
|
+
defs.push(...r.defs);
|
|
10448
|
+
uses.push(...r.uses);
|
|
10449
|
+
defId = r.nextDefId;
|
|
10450
|
+
useId = r.nextUseId;
|
|
10451
|
+
} else if (part.type === "catch_clause") {
|
|
10452
|
+
scopeStack.push(/* @__PURE__ */ new Map());
|
|
10453
|
+
for (const param of part.namedChildren) {
|
|
10454
|
+
if (!param || param.type !== "catch_formal_parameter") continue;
|
|
10455
|
+
const pname = param.childForFieldName("name");
|
|
10456
|
+
if (pname) {
|
|
10457
|
+
const def = { id: defId++, variable: getNodeText(pname), line: pname.startPosition.row + 1, column: pname.startPosition.column, kind: "local" };
|
|
10458
|
+
defs.push(def);
|
|
10459
|
+
currentScope(scopeStack).set(def.variable, def.id);
|
|
10460
|
+
markDeclared(currentScope(scopeStack), def.variable);
|
|
10461
|
+
}
|
|
10462
|
+
}
|
|
10463
|
+
const body2 = part.childForFieldName("body");
|
|
10464
|
+
if (body2) {
|
|
10465
|
+
const r = processStatement2(body2, defId, useId, scopeStack, isJavaScript);
|
|
10466
|
+
defs.push(...r.defs);
|
|
10467
|
+
uses.push(...r.uses);
|
|
10468
|
+
defId = r.nextDefId;
|
|
10469
|
+
useId = r.nextUseId;
|
|
10470
|
+
}
|
|
10471
|
+
carryBranchDefsOut(scopeStack.pop(), scopeStack, false);
|
|
10472
|
+
} else if (part.type === "finally_clause") {
|
|
10473
|
+
for (const b of part.namedChildren) {
|
|
10474
|
+
if (!b || b.type !== "block") continue;
|
|
10475
|
+
const r = processStatement2(b, defId, useId, scopeStack, isJavaScript);
|
|
10476
|
+
defs.push(...r.defs);
|
|
10477
|
+
uses.push(...r.uses);
|
|
10478
|
+
defId = r.nextDefId;
|
|
10479
|
+
useId = r.nextUseId;
|
|
10480
|
+
}
|
|
10481
|
+
}
|
|
10482
|
+
}
|
|
10483
|
+
carryBranchDefsOut(scopeStack.pop(), scopeStack, false);
|
|
10484
|
+
break;
|
|
10485
|
+
}
|
|
10384
10486
|
case "else_clause": {
|
|
10385
10487
|
for (let i2 = 0; i2 < stmt.childCount; i2++) {
|
|
10386
10488
|
const arm = stmt.child(i2);
|
|
@@ -10996,6 +11098,42 @@ function buildBashDFG(tree) {
|
|
|
10996
11098
|
defs.push(def);
|
|
10997
11099
|
currentScope(scopeStack).set(p, def.id);
|
|
10998
11100
|
}
|
|
11101
|
+
const bashFnDefs = /* @__PURE__ */ new Map();
|
|
11102
|
+
const bashFnByNodeId = /* @__PURE__ */ new Map();
|
|
11103
|
+
for (const fn of findNodes(tree.rootNode, "function_definition")) {
|
|
11104
|
+
const nameNode = fn.childForFieldName("name");
|
|
11105
|
+
if (!nameNode) continue;
|
|
11106
|
+
const fnName = getNodeText(nameNode);
|
|
11107
|
+
if (!fnName || bashFnDefs.has(fnName)) continue;
|
|
11108
|
+
const m = /* @__PURE__ */ new Map();
|
|
11109
|
+
for (const p of positionalParams) {
|
|
11110
|
+
const def = {
|
|
11111
|
+
id: defIdCounter++,
|
|
11112
|
+
variable: p,
|
|
11113
|
+
line: fn.startPosition.row + 1,
|
|
11114
|
+
kind: "param",
|
|
11115
|
+
expression: `bash-fn:${fnName}`
|
|
11116
|
+
};
|
|
11117
|
+
defs.push(def);
|
|
11118
|
+
m.set(p, def.id);
|
|
11119
|
+
}
|
|
11120
|
+
bashFnDefs.set(fnName, m);
|
|
11121
|
+
bashFnByNodeId.set(fn.id, fnName);
|
|
11122
|
+
}
|
|
11123
|
+
const enclosingBashFn = (node) => {
|
|
11124
|
+
let cur = node.parent;
|
|
11125
|
+
while (cur) {
|
|
11126
|
+
if (cur.type === "function_definition") return bashFnByNodeId.get(cur.id) ?? null;
|
|
11127
|
+
cur = cur.parent;
|
|
11128
|
+
}
|
|
11129
|
+
return null;
|
|
11130
|
+
};
|
|
11131
|
+
const positionalDef = (varName, node, fallback) => {
|
|
11132
|
+
if (!positionalParams.includes(varName)) return fallback;
|
|
11133
|
+
const fnName = enclosingBashFn(node);
|
|
11134
|
+
if (!fnName) return fallback;
|
|
11135
|
+
return bashFnDefs.get(fnName)?.get(varName) ?? fallback;
|
|
11136
|
+
};
|
|
10999
11137
|
walkTree(tree.rootNode, (node) => {
|
|
11000
11138
|
if (node.type === "variable_assignment") {
|
|
11001
11139
|
const nameNode = node.childForFieldName("name");
|
|
@@ -11048,7 +11186,7 @@ function buildBashDFG(tree) {
|
|
|
11048
11186
|
if (varNameNode) {
|
|
11049
11187
|
const varName = getNodeText(varNameNode);
|
|
11050
11188
|
if (varName && !varName.startsWith("?") && !varName.startsWith("#")) {
|
|
11051
|
-
let reachingDef = findReachingDef(varName, scopeStack);
|
|
11189
|
+
let reachingDef = positionalDef(varName, node, findReachingDef(varName, scopeStack));
|
|
11052
11190
|
if (reachingDef === null && !positionalParams.includes(varName)) {
|
|
11053
11191
|
const def = {
|
|
11054
11192
|
id: defIdCounter++,
|
|
@@ -11072,7 +11210,7 @@ function buildBashDFG(tree) {
|
|
|
11072
11210
|
const varNameNode = node.namedChildCount > 0 ? node.namedChild(0) : null;
|
|
11073
11211
|
if (varNameNode && varNameNode.type === "variable_name") {
|
|
11074
11212
|
const varName = getNodeText(varNameNode);
|
|
11075
|
-
let reachingDef = findReachingDef(varName, scopeStack);
|
|
11213
|
+
let reachingDef = positionalDef(varName, node, findReachingDef(varName, scopeStack));
|
|
11076
11214
|
if (reachingDef === null && !positionalParams.includes(varName)) {
|
|
11077
11215
|
const def = {
|
|
11078
11216
|
id: defIdCounter++,
|
|
@@ -11094,8 +11232,56 @@ function buildBashDFG(tree) {
|
|
|
11094
11232
|
}
|
|
11095
11233
|
});
|
|
11096
11234
|
const chains = computeChains(defs, uses);
|
|
11235
|
+
chains.push(...bashCallArgChains(tree, bashFnDefs, uses, positionalParams));
|
|
11097
11236
|
return { defs, uses, chains };
|
|
11098
11237
|
}
|
|
11238
|
+
function bashCallArgChains(tree, bashFnDefs, uses, positionalParams) {
|
|
11239
|
+
const chains = [];
|
|
11240
|
+
if (bashFnDefs.size === 0) return chains;
|
|
11241
|
+
const renumbers = /* @__PURE__ */ new Set();
|
|
11242
|
+
for (const fn of findNodes(tree.rootNode, "function_definition")) {
|
|
11243
|
+
const nameNode = fn.childForFieldName("name");
|
|
11244
|
+
if (!nameNode) continue;
|
|
11245
|
+
if (/\bshift\b|\bset\s+--/.test(getNodeText(fn))) renumbers.add(getNodeText(nameNode));
|
|
11246
|
+
}
|
|
11247
|
+
const usesByLine = /* @__PURE__ */ new Map();
|
|
11248
|
+
for (const u of uses) {
|
|
11249
|
+
const l = usesByLine.get(u.line) ?? [];
|
|
11250
|
+
l.push(u);
|
|
11251
|
+
usesByLine.set(u.line, l);
|
|
11252
|
+
}
|
|
11253
|
+
for (const cmd of findNodes(tree.rootNode, "command")) {
|
|
11254
|
+
const nameNode = cmd.childForFieldName("name");
|
|
11255
|
+
if (!nameNode) continue;
|
|
11256
|
+
const fnDefs = bashFnDefs.get(getNodeText(nameNode));
|
|
11257
|
+
if (!fnDefs) continue;
|
|
11258
|
+
const fnName = getNodeText(nameNode);
|
|
11259
|
+
let position = 0;
|
|
11260
|
+
for (let i2 = 0; i2 < cmd.childCount; i2++) {
|
|
11261
|
+
const arg = cmd.child(i2);
|
|
11262
|
+
if (!arg || arg.id === nameNode.id) continue;
|
|
11263
|
+
if (arg.type.includes("redirect") || arg.type === "heredoc_body" || arg.type === "file_descriptor") continue;
|
|
11264
|
+
const text = getNodeText(arg);
|
|
11265
|
+
if (!text.trim()) continue;
|
|
11266
|
+
position++;
|
|
11267
|
+
const names = /* @__PURE__ */ new Set();
|
|
11268
|
+
const re = /\$\{?([A-Za-z_][A-Za-z0-9_]*|[0-9@*])/g;
|
|
11269
|
+
let m;
|
|
11270
|
+
while ((m = re.exec(text)) !== null) names.add(m[1]);
|
|
11271
|
+
if (names.size === 0) continue;
|
|
11272
|
+
const spreads = /\$\{?[@*]/.test(text);
|
|
11273
|
+
const targets = spreads || renumbers.has(fnName) ? positionalParams.map((p) => fnDefs.get(p)).filter((d) => d !== void 0) : [fnDefs.get(String(position))].filter((d) => d !== void 0);
|
|
11274
|
+
const lineUses = usesByLine.get(arg.startPosition.row + 1) ?? [];
|
|
11275
|
+
for (const name2 of names) {
|
|
11276
|
+
for (const u of lineUses) {
|
|
11277
|
+
if (u.variable !== name2 || u.def_id === null) continue;
|
|
11278
|
+
for (const t of targets) chains.push({ from_def: u.def_id, to_def: t, via: name2 });
|
|
11279
|
+
}
|
|
11280
|
+
}
|
|
11281
|
+
}
|
|
11282
|
+
}
|
|
11283
|
+
return chains;
|
|
11284
|
+
}
|
|
11099
11285
|
function buildRustDFG(tree, cache) {
|
|
11100
11286
|
const defs = [];
|
|
11101
11287
|
const uses = [];
|
|
@@ -12331,6 +12517,195 @@ function parseDistributedSliceAttribute(attrItem) {
|
|
|
12331
12517
|
};
|
|
12332
12518
|
}
|
|
12333
12519
|
|
|
12520
|
+
// src/core/extractors/ts-declaration-detail.ts
|
|
12521
|
+
function named(node) {
|
|
12522
|
+
const out2 = [];
|
|
12523
|
+
for (let i2 = 0; i2 < node.namedChildCount; i2++) {
|
|
12524
|
+
const c = node.namedChild(i2);
|
|
12525
|
+
if (c && c.type !== "comment") out2.push(c);
|
|
12526
|
+
}
|
|
12527
|
+
return out2;
|
|
12528
|
+
}
|
|
12529
|
+
function tokens(node) {
|
|
12530
|
+
const out2 = [];
|
|
12531
|
+
for (let i2 = 0; i2 < node.childCount; i2++) {
|
|
12532
|
+
const c = node.child(i2);
|
|
12533
|
+
if (c && !c.isNamed) out2.push(c.text);
|
|
12534
|
+
}
|
|
12535
|
+
return out2;
|
|
12536
|
+
}
|
|
12537
|
+
function stringLiteral(node) {
|
|
12538
|
+
if (node.type !== "string") return void 0;
|
|
12539
|
+
return named(node).map((c) => c.text).join("");
|
|
12540
|
+
}
|
|
12541
|
+
function decoratorInfo(node) {
|
|
12542
|
+
const expr = named(node)[0];
|
|
12543
|
+
if (!expr) return null;
|
|
12544
|
+
let callee = expr;
|
|
12545
|
+
let args2 = [];
|
|
12546
|
+
if (expr.type === "call_expression") {
|
|
12547
|
+
callee = expr.childForFieldName("function");
|
|
12548
|
+
const a = expr.childForFieldName("arguments");
|
|
12549
|
+
args2 = a ? named(a) : [];
|
|
12550
|
+
}
|
|
12551
|
+
if (!callee) return null;
|
|
12552
|
+
const name2 = callee.type === "member_expression" ? callee.childForFieldName("property")?.text : callee.text;
|
|
12553
|
+
if (!name2) return null;
|
|
12554
|
+
return {
|
|
12555
|
+
name: name2,
|
|
12556
|
+
arguments: args2.map((a) => {
|
|
12557
|
+
const value = stringLiteral(a);
|
|
12558
|
+
return value === void 0 ? { text: a.text } : { text: a.text, value };
|
|
12559
|
+
})
|
|
12560
|
+
};
|
|
12561
|
+
}
|
|
12562
|
+
function decoratorsOf(nodes) {
|
|
12563
|
+
return nodes.filter((n) => n.type === "decorator").map(decoratorInfo).filter((d) => d !== null);
|
|
12564
|
+
}
|
|
12565
|
+
function exportModifiers(holder) {
|
|
12566
|
+
const stmt = holder.parent?.type === "export_statement" ? holder.parent : null;
|
|
12567
|
+
if (!stmt) return [];
|
|
12568
|
+
return tokens(stmt).includes("default") ? ["export", "default"] : ["export"];
|
|
12569
|
+
}
|
|
12570
|
+
var MEMBER_WORDS = ["readonly", "abstract", "override", "static", "declare"];
|
|
12571
|
+
function memberModifiers(member) {
|
|
12572
|
+
const out2 = [];
|
|
12573
|
+
for (let i2 = 0; i2 < member.childCount; i2++) {
|
|
12574
|
+
const c = member.child(i2);
|
|
12575
|
+
if (!c) continue;
|
|
12576
|
+
if (c.type === "accessibility_modifier") out2.push(c.text);
|
|
12577
|
+
else if (c.type === "override_modifier") out2.push("override");
|
|
12578
|
+
else if (!c.isNamed && MEMBER_WORDS.includes(c.text)) out2.push(c.text);
|
|
12579
|
+
}
|
|
12580
|
+
const name2 = member.childForFieldName("name") ?? member.childForFieldName("property");
|
|
12581
|
+
if (name2?.type === "private_property_identifier" && !out2.includes("private")) out2.push("private");
|
|
12582
|
+
return out2;
|
|
12583
|
+
}
|
|
12584
|
+
function annotationText(node) {
|
|
12585
|
+
return node ? node.text.replace(/^:\s*/, "") : null;
|
|
12586
|
+
}
|
|
12587
|
+
function addModifiers(target, extra) {
|
|
12588
|
+
for (const m of extra) if (!target.includes(m)) target.push(m);
|
|
12589
|
+
}
|
|
12590
|
+
function clauseExports(root) {
|
|
12591
|
+
const names = /* @__PURE__ */ new Set();
|
|
12592
|
+
let defaultName = null;
|
|
12593
|
+
for (const stmt of named(root)) {
|
|
12594
|
+
if (stmt.type !== "export_statement" || stmt.childForFieldName("source")) continue;
|
|
12595
|
+
const clause = named(stmt).find((c) => c.type === "export_clause");
|
|
12596
|
+
for (const s of clause ? named(clause) : []) {
|
|
12597
|
+
const name2 = s.childForFieldName("name")?.text;
|
|
12598
|
+
const alias = s.childForFieldName("alias")?.text;
|
|
12599
|
+
if (!name2) continue;
|
|
12600
|
+
if (alias === "default") defaultName = name2;
|
|
12601
|
+
else names.add(name2);
|
|
12602
|
+
}
|
|
12603
|
+
const value = stmt.childForFieldName("value");
|
|
12604
|
+
if (value?.type === "identifier") defaultName = value.text;
|
|
12605
|
+
}
|
|
12606
|
+
return { names, defaultName };
|
|
12607
|
+
}
|
|
12608
|
+
function enrichMethod(m, fn, decorators, extra) {
|
|
12609
|
+
addModifiers(m.modifiers, extra);
|
|
12610
|
+
const ret = fn.childForFieldName("return_type");
|
|
12611
|
+
if (ret && m.return_type === null) m.return_type = annotationText(ret);
|
|
12612
|
+
const decs = decoratorsOf(decorators);
|
|
12613
|
+
if (decs.length > 0) m.decorators = decs;
|
|
12614
|
+
const params = fn.childForFieldName("parameters");
|
|
12615
|
+
for (const p of params ? named(params) : []) {
|
|
12616
|
+
const pd = decoratorsOf(named(p));
|
|
12617
|
+
if (pd.length === 0) continue;
|
|
12618
|
+
const line = p.startPosition.row + 1;
|
|
12619
|
+
const info2 = m.parameters.find((x) => x.line === line && !x.decorators && p.text.includes(x.name.replace(/^\.\.\./, "")));
|
|
12620
|
+
if (info2) info2.decorators = pd;
|
|
12621
|
+
}
|
|
12622
|
+
}
|
|
12623
|
+
function enrichClassBody(type, body2) {
|
|
12624
|
+
let pending = [];
|
|
12625
|
+
for (const member of named(body2)) {
|
|
12626
|
+
if (member.type === "decorator") {
|
|
12627
|
+
pending.push(member);
|
|
12628
|
+
continue;
|
|
12629
|
+
}
|
|
12630
|
+
const line = member.startPosition.row + 1;
|
|
12631
|
+
const nameNode = member.childForFieldName("name") ?? member.childForFieldName("property");
|
|
12632
|
+
const name2 = nameNode?.text;
|
|
12633
|
+
if (member.type === "method_definition" && name2) {
|
|
12634
|
+
const m = type.methods.find((x) => x.name === name2 && x.start_line === line);
|
|
12635
|
+
if (m) enrichMethod(m, member, pending, memberModifiers(member));
|
|
12636
|
+
} else if ((member.type === "public_field_definition" || member.type === "field_definition") && name2) {
|
|
12637
|
+
const f = type.fields.find((x) => x.name === name2);
|
|
12638
|
+
if (f) {
|
|
12639
|
+
addModifiers(f.modifiers, memberModifiers(member));
|
|
12640
|
+
if (f.type === null) f.type = annotationText(member.childForFieldName("type"));
|
|
12641
|
+
const decs = decoratorsOf(pending);
|
|
12642
|
+
if (decs.length > 0) f.decorators = decs;
|
|
12643
|
+
}
|
|
12644
|
+
}
|
|
12645
|
+
pending = [];
|
|
12646
|
+
}
|
|
12647
|
+
}
|
|
12648
|
+
function enrichTsDeclarations(tree, types) {
|
|
12649
|
+
const root = tree.rootNode;
|
|
12650
|
+
const { names, defaultName } = clauseExports(root);
|
|
12651
|
+
const clauseMods = (name2) => name2 === defaultName ? ["export", "default"] : names.has(name2) ? ["export"] : [];
|
|
12652
|
+
const visit = (node) => {
|
|
12653
|
+
const t = node.type;
|
|
12654
|
+
if (t === "class_declaration" || t === "abstract_class_declaration" || t === "interface_declaration") {
|
|
12655
|
+
const line = node.startPosition.row + 1;
|
|
12656
|
+
const name2 = node.childForFieldName("name")?.text;
|
|
12657
|
+
const type = types.find((x) => x.name === name2 && x.start_line === line);
|
|
12658
|
+
if (type && name2) {
|
|
12659
|
+
const mods = [...exportModifiers(node), ...clauseMods(name2)];
|
|
12660
|
+
if (t === "abstract_class_declaration") mods.push("abstract");
|
|
12661
|
+
if (node.parent?.type === "ambient_declaration") mods.push("declare");
|
|
12662
|
+
type.modifiers = [...new Set(mods)];
|
|
12663
|
+
const decs = decoratorsOf([...node.parent?.type === "export_statement" ? named(node.parent) : [], ...named(node)]);
|
|
12664
|
+
if (decs.length > 0) type.decorators = decs;
|
|
12665
|
+
const body2 = node.childForFieldName("body");
|
|
12666
|
+
if (body2 && t !== "interface_declaration") enrichClassBody(type, body2);
|
|
12667
|
+
}
|
|
12668
|
+
} else if (t === "function_declaration" || t === "generator_function_declaration") {
|
|
12669
|
+
const module2 = types.find((x) => x.name === "<module>");
|
|
12670
|
+
const name2 = node.childForFieldName("name")?.text;
|
|
12671
|
+
const m = module2?.methods.find((x) => x.name === name2 && x.start_line === node.startPosition.row + 1);
|
|
12672
|
+
if (m && name2) enrichMethod(m, node, [], [...exportModifiers(node), ...clauseMods(name2)]);
|
|
12673
|
+
} else if (t === "variable_declarator") {
|
|
12674
|
+
const value = node.childForFieldName("value");
|
|
12675
|
+
const name2 = node.childForFieldName("name")?.text;
|
|
12676
|
+
const decl = node.parent;
|
|
12677
|
+
const module2 = types.find((x) => x.name === "<module>");
|
|
12678
|
+
const m = module2?.methods.find((x) => x.name === name2 && x.start_line === node.startPosition.row + 1);
|
|
12679
|
+
if (m && name2 && value?.type === "arrow_function" && decl) enrichMethod(m, value, [], [...exportModifiers(decl), ...clauseMods(name2)]);
|
|
12680
|
+
}
|
|
12681
|
+
for (const c of named(node)) visit(c);
|
|
12682
|
+
};
|
|
12683
|
+
visit(root);
|
|
12684
|
+
}
|
|
12685
|
+
function extractTsExports(types) {
|
|
12686
|
+
const esm = types.some((t) => t.modifiers?.includes("export") || t.methods.some((m) => m.modifiers.includes("export")));
|
|
12687
|
+
if (!esm) return null;
|
|
12688
|
+
const out2 = [];
|
|
12689
|
+
const visibility = (mods) => mods.includes("private") ? null : mods.includes("protected") ? "protected" : "public";
|
|
12690
|
+
for (const t of types) {
|
|
12691
|
+
if (t.name === "<module>") {
|
|
12692
|
+
for (const m of t.methods) if (m.modifiers.includes("export")) out2.push({ symbol: m.name, kind: "method", visibility: "public" });
|
|
12693
|
+
continue;
|
|
12694
|
+
}
|
|
12695
|
+
if (!t.modifiers?.includes("export")) continue;
|
|
12696
|
+
out2.push({ symbol: t.name, kind: t.kind === "enum" ? "class" : t.kind, visibility: "public" });
|
|
12697
|
+
for (const m of t.methods) {
|
|
12698
|
+
const v = visibility(m.modifiers);
|
|
12699
|
+
if (v) out2.push({ symbol: `${t.name}.${m.name}`, kind: "method", visibility: v });
|
|
12700
|
+
}
|
|
12701
|
+
for (const f of t.fields) {
|
|
12702
|
+
const v = visibility(f.modifiers);
|
|
12703
|
+
if (v) out2.push({ symbol: `${t.name}.${f.name}`, kind: "field", visibility: v });
|
|
12704
|
+
}
|
|
12705
|
+
}
|
|
12706
|
+
return out2;
|
|
12707
|
+
}
|
|
12708
|
+
|
|
12334
12709
|
// src/analysis/config-loader.ts
|
|
12335
12710
|
var DEFAULT_SOURCES = [
|
|
12336
12711
|
// HTTP Sources (Servlet API)
|
|
@@ -13032,6 +13407,10 @@ var DEFAULT_SOURCES = [
|
|
|
13032
13407
|
{ method: "get", class: "Redis", type: "db_input", severity: "medium", return_tainted: true, languages: ["javascript", "typescript"] },
|
|
13033
13408
|
{ method: "hget", class: "Redis", type: "db_input", severity: "medium", return_tainted: true, languages: ["javascript", "typescript"] },
|
|
13034
13409
|
{ method: "mget", class: "Redis", type: "db_input", severity: "medium", return_tainted: true, languages: ["javascript", "typescript"] },
|
|
13410
|
+
// Node redis clients are often bound to `cache` from `createClient()`; class
|
|
13411
|
+
// `cache` is scoped to JS/TS (Python Django cache above) and Map.get is
|
|
13412
|
+
// filtered in sink-filter-pass (#818 src14).
|
|
13413
|
+
{ method: "get", class: "cache", type: "db_input", severity: "medium", return_tainted: true, languages: ["javascript", "typescript"] },
|
|
13035
13414
|
{ method: "get", class: "Jedis", type: "db_input", severity: "medium", return_tainted: true, languages: ["java"] },
|
|
13036
13415
|
{ method: "hget", class: "Jedis", type: "db_input", severity: "medium", return_tainted: true, languages: ["java"] },
|
|
13037
13416
|
{ method: "mget", class: "Jedis", type: "db_input", severity: "medium", return_tainted: true, languages: ["java"] },
|
|
@@ -13211,7 +13590,9 @@ var TRUST_BOUNDARY_FRAMEWORK_SINKS = [
|
|
|
13211
13590
|
// on arg[1] (the *Cookie) catches struct-literal composite tainting.
|
|
13212
13591
|
{ method: "SetCookie", class: "http", type: "trust_boundary", cwe: "CWE-501", severity: "medium", arg_positions: [1], languages: ["go"] },
|
|
13213
13592
|
// gin: c.SetCookie(name, value, maxAge, path, domain, secure, httpOnly).
|
|
13214
|
-
{ method: "SetCookie", class: "Context", type: "trust_boundary", cwe: "CWE-501", severity: "medium", arg_positions: [1], languages: ["go"] }
|
|
13593
|
+
{ method: "SetCookie", class: "Context", type: "trust_boundary", cwe: "CWE-501", severity: "medium", arg_positions: [1], languages: ["go"] },
|
|
13594
|
+
// --- Rust: actix-session / tower-sessions write -------------------------
|
|
13595
|
+
{ method: "insert", class: "Session", type: "trust_boundary", cwe: "CWE-501", severity: "medium", arg_positions: [1], languages: ["rust"] }
|
|
13215
13596
|
];
|
|
13216
13597
|
var DEFAULT_SINKS = [
|
|
13217
13598
|
// SQL Injection (CWE-89)
|
|
@@ -13872,7 +14253,9 @@ var DEFAULT_SINKS = [
|
|
|
13872
14253
|
{ method: "addParametersToContext", class: "StaticParametersInterceptor", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
|
|
13873
14254
|
{ method: "build", class: "HttpParameters", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
|
|
13874
14255
|
// Cron expression parsing (DoS/ReDoS)
|
|
13875
|
-
|
|
14256
|
+
// cognium-dev#998 — Java cron-utils `CronParser`; scoped to Java so a JS/TS
|
|
14257
|
+
// `parser.parse(...)` (tree-sitter, a lockfile parser) never matches it by name.
|
|
14258
|
+
{ method: "parse", class: "CronParser", type: "code_injection", cwe: "CWE-94", severity: "high", arg_positions: [0], languages: ["java"] },
|
|
13876
14259
|
{ method: "isValid", class: "CronValidator", type: "code_injection", cwe: "CWE-94", severity: "high", arg_positions: [0] },
|
|
13877
14260
|
// ff4j feature flags
|
|
13878
14261
|
{ method: "check", class: "FF4j", type: "code_injection", cwe: "CWE-94", severity: "high", arg_positions: [0] },
|
|
@@ -13969,9 +14352,13 @@ var DEFAULT_SINKS = [
|
|
|
13969
14352
|
{ method: "selectNodes", class: "Node", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0] },
|
|
13970
14353
|
{ method: "selectSingleNode", class: "Node", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0] },
|
|
13971
14354
|
// XXE - XML External Entity (CWE-611)
|
|
13972
|
-
|
|
13973
|
-
|
|
13974
|
-
|
|
14355
|
+
// cognium-dev#998 — JAXP parsers are JVM classes. Scoped to Java so the
|
|
14356
|
+
// receiver-name heuristic (`parser` → SAXParser) cannot turn a JS/TS
|
|
14357
|
+
// `parser.parse(text)` into an XXE sink. JS/TS XML parsers (sax, xml2js,
|
|
14358
|
+
// libxmljs, DOMParser) have their own entries.
|
|
14359
|
+
{ method: "parse", class: "DocumentBuilder", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0], languages: ["java"] },
|
|
14360
|
+
{ method: "parse", class: "SAXParser", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0], languages: ["java"] },
|
|
14361
|
+
{ method: "parse", class: "XMLReader", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0], languages: ["java"] },
|
|
13975
14362
|
{ method: "unmarshal", class: "Unmarshaller", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0] },
|
|
13976
14363
|
{ method: "newTransformer", class: "TransformerFactory", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0] },
|
|
13977
14364
|
{ method: "transform", class: "Transformer", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0] },
|
|
@@ -14226,6 +14613,46 @@ var DEFAULT_SINKS = [
|
|
|
14226
14613
|
{ method: "each", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], allow_unresolved_receiver: true },
|
|
14227
14614
|
{ method: "get", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], allow_unresolved_receiver: true },
|
|
14228
14615
|
{ method: "exec", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], allow_unresolved_receiver: true },
|
|
14616
|
+
// DuckDB from JS/TS: `@duckdb/node-api` (DuckDBConnection), the classic
|
|
14617
|
+
// `duckdb` / `duckdb-async` bindings (Database / Connection) and
|
|
14618
|
+
// `react-native-duckdb` (Database / Transaction). The method names are
|
|
14619
|
+
// generic and the receivers come from factories (`await instance.connect()`,
|
|
14620
|
+
// `open({...})`), so the receiver cannot be typed: the import is the gate
|
|
14621
|
+
// (`requires_import`). Only the SQL text (arg 0) is a sink — values passed
|
|
14622
|
+
// as bound parameters (`run(sql, [v])`, `execute(sql, params)`,
|
|
14623
|
+
// `executeNamed(sql, {k: v})`, `stmt.bind(...)`) are parameterised and do
|
|
14624
|
+
// not reach it.
|
|
14625
|
+
{ method: "run", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["@duckdb/node-api", "duckdb", "duckdb-async"] },
|
|
14626
|
+
{ method: "prepare", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["@duckdb/node-api", "duckdb", "duckdb-async", "react-native-duckdb"] },
|
|
14627
|
+
{ method: "stream", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["@duckdb/node-api", "duckdb", "duckdb-async", "react-native-duckdb"] },
|
|
14628
|
+
{ method: "runAndRead", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["@duckdb/node-api"] },
|
|
14629
|
+
{ method: "runAndReadAll", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["@duckdb/node-api"] },
|
|
14630
|
+
{ method: "runAndReadUntil", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["@duckdb/node-api"] },
|
|
14631
|
+
{ method: "streamAndRead", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["@duckdb/node-api"] },
|
|
14632
|
+
{ method: "streamAndReadAll", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["@duckdb/node-api"] },
|
|
14633
|
+
{ method: "streamAndReadUntil", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["@duckdb/node-api"] },
|
|
14634
|
+
{ method: "start", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["@duckdb/node-api"] },
|
|
14635
|
+
{ method: "startStream", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["@duckdb/node-api"] },
|
|
14636
|
+
{ method: "extractStatements", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["@duckdb/node-api"] },
|
|
14637
|
+
{ method: "all", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["duckdb", "duckdb-async"] },
|
|
14638
|
+
{ method: "each", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["duckdb", "duckdb-async"] },
|
|
14639
|
+
{ method: "exec", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["duckdb", "duckdb-async"] },
|
|
14640
|
+
{ method: "arrowIPCAll", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["duckdb", "duckdb-async"] },
|
|
14641
|
+
{ method: "arrowIPCStream", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["duckdb", "duckdb-async"] },
|
|
14642
|
+
{ method: "execute", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["react-native-duckdb"] },
|
|
14643
|
+
{ method: "executeSync", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["react-native-duckdb"] },
|
|
14644
|
+
{ method: "executeNamed", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["react-native-duckdb"] },
|
|
14645
|
+
{ method: "executeSyncNamed", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["react-native-duckdb"] },
|
|
14646
|
+
{ method: "streamNamed", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["react-native-duckdb"] },
|
|
14647
|
+
// RxDB (Mango queries, any storage incl. SQLite): a selector or query
|
|
14648
|
+
// object, a field name passed to the query builder, or a `$regex` pattern
|
|
14649
|
+
// under caller control is NoSQL operator injection (CWE-943). Gated on the
|
|
14650
|
+
// `rxdb` import for the same reason as DuckDB above. `find` is already a
|
|
14651
|
+
// classless JS NoSQL sink in the JavaScript plugin.
|
|
14652
|
+
{ method: "findOne", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["rxdb"] },
|
|
14653
|
+
{ method: "count", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["rxdb"] },
|
|
14654
|
+
{ method: "where", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["rxdb"] },
|
|
14655
|
+
{ method: "regex", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["rxdb"] },
|
|
14229
14656
|
// TypeORM QueryBuilder raw fragments (CWE-89). TypeORM's raw `.query(sql)`
|
|
14230
14657
|
// escape hatch is already covered by the classless `query` sink in the JS
|
|
14231
14658
|
// plugin (getBuiltinSinks), so it is NOT re-registered here — the dedup
|
|
@@ -14336,6 +14763,8 @@ var DEFAULT_SINKS = [
|
|
|
14336
14763
|
{ method: "get", class: "http", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
|
|
14337
14764
|
{ method: "request", class: "https", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
|
|
14338
14765
|
{ method: "get", class: "https", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
|
|
14766
|
+
// Node.js dns.promises (CWE-918 / NUL-in-name SSRF class — #895)
|
|
14767
|
+
{ method: "resolve", class: "dns.promises", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
14339
14768
|
// needle library (used in NodeGoat)
|
|
14340
14769
|
{ method: "get", class: "needle", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
|
|
14341
14770
|
{ method: "post", class: "needle", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
|
|
@@ -14830,7 +15259,7 @@ var DEFAULT_SINKS = [
|
|
|
14830
15259
|
// receiver-taint or operator-LHS-taint tracking — the format string is the
|
|
14831
15260
|
// receiver, not an argument. Deferred to Sprint 6 (#86 follow-up).
|
|
14832
15261
|
// C-style: printf / fprintf / sprintf / snprintf via ctypes/cffi.
|
|
14833
|
-
{ method: "printf", type: "format_string", cwe: "CWE-134", severity: "high", arg_positions: [0], languages: ["python"] },
|
|
15262
|
+
{ method: "printf", type: "format_string", cwe: "CWE-134", severity: "high", arg_positions: [0], languages: ["python", "rust"] },
|
|
14834
15263
|
{ method: "fprintf", type: "format_string", cwe: "CWE-134", severity: "high", arg_positions: [1], languages: ["python"] },
|
|
14835
15264
|
// Go: fmt.Sprintf/Printf/Fprintf/Errorf — format string is first/second arg
|
|
14836
15265
|
{ method: "Sprintf", class: "fmt", type: "format_string", cwe: "CWE-134", severity: "medium", arg_positions: [0], languages: ["go"] },
|
|
@@ -16735,6 +17164,85 @@ function passesMutParam(line, n) {
|
|
|
16735
17164
|
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
17165
|
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
17166
|
}
|
|
17167
|
+
function findRustAllowlistTernarySanitizers(code) {
|
|
17168
|
+
const masked = maskRust(code);
|
|
17169
|
+
const origLines = code.split("\n");
|
|
17170
|
+
const maskedLines = masked.split("\n");
|
|
17171
|
+
const out2 = [];
|
|
17172
|
+
const allowlistVar = (cond) => {
|
|
17173
|
+
const c = cond.trim();
|
|
17174
|
+
let m = new RegExp(String.raw`^!?(\w+)\s*\.\s*contains\s*\(\s*&\s*(\w+)(?:\s*\.\s*as_str\s*\(\s*\))?\s*\)$`).exec(c);
|
|
17175
|
+
if (m) return m[2];
|
|
17176
|
+
if (m = new RegExp(String.raw`^!\s*\[\s*${STR}(?:\s*,\s*${STR})*\s*,?\s*\]\s*\.\s*contains\s*\(\s*&\s*${VAR}\s*\)$`).exec(c)) {
|
|
17177
|
+
return m[1];
|
|
17178
|
+
}
|
|
17179
|
+
return guardedVariable(c);
|
|
17180
|
+
};
|
|
17181
|
+
const LITERAL_CHAIN = /* @__PURE__ */ new Set(["to_string", "into", "to_owned", "clone"]);
|
|
17182
|
+
const literalOnly = (block) => {
|
|
17183
|
+
const maskedB = maskRust(block.trim());
|
|
17184
|
+
if (!maskedB.trim()) return false;
|
|
17185
|
+
const re = /[A-Za-z_]\w*/g;
|
|
17186
|
+
let m;
|
|
17187
|
+
while ((m = re.exec(maskedB)) !== null) {
|
|
17188
|
+
const id = m[0];
|
|
17189
|
+
if (KEYWORDS.has(id) || /[A-Z]/.test(id[0]) || LITERAL_CHAIN.has(id)) continue;
|
|
17190
|
+
return false;
|
|
17191
|
+
}
|
|
17192
|
+
return true;
|
|
17193
|
+
};
|
|
17194
|
+
const usesOnlyVar = (block, name2) => {
|
|
17195
|
+
const b = block.trim();
|
|
17196
|
+
if (!b) return false;
|
|
17197
|
+
const maskedB = maskRust(b);
|
|
17198
|
+
const re = new RegExp(String.raw`(?<![\w.])${name2}\b`);
|
|
17199
|
+
if (!re.test(maskedB)) return false;
|
|
17200
|
+
return !readsOtherValue(b, maskedB, name2);
|
|
17201
|
+
};
|
|
17202
|
+
for (const m of masked.matchAll(/\blet\s+(\w+)\s*=\s*if\b/g)) {
|
|
17203
|
+
const bound = m[1];
|
|
17204
|
+
const at = m.index + m[0].length - 2;
|
|
17205
|
+
let depth = 0;
|
|
17206
|
+
let open = -1;
|
|
17207
|
+
for (let i2 = at + 2; i2 < masked.length; i2++) {
|
|
17208
|
+
const ch = masked[i2];
|
|
17209
|
+
if (ch === "(" || ch === "[") depth++;
|
|
17210
|
+
else if (ch === ")" || ch === "]") depth--;
|
|
17211
|
+
else if (ch === "{" && depth === 0) {
|
|
17212
|
+
open = i2;
|
|
17213
|
+
break;
|
|
17214
|
+
} else if (ch === ";" || ch === "}") break;
|
|
17215
|
+
}
|
|
17216
|
+
if (open < 0) continue;
|
|
17217
|
+
const cond = code.slice(at + 2, open).trim();
|
|
17218
|
+
const guarded = allowlistVar(cond);
|
|
17219
|
+
if (!guarded) continue;
|
|
17220
|
+
const thenClose = matchBrace(masked, open);
|
|
17221
|
+
if (thenClose < 0) continue;
|
|
17222
|
+
const thenBody = code.slice(open + 1, thenClose);
|
|
17223
|
+
if (!/^\s*else\b/.test(masked.slice(thenClose + 1, thenClose + 10))) continue;
|
|
17224
|
+
const elseOpen = masked.indexOf("{", thenClose + 1);
|
|
17225
|
+
if (elseOpen < 0) continue;
|
|
17226
|
+
const elseClose = matchBrace(masked, elseOpen);
|
|
17227
|
+
if (elseClose < 0) continue;
|
|
17228
|
+
const elseBody = code.slice(elseOpen + 1, elseClose);
|
|
17229
|
+
const inverted = /^\s*!/.test(cond);
|
|
17230
|
+
const ok = inverted ? literalOnly(thenBody) && usesOnlyVar(elseBody, guarded) : usesOnlyVar(thenBody, guarded) && literalOnly(elseBody);
|
|
17231
|
+
if (!ok) continue;
|
|
17232
|
+
const assignLine = masked.slice(0, at).split("\n").length;
|
|
17233
|
+
const mention = new RegExp(String.raw`(?<![\w.])${bound}\b`);
|
|
17234
|
+
for (let l = assignLine; l < origLines.length; l++) {
|
|
17235
|
+
if (!mention.test(maskedLines[l])) continue;
|
|
17236
|
+
out2.push({
|
|
17237
|
+
type: "rust_allowlist_ternary",
|
|
17238
|
+
method: "guard",
|
|
17239
|
+
line: l + 1,
|
|
17240
|
+
sanitizes: CREDITED_TYPES
|
|
17241
|
+
});
|
|
17242
|
+
}
|
|
17243
|
+
}
|
|
17244
|
+
return out2;
|
|
17245
|
+
}
|
|
16738
17246
|
function findRustLiteralEqualityGuardSanitizers(code) {
|
|
16739
17247
|
const masked = maskRust(code);
|
|
16740
17248
|
const origLines = code.split("\n");
|
|
@@ -16855,7 +17363,7 @@ function findRustLiteralEqualityGuardSanitizers(code) {
|
|
|
16855
17363
|
});
|
|
16856
17364
|
}
|
|
16857
17365
|
}
|
|
16858
|
-
return out2;
|
|
17366
|
+
return [...out2, ...findRustAllowlistTernarySanitizers(code)];
|
|
16859
17367
|
}
|
|
16860
17368
|
function statementSpan(code, masked, lineStart, endIdx) {
|
|
16861
17369
|
let start2 = lineStart;
|
|
@@ -17009,6 +17517,198 @@ function constTableLookupDefIds(defs, code, language) {
|
|
|
17009
17517
|
return out2;
|
|
17010
17518
|
}
|
|
17011
17519
|
|
|
17520
|
+
// src/analysis/js-crlf-strip.ts
|
|
17521
|
+
var MAX_PATTERN_LENGTH = 200;
|
|
17522
|
+
var NEUTRAL_AFTER_REPLACE = /* @__PURE__ */ new Set([
|
|
17523
|
+
"replace",
|
|
17524
|
+
"replaceAll",
|
|
17525
|
+
"trim",
|
|
17526
|
+
"trimStart",
|
|
17527
|
+
"trimEnd",
|
|
17528
|
+
"toString",
|
|
17529
|
+
"toLowerCase",
|
|
17530
|
+
"toUpperCase",
|
|
17531
|
+
"slice",
|
|
17532
|
+
"substring"
|
|
17533
|
+
]);
|
|
17534
|
+
var PROBE = "a\r\rb\n\nc\r\nd\n\re x ";
|
|
17535
|
+
function readQuoted(s, i2) {
|
|
17536
|
+
const q = s[i2];
|
|
17537
|
+
if (q !== '"' && q !== "'" && q !== "`") return null;
|
|
17538
|
+
let out2 = "";
|
|
17539
|
+
for (let j = i2 + 1; j < s.length; j++) {
|
|
17540
|
+
const c = s[j];
|
|
17541
|
+
if (c === q) return { value: out2, end: j + 1 };
|
|
17542
|
+
if (q === "`" && c === "$" && s[j + 1] === "{") return null;
|
|
17543
|
+
if (c === "\\") {
|
|
17544
|
+
const n = s[j + 1];
|
|
17545
|
+
if (n === void 0) return null;
|
|
17546
|
+
switch (n) {
|
|
17547
|
+
case "n":
|
|
17548
|
+
out2 += "\n";
|
|
17549
|
+
j++;
|
|
17550
|
+
continue;
|
|
17551
|
+
case "r":
|
|
17552
|
+
out2 += "\r";
|
|
17553
|
+
j++;
|
|
17554
|
+
continue;
|
|
17555
|
+
case "t":
|
|
17556
|
+
out2 += " ";
|
|
17557
|
+
j++;
|
|
17558
|
+
continue;
|
|
17559
|
+
case "0":
|
|
17560
|
+
out2 += "\0";
|
|
17561
|
+
j++;
|
|
17562
|
+
continue;
|
|
17563
|
+
case "x": {
|
|
17564
|
+
const h = /^[0-9a-fA-F]{2}/.exec(s.slice(j + 2, j + 4));
|
|
17565
|
+
if (!h) return null;
|
|
17566
|
+
out2 += String.fromCharCode(parseInt(h[0], 16));
|
|
17567
|
+
j += 3;
|
|
17568
|
+
continue;
|
|
17569
|
+
}
|
|
17570
|
+
case "u": {
|
|
17571
|
+
const h = /^[0-9a-fA-F]{4}/.exec(s.slice(j + 2, j + 6));
|
|
17572
|
+
if (!h) return null;
|
|
17573
|
+
out2 += String.fromCharCode(parseInt(h[0], 16));
|
|
17574
|
+
j += 5;
|
|
17575
|
+
continue;
|
|
17576
|
+
}
|
|
17577
|
+
default:
|
|
17578
|
+
out2 += n;
|
|
17579
|
+
j++;
|
|
17580
|
+
continue;
|
|
17581
|
+
}
|
|
17582
|
+
}
|
|
17583
|
+
if (c === "\n" && q !== "`") return null;
|
|
17584
|
+
out2 += c;
|
|
17585
|
+
}
|
|
17586
|
+
return null;
|
|
17587
|
+
}
|
|
17588
|
+
function readRegexLiteral(s, i2) {
|
|
17589
|
+
if (s[i2] !== "/") return null;
|
|
17590
|
+
let inClass = false;
|
|
17591
|
+
for (let j = i2 + 1; j < s.length; j++) {
|
|
17592
|
+
const c = s[j];
|
|
17593
|
+
if (c === "\n") return null;
|
|
17594
|
+
if (c === "\\") {
|
|
17595
|
+
j++;
|
|
17596
|
+
continue;
|
|
17597
|
+
}
|
|
17598
|
+
if (inClass) {
|
|
17599
|
+
if (c === "]") inClass = false;
|
|
17600
|
+
continue;
|
|
17601
|
+
}
|
|
17602
|
+
if (c === "[") {
|
|
17603
|
+
inClass = true;
|
|
17604
|
+
continue;
|
|
17605
|
+
}
|
|
17606
|
+
if (c === "/") {
|
|
17607
|
+
const flags2 = /^[dgimsuvy]*/.exec(s.slice(j + 1))[0];
|
|
17608
|
+
return { source: s.slice(i2 + 1, j), flags: flags2, end: j + 1 + flags2.length };
|
|
17609
|
+
}
|
|
17610
|
+
}
|
|
17611
|
+
return null;
|
|
17612
|
+
}
|
|
17613
|
+
var skipWs = (s, i2) => {
|
|
17614
|
+
while (i2 < s.length && /\s/.test(s[i2])) i2++;
|
|
17615
|
+
return i2;
|
|
17616
|
+
};
|
|
17617
|
+
function parseSteps(chainText) {
|
|
17618
|
+
const steps = [];
|
|
17619
|
+
const callRe = /\.\s*(replaceAll|replace)\s*\(/g;
|
|
17620
|
+
let m;
|
|
17621
|
+
while ((m = callRe.exec(chainText)) !== null) {
|
|
17622
|
+
const kind = m[1];
|
|
17623
|
+
let i2 = skipWs(chainText, m.index + m[0].length);
|
|
17624
|
+
let pattern;
|
|
17625
|
+
const rx = readRegexLiteral(chainText, i2);
|
|
17626
|
+
if (rx) {
|
|
17627
|
+
if (rx.source.length > MAX_PATTERN_LENGTH) return null;
|
|
17628
|
+
try {
|
|
17629
|
+
pattern = new RegExp(rx.source, rx.flags);
|
|
17630
|
+
} catch {
|
|
17631
|
+
return null;
|
|
17632
|
+
}
|
|
17633
|
+
i2 = rx.end;
|
|
17634
|
+
} else {
|
|
17635
|
+
const str = readQuoted(chainText, i2);
|
|
17636
|
+
if (!str) return null;
|
|
17637
|
+
pattern = str.value;
|
|
17638
|
+
i2 = str.end;
|
|
17639
|
+
}
|
|
17640
|
+
i2 = skipWs(chainText, i2);
|
|
17641
|
+
if (chainText[i2] !== ",") return null;
|
|
17642
|
+
i2 = skipWs(chainText, i2 + 1);
|
|
17643
|
+
const rep = readQuoted(chainText, i2);
|
|
17644
|
+
if (!rep) return null;
|
|
17645
|
+
i2 = skipWs(chainText, rep.end);
|
|
17646
|
+
if (chainText[i2] !== ")") return null;
|
|
17647
|
+
steps.push({ kind, pattern, replacement: rep.value });
|
|
17648
|
+
callRe.lastIndex = i2 + 1;
|
|
17649
|
+
}
|
|
17650
|
+
return steps;
|
|
17651
|
+
}
|
|
17652
|
+
function run2(steps, input) {
|
|
17653
|
+
let s = input;
|
|
17654
|
+
try {
|
|
17655
|
+
for (const st of steps) {
|
|
17656
|
+
if (st.kind === "replaceAll" && st.pattern instanceof RegExp && !st.pattern.global) return null;
|
|
17657
|
+
s = st.kind === "replaceAll" ? s.replaceAll(st.pattern, st.replacement) : s.replace(st.pattern, st.replacement);
|
|
17658
|
+
}
|
|
17659
|
+
} catch {
|
|
17660
|
+
return null;
|
|
17661
|
+
}
|
|
17662
|
+
return s;
|
|
17663
|
+
}
|
|
17664
|
+
function jsReplaceChainVerdict(chainText) {
|
|
17665
|
+
const steps = parseSteps(chainText);
|
|
17666
|
+
if (!steps || steps.length === 0) return "undecided";
|
|
17667
|
+
const first = chainText.search(/\.\s*(?:replaceAll|replace)\s*\(/);
|
|
17668
|
+
for (const seg of chainText.slice(first + 1).matchAll(/\.\s*([A-Za-z_$][\w$]*)\s*\(/g)) {
|
|
17669
|
+
if (!NEUTRAL_AFTER_REPLACE.has(seg[1])) return "undecided";
|
|
17670
|
+
}
|
|
17671
|
+
const out2 = run2(steps, PROBE);
|
|
17672
|
+
if (out2 === null) return "undecided";
|
|
17673
|
+
return out2.includes("\r") || out2.includes("\n") ? "leaves-crlf" : "strips-crlf";
|
|
17674
|
+
}
|
|
17675
|
+
function jsChainTextAt(sourceLines, line, column) {
|
|
17676
|
+
const text = [(sourceLines[line - 1] ?? "").slice(column), ...sourceLines.slice(line, line + 7)].join("\n");
|
|
17677
|
+
let depth = 0;
|
|
17678
|
+
let prevSig = "";
|
|
17679
|
+
let i2 = 0;
|
|
17680
|
+
for (; i2 < text.length; i2++) {
|
|
17681
|
+
const c = text[i2];
|
|
17682
|
+
if (c === '"' || c === "'" || c === "`") {
|
|
17683
|
+
for (i2++; i2 < text.length && text[i2] !== c; i2++) if (text[i2] === "\\") i2++;
|
|
17684
|
+
prevSig = c;
|
|
17685
|
+
continue;
|
|
17686
|
+
}
|
|
17687
|
+
if (c === "/" && depth > 0 && (prevSig === "(" || prevSig === ",")) {
|
|
17688
|
+
const rx = readRegexLiteral(text, i2);
|
|
17689
|
+
if (rx) {
|
|
17690
|
+
i2 = rx.end - 1;
|
|
17691
|
+
prevSig = "/";
|
|
17692
|
+
continue;
|
|
17693
|
+
}
|
|
17694
|
+
}
|
|
17695
|
+
if (c === "(" || c === "[" || c === "{") {
|
|
17696
|
+
depth++;
|
|
17697
|
+
prevSig = c;
|
|
17698
|
+
continue;
|
|
17699
|
+
}
|
|
17700
|
+
if (c === ")" || c === "]" || c === "}") {
|
|
17701
|
+
if (depth === 0) break;
|
|
17702
|
+
depth--;
|
|
17703
|
+
prevSig = c;
|
|
17704
|
+
continue;
|
|
17705
|
+
}
|
|
17706
|
+
if (depth === 0 && !/[\w$.\s]/.test(c)) break;
|
|
17707
|
+
if (!/\s/.test(c)) prevSig = c;
|
|
17708
|
+
}
|
|
17709
|
+
return text.slice(0, i2);
|
|
17710
|
+
}
|
|
17711
|
+
|
|
17012
17712
|
// src/analysis/passes/language-sources-pass.ts
|
|
17013
17713
|
var JS_DOM_XSS_SINKS = [
|
|
17014
17714
|
{ pattern: /\.innerHTML\s*=/, type: "xss", cwe: "CWE-79", severity: "critical" },
|
|
@@ -17260,6 +17960,7 @@ var LanguageSourcesPass = class {
|
|
|
17260
17960
|
}
|
|
17261
17961
|
additionalSanitizers.push(...findBashRegexAllowlistSanitizers(code));
|
|
17262
17962
|
additionalSanitizers.push(...findBashRealpathPrefixGuardSanitizers(code));
|
|
17963
|
+
additionalSanitizers.push(...findBashVerifiedPackageDownloadSanitizers(code));
|
|
17263
17964
|
additionalSanitizers.push(...findBashShellQuoteSanitizers(code));
|
|
17264
17965
|
}
|
|
17265
17966
|
if (language === "go") {
|
|
@@ -17375,8 +18076,13 @@ var LanguageSourcesPass = class {
|
|
|
17375
18076
|
}
|
|
17376
18077
|
}
|
|
17377
18078
|
if (language === "rust") {
|
|
18079
|
+
additionalSanitizers.push(...findRustConstSliceMembershipGuardSanitizers(code));
|
|
17378
18080
|
additionalSanitizers.push(...findRustSetAllowlistGuardSanitizers(code));
|
|
17379
18081
|
additionalSanitizers.push(...findRustCanonicalizeGuardSanitizers(code));
|
|
18082
|
+
additionalSanitizers.push(
|
|
18083
|
+
...findRustNumericRangeCheckGuardSanitizers(code)
|
|
18084
|
+
);
|
|
18085
|
+
additionalSanitizers.push(...findRustPatInfoHttpOnlyResponseSanitizers(code));
|
|
17380
18086
|
additionalSanitizers.push(...findRustLiteralEqualityGuardSanitizers(code));
|
|
17381
18087
|
additionalSanitizers.push(...findRustArgvCommandSanitizers(code));
|
|
17382
18088
|
const rustMisconfigFindings = findRustPatternFindings(code, graph.ir.meta.file);
|
|
@@ -17434,6 +18140,9 @@ var LanguageSourcesPass = class {
|
|
|
17434
18140
|
additionalSanitizers.push(
|
|
17435
18141
|
...findJsCommandAllowlistGuardSanitizers(code)
|
|
17436
18142
|
);
|
|
18143
|
+
additionalSanitizers.push(
|
|
18144
|
+
...findJsNumericRangeCheckGuardSanitizers(code)
|
|
18145
|
+
);
|
|
17437
18146
|
for (const finding of findJsPatternFindings(code, graph.ir.meta.file)) {
|
|
17438
18147
|
ctx.addFinding(finding);
|
|
17439
18148
|
}
|
|
@@ -17467,6 +18176,12 @@ var LanguageSourcesPass = class {
|
|
|
17467
18176
|
)) {
|
|
17468
18177
|
ctx.addFinding(finding);
|
|
17469
18178
|
}
|
|
18179
|
+
for (const finding of findJsZipHeaderFailopenFindings(
|
|
18180
|
+
code,
|
|
18181
|
+
graph.ir.meta.file
|
|
18182
|
+
)) {
|
|
18183
|
+
ctx.addFinding(finding);
|
|
18184
|
+
}
|
|
17470
18185
|
for (const finding of findJsJsonParseBodyFindings(
|
|
17471
18186
|
code,
|
|
17472
18187
|
graph.ir.meta.file
|
|
@@ -17516,6 +18231,7 @@ var LanguageSourcesPass = class {
|
|
|
17516
18231
|
}
|
|
17517
18232
|
additionalSanitizers.push(...findJavaInlineCrlfStripLogSanitizers(code));
|
|
17518
18233
|
additionalSanitizers.push(...findCrlfWrapperFunctionSanitizers(code, "java"));
|
|
18234
|
+
additionalSanitizers.push(...findJavaTemplateDelimiterWrapperSanitizers(code));
|
|
17519
18235
|
additionalSanitizers.push(...findJavaArgvFormExecSanitizers(code));
|
|
17520
18236
|
for (const finding of findJavaPatternFindings(code, graph.ir.meta.file)) {
|
|
17521
18237
|
ctx.addFinding(finding);
|
|
@@ -18233,6 +18949,7 @@ function findCSharpRequestSources(sourceCode, language) {
|
|
|
18233
18949
|
const assignRe = /^\s*(?:await\s+)?(?:using\s*\(?\s*)?(?:var\s+|[A-Za-z_][\w.<>\[\]]*\s+)?([A-Za-z_]\w*)\s*=\s*(.+?);?\s*$/;
|
|
18234
18950
|
const arrayElementAssignRe = /^\s*([A-Za-z_]\w*)\s*\[[^\]]+\]\s*=\s*(.+?);?\s*$/;
|
|
18235
18951
|
const memberFieldAssignRe = /^\s*([A-Za-z_]\w*)\.([A-Za-z_]\w*)\s*=\s*(.+?);?\s*$/;
|
|
18952
|
+
const collectionAddRe = /^\s*([A-Za-z_]\w*)\s*\.\s*Add\s*\(\s*(.+?)\s*\)\s*;?\s*$/;
|
|
18236
18953
|
const consoleReadRe = /\bConsole\s*\.\s*ReadLine\s*\(/;
|
|
18237
18954
|
const envReadRe = /\bEnvironment\s*\.\s*GetEnvironmentVariables?\s*(?:\(|\[)/;
|
|
18238
18955
|
const formFileParams = /* @__PURE__ */ new Set();
|
|
@@ -18326,6 +19043,25 @@ function findCSharpRequestSources(sourceCode, language) {
|
|
|
18326
19043
|
continue;
|
|
18327
19044
|
}
|
|
18328
19045
|
}
|
|
19046
|
+
const addEl = collectionAddRe.exec(stmt);
|
|
19047
|
+
if (addEl) {
|
|
19048
|
+
const [, baseName, rhs2] = addEl;
|
|
19049
|
+
const type2 = classify(rhs2);
|
|
19050
|
+
if (type2 && baseName) {
|
|
19051
|
+
const lineNumber2 = i2 + 1;
|
|
19052
|
+
if (!sources.some((s) => s.line === lineNumber2 && s.variable === baseName)) {
|
|
19053
|
+
sources.push({
|
|
19054
|
+
type: type2,
|
|
19055
|
+
location: `${baseName}.Add(${rhs2.trim().substring(0, 50)}${rhs2.length > 50 ? "..." : ""})`,
|
|
19056
|
+
severity: type2 === "db_input" ? "medium" : "high",
|
|
19057
|
+
line: lineNumber2,
|
|
19058
|
+
confidence: 1,
|
|
19059
|
+
variable: baseName
|
|
19060
|
+
});
|
|
19061
|
+
}
|
|
19062
|
+
continue;
|
|
19063
|
+
}
|
|
19064
|
+
}
|
|
18329
19065
|
const m = assignRe.exec(stmt);
|
|
18330
19066
|
const rhsCrossesBlock = m !== null && m[2].includes("{");
|
|
18331
19067
|
if (!m || rhsCrossesBlock) {
|
|
@@ -18705,9 +19441,9 @@ function findGraphqlResolverArgSources(sourceCode) {
|
|
|
18705
19441
|
maps.push(open + inline.index + inline[0].length - 1);
|
|
18706
19442
|
continue;
|
|
18707
19443
|
}
|
|
18708
|
-
const
|
|
18709
|
-
if (!
|
|
18710
|
-
const ident =
|
|
19444
|
+
const named2 = opts.match(/\bresolvers\s*(?::\s*(\w+))?\s*[,}]/);
|
|
19445
|
+
if (!named2) continue;
|
|
19446
|
+
const ident = named2[1] ?? "resolvers";
|
|
18711
19447
|
const decl = new RegExp(
|
|
18712
19448
|
`\\b(?:const|let|var)\\s+${ident}\\s*(?::\\s*[A-Za-z_$][\\w$]*(?:\\s*\\.\\s*[A-Za-z_$][\\w$]*)*(?:\\s*<[^<>=]*(?:<[^<>=]*>[^<>=]*)*>)?\\s*)?=\\s*\\{`
|
|
18713
19449
|
).exec(sourceCode);
|
|
@@ -18870,15 +19606,15 @@ function evaluatePythonConstExpression(expr, consts) {
|
|
|
18870
19606
|
if (parts2.length !== 1) return void 0;
|
|
18871
19607
|
const matched = expr.match(/\d+|[A-Za-z_][A-Za-z0-9_]*|\/\/|[-+*/%()]|\S/g);
|
|
18872
19608
|
if (!matched) return void 0;
|
|
18873
|
-
const
|
|
19609
|
+
const tokens2 = matched;
|
|
18874
19610
|
let pos = 0;
|
|
18875
|
-
const peek = () =>
|
|
19611
|
+
const peek = () => tokens2[pos];
|
|
18876
19612
|
const parseFactor = () => {
|
|
18877
|
-
const tok =
|
|
19613
|
+
const tok = tokens2[pos++];
|
|
18878
19614
|
if (tok === void 0) return void 0;
|
|
18879
19615
|
if (tok === "(") {
|
|
18880
19616
|
const inner = parseSum();
|
|
18881
|
-
if (
|
|
19617
|
+
if (tokens2[pos++] !== ")") return void 0;
|
|
18882
19618
|
return inner;
|
|
18883
19619
|
}
|
|
18884
19620
|
if (tok === "-") {
|
|
@@ -18893,7 +19629,7 @@ function evaluatePythonConstExpression(expr, consts) {
|
|
|
18893
19629
|
let value = parseFactor();
|
|
18894
19630
|
if (value === void 0) return void 0;
|
|
18895
19631
|
while (peek() === "*" || peek() === "/" || peek() === "//" || peek() === "%") {
|
|
18896
|
-
const op =
|
|
19632
|
+
const op = tokens2[pos++];
|
|
18897
19633
|
const rhs = parseFactor();
|
|
18898
19634
|
if (rhs === void 0) return void 0;
|
|
18899
19635
|
if ((op === "/" || op === "//" || op === "%") && rhs === 0) return void 0;
|
|
@@ -18908,7 +19644,7 @@ function evaluatePythonConstExpression(expr, consts) {
|
|
|
18908
19644
|
let value = parseTerm();
|
|
18909
19645
|
if (value === void 0) return void 0;
|
|
18910
19646
|
while (peek() === "+" || peek() === "-") {
|
|
18911
|
-
const op =
|
|
19647
|
+
const op = tokens2[pos++];
|
|
18912
19648
|
const rhs = parseTerm();
|
|
18913
19649
|
if (rhs === void 0) return void 0;
|
|
18914
19650
|
value = op === "+" ? value + rhs : value - rhs;
|
|
@@ -18916,7 +19652,7 @@ function evaluatePythonConstExpression(expr, consts) {
|
|
|
18916
19652
|
return value;
|
|
18917
19653
|
}
|
|
18918
19654
|
const result = parseSum();
|
|
18919
|
-
return pos ===
|
|
19655
|
+
return pos === tokens2.length ? result : void 0;
|
|
18920
19656
|
}
|
|
18921
19657
|
function normalizeListIndex(index, length) {
|
|
18922
19658
|
const resolved = index < 0 ? length + index : index;
|
|
@@ -18964,6 +19700,140 @@ function computeDeadPythonBranchLines(lines) {
|
|
|
18964
19700
|
}
|
|
18965
19701
|
return dead;
|
|
18966
19702
|
}
|
|
19703
|
+
function parseJavaSwitchCaseLabel(label) {
|
|
19704
|
+
const t = label.trim();
|
|
19705
|
+
if (t === "default") return "default";
|
|
19706
|
+
const ch = t.match(/^'(\\.|[^'])'\s*$/);
|
|
19707
|
+
if (ch) {
|
|
19708
|
+
const inner = ch[1];
|
|
19709
|
+
if (inner.startsWith("\\")) {
|
|
19710
|
+
if (inner === "\\'") return 39;
|
|
19711
|
+
if (inner === "\\\\") return 92;
|
|
19712
|
+
if (inner === "\\n") return 10;
|
|
19713
|
+
return void 0;
|
|
19714
|
+
}
|
|
19715
|
+
return inner.charCodeAt(0);
|
|
19716
|
+
}
|
|
19717
|
+
const num = t.match(/^(-?\d+)\s*$/);
|
|
19718
|
+
if (num) return Number(num[1]);
|
|
19719
|
+
return void 0;
|
|
19720
|
+
}
|
|
19721
|
+
function parseJavaSwitchLabelLine(line) {
|
|
19722
|
+
const caseLine = line.match(/^\s*case\s+(.+?)\s*:\s*$/);
|
|
19723
|
+
if (caseLine) return parseJavaSwitchCaseLabel(caseLine[1]);
|
|
19724
|
+
if (/^\s*default\s*:\s*$/.test(line)) return "default";
|
|
19725
|
+
return void 0;
|
|
19726
|
+
}
|
|
19727
|
+
function markDeadJavaSwitchBodies(lines, switchLineIdx, switchValue, dead) {
|
|
19728
|
+
let depth = 0;
|
|
19729
|
+
let started = false;
|
|
19730
|
+
let groupLabels = [];
|
|
19731
|
+
let bodyStart = -1;
|
|
19732
|
+
let explicitCaseMatched = false;
|
|
19733
|
+
const groupIsTaken = () => {
|
|
19734
|
+
if (groupLabels.length === 0) return false;
|
|
19735
|
+
const explicit = groupLabels.some((v) => v !== "default" && v === switchValue);
|
|
19736
|
+
if (explicit) return true;
|
|
19737
|
+
return groupLabels.includes("default") && !explicitCaseMatched;
|
|
19738
|
+
};
|
|
19739
|
+
const flushGroup = (bodyEnd) => {
|
|
19740
|
+
if (bodyStart < 0 || groupLabels.length === 0) return;
|
|
19741
|
+
const taken = groupIsTaken();
|
|
19742
|
+
if (groupLabels.some((v) => v !== "default" && v === switchValue)) {
|
|
19743
|
+
explicitCaseMatched = true;
|
|
19744
|
+
}
|
|
19745
|
+
if (!taken) {
|
|
19746
|
+
for (let k = bodyStart; k < bodyEnd; k++) dead.add(k);
|
|
19747
|
+
}
|
|
19748
|
+
groupLabels = [];
|
|
19749
|
+
bodyStart = -1;
|
|
19750
|
+
};
|
|
19751
|
+
for (let j = switchLineIdx; j < lines.length; j++) {
|
|
19752
|
+
const line = lines[j];
|
|
19753
|
+
for (const ch of line) {
|
|
19754
|
+
if (ch === "{") {
|
|
19755
|
+
depth++;
|
|
19756
|
+
started = true;
|
|
19757
|
+
} else if (ch === "}") depth--;
|
|
19758
|
+
}
|
|
19759
|
+
if (!started) continue;
|
|
19760
|
+
const label = parseJavaSwitchLabelLine(line);
|
|
19761
|
+
if (label !== void 0) {
|
|
19762
|
+
if (bodyStart >= 0 && j === bodyStart) {
|
|
19763
|
+
groupLabels.push(label);
|
|
19764
|
+
bodyStart = j + 1;
|
|
19765
|
+
} else {
|
|
19766
|
+
flushGroup(j);
|
|
19767
|
+
groupLabels = [label];
|
|
19768
|
+
bodyStart = j + 1;
|
|
19769
|
+
}
|
|
19770
|
+
}
|
|
19771
|
+
if (started && depth === 0) {
|
|
19772
|
+
flushGroup(j);
|
|
19773
|
+
break;
|
|
19774
|
+
}
|
|
19775
|
+
}
|
|
19776
|
+
}
|
|
19777
|
+
function computeDeadJavaBranchLines(lines) {
|
|
19778
|
+
const dead = /* @__PURE__ */ new Set();
|
|
19779
|
+
const consts = /* @__PURE__ */ new Map();
|
|
19780
|
+
const stringConsts = /* @__PURE__ */ new Map();
|
|
19781
|
+
const charConsts = /* @__PURE__ */ new Map();
|
|
19782
|
+
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
19783
|
+
const line = lines[i2];
|
|
19784
|
+
const stringAssign = line.match(/^\s*String\s+([A-Za-z_]\w*)\s*=\s*"([^"]*)"\s*;\s*$/);
|
|
19785
|
+
if (stringAssign) {
|
|
19786
|
+
stringConsts.set(stringAssign[1], stringAssign[2]);
|
|
19787
|
+
continue;
|
|
19788
|
+
}
|
|
19789
|
+
const charAtAssign = line.match(
|
|
19790
|
+
/^\s*char\s+([A-Za-z_]\w*)\s*=\s*([A-Za-z_]\w*)\.charAt\s*\(\s*(\d+)\s*\)\s*;\s*(?:\/\/.*)?$/
|
|
19791
|
+
);
|
|
19792
|
+
if (charAtAssign) {
|
|
19793
|
+
const str = stringConsts.get(charAtAssign[2]);
|
|
19794
|
+
const idx = Number(charAtAssign[3]);
|
|
19795
|
+
if (str !== void 0 && idx >= 0 && idx < str.length) {
|
|
19796
|
+
charConsts.set(charAtAssign[1], str.charCodeAt(idx));
|
|
19797
|
+
} else {
|
|
19798
|
+
charConsts.delete(charAtAssign[1]);
|
|
19799
|
+
}
|
|
19800
|
+
continue;
|
|
19801
|
+
}
|
|
19802
|
+
const intAssign = line.match(/^\s*(?:int|long|short|byte)\s+([A-Za-z_]\w*)\s*=\s*(.+?);\s*$/);
|
|
19803
|
+
if (intAssign) {
|
|
19804
|
+
const v = evaluatePythonConstExpression(intAssign[2].trim(), consts);
|
|
19805
|
+
if (typeof v === "number") consts.set(intAssign[1], v);
|
|
19806
|
+
else consts.delete(intAssign[1]);
|
|
19807
|
+
continue;
|
|
19808
|
+
}
|
|
19809
|
+
const switchMatch = line.match(/^\s*switch\s*\(([^)]+)\)\s*\{?\s*$/);
|
|
19810
|
+
if (switchMatch) {
|
|
19811
|
+
const sel = switchMatch[1].trim();
|
|
19812
|
+
let switchVal = charConsts.get(sel);
|
|
19813
|
+
if (switchVal === void 0) {
|
|
19814
|
+
const n = evaluatePythonConstExpression(sel, consts);
|
|
19815
|
+
if (typeof n === "number") switchVal = n;
|
|
19816
|
+
}
|
|
19817
|
+
if (switchVal !== void 0) {
|
|
19818
|
+
markDeadJavaSwitchBodies(lines, i2, switchVal, dead);
|
|
19819
|
+
}
|
|
19820
|
+
continue;
|
|
19821
|
+
}
|
|
19822
|
+
const ifMatch = line.match(/^(\s*)if\s*\((.+)\)\s+(.+)$/);
|
|
19823
|
+
if (!ifMatch) continue;
|
|
19824
|
+
const decided = evaluatePythonConstExpression(ifMatch[2].trim(), consts);
|
|
19825
|
+
if (decided !== true && decided !== false) continue;
|
|
19826
|
+
const rest = ifMatch[3].trim();
|
|
19827
|
+
if (/\belse\b/.test(rest)) continue;
|
|
19828
|
+
if (i2 + 1 < lines.length && /^\s*else\b/.test(lines[i2 + 1])) {
|
|
19829
|
+
if (decided === true) dead.add(i2 + 1);
|
|
19830
|
+
else dead.add(i2);
|
|
19831
|
+
} else if (decided === false) {
|
|
19832
|
+
dead.add(i2);
|
|
19833
|
+
}
|
|
19834
|
+
}
|
|
19835
|
+
return dead;
|
|
19836
|
+
}
|
|
18967
19837
|
function buildPythonTaintedVars(sourceCode) {
|
|
18968
19838
|
const tainted = /* @__PURE__ */ new Map();
|
|
18969
19839
|
const containerTainted = /* @__PURE__ */ new Map();
|
|
@@ -19683,6 +20553,82 @@ function buildRustTaintedVars(sourceCode, seedVars) {
|
|
|
19683
20553
|
}
|
|
19684
20554
|
return derived;
|
|
19685
20555
|
}
|
|
20556
|
+
function stripJavaTrailingLineComment(line) {
|
|
20557
|
+
let quote = null;
|
|
20558
|
+
for (let i2 = 0; i2 < line.length; i2++) {
|
|
20559
|
+
const c = line[i2];
|
|
20560
|
+
if (quote) {
|
|
20561
|
+
if (c === "\\") i2++;
|
|
20562
|
+
else if (c === quote) quote = null;
|
|
20563
|
+
} else if (c === '"' || c === "'") {
|
|
20564
|
+
quote = c;
|
|
20565
|
+
} else if (c === "/" && line[i2 + 1] === "/") {
|
|
20566
|
+
return line.slice(0, i2).trimEnd();
|
|
20567
|
+
}
|
|
20568
|
+
}
|
|
20569
|
+
return line;
|
|
20570
|
+
}
|
|
20571
|
+
function javaExpressionIdents(expr) {
|
|
20572
|
+
const code = expr.replace(/"(?:[^"\\]|\\.)*"/g, " ").replace(/'(?:[^'\\]|\\.)'/g, " ");
|
|
20573
|
+
return code.match(/[\p{L}\p{N}_]+/gu) ?? [];
|
|
20574
|
+
}
|
|
20575
|
+
function recordJavaSlotStatement(line, listElems, mapSlots, intConsts) {
|
|
20576
|
+
const t = line.trimEnd().replace(/;\s*\/\/[^"]*$/, ";");
|
|
20577
|
+
const intDecl = /^\s*(?:final\s+)?(?:int|long|short|byte)\s+([A-Za-z_]\w*)\s*=\s*([^;]+);\s*$/.exec(t);
|
|
20578
|
+
if (intDecl) {
|
|
20579
|
+
const v = evaluatePythonConstExpression(intDecl[2].trim(), intConsts);
|
|
20580
|
+
if (typeof v === "number") intConsts.set(intDecl[1], v);
|
|
20581
|
+
else intConsts.delete(intDecl[1]);
|
|
20582
|
+
return false;
|
|
20583
|
+
}
|
|
20584
|
+
const add = /^\s*([A-Za-z_]\w*)\.add\s*\(\s*([A-Za-z_]\w*|"(?:[^"\\]|\\.)*")\s*\)\s*;\s*$/.exec(t);
|
|
20585
|
+
if (add) {
|
|
20586
|
+
const elems = listElems.get(add[1]) ?? [];
|
|
20587
|
+
elems.push(add[2].startsWith('"') ? null : add[2]);
|
|
20588
|
+
listElems.set(add[1], elems);
|
|
20589
|
+
return true;
|
|
20590
|
+
}
|
|
20591
|
+
const remove = /^\s*([A-Za-z_]\w*)\.remove\s*\(\s*(\d+)\s*\)\s*;\s*$/.exec(t);
|
|
20592
|
+
if (remove && listElems.has(remove[1])) {
|
|
20593
|
+
const elems = listElems.get(remove[1]);
|
|
20594
|
+
const idx = Number(remove[2]);
|
|
20595
|
+
if (idx < elems.length) elems.splice(idx, 1);
|
|
20596
|
+
else listElems.delete(remove[1]);
|
|
20597
|
+
return true;
|
|
20598
|
+
}
|
|
20599
|
+
const put = /^\s*([A-Za-z_]\w*)\.put\s*\(\s*"((?:[^"\\]|\\.)*)"\s*,\s*(.+?)\s*\)\s*;\s*$/.exec(t);
|
|
20600
|
+
if (put) {
|
|
20601
|
+
const keys = mapSlots.get(put[1]) ?? /* @__PURE__ */ new Map();
|
|
20602
|
+
keys.set(put[2], javaExpressionIdents(put[3]));
|
|
20603
|
+
mapSlots.set(put[1], keys);
|
|
20604
|
+
return true;
|
|
20605
|
+
}
|
|
20606
|
+
for (const table of [listElems, mapSlots]) {
|
|
20607
|
+
for (const name2 of [...table.keys()]) {
|
|
20608
|
+
const mention = new RegExp(`(?<![\\w.])${name2}(?!\\w)`);
|
|
20609
|
+
if (!mention.test(t)) continue;
|
|
20610
|
+
const reads = new RegExp(`(?<![\\w.])${name2}\\.(?:get|size|isEmpty|contains\\w*)\\s*\\(`, "g");
|
|
20611
|
+
if (mention.test(t.replace(reads, " "))) table.delete(name2);
|
|
20612
|
+
}
|
|
20613
|
+
}
|
|
20614
|
+
return false;
|
|
20615
|
+
}
|
|
20616
|
+
function javaSlotRead(rhs, listElems, mapSlots) {
|
|
20617
|
+
const m = /^(?:\(\s*[A-Za-z_][\w.<>]*\s*\)\s*)?([A-Za-z_]\w*)\.get\s*\(\s*(\d+|"(?:[^"\\]|\\.)*")\s*\)$/.exec(rhs.trim());
|
|
20618
|
+
if (!m) return void 0;
|
|
20619
|
+
const [, recv, arg] = m;
|
|
20620
|
+
if (arg.startsWith('"')) {
|
|
20621
|
+
const keys = mapSlots.get(recv);
|
|
20622
|
+
if (!keys) return void 0;
|
|
20623
|
+
return keys.get(arg.slice(1, -1)) ?? [];
|
|
20624
|
+
}
|
|
20625
|
+
const elems = listElems.get(recv);
|
|
20626
|
+
if (!elems) return void 0;
|
|
20627
|
+
const idx = Number(arg);
|
|
20628
|
+
if (idx >= elems.length) return void 0;
|
|
20629
|
+
const slot = elems[idx];
|
|
20630
|
+
return slot === null ? [] : [slot];
|
|
20631
|
+
}
|
|
19686
20632
|
function joinWrappedJavaStatement(lines, i2) {
|
|
19687
20633
|
const strip = (l) => l.replace(/"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)'/g, '""').replace(/\/\/.*$/, "");
|
|
19688
20634
|
const first = strip(lines[i2]);
|
|
@@ -19704,12 +20650,19 @@ function joinWrappedJavaStatement(lines, i2) {
|
|
|
19704
20650
|
}
|
|
19705
20651
|
return null;
|
|
19706
20652
|
}
|
|
19707
|
-
function buildJavaTaintedVars(sourceCode, seedVars) {
|
|
20653
|
+
function buildJavaTaintedVars(sourceCode, seedVars, deadBranchLines, options) {
|
|
19708
20654
|
const derived = /* @__PURE__ */ new Map();
|
|
19709
20655
|
const knownTainted = new Set(seedVars);
|
|
19710
|
-
const
|
|
20656
|
+
const trackSlots = options?.trackSlots === true;
|
|
20657
|
+
const lines = trackSlots ? sourceCode.split("\n").map(stripJavaTrailingLineComment) : sourceCode.split("\n");
|
|
20658
|
+
const dead = deadBranchLines ?? /* @__PURE__ */ new Set();
|
|
20659
|
+
const listElems = /* @__PURE__ */ new Map();
|
|
20660
|
+
const mapSlots = /* @__PURE__ */ new Map();
|
|
20661
|
+
const intConsts = /* @__PURE__ */ new Map();
|
|
19711
20662
|
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*$/;
|
|
19712
20663
|
const assignRe = /^\s*([A-Za-z_]\w*)\s*=\s*(.+?);\s*$/;
|
|
20664
|
+
const thisFieldAssignRe = /^\s*this\.([A-Za-z_]\w*)\s*=\s*(.+?);\s*$/;
|
|
20665
|
+
const beanSetterCallRe = /\bset([A-Z]\w*)\s*\((.*)\)\s*;?\s*$/;
|
|
19713
20666
|
const JAVA_KEYWORDS = /* @__PURE__ */ new Set([
|
|
19714
20667
|
"if",
|
|
19715
20668
|
"else",
|
|
@@ -19745,10 +20698,23 @@ function buildJavaTaintedVars(sourceCode, seedVars) {
|
|
|
19745
20698
|
while (changed && guard < lines.length + 2) {
|
|
19746
20699
|
changed = false;
|
|
19747
20700
|
guard++;
|
|
20701
|
+
listElems.clear();
|
|
20702
|
+
mapSlots.clear();
|
|
20703
|
+
intConsts.clear();
|
|
19748
20704
|
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
20705
|
+
if (deadBranchLines !== void 0 && dead.has(i2)) continue;
|
|
19749
20706
|
const line = lines[i2];
|
|
19750
20707
|
const trimmed = line.trimStart();
|
|
19751
20708
|
if (trimmed.startsWith("//") || trimmed.startsWith("*")) continue;
|
|
20709
|
+
if (trackSlots && recordJavaSlotStatement(line, listElems, mapSlots, intConsts)) {
|
|
20710
|
+
const add = /^\s*([A-Za-z_]\w*)\.add\s*\(\s*([A-Za-z_]\w*)\s*\)\s*;/.exec(line);
|
|
20711
|
+
if (add && knownTainted.has(add[2]) && !knownTainted.has(add[1])) {
|
|
20712
|
+
derived.set(add[1], i2 + 1);
|
|
20713
|
+
knownTainted.add(add[1]);
|
|
20714
|
+
changed = true;
|
|
20715
|
+
}
|
|
20716
|
+
continue;
|
|
20717
|
+
}
|
|
19752
20718
|
const arrayCopyRe = /\bSystem\.arraycopy\s*\(\s*([A-Za-z_]\w*)\s*,\s*[^,]+\s*,\s*([A-Za-z_]\w*)\s*,/;
|
|
19753
20719
|
const acMatch = arrayCopyRe.exec(line);
|
|
19754
20720
|
if (acMatch) {
|
|
@@ -19778,17 +20744,79 @@ function buildJavaTaintedVars(sourceCode, seedVars) {
|
|
|
19778
20744
|
let assignMatch = !declMatch ? assignRe.exec(statement) : null;
|
|
19779
20745
|
if (!declMatch && !assignMatch) {
|
|
19780
20746
|
const joined = joinWrappedJavaStatement(lines, i2);
|
|
19781
|
-
if (joined
|
|
19782
|
-
|
|
19783
|
-
|
|
19784
|
-
|
|
20747
|
+
if (joined !== null) {
|
|
20748
|
+
statement = joined;
|
|
20749
|
+
declMatch = declRe.exec(statement);
|
|
20750
|
+
assignMatch = !declMatch ? assignRe.exec(statement) : null;
|
|
20751
|
+
}
|
|
20752
|
+
}
|
|
20753
|
+
const thisFieldMatch = thisFieldAssignRe.exec(statement.trim());
|
|
20754
|
+
if (thisFieldMatch) {
|
|
20755
|
+
const fieldName = thisFieldMatch[1];
|
|
20756
|
+
const rhs2 = thisFieldMatch[2];
|
|
20757
|
+
const rhsCode2 = rhs2.replace(/\$@?"(?:[^"\\]|\\.)*"/g, (lit) => " " + [...lit.matchAll(/\{([^}]*)\}/g)].map((x) => x[1]).join(" ") + " ").replace(/@?"(?:[^"\\]|\\.)*"/g, " ").replace(/'(?:[^'\\]|\\.)'/g, " ");
|
|
20758
|
+
const rhsIdents2 = rhsCode2.match(/[\p{L}\p{N}_]+/gu);
|
|
20759
|
+
const ref2 = rhsIdents2 !== null && rhsIdents2.some((t) => knownTainted.has(t)) || complexTaintedRes.length > 0 && complexTaintedRes.some((re) => re.test(rhsCode2));
|
|
20760
|
+
if (ref2) {
|
|
20761
|
+
const thisField = `this.${fieldName}`;
|
|
20762
|
+
if (!knownTainted.has(thisField)) {
|
|
20763
|
+
derived.set(thisField, i2 + 1);
|
|
20764
|
+
knownTainted.add(thisField);
|
|
20765
|
+
changed = true;
|
|
20766
|
+
}
|
|
20767
|
+
if (!knownTainted.has(fieldName)) {
|
|
20768
|
+
derived.set(fieldName, i2 + 1);
|
|
20769
|
+
knownTainted.add(fieldName);
|
|
20770
|
+
changed = true;
|
|
20771
|
+
}
|
|
20772
|
+
}
|
|
20773
|
+
continue;
|
|
20774
|
+
}
|
|
20775
|
+
const setterCall = beanSetterCallRe.exec(statement);
|
|
20776
|
+
if (setterCall) {
|
|
20777
|
+
const fieldName = setterCall[1].charAt(0).toLowerCase() + setterCall[1].slice(1);
|
|
20778
|
+
const arg = setterCall[2];
|
|
20779
|
+
const rhsCode2 = arg.replace(/\$@?"(?:[^"\\]|\\.)*"/g, (lit) => " " + [...lit.matchAll(/\{([^}]*)\}/g)].map((x) => x[1]).join(" ") + " ").replace(/@?"(?:[^"\\]|\\.)*"/g, " ").replace(/'(?:[^'\\]|\\.)'/g, " ");
|
|
20780
|
+
const rhsIdents2 = rhsCode2.match(/[\p{L}\p{N}_]+/gu);
|
|
20781
|
+
const ref2 = rhsIdents2 !== null && rhsIdents2.some((t) => knownTainted.has(t)) || complexTaintedRes.length > 0 && complexTaintedRes.some((re) => re.test(rhsCode2));
|
|
20782
|
+
if (ref2) {
|
|
20783
|
+
const thisField = `this.${fieldName}`;
|
|
20784
|
+
if (!knownTainted.has(thisField)) {
|
|
20785
|
+
derived.set(thisField, i2 + 1);
|
|
20786
|
+
knownTainted.add(thisField);
|
|
20787
|
+
changed = true;
|
|
20788
|
+
}
|
|
20789
|
+
if (!knownTainted.has(fieldName)) {
|
|
20790
|
+
derived.set(fieldName, i2 + 1);
|
|
20791
|
+
knownTainted.add(fieldName);
|
|
20792
|
+
changed = true;
|
|
20793
|
+
}
|
|
20794
|
+
}
|
|
20795
|
+
continue;
|
|
19785
20796
|
}
|
|
19786
20797
|
const m = declMatch ?? assignMatch;
|
|
19787
20798
|
if (!m) continue;
|
|
19788
20799
|
const lhs = m[1];
|
|
19789
|
-
|
|
20800
|
+
let rhs = m[2];
|
|
19790
20801
|
if (JAVA_KEYWORDS.has(lhs)) continue;
|
|
19791
20802
|
if (knownTainted.has(lhs)) continue;
|
|
20803
|
+
if (trackSlots) {
|
|
20804
|
+
const slot = javaSlotRead(rhs, listElems, mapSlots);
|
|
20805
|
+
if (slot !== void 0) {
|
|
20806
|
+
if (slot.some((t) => knownTainted.has(t))) {
|
|
20807
|
+
derived.set(lhs, i2 + 1);
|
|
20808
|
+
knownTainted.add(lhs);
|
|
20809
|
+
changed = true;
|
|
20810
|
+
}
|
|
20811
|
+
continue;
|
|
20812
|
+
}
|
|
20813
|
+
const tern = /^([^?]+?)\s*\?\s*([^:]+?)\s*:\s*(.+)$/.exec(rhs.trim());
|
|
20814
|
+
if (tern) {
|
|
20815
|
+
const decided = evaluatePythonConstExpression(tern[1].trim().replace(/^\((.*)\)$/, "$1"), intConsts);
|
|
20816
|
+
if (decided === true) rhs = tern[2];
|
|
20817
|
+
else if (decided === false) rhs = tern[3];
|
|
20818
|
+
}
|
|
20819
|
+
}
|
|
19792
20820
|
const rhsCode = rhs.replace(/\$@?"(?:[^"\\]|\\.)*"/g, (lit) => " " + [...lit.matchAll(/\{([^}]*)\}/g)].map((x) => x[1]).join(" ") + " ").replace(/@?"(?:[^"\\]|\\.)*"/g, " ").replace(/'(?:[^'\\]|\\.)'/g, " ");
|
|
19793
20821
|
const rhsIdents = rhsCode.match(/[\p{L}\p{N}_]+/gu);
|
|
19794
20822
|
const ref = rhsIdents !== null && rhsIdents.some((t) => knownTainted.has(t)) || complexTaintedRes.length > 0 && complexTaintedRes.some((re) => re.test(rhsCode));
|
|
@@ -19840,8 +20868,11 @@ function findBashTaintSources(sourceCode, dfg) {
|
|
|
19840
20868
|
const insideFunction = braceDepth > 0;
|
|
19841
20869
|
if (!insideFunction) {
|
|
19842
20870
|
const positionalRe = /\$([1-9@*])|\$\{([1-9@*])\}/g;
|
|
20871
|
+
const header = /^\s*(?:function\s+)?[A-Za-z_][\w-]*\s*(?:\(\s*\))?\s*\{/.exec(line);
|
|
20872
|
+
const isFnHeader = header !== null && (/\(\s*\)/.test(header[0]) || /^\s*function\s/.test(header[0]));
|
|
20873
|
+
const topLevelText = (isFnHeader ? line.slice(0, header[0].length) : line).replace(/'[^']*'/g, "''");
|
|
19843
20874
|
let m;
|
|
19844
|
-
while ((m = positionalRe.exec(
|
|
20875
|
+
while ((m = positionalRe.exec(topLevelText)) !== null) {
|
|
19845
20876
|
const param = m[1] ?? m[2];
|
|
19846
20877
|
const alreadyExists = sources.some((s) => s.line === lineNumber && s.variable === param);
|
|
19847
20878
|
if (!alreadyExists) {
|
|
@@ -19897,10 +20928,10 @@ function findBashTaintSources(sourceCode, dfg) {
|
|
|
19897
20928
|
if (readMatch) {
|
|
19898
20929
|
const argStr = readMatch[1].trim();
|
|
19899
20930
|
const noRedirect = argStr.replace(/\s*<[^<].*$/, "").trim();
|
|
19900
|
-
const
|
|
20931
|
+
const tokens2 = noRedirect.split(/\s+/);
|
|
19901
20932
|
const varNames = [];
|
|
19902
|
-
for (let ti = 0; ti <
|
|
19903
|
-
const tok =
|
|
20933
|
+
for (let ti = 0; ti < tokens2.length; ti++) {
|
|
20934
|
+
const tok = tokens2[ti];
|
|
19904
20935
|
if (!tok) continue;
|
|
19905
20936
|
if (tok.startsWith("-")) {
|
|
19906
20937
|
if (/^-[antpidu]$|^-N$/.test(tok)) ti++;
|
|
@@ -19925,10 +20956,10 @@ function findBashTaintSources(sourceCode, dfg) {
|
|
|
19925
20956
|
const mapfileMatch = /^(?:mapfile|readarray)\s*\(/.test(trimmed) ? null : trimmed.match(/^(?:mapfile|readarray)\b([^#(]*)$/);
|
|
19926
20957
|
if (mapfileMatch) {
|
|
19927
20958
|
const argStr = mapfileMatch[1].trim();
|
|
19928
|
-
const
|
|
20959
|
+
const tokens2 = argStr.split(/\s+/);
|
|
19929
20960
|
const varNames = [];
|
|
19930
|
-
for (let ti = 0; ti <
|
|
19931
|
-
const tok =
|
|
20961
|
+
for (let ti = 0; ti < tokens2.length; ti++) {
|
|
20962
|
+
const tok = tokens2[ti];
|
|
19932
20963
|
if (!tok) continue;
|
|
19933
20964
|
if (tok.startsWith("-")) {
|
|
19934
20965
|
if (/^-[cCnOsud]$/.test(tok)) ti++;
|
|
@@ -20313,6 +21344,30 @@ function findBashRealpathPrefixGuardSanitizers(code) {
|
|
|
20313
21344
|
}
|
|
20314
21345
|
return sanitizers;
|
|
20315
21346
|
}
|
|
21347
|
+
function findBashVerifiedPackageDownloadSanitizers(code) {
|
|
21348
|
+
const lines = code.split("\n");
|
|
21349
|
+
if (!hasIntegrityVerifierAnywhere(lines)) {
|
|
21350
|
+
return [];
|
|
21351
|
+
}
|
|
21352
|
+
const hasDownload = lines.some((l) => /\b(curl|wget)\b/.test(l));
|
|
21353
|
+
const hasInstall = lines.some((l) => matchUnverifiedPackageInstall(l.trim()) !== null);
|
|
21354
|
+
if (!hasDownload || !hasInstall) {
|
|
21355
|
+
return [];
|
|
21356
|
+
}
|
|
21357
|
+
const sanitizers = [];
|
|
21358
|
+
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
21359
|
+
const line = lines[i2];
|
|
21360
|
+
if (line.trimStart().startsWith("#")) continue;
|
|
21361
|
+
if (!/\b(curl|wget)\b/.test(line)) continue;
|
|
21362
|
+
sanitizers.push({
|
|
21363
|
+
type: "verified_package_download",
|
|
21364
|
+
method: "curl",
|
|
21365
|
+
line: i2 + 1,
|
|
21366
|
+
sanitizes: ["ssrf"]
|
|
21367
|
+
});
|
|
21368
|
+
}
|
|
21369
|
+
return sanitizers;
|
|
21370
|
+
}
|
|
20316
21371
|
function findGoMapAllowlistGuardSanitizers(code) {
|
|
20317
21372
|
const sanitizers = [];
|
|
20318
21373
|
const lines = code.split("\n");
|
|
@@ -20468,6 +21523,160 @@ function findPythonNetlocAllowlistGuardSanitizers(code) {
|
|
|
20468
21523
|
}
|
|
20469
21524
|
return sanitizers;
|
|
20470
21525
|
}
|
|
21526
|
+
function findJsNumericRangeCheckGuardSanitizers(code) {
|
|
21527
|
+
const sanitizers = [];
|
|
21528
|
+
const lines = code.split("\n");
|
|
21529
|
+
const coerceDecl = /^\s*(?:const|let|var)\s+([A-Za-z_]\w*)\s*=\s*(?:Number|parseInt)\s*\(/;
|
|
21530
|
+
const lowerGuard = /\bif\s*\(\s*!(?:Number\.)?isFinite\s*\(\s*([A-Za-z_]\w*)\s*\)|\bif\s*\([^)]*\b([A-Za-z_]\w*)\s*<=\s*0/;
|
|
21531
|
+
const upperGuard = /\bif\s*\(\s*([A-Za-z_]\w*)\s*>\s*(?:\d+|[A-Z][A-Z0-9_]+)\s*\)/;
|
|
21532
|
+
const terminator = /\b(?:return|throw|res\s*\.\s*status\s*\([^)]*\)\s*\.\s*(?:send|end|json))/;
|
|
21533
|
+
const coerced = /* @__PURE__ */ new Map();
|
|
21534
|
+
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
21535
|
+
const m = coerceDecl.exec(lines[i2]);
|
|
21536
|
+
if (m) coerced.set(m[1], i2);
|
|
21537
|
+
}
|
|
21538
|
+
if (coerced.size === 0) return sanitizers;
|
|
21539
|
+
const state = /* @__PURE__ */ new Map();
|
|
21540
|
+
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
21541
|
+
const line = lines[i2];
|
|
21542
|
+
let varName = null;
|
|
21543
|
+
let kind = null;
|
|
21544
|
+
const low = lowerGuard.exec(line);
|
|
21545
|
+
if (low) {
|
|
21546
|
+
varName = low[1] ?? low[2];
|
|
21547
|
+
kind = "lower";
|
|
21548
|
+
} else {
|
|
21549
|
+
const up = upperGuard.exec(line);
|
|
21550
|
+
if (up) {
|
|
21551
|
+
varName = up[1];
|
|
21552
|
+
kind = "upper";
|
|
21553
|
+
}
|
|
21554
|
+
}
|
|
21555
|
+
if (!varName || !kind || !coerced.has(varName)) continue;
|
|
21556
|
+
if (!terminator.test(line)) {
|
|
21557
|
+
let bodyHasTerminator = false;
|
|
21558
|
+
let blockEnd = i2;
|
|
21559
|
+
let braceDepth = 0;
|
|
21560
|
+
let started = false;
|
|
21561
|
+
const maxScan = Math.min(lines.length, i2 + 26);
|
|
21562
|
+
for (let j = i2; j < maxScan; j++) {
|
|
21563
|
+
const ln = lines[j];
|
|
21564
|
+
for (const ch of ln) {
|
|
21565
|
+
if (ch === "{") {
|
|
21566
|
+
braceDepth++;
|
|
21567
|
+
started = true;
|
|
21568
|
+
} else if (ch === "}") {
|
|
21569
|
+
braceDepth--;
|
|
21570
|
+
if (started && braceDepth === 0) {
|
|
21571
|
+
blockEnd = j;
|
|
21572
|
+
break;
|
|
21573
|
+
}
|
|
21574
|
+
}
|
|
21575
|
+
}
|
|
21576
|
+
if (j > i2 && terminator.test(ln)) bodyHasTerminator = true;
|
|
21577
|
+
if (started && braceDepth === 0 && blockEnd !== i2) break;
|
|
21578
|
+
}
|
|
21579
|
+
if (!bodyHasTerminator && !started) continue;
|
|
21580
|
+
if (!bodyHasTerminator) continue;
|
|
21581
|
+
}
|
|
21582
|
+
const st = state.get(varName) ?? { lower: false, upper: false, lastGuardEnd: i2 };
|
|
21583
|
+
if (kind === "lower") st.lower = true;
|
|
21584
|
+
else st.upper = true;
|
|
21585
|
+
st.lastGuardEnd = i2;
|
|
21586
|
+
state.set(varName, st);
|
|
21587
|
+
}
|
|
21588
|
+
for (const [varName, st] of state) {
|
|
21589
|
+
if (!st.lower || !st.upper) continue;
|
|
21590
|
+
const varRefRe = new RegExp(`\\b${varName}\\b`);
|
|
21591
|
+
for (let l = st.lastGuardEnd + 1; l < lines.length; l++) {
|
|
21592
|
+
if (!varRefRe.test(lines[l])) continue;
|
|
21593
|
+
sanitizers.push({
|
|
21594
|
+
type: "js_numeric_range_check_guard",
|
|
21595
|
+
method: "if",
|
|
21596
|
+
line: l + 1,
|
|
21597
|
+
sanitizes: ["xss", "external_taint_escape"]
|
|
21598
|
+
});
|
|
21599
|
+
}
|
|
21600
|
+
}
|
|
21601
|
+
return sanitizers;
|
|
21602
|
+
}
|
|
21603
|
+
function findRustNumericRangeCheckGuardSanitizers(code) {
|
|
21604
|
+
const sanitizers = [];
|
|
21605
|
+
const lines = code.split("\n");
|
|
21606
|
+
const coerceDecl = /^\s*let\s+([A-Za-z_]\w*)\s*(?::\s*(?:i\d+|u\d+|isize|usize))?\s*=\s*[^;]*\.parse\s*(?:\(\s*\)|::<)/;
|
|
21607
|
+
const lowerGuard = /\bif\s+([A-Za-z_]\w*)\s*<=\s*0\b/;
|
|
21608
|
+
const upperGuard = /\bif\s+([A-Za-z_]\w*)\s*>\s*(?:\d+|[A-Z][A-Z0-9_]+)\b/;
|
|
21609
|
+
const terminator = /\b(?:return\s+HttpResponse::|return\s+StatusCode::|return\s+Err\s*\(|panic!\s*\()/;
|
|
21610
|
+
const coerced = /* @__PURE__ */ new Map();
|
|
21611
|
+
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
21612
|
+
const m = coerceDecl.exec(lines[i2]);
|
|
21613
|
+
if (m) coerced.set(m[1], i2);
|
|
21614
|
+
}
|
|
21615
|
+
if (coerced.size === 0) return sanitizers;
|
|
21616
|
+
const state = /* @__PURE__ */ new Map();
|
|
21617
|
+
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
21618
|
+
const line = lines[i2];
|
|
21619
|
+
let varName = null;
|
|
21620
|
+
let kind = null;
|
|
21621
|
+
const low = lowerGuard.exec(line);
|
|
21622
|
+
if (low) {
|
|
21623
|
+
varName = low[1];
|
|
21624
|
+
kind = "lower";
|
|
21625
|
+
} else {
|
|
21626
|
+
const up = upperGuard.exec(line);
|
|
21627
|
+
if (up) {
|
|
21628
|
+
varName = up[1];
|
|
21629
|
+
kind = "upper";
|
|
21630
|
+
}
|
|
21631
|
+
}
|
|
21632
|
+
if (!varName || !kind || !coerced.has(varName)) continue;
|
|
21633
|
+
if (!terminator.test(line)) {
|
|
21634
|
+
let bodyHasTerminator = false;
|
|
21635
|
+
let blockEnd = i2;
|
|
21636
|
+
let braceDepth = 0;
|
|
21637
|
+
let started = false;
|
|
21638
|
+
const maxScan = Math.min(lines.length, i2 + 26);
|
|
21639
|
+
for (let j = i2; j < maxScan; j++) {
|
|
21640
|
+
const ln = lines[j];
|
|
21641
|
+
for (const ch of ln) {
|
|
21642
|
+
if (ch === "{") {
|
|
21643
|
+
braceDepth++;
|
|
21644
|
+
started = true;
|
|
21645
|
+
} else if (ch === "}") {
|
|
21646
|
+
braceDepth--;
|
|
21647
|
+
if (started && braceDepth === 0) {
|
|
21648
|
+
blockEnd = j;
|
|
21649
|
+
break;
|
|
21650
|
+
}
|
|
21651
|
+
}
|
|
21652
|
+
}
|
|
21653
|
+
if (j > i2 && terminator.test(ln)) bodyHasTerminator = true;
|
|
21654
|
+
if (started && braceDepth === 0 && blockEnd !== i2) break;
|
|
21655
|
+
}
|
|
21656
|
+
if (!bodyHasTerminator && !started) continue;
|
|
21657
|
+
if (!bodyHasTerminator) continue;
|
|
21658
|
+
}
|
|
21659
|
+
const st = state.get(varName) ?? { lower: false, upper: false, lastGuardEnd: i2 };
|
|
21660
|
+
if (kind === "lower") st.lower = true;
|
|
21661
|
+
else st.upper = true;
|
|
21662
|
+
st.lastGuardEnd = i2;
|
|
21663
|
+
state.set(varName, st);
|
|
21664
|
+
}
|
|
21665
|
+
for (const [varName, st] of state) {
|
|
21666
|
+
if (!st.lower || !st.upper) continue;
|
|
21667
|
+
const varRefRe = new RegExp(`\\b${varName}\\b`);
|
|
21668
|
+
for (let l = st.lastGuardEnd + 1; l < lines.length; l++) {
|
|
21669
|
+
if (!varRefRe.test(lines[l])) continue;
|
|
21670
|
+
sanitizers.push({
|
|
21671
|
+
type: "rust_numeric_range_check_guard",
|
|
21672
|
+
method: "if",
|
|
21673
|
+
line: l + 1,
|
|
21674
|
+
sanitizes: ["xss", "external_taint_escape"]
|
|
21675
|
+
});
|
|
21676
|
+
}
|
|
21677
|
+
}
|
|
21678
|
+
return sanitizers;
|
|
21679
|
+
}
|
|
20471
21680
|
function findPythonRangeCheckGuardSanitizers(code) {
|
|
20472
21681
|
const sanitizers = [];
|
|
20473
21682
|
const lines = code.split("\n");
|
|
@@ -21054,6 +22263,127 @@ function findPythonDefusedXmlSanitizers(code) {
|
|
|
21054
22263
|
}
|
|
21055
22264
|
return sanitizers;
|
|
21056
22265
|
}
|
|
22266
|
+
function findRustPatInfoHttpOnlyResponseSanitizers(code) {
|
|
22267
|
+
const sanitizers = [];
|
|
22268
|
+
const lines = code.split("\n");
|
|
22269
|
+
const bodyFormatRe = /HttpResponse::\w+\(\)\s*\.\s*body\s*\(\s*format!\s*\(/;
|
|
22270
|
+
const envBindRe = /(?:^|[^:])env::var\s*\(|std::env::/;
|
|
22271
|
+
const httpGetRe = /\.get\s*\(\s*"[^"]+"\s*\)/;
|
|
22272
|
+
const letVarRe = (v) => new RegExp(String.raw`^\s*let\s+(?:mut\s+)?${v}\s*=`);
|
|
22273
|
+
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
22274
|
+
const sinkLine = lines[i2];
|
|
22275
|
+
if (!bodyFormatRe.test(sinkLine)) continue;
|
|
22276
|
+
if (envBindRe.test(sinkLine)) continue;
|
|
22277
|
+
const varRefs = [
|
|
22278
|
+
...sinkLine.matchAll(/\{([A-Za-z_][A-Za-z0-9_]*)\}/g)
|
|
22279
|
+
].map((m) => m[1]);
|
|
22280
|
+
if (varRefs.length === 0) continue;
|
|
22281
|
+
let allHttpOnly = true;
|
|
22282
|
+
for (const v of varRefs) {
|
|
22283
|
+
let sawBinding = false;
|
|
22284
|
+
let boundEnv = false;
|
|
22285
|
+
let boundHttp = false;
|
|
22286
|
+
for (let j = i2 - 1; j >= Math.max(0, i2 - 24); j--) {
|
|
22287
|
+
const line = lines[j];
|
|
22288
|
+
if (!letVarRe(v).test(line)) continue;
|
|
22289
|
+
sawBinding = true;
|
|
22290
|
+
if (envBindRe.test(line)) boundEnv = true;
|
|
22291
|
+
if (httpGetRe.test(line)) boundHttp = true;
|
|
22292
|
+
break;
|
|
22293
|
+
}
|
|
22294
|
+
if (!sawBinding || boundEnv || !boundHttp) {
|
|
22295
|
+
allHttpOnly = false;
|
|
22296
|
+
break;
|
|
22297
|
+
}
|
|
22298
|
+
}
|
|
22299
|
+
if (!allHttpOnly) continue;
|
|
22300
|
+
sanitizers.push({
|
|
22301
|
+
type: "rust_pat_info_http_only_body",
|
|
22302
|
+
method: "format",
|
|
22303
|
+
line: i2 + 1,
|
|
22304
|
+
sanitizes: [
|
|
22305
|
+
"xss",
|
|
22306
|
+
"external_taint_escape",
|
|
22307
|
+
"open_redirect",
|
|
22308
|
+
"log_injection"
|
|
22309
|
+
]
|
|
22310
|
+
});
|
|
22311
|
+
}
|
|
22312
|
+
return sanitizers;
|
|
22313
|
+
}
|
|
22314
|
+
function collectRustConstStrSliceNames(code) {
|
|
22315
|
+
const names = /* @__PURE__ */ new Set();
|
|
22316
|
+
const re = /^\s*const\s+([A-Z][A-Z0-9_]*)\s*:\s*&\s*\[\s*&\s*str\s*\]\s*=\s*&\s*\[([^\]]*)\]\s*;/;
|
|
22317
|
+
for (const line of code.split("\n")) {
|
|
22318
|
+
const m = re.exec(line);
|
|
22319
|
+
if (!m) continue;
|
|
22320
|
+
const inner = m[2].trim();
|
|
22321
|
+
if (!inner) {
|
|
22322
|
+
names.add(m[1]);
|
|
22323
|
+
continue;
|
|
22324
|
+
}
|
|
22325
|
+
const elems = inner.split(",").map((s) => s.trim()).filter(Boolean);
|
|
22326
|
+
if (elems.every((e) => /^"(?:[^"\\]|\\.)*"$/.test(e))) names.add(m[1]);
|
|
22327
|
+
}
|
|
22328
|
+
return names;
|
|
22329
|
+
}
|
|
22330
|
+
function findRustConstSliceMembershipGuardSanitizers(code) {
|
|
22331
|
+
const sanitizers = [];
|
|
22332
|
+
const slices = collectRustConstStrSliceNames(code);
|
|
22333
|
+
if (slices.size === 0) return sanitizers;
|
|
22334
|
+
const lines = code.split("\n");
|
|
22335
|
+
const guardOpen = /^\s*if\s+!\s*([A-Z][A-Z0-9_]*)\s*\.\s*contains\s*\(\s*&\s*([A-Za-z_]\w*)(?:\s*\.\s*as_str\s*\(\s*\))?\s*\)\s*\{?\s*$/;
|
|
22336
|
+
const terminator = /\b(?:return\s+HttpResponse::|return\s+StatusCode::|return\s+Err\s*\(|panic!\s*\()/;
|
|
22337
|
+
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
22338
|
+
const m = guardOpen.exec(lines[i2]);
|
|
22339
|
+
if (!m) continue;
|
|
22340
|
+
const sliceName = m[1];
|
|
22341
|
+
const guardedVar = m[2];
|
|
22342
|
+
if (!slices.has(sliceName)) continue;
|
|
22343
|
+
let depth = 0;
|
|
22344
|
+
for (const ch of lines[i2]) {
|
|
22345
|
+
if (ch === "{") depth++;
|
|
22346
|
+
else if (ch === "}") depth--;
|
|
22347
|
+
}
|
|
22348
|
+
let closeLine = i2;
|
|
22349
|
+
let bodyHasTerminator = terminator.test(lines[i2]);
|
|
22350
|
+
if (depth > 0) {
|
|
22351
|
+
for (let j = i2 + 1; j < Math.min(lines.length, i2 + 26); j++) {
|
|
22352
|
+
if (terminator.test(lines[j])) bodyHasTerminator = true;
|
|
22353
|
+
for (const ch of lines[j]) {
|
|
22354
|
+
if (ch === "{") depth++;
|
|
22355
|
+
else if (ch === "}") depth--;
|
|
22356
|
+
}
|
|
22357
|
+
if (depth === 0) {
|
|
22358
|
+
closeLine = j;
|
|
22359
|
+
break;
|
|
22360
|
+
}
|
|
22361
|
+
}
|
|
22362
|
+
}
|
|
22363
|
+
if (!bodyHasTerminator) continue;
|
|
22364
|
+
const varRe = new RegExp(String.raw`\b${guardedVar}\b`);
|
|
22365
|
+
const reassign = new RegExp(String.raw`^\s*(?:let\s+)?(?:mut\s+)?${guardedVar}\s*=(?!=)`);
|
|
22366
|
+
for (let l = closeLine + 2; l <= lines.length; l++) {
|
|
22367
|
+
const lineText = lines[l - 1];
|
|
22368
|
+
if (!lineText || !varRe.test(lineText)) continue;
|
|
22369
|
+
if (reassign.test(lineText)) break;
|
|
22370
|
+
sanitizers.push({
|
|
22371
|
+
type: "rust_const_slice_membership_guard",
|
|
22372
|
+
method: "if",
|
|
22373
|
+
line: l,
|
|
22374
|
+
sanitizes: [
|
|
22375
|
+
"trust_boundary",
|
|
22376
|
+
"external_taint_escape",
|
|
22377
|
+
"xss",
|
|
22378
|
+
"open_redirect",
|
|
22379
|
+
"ssrf",
|
|
22380
|
+
"command_injection"
|
|
22381
|
+
]
|
|
22382
|
+
});
|
|
22383
|
+
}
|
|
22384
|
+
}
|
|
22385
|
+
return sanitizers;
|
|
22386
|
+
}
|
|
21057
22387
|
function findRustSetAllowlistGuardSanitizers(code) {
|
|
21058
22388
|
const sanitizers = [];
|
|
21059
22389
|
const lines = code.split("\n");
|
|
@@ -21210,6 +22540,13 @@ function findJsCsvFormulaPrefixSanitizers(code) {
|
|
|
21210
22540
|
}
|
|
21211
22541
|
return sanitizers;
|
|
21212
22542
|
}
|
|
22543
|
+
function jsWrapperBodyReplaceChainText(body2) {
|
|
22544
|
+
const returnM = /\breturn\s+([^;}\n]+)/.exec(body2);
|
|
22545
|
+
if (returnM) return returnM[1].trim();
|
|
22546
|
+
const arrowM = /=>\s*([^;}\n]+)/.exec(body2);
|
|
22547
|
+
if (arrowM) return arrowM[1].trim();
|
|
22548
|
+
return null;
|
|
22549
|
+
}
|
|
21213
22550
|
function findJsWrapperFunctionSanitizers(code) {
|
|
21214
22551
|
const sanitizers = [];
|
|
21215
22552
|
const lines = code.split("\n");
|
|
@@ -21229,8 +22566,12 @@ function findJsWrapperFunctionSanitizers(code) {
|
|
|
21229
22566
|
kinds.add("external_taint_escape");
|
|
21230
22567
|
}
|
|
21231
22568
|
if (crlfCharClass.test(body2)) {
|
|
21232
|
-
|
|
21233
|
-
|
|
22569
|
+
const chainText = jsWrapperBodyReplaceChainText(body2);
|
|
22570
|
+
const verdict = chainText ? jsReplaceChainVerdict(chainText) : "undecided";
|
|
22571
|
+
if (verdict !== "leaves-crlf") {
|
|
22572
|
+
kinds.add("log_injection");
|
|
22573
|
+
kinds.add("external_taint_escape");
|
|
22574
|
+
}
|
|
21234
22575
|
}
|
|
21235
22576
|
if (kinds.size > 0) wrappers.set(name2, kinds);
|
|
21236
22577
|
}
|
|
@@ -22032,6 +23373,74 @@ function javaBodyStripsCrAndLf(body2) {
|
|
|
22032
23373
|
}
|
|
22033
23374
|
return cr && lf;
|
|
22034
23375
|
}
|
|
23376
|
+
function javaBodyStripsTemplateDelimiters(body2) {
|
|
23377
|
+
let open = false;
|
|
23378
|
+
let close = false;
|
|
23379
|
+
for (const m of body2.matchAll(/\.\s*(replace|replaceAll)\s*\(\s*(?:"((?:\\.|[^"\\])*)"|'(\\.|[^'\\])')/g)) {
|
|
23380
|
+
const lit = m[2] ?? m[3] ?? "";
|
|
23381
|
+
if (lit === "{") open = true;
|
|
23382
|
+
if (lit === "}") close = true;
|
|
23383
|
+
}
|
|
23384
|
+
return open && close;
|
|
23385
|
+
}
|
|
23386
|
+
function findJavaTemplateDelimiterWrapperSanitizers(code) {
|
|
23387
|
+
const sanitizers = [];
|
|
23388
|
+
const lines = code.split("\n");
|
|
23389
|
+
const declRe = /^\s*(?:public|private|protected)?\s*(?:static\s+)?(?:final\s+)?(?:String|CharSequence)\s+([A-Za-z_]\w*)\s*\(/;
|
|
23390
|
+
const bodyWindow = 10;
|
|
23391
|
+
const wrappers = /* @__PURE__ */ new Set();
|
|
23392
|
+
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
23393
|
+
const m = declRe.exec(lines[i2]);
|
|
23394
|
+
if (!m) continue;
|
|
23395
|
+
let end = Math.min(lines.length, i2 + bodyWindow);
|
|
23396
|
+
for (let j = i2 + 1; j < end; j++) {
|
|
23397
|
+
if (declRe.test(lines[j])) {
|
|
23398
|
+
end = j;
|
|
23399
|
+
break;
|
|
23400
|
+
}
|
|
23401
|
+
}
|
|
23402
|
+
const body2 = lines.slice(i2, end).join("\n");
|
|
23403
|
+
if (javaBodyStripsTemplateDelimiters(body2)) wrappers.add(m[1]);
|
|
23404
|
+
}
|
|
23405
|
+
if (wrappers.size === 0) return sanitizers;
|
|
23406
|
+
const kinds = ["code_injection", "external_taint_escape"];
|
|
23407
|
+
for (const name2 of wrappers) {
|
|
23408
|
+
const assignedCallRe = new RegExp(
|
|
23409
|
+
`\\b([A-Za-z_][A-Za-z0-9_]*)\\s*=\\s*${name2}\\s*\\(`
|
|
23410
|
+
);
|
|
23411
|
+
const bareCallRe = new RegExp(`\\b${name2}\\s*\\(`);
|
|
23412
|
+
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
23413
|
+
if (!bareCallRe.test(lines[i2])) continue;
|
|
23414
|
+
if (declRe.test(lines[i2]) && bareCallRe.test(lines[i2])) continue;
|
|
23415
|
+
sanitizers.push({
|
|
23416
|
+
type: "java_wrapper_template_delim_strip",
|
|
23417
|
+
method: name2,
|
|
23418
|
+
line: i2 + 1,
|
|
23419
|
+
sanitizes: [...kinds]
|
|
23420
|
+
});
|
|
23421
|
+
const am = assignedCallRe.exec(lines[i2]);
|
|
23422
|
+
if (!am) continue;
|
|
23423
|
+
const callIndent = lines[i2].length - lines[i2].trimStart().length;
|
|
23424
|
+
const validated = am[1];
|
|
23425
|
+
const validatedRe = new RegExp(`\\b${validated}\\b`);
|
|
23426
|
+
for (let j = i2 + 1; j < lines.length; j++) {
|
|
23427
|
+
const jline = lines[j];
|
|
23428
|
+
if (jline.trim() === "") continue;
|
|
23429
|
+
const jindent = jline.length - jline.trimStart().length;
|
|
23430
|
+
if (jindent < callIndent) break;
|
|
23431
|
+
if (validatedRe.test(jline)) {
|
|
23432
|
+
sanitizers.push({
|
|
23433
|
+
type: "java_wrapper_template_delim_strip",
|
|
23434
|
+
method: name2,
|
|
23435
|
+
line: j + 1,
|
|
23436
|
+
sanitizes: [...kinds]
|
|
23437
|
+
});
|
|
23438
|
+
}
|
|
23439
|
+
}
|
|
23440
|
+
}
|
|
23441
|
+
}
|
|
23442
|
+
return sanitizers;
|
|
23443
|
+
}
|
|
22035
23444
|
function findCrlfWrapperFunctionSanitizers(code, language) {
|
|
22036
23445
|
const sanitizers = [];
|
|
22037
23446
|
const lines = code.split("\n");
|
|
@@ -24447,6 +25856,79 @@ function findJsLdapInjectionFindings(code, file) {
|
|
|
24447
25856
|
snippet: line.trim()
|
|
24448
25857
|
});
|
|
24449
25858
|
}
|
|
25859
|
+
const positionalSearchRe = /\.\s*search\s*\(\s*(?:'[^']*'|"[^"]*")\s*,\s*([^,{]+)/;
|
|
25860
|
+
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
25861
|
+
const line = lines[i2];
|
|
25862
|
+
const pm = line.match(positionalSearchRe);
|
|
25863
|
+
if (!pm) continue;
|
|
25864
|
+
const expr = pm[1].trim();
|
|
25865
|
+
if (expr.startsWith("{")) continue;
|
|
25866
|
+
let tainted = false;
|
|
25867
|
+
for (const v of taintedVars) {
|
|
25868
|
+
if (new RegExp(`\\b${v}\\b`).test(expr)) {
|
|
25869
|
+
tainted = true;
|
|
25870
|
+
break;
|
|
25871
|
+
}
|
|
25872
|
+
}
|
|
25873
|
+
if (!tainted) continue;
|
|
25874
|
+
const lineNo = i2 + 1;
|
|
25875
|
+
if (seen.has(lineNo)) continue;
|
|
25876
|
+
seen.add(lineNo);
|
|
25877
|
+
findings.push({
|
|
25878
|
+
id: `ldap_injection-${file}-${lineNo}-js-ldap-positional-filter`,
|
|
25879
|
+
pass: "language-sources",
|
|
25880
|
+
category: "security",
|
|
25881
|
+
rule_id: "ldap_injection",
|
|
25882
|
+
cwe: "CWE-90",
|
|
25883
|
+
severity: "critical",
|
|
25884
|
+
level: "error",
|
|
25885
|
+
message: "LDAP injection: ldapjs/ldapts `client.search(base, <tainted-filter>, ...)` lets the attacker break out of the filter expression. Escape user input or use a structured filter builder.",
|
|
25886
|
+
file,
|
|
25887
|
+
line: lineNo,
|
|
25888
|
+
snippet: line.trim()
|
|
25889
|
+
});
|
|
25890
|
+
}
|
|
25891
|
+
return findings;
|
|
25892
|
+
}
|
|
25893
|
+
function findJsZipHeaderFailopenFindings(code, file) {
|
|
25894
|
+
const findings = [];
|
|
25895
|
+
if (typeof code !== "string" || code.length === 0) return findings;
|
|
25896
|
+
const lines = code.split("\n");
|
|
25897
|
+
const hasPkLocalSig = lines.some((l) => {
|
|
25898
|
+
const t = l.trim();
|
|
25899
|
+
if (t.startsWith("//")) return false;
|
|
25900
|
+
return /0x50/.test(t) && /0x4[bB]/.test(t) && /0x03/.test(t) && /0x04/.test(t);
|
|
25901
|
+
});
|
|
25902
|
+
if (!hasPkLocalSig) return findings;
|
|
25903
|
+
const seen = /* @__PURE__ */ new Set();
|
|
25904
|
+
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
25905
|
+
const trimmed = lines[i2].trim();
|
|
25906
|
+
if (trimmed.startsWith("//")) continue;
|
|
25907
|
+
const failBranch = trimmed.match(/^if\s*\(\s*!(\w+)\s*\)\s*(.+)$/);
|
|
25908
|
+
if (!failBranch) continue;
|
|
25909
|
+
const tail = failBranch[2];
|
|
25910
|
+
if (/\bthrow\b/.test(tail)) continue;
|
|
25911
|
+
if (!/\breturn\b/.test(tail)) continue;
|
|
25912
|
+
if (!/\.(subarray|slice)\s*\(/.test(tail) && !/Buffer\.from\s*\(/.test(tail)) {
|
|
25913
|
+
continue;
|
|
25914
|
+
}
|
|
25915
|
+
const lineNo = i2 + 1;
|
|
25916
|
+
if (seen.has(lineNo)) continue;
|
|
25917
|
+
seen.add(lineNo);
|
|
25918
|
+
findings.push({
|
|
25919
|
+
id: `input_validation-${file}-${lineNo}-js-zip-header-failopen`,
|
|
25920
|
+
pass: "language-sources",
|
|
25921
|
+
category: "security",
|
|
25922
|
+
rule_id: "input_validation",
|
|
25923
|
+
cwe: "CWE-20",
|
|
25924
|
+
severity: "high",
|
|
25925
|
+
level: "error",
|
|
25926
|
+
message: "Input validation fail-open: an invalid ZIP local header check still returns a slice of the caller-controlled buffer. On parse failure, throw or return empty \u2014 do not pass through untrusted bytes.",
|
|
25927
|
+
file,
|
|
25928
|
+
line: lineNo,
|
|
25929
|
+
snippet: trimmed.substring(0, 120)
|
|
25930
|
+
});
|
|
25931
|
+
}
|
|
24450
25932
|
return findings;
|
|
24451
25933
|
}
|
|
24452
25934
|
function findGoLdapInjectionFindings(code, file) {
|
|
@@ -24949,6 +26431,40 @@ function findGoGobDeserializationFindings(code, file) {
|
|
|
24949
26431
|
}
|
|
24950
26432
|
return findings;
|
|
24951
26433
|
}
|
|
26434
|
+
function maskJsLiteralsAndComments(code) {
|
|
26435
|
+
const out2 = code.split("");
|
|
26436
|
+
let i2 = 0;
|
|
26437
|
+
const blank = (from, to) => {
|
|
26438
|
+
for (let k = from; k < to; k++) if (out2[k] !== "\n") out2[k] = " ";
|
|
26439
|
+
};
|
|
26440
|
+
while (i2 < code.length) {
|
|
26441
|
+
const c = code[i2];
|
|
26442
|
+
const n = code[i2 + 1];
|
|
26443
|
+
if (c === "/" && n === "/") {
|
|
26444
|
+
let j = i2;
|
|
26445
|
+
while (j < code.length && code[j] !== "\n") j++;
|
|
26446
|
+
blank(i2, j);
|
|
26447
|
+
i2 = j;
|
|
26448
|
+
} else if (c === "/" && n === "*") {
|
|
26449
|
+
const end = code.indexOf("*/", i2 + 2);
|
|
26450
|
+
const j = end < 0 ? code.length : end + 2;
|
|
26451
|
+
blank(i2, j);
|
|
26452
|
+
i2 = j;
|
|
26453
|
+
} else if (c === '"' || c === "'" || c === "`") {
|
|
26454
|
+
let j = i2 + 1;
|
|
26455
|
+
while (j < code.length && code[j] !== c) {
|
|
26456
|
+
if (code[j] === "\\") j++;
|
|
26457
|
+
else if (c !== "`" && code[j] === "\n") break;
|
|
26458
|
+
j++;
|
|
26459
|
+
}
|
|
26460
|
+
blank(i2 + 1, Math.min(j, code.length));
|
|
26461
|
+
i2 = j + 1;
|
|
26462
|
+
} else {
|
|
26463
|
+
i2++;
|
|
26464
|
+
}
|
|
26465
|
+
}
|
|
26466
|
+
return out2.join("");
|
|
26467
|
+
}
|
|
24952
26468
|
function findJsJsonParseBodyFindings(code, file) {
|
|
24953
26469
|
const findings = [];
|
|
24954
26470
|
if (typeof code !== "string" || code.length === 0) return findings;
|
|
@@ -24982,12 +26498,13 @@ function findJsJsonParseBodyFindings(code, file) {
|
|
|
24982
26498
|
}
|
|
24983
26499
|
const callRe = /\bJSON\s*\.\s*parse\s*\(\s*([^)]+?)\s*\)/g;
|
|
24984
26500
|
const seen = /* @__PURE__ */ new Set();
|
|
26501
|
+
const codeLines = maskJsLiteralsAndComments(code).split("\n");
|
|
24985
26502
|
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
24986
26503
|
const line = lines[i2];
|
|
24987
|
-
|
|
26504
|
+
const codeLine = codeLines[i2] ?? "";
|
|
24988
26505
|
callRe.lastIndex = 0;
|
|
24989
26506
|
let m;
|
|
24990
|
-
while ((m = callRe.exec(
|
|
26507
|
+
while ((m = callRe.exec(codeLine)) !== null) {
|
|
24991
26508
|
const arg = m[1];
|
|
24992
26509
|
let tainted = reqBodyRe.test(arg);
|
|
24993
26510
|
if (!tainted) {
|
|
@@ -25407,198 +26924,6 @@ function findJavaSpringReturnXSSSinks(code, types, sources, xssSanitizerLines) {
|
|
|
25407
26924
|
return out2;
|
|
25408
26925
|
}
|
|
25409
26926
|
|
|
25410
|
-
// src/analysis/js-crlf-strip.ts
|
|
25411
|
-
var MAX_PATTERN_LENGTH = 200;
|
|
25412
|
-
var NEUTRAL_AFTER_REPLACE = /* @__PURE__ */ new Set([
|
|
25413
|
-
"replace",
|
|
25414
|
-
"replaceAll",
|
|
25415
|
-
"trim",
|
|
25416
|
-
"trimStart",
|
|
25417
|
-
"trimEnd",
|
|
25418
|
-
"toString",
|
|
25419
|
-
"toLowerCase",
|
|
25420
|
-
"toUpperCase",
|
|
25421
|
-
"slice",
|
|
25422
|
-
"substring"
|
|
25423
|
-
]);
|
|
25424
|
-
var PROBE = "a\r\rb\n\nc\r\nd\n\re x ";
|
|
25425
|
-
function readQuoted(s, i2) {
|
|
25426
|
-
const q = s[i2];
|
|
25427
|
-
if (q !== '"' && q !== "'" && q !== "`") return null;
|
|
25428
|
-
let out2 = "";
|
|
25429
|
-
for (let j = i2 + 1; j < s.length; j++) {
|
|
25430
|
-
const c = s[j];
|
|
25431
|
-
if (c === q) return { value: out2, end: j + 1 };
|
|
25432
|
-
if (q === "`" && c === "$" && s[j + 1] === "{") return null;
|
|
25433
|
-
if (c === "\\") {
|
|
25434
|
-
const n = s[j + 1];
|
|
25435
|
-
if (n === void 0) return null;
|
|
25436
|
-
switch (n) {
|
|
25437
|
-
case "n":
|
|
25438
|
-
out2 += "\n";
|
|
25439
|
-
j++;
|
|
25440
|
-
continue;
|
|
25441
|
-
case "r":
|
|
25442
|
-
out2 += "\r";
|
|
25443
|
-
j++;
|
|
25444
|
-
continue;
|
|
25445
|
-
case "t":
|
|
25446
|
-
out2 += " ";
|
|
25447
|
-
j++;
|
|
25448
|
-
continue;
|
|
25449
|
-
case "0":
|
|
25450
|
-
out2 += "\0";
|
|
25451
|
-
j++;
|
|
25452
|
-
continue;
|
|
25453
|
-
case "x": {
|
|
25454
|
-
const h = /^[0-9a-fA-F]{2}/.exec(s.slice(j + 2, j + 4));
|
|
25455
|
-
if (!h) return null;
|
|
25456
|
-
out2 += String.fromCharCode(parseInt(h[0], 16));
|
|
25457
|
-
j += 3;
|
|
25458
|
-
continue;
|
|
25459
|
-
}
|
|
25460
|
-
case "u": {
|
|
25461
|
-
const h = /^[0-9a-fA-F]{4}/.exec(s.slice(j + 2, j + 6));
|
|
25462
|
-
if (!h) return null;
|
|
25463
|
-
out2 += String.fromCharCode(parseInt(h[0], 16));
|
|
25464
|
-
j += 5;
|
|
25465
|
-
continue;
|
|
25466
|
-
}
|
|
25467
|
-
default:
|
|
25468
|
-
out2 += n;
|
|
25469
|
-
j++;
|
|
25470
|
-
continue;
|
|
25471
|
-
}
|
|
25472
|
-
}
|
|
25473
|
-
if (c === "\n" && q !== "`") return null;
|
|
25474
|
-
out2 += c;
|
|
25475
|
-
}
|
|
25476
|
-
return null;
|
|
25477
|
-
}
|
|
25478
|
-
function readRegexLiteral(s, i2) {
|
|
25479
|
-
if (s[i2] !== "/") return null;
|
|
25480
|
-
let inClass = false;
|
|
25481
|
-
for (let j = i2 + 1; j < s.length; j++) {
|
|
25482
|
-
const c = s[j];
|
|
25483
|
-
if (c === "\n") return null;
|
|
25484
|
-
if (c === "\\") {
|
|
25485
|
-
j++;
|
|
25486
|
-
continue;
|
|
25487
|
-
}
|
|
25488
|
-
if (inClass) {
|
|
25489
|
-
if (c === "]") inClass = false;
|
|
25490
|
-
continue;
|
|
25491
|
-
}
|
|
25492
|
-
if (c === "[") {
|
|
25493
|
-
inClass = true;
|
|
25494
|
-
continue;
|
|
25495
|
-
}
|
|
25496
|
-
if (c === "/") {
|
|
25497
|
-
const flags2 = /^[dgimsuvy]*/.exec(s.slice(j + 1))[0];
|
|
25498
|
-
return { source: s.slice(i2 + 1, j), flags: flags2, end: j + 1 + flags2.length };
|
|
25499
|
-
}
|
|
25500
|
-
}
|
|
25501
|
-
return null;
|
|
25502
|
-
}
|
|
25503
|
-
var skipWs = (s, i2) => {
|
|
25504
|
-
while (i2 < s.length && /\s/.test(s[i2])) i2++;
|
|
25505
|
-
return i2;
|
|
25506
|
-
};
|
|
25507
|
-
function parseSteps(chainText) {
|
|
25508
|
-
const steps = [];
|
|
25509
|
-
const callRe = /\.\s*(replaceAll|replace)\s*\(/g;
|
|
25510
|
-
let m;
|
|
25511
|
-
while ((m = callRe.exec(chainText)) !== null) {
|
|
25512
|
-
const kind = m[1];
|
|
25513
|
-
let i2 = skipWs(chainText, m.index + m[0].length);
|
|
25514
|
-
let pattern;
|
|
25515
|
-
const rx = readRegexLiteral(chainText, i2);
|
|
25516
|
-
if (rx) {
|
|
25517
|
-
if (rx.source.length > MAX_PATTERN_LENGTH) return null;
|
|
25518
|
-
try {
|
|
25519
|
-
pattern = new RegExp(rx.source, rx.flags);
|
|
25520
|
-
} catch {
|
|
25521
|
-
return null;
|
|
25522
|
-
}
|
|
25523
|
-
i2 = rx.end;
|
|
25524
|
-
} else {
|
|
25525
|
-
const str = readQuoted(chainText, i2);
|
|
25526
|
-
if (!str) return null;
|
|
25527
|
-
pattern = str.value;
|
|
25528
|
-
i2 = str.end;
|
|
25529
|
-
}
|
|
25530
|
-
i2 = skipWs(chainText, i2);
|
|
25531
|
-
if (chainText[i2] !== ",") return null;
|
|
25532
|
-
i2 = skipWs(chainText, i2 + 1);
|
|
25533
|
-
const rep = readQuoted(chainText, i2);
|
|
25534
|
-
if (!rep) return null;
|
|
25535
|
-
i2 = skipWs(chainText, rep.end);
|
|
25536
|
-
if (chainText[i2] !== ")") return null;
|
|
25537
|
-
steps.push({ kind, pattern, replacement: rep.value });
|
|
25538
|
-
callRe.lastIndex = i2 + 1;
|
|
25539
|
-
}
|
|
25540
|
-
return steps;
|
|
25541
|
-
}
|
|
25542
|
-
function run2(steps, input) {
|
|
25543
|
-
let s = input;
|
|
25544
|
-
try {
|
|
25545
|
-
for (const st of steps) {
|
|
25546
|
-
if (st.kind === "replaceAll" && st.pattern instanceof RegExp && !st.pattern.global) return null;
|
|
25547
|
-
s = st.kind === "replaceAll" ? s.replaceAll(st.pattern, st.replacement) : s.replace(st.pattern, st.replacement);
|
|
25548
|
-
}
|
|
25549
|
-
} catch {
|
|
25550
|
-
return null;
|
|
25551
|
-
}
|
|
25552
|
-
return s;
|
|
25553
|
-
}
|
|
25554
|
-
function jsReplaceChainVerdict(chainText) {
|
|
25555
|
-
const steps = parseSteps(chainText);
|
|
25556
|
-
if (!steps || steps.length === 0) return "undecided";
|
|
25557
|
-
const first = chainText.search(/\.\s*(?:replaceAll|replace)\s*\(/);
|
|
25558
|
-
for (const seg of chainText.slice(first + 1).matchAll(/\.\s*([A-Za-z_$][\w$]*)\s*\(/g)) {
|
|
25559
|
-
if (!NEUTRAL_AFTER_REPLACE.has(seg[1])) return "undecided";
|
|
25560
|
-
}
|
|
25561
|
-
const out2 = run2(steps, PROBE);
|
|
25562
|
-
if (out2 === null) return "undecided";
|
|
25563
|
-
return out2.includes("\r") || out2.includes("\n") ? "leaves-crlf" : "strips-crlf";
|
|
25564
|
-
}
|
|
25565
|
-
function jsChainTextAt(sourceLines, line, column) {
|
|
25566
|
-
const text = [(sourceLines[line - 1] ?? "").slice(column), ...sourceLines.slice(line, line + 7)].join("\n");
|
|
25567
|
-
let depth = 0;
|
|
25568
|
-
let prevSig = "";
|
|
25569
|
-
let i2 = 0;
|
|
25570
|
-
for (; i2 < text.length; i2++) {
|
|
25571
|
-
const c = text[i2];
|
|
25572
|
-
if (c === '"' || c === "'" || c === "`") {
|
|
25573
|
-
for (i2++; i2 < text.length && text[i2] !== c; i2++) if (text[i2] === "\\") i2++;
|
|
25574
|
-
prevSig = c;
|
|
25575
|
-
continue;
|
|
25576
|
-
}
|
|
25577
|
-
if (c === "/" && depth > 0 && (prevSig === "(" || prevSig === ",")) {
|
|
25578
|
-
const rx = readRegexLiteral(text, i2);
|
|
25579
|
-
if (rx) {
|
|
25580
|
-
i2 = rx.end - 1;
|
|
25581
|
-
prevSig = "/";
|
|
25582
|
-
continue;
|
|
25583
|
-
}
|
|
25584
|
-
}
|
|
25585
|
-
if (c === "(" || c === "[" || c === "{") {
|
|
25586
|
-
depth++;
|
|
25587
|
-
prevSig = c;
|
|
25588
|
-
continue;
|
|
25589
|
-
}
|
|
25590
|
-
if (c === ")" || c === "]" || c === "}") {
|
|
25591
|
-
if (depth === 0) break;
|
|
25592
|
-
depth--;
|
|
25593
|
-
prevSig = c;
|
|
25594
|
-
continue;
|
|
25595
|
-
}
|
|
25596
|
-
if (depth === 0 && !/[\w$.\s]/.test(c)) break;
|
|
25597
|
-
if (!/\s/.test(c)) prevSig = c;
|
|
25598
|
-
}
|
|
25599
|
-
return text.slice(0, i2);
|
|
25600
|
-
}
|
|
25601
|
-
|
|
25602
26927
|
// src/analysis/constant-propagation/patterns.ts
|
|
25603
26928
|
var TAINT_PATTERNS = [
|
|
25604
26929
|
// HTTP Sources (Servlet API)
|
|
@@ -25996,13 +27321,35 @@ function dropCSharpObjectCarriedWaypoints(sinks, calls) {
|
|
|
25996
27321
|
function analyzeTaint(calls, types, config = getDefaultConfig(), typeHierarchy, language, code, imports) {
|
|
25997
27322
|
const sourceLines = code !== void 0 ? code.split("\n") : void 0;
|
|
25998
27323
|
const sources = findSources(calls, types, config.sources, sourceLines, language, imports);
|
|
25999
|
-
let sinkPatterns =
|
|
27324
|
+
let sinkPatterns = config.sinks.filter((p) => sinkPatternImportApplies(p, imports));
|
|
27325
|
+
sinkPatterns = expandPromisifyAliases(sinkPatterns, sourceLines, language);
|
|
26000
27326
|
sinkPatterns = expandIndirectEvalAliases(sinkPatterns, sourceLines, language);
|
|
26001
27327
|
const sinks = findSinks(calls, sinkPatterns, typeHierarchy, language, sourceLines, types);
|
|
26002
27328
|
const gatedSinks = language === "csharp" ? dropCSharpObjectCarriedWaypoints(sinks, calls) : sinks;
|
|
26003
27329
|
const sanitizers = findSanitizers(calls, types, config.sanitizers, sourceLines, language);
|
|
26004
27330
|
return { sources, sinks: gatedSinks, sanitizers };
|
|
26005
27331
|
}
|
|
27332
|
+
function isBoundParameterCall(call, pattern, sourceLines) {
|
|
27333
|
+
if (!pattern.requires_import || pattern.type !== "sql_injection") return false;
|
|
27334
|
+
const first = call.arguments[0]?.expression?.trim() ?? "";
|
|
27335
|
+
if (first.startsWith("[") || first.startsWith("{")) return true;
|
|
27336
|
+
const receiver = call.receiver;
|
|
27337
|
+
if (!receiver || !sourceLines || !/^[A-Za-z_$][\w$]*$/.test(receiver)) return false;
|
|
27338
|
+
const assignedFromPrepare = new RegExp(
|
|
27339
|
+
`\\b${receiver.replace(/\$/g, "\\$")}\\s*(?::[^=]+)?=\\s*(?:await\\s+)?[\\w$.]+\\.prepare\\s*\\(`
|
|
27340
|
+
);
|
|
27341
|
+
return sourceLines.some((line) => assignedFromPrepare.test(line));
|
|
27342
|
+
}
|
|
27343
|
+
function sinkPatternImportApplies(pattern, imports) {
|
|
27344
|
+
const required = pattern.requires_import;
|
|
27345
|
+
if (!required || required.length === 0) return true;
|
|
27346
|
+
if (!imports || imports.length === 0) return false;
|
|
27347
|
+
return imports.some((imp) => {
|
|
27348
|
+
const from = imp.from_package;
|
|
27349
|
+
if (!from) return false;
|
|
27350
|
+
return required.some((m) => from === m || from.startsWith(m + "/"));
|
|
27351
|
+
});
|
|
27352
|
+
}
|
|
26006
27353
|
function sinkPatternAppliesTo(pattern, language) {
|
|
26007
27354
|
if (language === void 0) return true;
|
|
26008
27355
|
if (pattern.languages && pattern.languages.length > 0 && !pattern.languages.includes(language)) {
|
|
@@ -26404,6 +27751,9 @@ function isInterproceduralTaintableType(typeName, language) {
|
|
|
26404
27751
|
"IEnumerable",
|
|
26405
27752
|
"ICollection",
|
|
26406
27753
|
"IList",
|
|
27754
|
+
"List",
|
|
27755
|
+
"Hashtable",
|
|
27756
|
+
"ArrayList",
|
|
26407
27757
|
"Dictionary",
|
|
26408
27758
|
"IDictionary",
|
|
26409
27759
|
"IReadOnlyList",
|
|
@@ -26417,6 +27767,46 @@ function isInterproceduralTaintableType(typeName, language) {
|
|
|
26417
27767
|
const leaf = baseType.split(".").pop() ?? baseType;
|
|
26418
27768
|
if (csharpTaintable.includes(baseType) || csharpTaintable.includes(leaf)) return true;
|
|
26419
27769
|
}
|
|
27770
|
+
if (language === "rust") {
|
|
27771
|
+
const trimmed = typeName.trim();
|
|
27772
|
+
const unref = trimmed.replace(/^&mut\s+/, "").replace(/^&\s*/, "");
|
|
27773
|
+
const rustBase = unref.split("<")[0].trim();
|
|
27774
|
+
const rustScalars = [
|
|
27775
|
+
"String",
|
|
27776
|
+
"str",
|
|
27777
|
+
"bool",
|
|
27778
|
+
"char",
|
|
27779
|
+
"i8",
|
|
27780
|
+
"i16",
|
|
27781
|
+
"i32",
|
|
27782
|
+
"i64",
|
|
27783
|
+
"i128",
|
|
27784
|
+
"isize",
|
|
27785
|
+
"u8",
|
|
27786
|
+
"u16",
|
|
27787
|
+
"u32",
|
|
27788
|
+
"u64",
|
|
27789
|
+
"u128",
|
|
27790
|
+
"usize",
|
|
27791
|
+
"f32",
|
|
27792
|
+
"f64"
|
|
27793
|
+
];
|
|
27794
|
+
if (rustScalars.includes(rustBase)) return true;
|
|
27795
|
+
if (rustBase === "()" || rustBase === "!") return false;
|
|
27796
|
+
if (unref.startsWith("Vec<") || unref.startsWith("Option<") || unref.startsWith("Result<")) {
|
|
27797
|
+
return true;
|
|
27798
|
+
}
|
|
27799
|
+
if (/^[A-Z][A-Za-z0-9_]*$/.test(rustBase)) return true;
|
|
27800
|
+
}
|
|
27801
|
+
if (language === "javascript" || language === "typescript") {
|
|
27802
|
+
const trimmed = typeName.trim();
|
|
27803
|
+
const jsScalars = ["string", "number", "boolean", "bigint", "any", "unknown", "object"];
|
|
27804
|
+
if (jsScalars.includes(baseType)) return true;
|
|
27805
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[") || trimmed.startsWith("(")) return true;
|
|
27806
|
+
if (/\bRecord\s*</.test(trimmed)) return true;
|
|
27807
|
+
if (["void", "undefined", "null", "never"].includes(baseType)) return false;
|
|
27808
|
+
if (/^[A-Za-z_][\w.$]*$/.test(baseType)) return true;
|
|
27809
|
+
}
|
|
26420
27810
|
if (typeName.endsWith("[]")) {
|
|
26421
27811
|
const elementType = typeName.slice(0, -2);
|
|
26422
27812
|
if (elementType === "String" || elementType === "Object" || elementType === "byte") {
|
|
@@ -26531,6 +27921,31 @@ function isSafeGoExecCommandCall(call, pattern, language) {
|
|
|
26531
27921
|
return false;
|
|
26532
27922
|
}
|
|
26533
27923
|
}
|
|
27924
|
+
if (program === "git") {
|
|
27925
|
+
const subcommandArgPos = pattern.method === "CommandContext" ? 2 : 1;
|
|
27926
|
+
const subcommandArg = call.arguments.find((a) => a.position === subcommandArgPos);
|
|
27927
|
+
let subcommand;
|
|
27928
|
+
if (subcommandArg?.literal !== null && subcommandArg?.literal !== void 0) {
|
|
27929
|
+
subcommand = String(subcommandArg.literal);
|
|
27930
|
+
} else {
|
|
27931
|
+
const expr = (subcommandArg?.expression ?? "").trim();
|
|
27932
|
+
if (expr.startsWith('"') || expr.startsWith("`") || expr.startsWith("'")) {
|
|
27933
|
+
subcommand = expr.slice(1, -1);
|
|
27934
|
+
}
|
|
27935
|
+
}
|
|
27936
|
+
if (subcommand === "ls-remote") {
|
|
27937
|
+
const hasEndOfOptions = call.arguments.some((a) => {
|
|
27938
|
+
if (a.literal !== null && a.literal !== void 0) {
|
|
27939
|
+
return String(a.literal) === "--";
|
|
27940
|
+
}
|
|
27941
|
+
const expr = (a.expression ?? "").trim();
|
|
27942
|
+
return expr === '"--"' || expr === "'--'" || expr === "`--`";
|
|
27943
|
+
});
|
|
27944
|
+
if (!hasEndOfOptions) {
|
|
27945
|
+
return false;
|
|
27946
|
+
}
|
|
27947
|
+
}
|
|
27948
|
+
}
|
|
26534
27949
|
return true;
|
|
26535
27950
|
}
|
|
26536
27951
|
function isSafeJSChildProcessCall(call, pattern, language) {
|
|
@@ -26934,6 +28349,29 @@ function isRegexReceiver(receiver, sourceLines) {
|
|
|
26934
28349
|
const decl = new RegExp(`(?:const|let|var)\\s+${escapeRe(r)}\\s*=\\s*(?:/|(?:new\\s+)?RegExp\\s*\\()`);
|
|
26935
28350
|
return sourceLines.some((l) => decl.test(l));
|
|
26936
28351
|
}
|
|
28352
|
+
function isNavigationReceiverName(receiver) {
|
|
28353
|
+
const r = (receiver ?? "").trim();
|
|
28354
|
+
if (!r) return false;
|
|
28355
|
+
const last = r.slice(r.lastIndexOf(".") + 1).replace(/\(\)$/, "");
|
|
28356
|
+
return /router|history|navigat|^nav$/i.test(last);
|
|
28357
|
+
}
|
|
28358
|
+
function isInMemoryMapGetReceiver(receiver, sourceLines) {
|
|
28359
|
+
const raw = (receiver ?? "").trim();
|
|
28360
|
+
if (!raw || !sourceLines) return false;
|
|
28361
|
+
if (/\.properties$/i.test(raw)) return true;
|
|
28362
|
+
const lastDot = raw.lastIndexOf(".");
|
|
28363
|
+
const r = (lastDot >= 0 ? raw.slice(lastDot + 1) : raw).trim();
|
|
28364
|
+
if (/^(properties|memoryCache)$/i.test(r)) return true;
|
|
28365
|
+
if (!/^[A-Za-z_$][\w$]*$/.test(r)) return false;
|
|
28366
|
+
const n = escapeRe(r);
|
|
28367
|
+
const mapCtor = new RegExp(
|
|
28368
|
+
`\\b${n}\\s*(?::[^=;]*)?=\\s*new\\s+(?:Weak)?Map\\s*(?:<.*>)?\\s*\\(`
|
|
28369
|
+
);
|
|
28370
|
+
const mapType = new RegExp(
|
|
28371
|
+
`\\b${n}\\s*\\??\\s*:\\s*(?:Readonly)?(?:Weak)?Map\\s*<`
|
|
28372
|
+
);
|
|
28373
|
+
return sourceLines.some((l) => mapCtor.test(l) || mapType.test(l));
|
|
28374
|
+
}
|
|
26937
28375
|
function isDeclaredFunctionReference(arg, sourceLines) {
|
|
26938
28376
|
if (arg.literal !== null && arg.literal !== void 0) return false;
|
|
26939
28377
|
if (!sourceLines) return false;
|
|
@@ -27138,6 +28576,12 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types)
|
|
|
27138
28576
|
if (isParameterizedQueryCall(call, pattern)) {
|
|
27139
28577
|
continue;
|
|
27140
28578
|
}
|
|
28579
|
+
if ((language === "javascript" || language === "typescript") && (pattern.type === "sql_injection" || pattern.type === "nosql_injection") && call.arguments.length === 0 && pattern.arg_positions.every((p) => p >= 0)) {
|
|
28580
|
+
continue;
|
|
28581
|
+
}
|
|
28582
|
+
if (isBoundParameterCall(call, pattern, sourceLines)) {
|
|
28583
|
+
continue;
|
|
28584
|
+
}
|
|
27141
28585
|
if (isSafePythonSubprocessCall(call, pattern, language)) {
|
|
27142
28586
|
continue;
|
|
27143
28587
|
}
|
|
@@ -27195,9 +28639,15 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types)
|
|
|
27195
28639
|
if (isArgumentlessJsResponseWriter(call, pattern, language)) {
|
|
27196
28640
|
continue;
|
|
27197
28641
|
}
|
|
28642
|
+
if (pattern.type === "open_redirect" && (language === "javascript" || language === "typescript") && (call.method_name === "push" || call.method_name === "replace") && (pattern.class ?? "").toLowerCase() === "router" && !call.receiver_type && !call.receiver_type_fqn && !isNavigationReceiverName(call.receiver)) {
|
|
28643
|
+
continue;
|
|
28644
|
+
}
|
|
27198
28645
|
if ((pattern.type === "command_injection" || pattern.type === "sql_injection") && call.method_name === "exec" && (language === "javascript" || language === "typescript") && isRegexReceiver(call.receiver, sourceLines)) {
|
|
27199
28646
|
continue;
|
|
27200
28647
|
}
|
|
28648
|
+
if (pattern.type === "sql_injection" && call.method_name === "get" && pattern.class === "Connection" && (language === "javascript" || language === "typescript") && isInMemoryMapGetReceiver(call.receiver, sourceLines)) {
|
|
28649
|
+
continue;
|
|
28650
|
+
}
|
|
27201
28651
|
if (pattern.type === "code_injection" && language === "python" && call.method_name === "compile" && call.receiver && call.receiver !== "builtins") {
|
|
27202
28652
|
continue;
|
|
27203
28653
|
}
|
|
@@ -27214,7 +28664,7 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types)
|
|
|
27214
28664
|
continue;
|
|
27215
28665
|
}
|
|
27216
28666
|
const location = formatCallLocation(call);
|
|
27217
|
-
const key = `${location}:${call.location.line}:${pattern.cwe}`;
|
|
28667
|
+
const key = `${location}:${call.location.line}:${call.location.column}:${pattern.cwe}`;
|
|
27218
28668
|
const confidence = calculateSinkConfidence(call, pattern);
|
|
27219
28669
|
const existing = sinkMap.get(key);
|
|
27220
28670
|
if (!existing || confidence > existing.confidence) {
|
|
@@ -27225,6 +28675,7 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types)
|
|
|
27225
28675
|
cwe: pattern.cwe,
|
|
27226
28676
|
location,
|
|
27227
28677
|
line: call.location.line,
|
|
28678
|
+
callColumn: call.location.column,
|
|
27228
28679
|
confidence,
|
|
27229
28680
|
method: call.method_name,
|
|
27230
28681
|
argPositions: pattern.arg_positions,
|
|
@@ -27262,6 +28713,15 @@ function matchesSourcePattern(call, pattern, language, types, imports) {
|
|
|
27262
28713
|
return false;
|
|
27263
28714
|
}
|
|
27264
28715
|
}
|
|
28716
|
+
if (!pattern.class && pattern.return_tainted && pattern.method && (language === "javascript" || language === "typescript")) {
|
|
28717
|
+
const expressRequestAccessors = /* @__PURE__ */ new Set(["query", "body", "params", "headers", "cookies"]);
|
|
28718
|
+
if (expressRequestAccessors.has(pattern.method)) {
|
|
28719
|
+
const recv = call.receiver?.toLowerCase() ?? "";
|
|
28720
|
+
if (recv !== "req" && recv !== "request" && recv !== "ctx") {
|
|
28721
|
+
return false;
|
|
28722
|
+
}
|
|
28723
|
+
}
|
|
28724
|
+
}
|
|
27265
28725
|
return pattern.return_tainted === true;
|
|
27266
28726
|
}
|
|
27267
28727
|
return false;
|
|
@@ -27355,6 +28815,7 @@ var SAFE_RECEIVERS_BY_METHOD = {
|
|
|
27355
28815
|
"database",
|
|
27356
28816
|
"sqlite",
|
|
27357
28817
|
"conn",
|
|
28818
|
+
"con",
|
|
27358
28819
|
"connection",
|
|
27359
28820
|
"client",
|
|
27360
28821
|
"pool",
|
|
@@ -27631,7 +29092,7 @@ function matchesSinkPattern(call, pattern, typeHierarchy, language) {
|
|
|
27631
29092
|
}
|
|
27632
29093
|
}
|
|
27633
29094
|
}
|
|
27634
|
-
if (!pattern.class && call.receiver) {
|
|
29095
|
+
if (!pattern.class && call.receiver && !pattern.requires_import) {
|
|
27635
29096
|
if (isKnownSafeReceiverForMethod(call.receiver, call.method_name, pattern.type)) {
|
|
27636
29097
|
return false;
|
|
27637
29098
|
}
|
|
@@ -28364,6 +29825,8 @@ function javaReplaceChainCredit(chainText) {
|
|
|
28364
29825
|
let lf = false;
|
|
28365
29826
|
let lt = false;
|
|
28366
29827
|
let gt = false;
|
|
29828
|
+
let openBrace = false;
|
|
29829
|
+
let closeBrace = false;
|
|
28367
29830
|
for (const m of chainText.matchAll(JAVA_REPLACE_ARGS_RE)) {
|
|
28368
29831
|
const kind = m[1];
|
|
28369
29832
|
const rawFrom = m[2] ?? m[3] ?? "";
|
|
@@ -28374,10 +29837,13 @@ function javaReplaceChainCredit(chainText) {
|
|
|
28374
29837
|
if (cov.lf) lf = true;
|
|
28375
29838
|
if (from === "<" && to.startsWith("&")) lt = true;
|
|
28376
29839
|
if (from === ">" && to.startsWith("&")) gt = true;
|
|
29840
|
+
if (from === "{") openBrace = true;
|
|
29841
|
+
if (from === "}") closeBrace = true;
|
|
28377
29842
|
}
|
|
28378
29843
|
const out2 = [];
|
|
28379
29844
|
if (cr && lf) out2.push("log_injection");
|
|
28380
29845
|
if (lt && gt) out2.push("xss");
|
|
29846
|
+
if (openBrace && closeBrace) out2.push("code_injection");
|
|
28381
29847
|
return out2;
|
|
28382
29848
|
}
|
|
28383
29849
|
function matchesSanitizerPattern(call, pattern) {
|
|
@@ -30089,7 +31555,7 @@ var AnalysisPipeline = class {
|
|
|
30089
31555
|
this.passes.push(pass);
|
|
30090
31556
|
return this;
|
|
30091
31557
|
}
|
|
30092
|
-
run(graph, code, language, config, resources) {
|
|
31558
|
+
run(graph, code, language, config, resources, limit) {
|
|
30093
31559
|
const results = /* @__PURE__ */ new Map();
|
|
30094
31560
|
const findings = [];
|
|
30095
31561
|
const context = {
|
|
@@ -30118,6 +31584,7 @@ var AnalysisPipeline = class {
|
|
|
30118
31584
|
};
|
|
30119
31585
|
const timingEnabled = globalThis.__circleIrPassTiming === true;
|
|
30120
31586
|
for (const pass of this.passes) {
|
|
31587
|
+
if (limit && Date.now() > limit.deadline) throw limit.onExpired(pass.name);
|
|
30121
31588
|
if (timingEnabled) {
|
|
30122
31589
|
console.error(`[pass-start] ${pass.name} file=${graph.ir.meta.file}`);
|
|
30123
31590
|
}
|
|
@@ -30980,10 +32447,49 @@ var JAVA_LANG_EXCEPTION_TYPES = /* @__PURE__ */ new Set([
|
|
|
30980
32447
|
"Error",
|
|
30981
32448
|
"AssertionError"
|
|
30982
32449
|
]);
|
|
32450
|
+
var JAVA_LANG_STRING_ACCUMULATOR_TYPES = /* @__PURE__ */ new Set(["StringBuilder", "StringBuffer"]);
|
|
30983
32451
|
var JAVA_FORMATTER_CLASSES = /* @__PURE__ */ new Map([
|
|
30984
32452
|
["String", "java.lang"],
|
|
30985
32453
|
["MessageFormat", "java.text"]
|
|
30986
32454
|
]);
|
|
32455
|
+
var JAVA_LOGGER_METHODS = /* @__PURE__ */ new Set([
|
|
32456
|
+
"severe",
|
|
32457
|
+
"warning",
|
|
32458
|
+
"config",
|
|
32459
|
+
"fine",
|
|
32460
|
+
"finer",
|
|
32461
|
+
"finest",
|
|
32462
|
+
"logp",
|
|
32463
|
+
"logrb",
|
|
32464
|
+
"log",
|
|
32465
|
+
"trace",
|
|
32466
|
+
"debug",
|
|
32467
|
+
"info",
|
|
32468
|
+
"warn",
|
|
32469
|
+
"error",
|
|
32470
|
+
"fatal",
|
|
32471
|
+
"Log",
|
|
32472
|
+
"LogInformation",
|
|
32473
|
+
"LogWarning",
|
|
32474
|
+
"LogError",
|
|
32475
|
+
"LogDebug",
|
|
32476
|
+
"LogTrace",
|
|
32477
|
+
"LogCritical",
|
|
32478
|
+
"Information",
|
|
32479
|
+
"Warning",
|
|
32480
|
+
"Verbose",
|
|
32481
|
+
"Fatal",
|
|
32482
|
+
"BeginScope"
|
|
32483
|
+
]);
|
|
32484
|
+
function isJavaLoggerCall(call) {
|
|
32485
|
+
return JAVA_LOGGER_METHODS.has(call.method_name);
|
|
32486
|
+
}
|
|
32487
|
+
var JAVA_OBJECTS_CLASS = /* @__PURE__ */ new Map([["Objects", "java.util"]]);
|
|
32488
|
+
var JAVA_OBJECTS_NULL_CHECK_METHODS = /* @__PURE__ */ new Set([
|
|
32489
|
+
"requireNonNull",
|
|
32490
|
+
"requireNonNullElse",
|
|
32491
|
+
"requireNonNullElseGet"
|
|
32492
|
+
]);
|
|
30987
32493
|
function isJavaStaticFormatterCall(call, declaredTypeNames, imports, calls) {
|
|
30988
32494
|
if (call.method_name !== "format" || !call.receiver) return false;
|
|
30989
32495
|
const needle = `${call.receiver}.format(`;
|
|
@@ -30993,6 +32499,10 @@ function isJavaStaticFormatterCall(call, declaredTypeNames, imports, calls) {
|
|
|
30993
32499
|
}
|
|
30994
32500
|
return isJdkClassReceiver(call.receiver, JAVA_FORMATTER_CLASSES, declaredTypeNames, imports);
|
|
30995
32501
|
}
|
|
32502
|
+
function isJavaObjectsNullCheckCall(call, declaredTypeNames, imports) {
|
|
32503
|
+
if (!call.receiver || !JAVA_OBJECTS_NULL_CHECK_METHODS.has(call.method_name)) return false;
|
|
32504
|
+
return isJdkClassReceiver(call.receiver, JAVA_OBJECTS_CLASS, declaredTypeNames, imports);
|
|
32505
|
+
}
|
|
30996
32506
|
function isJdkClassReceiver(receiver, classes, declaredTypeNames, imports) {
|
|
30997
32507
|
const dot = receiver.lastIndexOf(".");
|
|
30998
32508
|
const simple = dot < 0 ? receiver : receiver.slice(dot + 1);
|
|
@@ -31035,12 +32545,18 @@ var JAVA_CHARACTER_PREDICATES = /* @__PURE__ */ new Set([
|
|
|
31035
32545
|
"isBmpCodePoint",
|
|
31036
32546
|
"isSupplementaryCodePoint"
|
|
31037
32547
|
]);
|
|
32548
|
+
var JAVA_CHARACTER_METADATA = /* @__PURE__ */ new Set(["getName", "getType", "getNumericValue"]);
|
|
31038
32549
|
function isJavaCharacterPredicateCall(call, declaredTypeNames, imports) {
|
|
31039
|
-
if (!call.receiver
|
|
32550
|
+
if (!call.receiver) return false;
|
|
32551
|
+
if (!JAVA_CHARACTER_PREDICATES.has(call.method_name) && !JAVA_CHARACTER_METADATA.has(call.method_name)) {
|
|
32552
|
+
return false;
|
|
32553
|
+
}
|
|
31040
32554
|
return isJdkClassReceiver(call.receiver, JAVA_CHARACTER_CLASS, declaredTypeNames, imports);
|
|
31041
32555
|
}
|
|
31042
32556
|
function isJavaNonEscapeCall(call, declaredTypeNames, imports, calls) {
|
|
31043
32557
|
if (isJavaStaticFormatterCall(call, declaredTypeNames, imports, calls)) return true;
|
|
32558
|
+
if (isJavaObjectsNullCheckCall(call, declaredTypeNames, imports)) return true;
|
|
32559
|
+
if (isJavaLoggerCall(call)) return true;
|
|
31044
32560
|
if (isJavaCharacterPredicateCall(call, declaredTypeNames, imports)) return true;
|
|
31045
32561
|
if (call.receiver) return false;
|
|
31046
32562
|
const isCtor = call.is_constructor === true || (call.resolution?.target ?? "").endsWith(".<init>");
|
|
@@ -31048,11 +32564,11 @@ function isJavaNonEscapeCall(call, declaredTypeNames, imports, calls) {
|
|
|
31048
32564
|
const written = call.method_name;
|
|
31049
32565
|
const simple = written.split(".").pop() ?? written;
|
|
31050
32566
|
if (written.includes(".")) {
|
|
31051
|
-
return written.startsWith("java.lang.") && (JAVA_LANG_EXCEPTION_TYPES.has(simple) || /(Exception|Error)$/.test(simple));
|
|
32567
|
+
return written.startsWith("java.lang.") && (JAVA_LANG_EXCEPTION_TYPES.has(simple) || JAVA_LANG_STRING_ACCUMULATOR_TYPES.has(simple) || /(Exception|Error)$/.test(simple));
|
|
31052
32568
|
}
|
|
31053
32569
|
if (call.receiver_type_fqn && !call.receiver_type_fqn.startsWith("java.lang.")) return false;
|
|
31054
32570
|
if (declaredTypeNames.has(simple)) return false;
|
|
31055
|
-
return JAVA_LANG_EXCEPTION_TYPES.has(simple);
|
|
32571
|
+
return JAVA_LANG_EXCEPTION_TYPES.has(simple) || JAVA_LANG_STRING_ACCUMULATOR_TYPES.has(simple);
|
|
31056
32572
|
}
|
|
31057
32573
|
function isGoConstRegexMatchCall(call, graph, calls) {
|
|
31058
32574
|
if (call.method_name !== "MatchString" || call.arguments.length !== 1) return false;
|
|
@@ -31313,6 +32829,14 @@ function analyzeInterprocedural(graphOrTypes, callsOrSources, dfgOrSinks, source
|
|
|
31313
32829
|
sanitizerMethods.add(match[1]);
|
|
31314
32830
|
}
|
|
31315
32831
|
}
|
|
32832
|
+
const bashScriptFunctions = /* @__PURE__ */ new Set();
|
|
32833
|
+
if (graph.ir.meta.language === "bash") {
|
|
32834
|
+
for (const d of graph.ir.dfg.defs) {
|
|
32835
|
+
if (d.kind === "param" && d.expression?.startsWith("bash-fn:")) {
|
|
32836
|
+
bashScriptFunctions.add(d.expression.slice("bash-fn:".length));
|
|
32837
|
+
}
|
|
32838
|
+
}
|
|
32839
|
+
}
|
|
31316
32840
|
for (const call of calls) {
|
|
31317
32841
|
const taintedArgPositions = [];
|
|
31318
32842
|
const taintedArgVars = [];
|
|
@@ -31336,7 +32860,8 @@ function analyzeInterprocedural(graphOrTypes, callsOrSources, dfgOrSinks, source
|
|
|
31336
32860
|
}
|
|
31337
32861
|
}
|
|
31338
32862
|
}
|
|
31339
|
-
const
|
|
32863
|
+
const resolvedCalleeFqn = resolveMethodCall2(call, methodNodes, types);
|
|
32864
|
+
const targetMethod = resolvedCalleeFqn ? methodNodes.byFqn.get(resolvedCalleeFqn) : getMethodNode(methodNodes, call.method_name);
|
|
31340
32865
|
if (!targetMethod) {
|
|
31341
32866
|
if (taintedArgPositions.length > 0 && !collectionMethods.has(call.method_name) && !sanitizerMethods.has(call.method_name) && !safeUtilityMethods.has(call.method_name) && !ormQueryBuilderMethods.has(call.method_name)) {
|
|
31342
32867
|
const isBash = graph.ir.meta.language === "bash";
|
|
@@ -31370,6 +32895,9 @@ function analyzeInterprocedural(graphOrTypes, callsOrSources, dfgOrSinks, source
|
|
|
31370
32895
|
if (isBash && bashSafeBuiltins.has(call.method_name)) {
|
|
31371
32896
|
continue;
|
|
31372
32897
|
}
|
|
32898
|
+
if (isBash && bashScriptFunctions.has(call.method_name)) {
|
|
32899
|
+
continue;
|
|
32900
|
+
}
|
|
31373
32901
|
if (isBash && taintedArgPositions.length > 0) {
|
|
31374
32902
|
const terminatorPos = call.arguments.filter((a) => a.expression === "--").map((a) => a.position).reduce((min, p) => min === null || p < min ? p : min, null);
|
|
31375
32903
|
const earliestTainted = Math.min(...taintedArgPositions);
|
|
@@ -31427,6 +32955,12 @@ function analyzeInterprocedural(graphOrTypes, callsOrSources, dfgOrSinks, source
|
|
|
31427
32955
|
if (pos < targetMethod.parameters.length) {
|
|
31428
32956
|
targetMethod.parameters[pos].isTainted = true;
|
|
31429
32957
|
targetMethod.parameters[pos].sourceLine = call.location.line;
|
|
32958
|
+
const paramName = targetMethod.parameters[pos].name;
|
|
32959
|
+
for (const def of graph.ir.dfg.defs) {
|
|
32960
|
+
if (def.kind === "param" && def.variable === paramName && def.line >= targetMethod.startLine && def.line <= targetMethod.endLine) {
|
|
32961
|
+
taintedDefIds.add(def.id);
|
|
32962
|
+
}
|
|
32963
|
+
}
|
|
31430
32964
|
}
|
|
31431
32965
|
}
|
|
31432
32966
|
taintedMethods.add(targetMethod.fqn);
|
|
@@ -32027,6 +33561,11 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
32027
33561
|
taintedArrayElements = /* @__PURE__ */ new Map();
|
|
32028
33562
|
// Track current method name for scoping local variables
|
|
32029
33563
|
currentMethod = null;
|
|
33564
|
+
/** Start lines of nested `{ ... }` blocks — disambiguate same-named locals (#476 Collections10). */
|
|
33565
|
+
blockScopeStack = [];
|
|
33566
|
+
lineBlockScopes = /* @__PURE__ */ new Map();
|
|
33567
|
+
/** Skip block-scope push on the method/constructor body `{ ... }` (#476). */
|
|
33568
|
+
skipMethodBodyBlockScope = false;
|
|
32030
33569
|
// Track conditional taints: which variables were tainted under which conditions
|
|
32031
33570
|
// Maps condition expression string → set of variables tainted under that condition
|
|
32032
33571
|
conditionalTaints = /* @__PURE__ */ new Map();
|
|
@@ -32040,6 +33579,8 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
32040
33579
|
inSynchronizedBlock = false;
|
|
32041
33580
|
// Track iterator sources: iterator variable name → collection name it was created from
|
|
32042
33581
|
iteratorSources = /* @__PURE__ */ new Map();
|
|
33582
|
+
/** Vars assigned from `iter.next()` — do not retro-clear on later iterator rebind (#476). */
|
|
33583
|
+
taintedFromIteratorNext = /* @__PURE__ */ new Set();
|
|
32043
33584
|
// Track class field names (declared at class level, not local variables)
|
|
32044
33585
|
classFields = /* @__PURE__ */ new Set();
|
|
32045
33586
|
// Track tainted method parameters for inter-procedural analysis
|
|
@@ -32097,12 +33638,16 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
32097
33638
|
this.sanitizedVars.clear();
|
|
32098
33639
|
this.sanitizedVarUncovered.clear();
|
|
32099
33640
|
this.currentMethod = null;
|
|
33641
|
+
this.blockScopeStack = [];
|
|
33642
|
+
this.lineBlockScopes.clear();
|
|
33643
|
+
this.skipMethodBodyBlockScope = false;
|
|
32100
33644
|
this.conditionalTaints.clear();
|
|
32101
33645
|
this.conditionStack = [];
|
|
32102
33646
|
this.lineConditions.clear();
|
|
32103
33647
|
this.synchronizedLines.clear();
|
|
32104
33648
|
this.inSynchronizedBlock = false;
|
|
32105
33649
|
this.iteratorSources.clear();
|
|
33650
|
+
this.taintedFromIteratorNext.clear();
|
|
32106
33651
|
this.classFields.clear();
|
|
32107
33652
|
this.instanceFieldTaint.clear();
|
|
32108
33653
|
this.currentClassName = null;
|
|
@@ -32127,6 +33672,9 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
32127
33672
|
const resultSymbols = new Map(this.symbols);
|
|
32128
33673
|
for (const name2 of this.tainted) {
|
|
32129
33674
|
if (name2.includes(":")) {
|
|
33675
|
+
if (/^[^:]+:b\d+:/.test(name2)) {
|
|
33676
|
+
continue;
|
|
33677
|
+
}
|
|
32130
33678
|
const unscoped = name2.substring(name2.indexOf(":") + 1);
|
|
32131
33679
|
resultTainted.add(unscoped);
|
|
32132
33680
|
}
|
|
@@ -32141,6 +33689,9 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
32141
33689
|
for (const [name2, value] of this.symbols) {
|
|
32142
33690
|
if (this.goScopedNames.has(name2)) continue;
|
|
32143
33691
|
if (name2.includes(":")) {
|
|
33692
|
+
if (/^[^:]+:b\d+:/.test(name2)) {
|
|
33693
|
+
continue;
|
|
33694
|
+
}
|
|
32144
33695
|
const unscoped = name2.substring(name2.indexOf(":") + 1);
|
|
32145
33696
|
if (!resultSymbols.has(unscoped)) {
|
|
32146
33697
|
resultSymbols.set(unscoped, value);
|
|
@@ -32150,6 +33701,7 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
32150
33701
|
return {
|
|
32151
33702
|
symbols: resultSymbols,
|
|
32152
33703
|
tainted: resultTainted,
|
|
33704
|
+
lineBlockScopes: new Map(this.lineBlockScopes),
|
|
32153
33705
|
unreachableLines: new Set(this.unreachableLines),
|
|
32154
33706
|
taintedCollections: new Map(this.taintedCollections),
|
|
32155
33707
|
taintedArrayElements: new Map(this.taintedArrayElements),
|
|
@@ -32693,6 +34245,18 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
32693
34245
|
// ===========================================================================
|
|
32694
34246
|
// Taint Refinement
|
|
32695
34247
|
// ===========================================================================
|
|
34248
|
+
/** True when `node` is `iter.next()` (possibly under a cast). */
|
|
34249
|
+
isIteratorNextExpression(node) {
|
|
34250
|
+
const text = getNodeText2(node, this.source);
|
|
34251
|
+
if (/\.\s*next\s*\(/.test(text)) return true;
|
|
34252
|
+
let current = node;
|
|
34253
|
+
if (current.type === "cast_expression" || current.type === "parenthesized_expression") {
|
|
34254
|
+
current = current.childForFieldName("operand") ?? current.childForFieldName("expression");
|
|
34255
|
+
}
|
|
34256
|
+
if (!current || current.type !== "method_invocation") return false;
|
|
34257
|
+
const nameNode = current.childForFieldName("name");
|
|
34258
|
+
return nameNode ? getNodeText2(nameNode, this.source) === "next" : false;
|
|
34259
|
+
}
|
|
32696
34260
|
refineTaintFromConstants() {
|
|
32697
34261
|
let changed = true;
|
|
32698
34262
|
let iterations = 0;
|
|
@@ -32702,6 +34266,9 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
32702
34266
|
iterations++;
|
|
32703
34267
|
const toRemove = [];
|
|
32704
34268
|
for (const varName of this.tainted) {
|
|
34269
|
+
if (this.taintedFromIteratorNext.has(varName)) {
|
|
34270
|
+
continue;
|
|
34271
|
+
}
|
|
32705
34272
|
const symbol = this.symbols.get(varName);
|
|
32706
34273
|
if (symbol && symbol.type !== "unknown") {
|
|
32707
34274
|
toRemove.push(varName);
|
|
@@ -32750,6 +34317,9 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
32750
34317
|
if (this.unreachableLines.has(line)) {
|
|
32751
34318
|
return true;
|
|
32752
34319
|
}
|
|
34320
|
+
if (this.blockScopeStack.length > 0) {
|
|
34321
|
+
this.lineBlockScopes.set(line, this.blockScopeStack[this.blockScopeStack.length - 1]);
|
|
34322
|
+
}
|
|
32753
34323
|
if (this.conditionStack.length > 0 && !this.lineConditions.has(line)) {
|
|
32754
34324
|
this.lineConditions.set(line, this.conditionStack[this.conditionStack.length - 1]);
|
|
32755
34325
|
}
|
|
@@ -32775,12 +34345,24 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
32775
34345
|
case "assignment_statement":
|
|
32776
34346
|
this.handleGoAssignment(node);
|
|
32777
34347
|
return false;
|
|
34348
|
+
case "var_declaration":
|
|
34349
|
+
this.handleGoVarDeclaration(node);
|
|
34350
|
+
return false;
|
|
32778
34351
|
case "update_expression":
|
|
32779
34352
|
this.handleUpdateExpression(node);
|
|
32780
34353
|
return false;
|
|
32781
34354
|
case "if_statement":
|
|
32782
34355
|
this.handleIfStatement(node);
|
|
32783
34356
|
return true;
|
|
34357
|
+
case "block": {
|
|
34358
|
+
if (this.skipMethodBodyBlockScope) {
|
|
34359
|
+
this.skipMethodBodyBlockScope = false;
|
|
34360
|
+
}
|
|
34361
|
+
for (const child of node.children) {
|
|
34362
|
+
this.visit(child);
|
|
34363
|
+
}
|
|
34364
|
+
return true;
|
|
34365
|
+
}
|
|
32784
34366
|
case "switch_expression":
|
|
32785
34367
|
case "switch_statement":
|
|
32786
34368
|
this.handleSwitch(node);
|
|
@@ -32866,7 +34448,9 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
32866
34448
|
}
|
|
32867
34449
|
const body2 = node.childForFieldName("body");
|
|
32868
34450
|
if (body2) {
|
|
34451
|
+
this.skipMethodBodyBlockScope = true;
|
|
32869
34452
|
this.visit(body2);
|
|
34453
|
+
this.skipMethodBodyBlockScope = false;
|
|
32870
34454
|
}
|
|
32871
34455
|
this.currentMethod = prevMethod;
|
|
32872
34456
|
this.inConstructor = prevInConstructor;
|
|
@@ -32880,10 +34464,31 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
32880
34464
|
if (varName.includes(".") || varName.includes(":")) {
|
|
32881
34465
|
return varName;
|
|
32882
34466
|
}
|
|
34467
|
+
const blockId = this.blockScopeStack.length > 0 ? this.blockScopeStack[this.blockScopeStack.length - 1] : void 0;
|
|
32883
34468
|
if (this.currentMethod) {
|
|
32884
|
-
return `${this.currentMethod}:${varName}`;
|
|
34469
|
+
return blockId !== void 0 ? `${this.currentMethod}:b${blockId}:${varName}` : `${this.currentMethod}:${varName}`;
|
|
32885
34470
|
}
|
|
32886
|
-
return varName;
|
|
34471
|
+
return blockId !== void 0 ? `b${blockId}:${varName}` : varName;
|
|
34472
|
+
}
|
|
34473
|
+
/** Innermost block → method → unscoped, for reads that may bind to an outer declaration. */
|
|
34474
|
+
getScopedNameCandidates(varName) {
|
|
34475
|
+
if (varName.includes(".") || varName.includes(":")) {
|
|
34476
|
+
return [varName];
|
|
34477
|
+
}
|
|
34478
|
+
const out2 = [];
|
|
34479
|
+
for (let i2 = this.blockScopeStack.length - 1; i2 >= 0; i2--) {
|
|
34480
|
+
const blockId = this.blockScopeStack[i2];
|
|
34481
|
+
if (this.currentMethod) {
|
|
34482
|
+
out2.push(`${this.currentMethod}:b${blockId}:${varName}`);
|
|
34483
|
+
} else {
|
|
34484
|
+
out2.push(`b${blockId}:${varName}`);
|
|
34485
|
+
}
|
|
34486
|
+
}
|
|
34487
|
+
if (this.currentMethod) {
|
|
34488
|
+
out2.push(`${this.currentMethod}:${varName}`);
|
|
34489
|
+
}
|
|
34490
|
+
out2.push(varName);
|
|
34491
|
+
return out2;
|
|
32887
34492
|
}
|
|
32888
34493
|
/**
|
|
32889
34494
|
* Look up a variable value, checking both scoped and unscoped names.
|
|
@@ -32891,11 +34496,12 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
32891
34496
|
* either local (scoped) or global (unscoped, like class fields).
|
|
32892
34497
|
*/
|
|
32893
34498
|
lookupSymbol(varName) {
|
|
32894
|
-
if (
|
|
32895
|
-
const scopedName
|
|
32896
|
-
|
|
32897
|
-
|
|
32898
|
-
|
|
34499
|
+
if (!varName.includes(".") && !varName.includes(":")) {
|
|
34500
|
+
for (const scopedName of this.getScopedNameCandidates(varName)) {
|
|
34501
|
+
const scopedValue = this.symbols.get(scopedName);
|
|
34502
|
+
if (scopedValue) {
|
|
34503
|
+
return scopedValue;
|
|
34504
|
+
}
|
|
32899
34505
|
}
|
|
32900
34506
|
}
|
|
32901
34507
|
return this.symbols.get(varName);
|
|
@@ -32947,8 +34553,22 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
32947
34553
|
this.trackIteratorsInNode(initNode);
|
|
32948
34554
|
}
|
|
32949
34555
|
}
|
|
32950
|
-
|
|
32951
|
-
|
|
34556
|
+
if (node.type === "while_statement" || node.type === "do_statement") {
|
|
34557
|
+
const body2 = node.childForFieldName("body");
|
|
34558
|
+
for (const child of node.children) {
|
|
34559
|
+
if (child !== body2) this.visit(child);
|
|
34560
|
+
}
|
|
34561
|
+
if (body2?.type === "block") {
|
|
34562
|
+
this.blockScopeStack.push(getNodeLine(body2));
|
|
34563
|
+
this.visit(body2);
|
|
34564
|
+
this.blockScopeStack.pop();
|
|
34565
|
+
} else if (body2) {
|
|
34566
|
+
this.visit(body2);
|
|
34567
|
+
}
|
|
34568
|
+
} else {
|
|
34569
|
+
for (const child of node.children) {
|
|
34570
|
+
this.visit(child);
|
|
34571
|
+
}
|
|
32952
34572
|
}
|
|
32953
34573
|
for (const varName of loopVarNames) {
|
|
32954
34574
|
this.symbols.set(varName, createUnknown(getNodeLine(node)));
|
|
@@ -33090,6 +34710,10 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
33090
34710
|
this.sanitizedVars.delete(scopedName);
|
|
33091
34711
|
this.sanitizedVarUncovered.delete(scopedName);
|
|
33092
34712
|
this.definitionNodes.set(scopedName, valueNode);
|
|
34713
|
+
if (this.isIteratorNextExpression(valueNode)) {
|
|
34714
|
+
this.taintedFromIteratorNext.add(scopedName);
|
|
34715
|
+
this.taintedFromIteratorNext.add(varName);
|
|
34716
|
+
}
|
|
33093
34717
|
this.symbols.set(scopedName, createUnknown(line));
|
|
33094
34718
|
} else {
|
|
33095
34719
|
if (this.inConditionalBranch) {
|
|
@@ -33131,6 +34755,64 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
33131
34755
|
* helper that always returns a constant — clears the variable. Anything else stops being tracked here, which is what Go
|
|
33132
34756
|
* assignments got before this handler existed: the DFG decides.
|
|
33133
34757
|
*/
|
|
34758
|
+
/** `var x = expr` / `var x T = expr` — same clean/taint rules as `:=` (#783). */
|
|
34759
|
+
handleGoVarDeclaration(node) {
|
|
34760
|
+
for (let i2 = 0; i2 < node.childCount; i2++) {
|
|
34761
|
+
const spec = node.child(i2);
|
|
34762
|
+
if (!spec || spec.type !== "var_spec") continue;
|
|
34763
|
+
const nameNode = spec.childForFieldName("name");
|
|
34764
|
+
let valueNode = spec.childForFieldName("value");
|
|
34765
|
+
if (!nameNode || !valueNode) continue;
|
|
34766
|
+
const names = [];
|
|
34767
|
+
if (nameNode.type === "identifier") {
|
|
34768
|
+
names.push(getNodeText2(nameNode, this.source));
|
|
34769
|
+
} else if (nameNode.type === "identifier_list") {
|
|
34770
|
+
for (const c of nameNode.children) {
|
|
34771
|
+
if (c.type === "identifier") names.push(getNodeText2(c, this.source));
|
|
34772
|
+
}
|
|
34773
|
+
}
|
|
34774
|
+
if (valueNode.type === "expression_list") {
|
|
34775
|
+
valueNode = valueNode.children.find((c) => c.type !== "," && c.type !== "comment") ?? valueNode;
|
|
34776
|
+
}
|
|
34777
|
+
if (names.length === 0) continue;
|
|
34778
|
+
const line = getNodeLine(node);
|
|
34779
|
+
for (const varName of names) {
|
|
34780
|
+
if (varName === "_") continue;
|
|
34781
|
+
const scopedName = this.getScopedName(varName);
|
|
34782
|
+
this.goScopedNames.add(scopedName);
|
|
34783
|
+
if (this.isTaintedExpression(valueNode)) {
|
|
34784
|
+
this.goUnknownVars.add(scopedName);
|
|
34785
|
+
this.tainted.add(scopedName);
|
|
34786
|
+
this.sanitizedVars.delete(scopedName);
|
|
34787
|
+
this.sanitizedVarUncovered.delete(scopedName);
|
|
34788
|
+
this.definitionNodes.set(scopedName, valueNode);
|
|
34789
|
+
this.symbols.set(scopedName, createUnknown(line));
|
|
34790
|
+
continue;
|
|
34791
|
+
}
|
|
34792
|
+
if (!this.isGoKnownCleanRhs(valueNode) || this.goUnknownVars.has(scopedName)) {
|
|
34793
|
+
this.goUnknownVars.add(scopedName);
|
|
34794
|
+
this.symbols.delete(scopedName);
|
|
34795
|
+
this.sanitizedVars.delete(scopedName);
|
|
34796
|
+
this.sanitizedVarUncovered.delete(scopedName);
|
|
34797
|
+
if (!this.inConditionalBranch) {
|
|
34798
|
+
this.tainted.delete(scopedName);
|
|
34799
|
+
this.definitionNodes.delete(scopedName);
|
|
34800
|
+
}
|
|
34801
|
+
continue;
|
|
34802
|
+
}
|
|
34803
|
+
if (this.inConditionalBranch) {
|
|
34804
|
+
this.goUnknownVars.add(scopedName);
|
|
34805
|
+
this.symbols.delete(scopedName);
|
|
34806
|
+
continue;
|
|
34807
|
+
}
|
|
34808
|
+
this.tainted.delete(scopedName);
|
|
34809
|
+
this.definitionNodes.delete(scopedName);
|
|
34810
|
+
this.symbols.set(scopedName, this.evaluateExpression(valueNode));
|
|
34811
|
+
this.sanitizedVars.delete(scopedName);
|
|
34812
|
+
this.sanitizedVarUncovered.delete(scopedName);
|
|
34813
|
+
}
|
|
34814
|
+
}
|
|
34815
|
+
}
|
|
33134
34816
|
handleGoAssignment(node) {
|
|
33135
34817
|
const left = node.childForFieldName("left");
|
|
33136
34818
|
const right = node.childForFieldName("right");
|
|
@@ -33217,6 +34899,12 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
33217
34899
|
this.sanitizedVars.delete(scopedName);
|
|
33218
34900
|
this.sanitizedVarUncovered.delete(scopedName);
|
|
33219
34901
|
this.definitionNodes.set(scopedName, right);
|
|
34902
|
+
if (this.isIteratorNextExpression(right)) {
|
|
34903
|
+
this.taintedFromIteratorNext.add(scopedName);
|
|
34904
|
+
if (!varName.includes(".")) {
|
|
34905
|
+
this.taintedFromIteratorNext.add(varName);
|
|
34906
|
+
}
|
|
34907
|
+
}
|
|
33220
34908
|
this.symbols.set(scopedName, createUnknown(line));
|
|
33221
34909
|
if (this.inConstructor && varName.startsWith("this.")) {
|
|
33222
34910
|
const fieldName = varName.substring(5);
|
|
@@ -33941,6 +35629,24 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
33941
35629
|
cache.set(rootId, false);
|
|
33942
35630
|
return false;
|
|
33943
35631
|
}
|
|
35632
|
+
/** Is an argument of `call` an inline sanitizer call over tainted input (itself possibly nested)? */
|
|
35633
|
+
argIsSanitizedTaint(call) {
|
|
35634
|
+
const argsNode = call.childForFieldName("arguments");
|
|
35635
|
+
if (!argsNode) return false;
|
|
35636
|
+
for (const arg of argsNode.children) {
|
|
35637
|
+
if (arg.type !== "method_invocation") continue;
|
|
35638
|
+
const name2 = arg.childForFieldName("name");
|
|
35639
|
+
if (!name2 || !SANITIZER_METHODS.has(getNodeText2(name2, this.source))) continue;
|
|
35640
|
+
const inner = arg.childForFieldName("arguments");
|
|
35641
|
+
if (!inner) continue;
|
|
35642
|
+
for (const a of inner.children) {
|
|
35643
|
+
if (a.type === "(" || a.type === ")" || a.type === ",") continue;
|
|
35644
|
+
if (this.isTaintedExpression(a)) return true;
|
|
35645
|
+
}
|
|
35646
|
+
if (this.argIsSanitizedTaint(arg)) return true;
|
|
35647
|
+
}
|
|
35648
|
+
return false;
|
|
35649
|
+
}
|
|
33944
35650
|
isTaintedExpressionStep(node) {
|
|
33945
35651
|
const text = getNodeText2(node, this.source);
|
|
33946
35652
|
if (node.type === "method_invocation") {
|
|
@@ -33948,6 +35654,9 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
33948
35654
|
const objectNode = node.childForFieldName("object");
|
|
33949
35655
|
if (nameNode) {
|
|
33950
35656
|
const methodName = getNodeText2(nameNode, this.source);
|
|
35657
|
+
if (ANTI_SANITIZER_METHODS.has(methodName) && this.argIsSanitizedTaint(node)) {
|
|
35658
|
+
return true;
|
|
35659
|
+
}
|
|
33951
35660
|
if (SANITIZER_METHODS.has(methodName) && !this.localNonSanitizing.has(methodName)) {
|
|
33952
35661
|
return false;
|
|
33953
35662
|
}
|
|
@@ -34207,10 +35916,17 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
34207
35916
|
}
|
|
34208
35917
|
}
|
|
34209
35918
|
if (node.type === "identifier") {
|
|
34210
|
-
|
|
34211
|
-
|
|
34212
|
-
|
|
34213
|
-
if (this.
|
|
35919
|
+
let isTainted = false;
|
|
35920
|
+
let boundName = null;
|
|
35921
|
+
for (const candidate of this.getScopedNameCandidates(text)) {
|
|
35922
|
+
if (this.tainted.has(candidate)) {
|
|
35923
|
+
isTainted = true;
|
|
35924
|
+
boundName = candidate;
|
|
35925
|
+
break;
|
|
35926
|
+
}
|
|
35927
|
+
}
|
|
35928
|
+
if (isTainted && boundName) {
|
|
35929
|
+
if (this.isExcludedByCondition(boundName) || this.isExcludedByCondition(text)) {
|
|
34214
35930
|
return false;
|
|
34215
35931
|
}
|
|
34216
35932
|
}
|
|
@@ -34417,6 +36133,14 @@ function analyzeConstantPropagation(tree, sourceCode, options = {}) {
|
|
|
34417
36133
|
options.taintedParameters ?? []
|
|
34418
36134
|
);
|
|
34419
36135
|
}
|
|
36136
|
+
function constPropScopedVar(method, varName, line, result) {
|
|
36137
|
+
if (!method) return varName;
|
|
36138
|
+
const blockId = result.lineBlockScopes?.get(line);
|
|
36139
|
+
if (blockId !== void 0) {
|
|
36140
|
+
return `${method}:b${blockId}:${varName}`;
|
|
36141
|
+
}
|
|
36142
|
+
return `${method}:${varName}`;
|
|
36143
|
+
}
|
|
34420
36144
|
function isFalsePositive(result, sinkLine, taintedVar) {
|
|
34421
36145
|
if (result.unreachableLines.has(sinkLine)) {
|
|
34422
36146
|
return { isFalsePositive: true, reason: "sink_in_dead_code" };
|
|
@@ -34908,6 +36632,8 @@ function classifyEntryPointTier(method, enclosingType, ctx) {
|
|
|
34908
36632
|
return classifyJsTsEntryPoint(method, enclosingType, ctx);
|
|
34909
36633
|
case "go":
|
|
34910
36634
|
return classifyGoEntryPoint(method, enclosingType, ctx);
|
|
36635
|
+
case "csharp":
|
|
36636
|
+
return classifyCSharpEntryPoint(method, enclosingType, ctx);
|
|
34911
36637
|
case "bash":
|
|
34912
36638
|
case "shell":
|
|
34913
36639
|
return classifyBashEntryPoint(method, enclosingType, ctx);
|
|
@@ -35160,6 +36886,84 @@ function fileHasJsTsFrameworkRegistration(regs) {
|
|
|
35160
36886
|
}
|
|
35161
36887
|
return false;
|
|
35162
36888
|
}
|
|
36889
|
+
var CSHARP_TIER_1_METHOD_ANNOTATIONS = /* @__PURE__ */ new Set([
|
|
36890
|
+
"HttpGet",
|
|
36891
|
+
"HttpPost",
|
|
36892
|
+
"HttpPut",
|
|
36893
|
+
"HttpDelete",
|
|
36894
|
+
"HttpPatch",
|
|
36895
|
+
"HttpHead",
|
|
36896
|
+
"HttpOptions",
|
|
36897
|
+
"Route",
|
|
36898
|
+
"AcceptVerbs",
|
|
36899
|
+
"MapGet",
|
|
36900
|
+
"MapPost",
|
|
36901
|
+
"MapPut",
|
|
36902
|
+
"MapDelete",
|
|
36903
|
+
"MapPatch"
|
|
36904
|
+
]);
|
|
36905
|
+
var CSHARP_TIER_1_PARAM_ANNOTATIONS = /* @__PURE__ */ new Set([
|
|
36906
|
+
"FromQuery",
|
|
36907
|
+
"FromBody",
|
|
36908
|
+
"FromRoute",
|
|
36909
|
+
"FromHeader",
|
|
36910
|
+
"FromForm",
|
|
36911
|
+
"FromServices"
|
|
36912
|
+
]);
|
|
36913
|
+
var CSHARP_TIER_1_CLASS_ANNOTATIONS = /* @__PURE__ */ new Set([
|
|
36914
|
+
"ApiController",
|
|
36915
|
+
"Controller",
|
|
36916
|
+
"Route"
|
|
36917
|
+
]);
|
|
36918
|
+
var CSHARP_MVC_BASE_SIMPLE_NAMES = /* @__PURE__ */ new Set([
|
|
36919
|
+
"Controller",
|
|
36920
|
+
"ControllerBase",
|
|
36921
|
+
"PageModel"
|
|
36922
|
+
]);
|
|
36923
|
+
function csharpBaseTypeLooksLikeMvc(base) {
|
|
36924
|
+
const simple = (base.split(".").pop() ?? base).trim();
|
|
36925
|
+
if (CSHARP_MVC_BASE_SIMPLE_NAMES.has(simple)) return true;
|
|
36926
|
+
return simple.endsWith("Controller") && simple !== "Controller";
|
|
36927
|
+
}
|
|
36928
|
+
function csharpTypeLooksLikeController(enclosingType) {
|
|
36929
|
+
if (!enclosingType) return false;
|
|
36930
|
+
if (annotationsInclude(enclosingType.annotations, CSHARP_TIER_1_CLASS_ANNOTATIONS)) return true;
|
|
36931
|
+
if (enclosingType.extends && csharpBaseTypeLooksLikeMvc(enclosingType.extends)) return true;
|
|
36932
|
+
const name2 = enclosingType.name ?? "";
|
|
36933
|
+
if (csharpBaseTypeLooksLikeMvc(name2)) return true;
|
|
36934
|
+
return name2.endsWith("Controller") && name2 !== "Controller";
|
|
36935
|
+
}
|
|
36936
|
+
function csharpMethodHasMvcBindingParams(method) {
|
|
36937
|
+
for (const p of method.parameters ?? []) {
|
|
36938
|
+
if (annotationsInclude(p.annotations, CSHARP_TIER_1_PARAM_ANNOTATIONS)) return true;
|
|
36939
|
+
}
|
|
36940
|
+
return false;
|
|
36941
|
+
}
|
|
36942
|
+
function looksLikeCSharpMain(method) {
|
|
36943
|
+
const mods = method.modifiers ?? [];
|
|
36944
|
+
if (!mods.includes("static")) return false;
|
|
36945
|
+
if (method.name !== "Main") return false;
|
|
36946
|
+
const params = method.parameters ?? [];
|
|
36947
|
+
return params.length === 0 || params.length === 1 && /\bstring\s*\[\s*\]/.test(params[0]?.type ?? "");
|
|
36948
|
+
}
|
|
36949
|
+
function classifyCSharpEntryPoint(method, enclosingType, ctx) {
|
|
36950
|
+
if (pathLooksLikeLibraryOrTest(ctx.filePath)) {
|
|
36951
|
+
return "TIER_3_LIBRARY_API";
|
|
36952
|
+
}
|
|
36953
|
+
if (annotationsInclude(method.annotations, CSHARP_TIER_1_METHOD_ANNOTATIONS)) {
|
|
36954
|
+
return "TIER_1_ENTRY_POINT";
|
|
36955
|
+
}
|
|
36956
|
+
if (csharpMethodHasMvcBindingParams(method)) {
|
|
36957
|
+
return "TIER_1_ENTRY_POINT";
|
|
36958
|
+
}
|
|
36959
|
+
if (csharpTypeLooksLikeController(enclosingType)) {
|
|
36960
|
+
return "TIER_1_ENTRY_POINT";
|
|
36961
|
+
}
|
|
36962
|
+
if (looksLikeCSharpMain(method)) {
|
|
36963
|
+
return "TIER_1_ENTRY_POINT";
|
|
36964
|
+
}
|
|
36965
|
+
return "TIER_3_LIBRARY_API";
|
|
36966
|
+
}
|
|
35163
36967
|
var GO_HTTP_REGISTRAR_METHODS = /* @__PURE__ */ new Set([
|
|
35164
36968
|
// net/http
|
|
35165
36969
|
"HandleFunc",
|
|
@@ -38768,6 +40572,10 @@ var CSharpCredit = class {
|
|
|
38768
40572
|
if (type === "path_traversal") {
|
|
38769
40573
|
if (/\b([A-Za-z_]\w*)\s*\.\s*StartsWith\s*\([^)]*\)\s*\?\s*\1\s*:\s*null\b/.test(text)) return true;
|
|
38770
40574
|
}
|
|
40575
|
+
if (type === "open_redirect") {
|
|
40576
|
+
if (/\b(?:Url\s*\.\s*)?IsLocalUrl\s*\(/.test(text)) return true;
|
|
40577
|
+
if (/\bLocalRedirect\s*\(/.test(text)) return true;
|
|
40578
|
+
}
|
|
38771
40579
|
return false;
|
|
38772
40580
|
}
|
|
38773
40581
|
/**
|
|
@@ -38781,6 +40589,10 @@ var CSharpCredit = class {
|
|
|
38781
40589
|
const set = /* @__PURE__ */ new Set();
|
|
38782
40590
|
this.helperCache.set(type, set);
|
|
38783
40591
|
for (const m of this.methods) {
|
|
40592
|
+
if (type === "open_redirect" && this.openRedirectHelperMethod(m)) {
|
|
40593
|
+
set.add(m.name);
|
|
40594
|
+
continue;
|
|
40595
|
+
}
|
|
38784
40596
|
const returns = this.returnExprs(m);
|
|
38785
40597
|
if (returns.length === 0) continue;
|
|
38786
40598
|
const ok = returns.every((r) => {
|
|
@@ -38792,6 +40604,23 @@ var CSharpCredit = class {
|
|
|
38792
40604
|
}
|
|
38793
40605
|
return set;
|
|
38794
40606
|
}
|
|
40607
|
+
/**
|
|
40608
|
+
* Same-file helper whose returns are safe for open_redirect: literal fallbacks
|
|
40609
|
+
* and parameters returned only under `Url.IsLocalUrl` (cognium-dev#946).
|
|
40610
|
+
*/
|
|
40611
|
+
openRedirectHelperMethod(m) {
|
|
40612
|
+
const returns = this.returnExprs(m);
|
|
40613
|
+
if (returns.length === 0) return false;
|
|
40614
|
+
for (const r of returns) {
|
|
40615
|
+
const e = r.expr.trim();
|
|
40616
|
+
if (/^"[^"]*"$/.test(e) || /^'[^']*'$/.test(e)) continue;
|
|
40617
|
+
if (this.builtinSanitizes("open_redirect", e)) continue;
|
|
40618
|
+
const id = /^([A-Za-z_]\w*)$/.exec(e);
|
|
40619
|
+
if (!id) return false;
|
|
40620
|
+
if (!this.guardedVarsAt("open_redirect", r.line).has(id[1])) return false;
|
|
40621
|
+
}
|
|
40622
|
+
return true;
|
|
40623
|
+
}
|
|
38795
40624
|
/** Every returned expression of a method: `return e;` and expression bodies. */
|
|
38796
40625
|
returnExprs(m) {
|
|
38797
40626
|
const out2 = [];
|
|
@@ -39024,6 +40853,14 @@ var CSharpCredit = class {
|
|
|
39024
40853
|
}
|
|
39025
40854
|
return out2;
|
|
39026
40855
|
}
|
|
40856
|
+
/** Variables proven local by `Url.IsLocalUrl(x)` (cognium-dev#275/#946). */
|
|
40857
|
+
localUrlVars(cond) {
|
|
40858
|
+
const out2 = [];
|
|
40859
|
+
for (const m of cond.matchAll(/\b(?:Url\s*\.\s*)?IsLocalUrl\s*\(\s*([A-Za-z_]\w*)\s*\)/g)) {
|
|
40860
|
+
out2.push(m[1]);
|
|
40861
|
+
}
|
|
40862
|
+
return out2;
|
|
40863
|
+
}
|
|
39027
40864
|
/** Variables whose host a condition compares against a constant or an allowlist. */
|
|
39028
40865
|
hostVars(cond, hostOf) {
|
|
39029
40866
|
const out2 = [];
|
|
@@ -39065,7 +40902,7 @@ var CSharpCredit = class {
|
|
|
39065
40902
|
return map;
|
|
39066
40903
|
}
|
|
39067
40904
|
provenBy(kind, cond, parsedFrom, hostOf, withHelpers) {
|
|
39068
|
-
const out2 = kind === "allowlist" ? this.allowlistVars(cond) : kind === "numeric" ? this.numericVars(cond, parsedFrom) : this.hostVars(cond, hostOf);
|
|
40905
|
+
const out2 = kind === "allowlist" ? this.allowlistVars(cond) : kind === "numeric" ? this.numericVars(cond, parsedFrom) : kind === "localUrl" ? this.localUrlVars(cond) : this.hostVars(cond, hostOf);
|
|
39069
40906
|
if (withHelpers) {
|
|
39070
40907
|
for (const [name2, pos] of this.guardHelpers(kind)) {
|
|
39071
40908
|
for (const call of cond.matchAll(new RegExp(`(?<![\\w.])${escapeRegex(name2)}\\s*\\(`, "g"))) {
|
|
@@ -39095,6 +40932,7 @@ var CSharpCredit = class {
|
|
|
39095
40932
|
const kinds = [];
|
|
39096
40933
|
if (INERT_WHEN_ALPHANUMERIC.has(type)) kinds.push("allowlist", "numeric");
|
|
39097
40934
|
if (type === "ssrf") kinds.push("host");
|
|
40935
|
+
if (type === "open_redirect") kinds.push("localUrl");
|
|
39098
40936
|
const proven = /* @__PURE__ */ new Set();
|
|
39099
40937
|
if (kinds.length === 0) return proven;
|
|
39100
40938
|
const scopeStart = this.methodAt(sinkLine)?.start_line ?? 1;
|
|
@@ -39337,6 +41175,15 @@ var InterproceduralPass = class {
|
|
|
39337
41175
|
}
|
|
39338
41176
|
}
|
|
39339
41177
|
}
|
|
41178
|
+
appendCalleeSourceCallerSinkFlows(
|
|
41179
|
+
graph,
|
|
41180
|
+
sources,
|
|
41181
|
+
sinks,
|
|
41182
|
+
interProc,
|
|
41183
|
+
additionalFlows,
|
|
41184
|
+
language === "java" ? ctx.code : void 0,
|
|
41185
|
+
language === "java" ? constProp : void 0
|
|
41186
|
+
);
|
|
39340
41187
|
const taintBridges = findTaintBridges(interProc);
|
|
39341
41188
|
interprocedural = {
|
|
39342
41189
|
tainted_methods: Array.from(interProc.taintedMethods),
|
|
@@ -39462,6 +41309,401 @@ var InterproceduralPass = class {
|
|
|
39462
41309
|
return { additionalSinks: filteredAdditionalSinks, additionalFlows: filteredAdditionalFlows, interprocedural };
|
|
39463
41310
|
}
|
|
39464
41311
|
};
|
|
41312
|
+
function assigneeIsConstPropClean(constProp, method, variable) {
|
|
41313
|
+
if (!method) return false;
|
|
41314
|
+
const scoped = `${method}:${variable}`;
|
|
41315
|
+
if (!constProp.symbols.has(scoped)) return false;
|
|
41316
|
+
if (constProp.tainted.has(scoped)) return false;
|
|
41317
|
+
for (const t of constProp.tainted) {
|
|
41318
|
+
if (t.startsWith(`${method}:`) && t.endsWith(`:${variable}`)) return false;
|
|
41319
|
+
}
|
|
41320
|
+
if (constProp.sanitizedVarUncovered?.has(scoped)) return false;
|
|
41321
|
+
return true;
|
|
41322
|
+
}
|
|
41323
|
+
function constPropTaints(constProp, method, variable) {
|
|
41324
|
+
if (constProp.tainted.has(`${method}:${variable}`)) return true;
|
|
41325
|
+
for (const t of constProp.tainted) {
|
|
41326
|
+
if (t.startsWith(`${method}:`) && t.endsWith(`:${variable}`)) return true;
|
|
41327
|
+
}
|
|
41328
|
+
return false;
|
|
41329
|
+
}
|
|
41330
|
+
function callArgumentIsConstPropTainted(graph, constProp, method, line) {
|
|
41331
|
+
if (!method) return false;
|
|
41332
|
+
for (const call of graph.ir.calls) {
|
|
41333
|
+
if (call.location.line !== line) continue;
|
|
41334
|
+
for (const arg of call.arguments) {
|
|
41335
|
+
const names = arg.variable ? [arg.variable] : (arg.expression ?? "").match(/[A-Za-z_]\w*/g) ?? [];
|
|
41336
|
+
if (names.some((n) => constPropTaints(constProp, method, n))) return true;
|
|
41337
|
+
}
|
|
41338
|
+
}
|
|
41339
|
+
return false;
|
|
41340
|
+
}
|
|
41341
|
+
function calleeReturnsTaintedContainer(constProp, code, startLine, endLine) {
|
|
41342
|
+
const lines = code.split("\n");
|
|
41343
|
+
for (let l = startLine; l <= endLine; l++) {
|
|
41344
|
+
const m = /^\s*return\s+([A-Za-z_]\w*)\s*;/.exec(lines[l - 1] ?? "");
|
|
41345
|
+
if (!m) continue;
|
|
41346
|
+
if ((constProp.taintedArrayElements.get(m[1])?.size ?? 0) > 0) return true;
|
|
41347
|
+
if ((constProp.taintedCollections.get(m[1])?.size ?? 0) > 0) return true;
|
|
41348
|
+
}
|
|
41349
|
+
return false;
|
|
41350
|
+
}
|
|
41351
|
+
var DEAD_BRANCH_CHECKED_SINKS = /* @__PURE__ */ new Set([
|
|
41352
|
+
"sql_injection",
|
|
41353
|
+
"trust_boundary",
|
|
41354
|
+
"path_traversal",
|
|
41355
|
+
"ldap_injection",
|
|
41356
|
+
"xpath_injection"
|
|
41357
|
+
]);
|
|
41358
|
+
function calleeSourceReachesReturn(graph, code, startLine, endLine, sourceLine) {
|
|
41359
|
+
const lines = code.split("\n");
|
|
41360
|
+
const returnLines = /* @__PURE__ */ new Set();
|
|
41361
|
+
for (let l = startLine; l <= endLine; l++) {
|
|
41362
|
+
if (/^\s*return\b/.test(lines[l - 1] ?? "")) returnLines.add(l);
|
|
41363
|
+
}
|
|
41364
|
+
if (returnLines.size === 0) return true;
|
|
41365
|
+
if (returnLines.has(sourceLine)) return true;
|
|
41366
|
+
const inRange = (l) => l >= startLine && l <= endLine;
|
|
41367
|
+
const ends = (l) => /[;{}]\s*$/.test((lines[l - 1] ?? "").replace(/\/\/.*$/, "").trimEnd());
|
|
41368
|
+
const statementStart = (l) => {
|
|
41369
|
+
let s = l;
|
|
41370
|
+
while (s > startLine && !ends(s - 1) && (lines[s - 2] ?? "").trim() !== "") s--;
|
|
41371
|
+
return s;
|
|
41372
|
+
};
|
|
41373
|
+
const reached = new Set(
|
|
41374
|
+
graph.ir.dfg.defs.filter((d) => d.line === sourceLine && d.kind === "local").map((d) => d.id)
|
|
41375
|
+
);
|
|
41376
|
+
if (reached.size === 0) return true;
|
|
41377
|
+
let changed = true;
|
|
41378
|
+
while (changed) {
|
|
41379
|
+
changed = false;
|
|
41380
|
+
for (const u of graph.ir.dfg.uses) {
|
|
41381
|
+
if (u.def_id === null || !reached.has(u.def_id) || !inRange(u.line)) continue;
|
|
41382
|
+
const stmt = statementStart(u.line);
|
|
41383
|
+
if (returnLines.has(u.line) || returnLines.has(stmt)) return true;
|
|
41384
|
+
for (const d of graph.ir.dfg.defs) {
|
|
41385
|
+
if ((d.line === u.line || d.line === stmt) && d.kind === "local" && !reached.has(d.id)) {
|
|
41386
|
+
reached.add(d.id);
|
|
41387
|
+
changed = true;
|
|
41388
|
+
}
|
|
41389
|
+
}
|
|
41390
|
+
const base = storeBase(lines[stmt - 1] ?? "");
|
|
41391
|
+
if (base && base !== u.variable) {
|
|
41392
|
+
for (const b of graph.ir.dfg.uses) {
|
|
41393
|
+
if ((b.line === u.line || b.line === stmt) && b.variable === base && b.def_id !== null && !reached.has(b.def_id)) {
|
|
41394
|
+
reached.add(b.def_id);
|
|
41395
|
+
changed = true;
|
|
41396
|
+
}
|
|
41397
|
+
}
|
|
41398
|
+
}
|
|
41399
|
+
}
|
|
41400
|
+
}
|
|
41401
|
+
return false;
|
|
41402
|
+
}
|
|
41403
|
+
function storeBase(line) {
|
|
41404
|
+
const m = /^\s*([A-Za-z_]\w*)\s*(?:\[[^\]=]*\]\s*)+=(?!=)/.exec(line) ?? /^\s*([A-Za-z_]\w*)\s*\.\s*[A-Za-z_]\w*\s*=(?!=)/.exec(line);
|
|
41405
|
+
if (!m || m[1] === "this") return null;
|
|
41406
|
+
return m[1];
|
|
41407
|
+
}
|
|
41408
|
+
function appendCalleeSourceCallerSinkFlows(graph, sources, sinks, interProc, additionalFlows, fileCode, constProp) {
|
|
41409
|
+
const types = graph.ir.types ?? [];
|
|
41410
|
+
for (const edge of interProc.callEdges) {
|
|
41411
|
+
const callee = interProc.methodNodes.get(edge.calleeMethod);
|
|
41412
|
+
if (!callee) continue;
|
|
41413
|
+
const calleeSources = sources.filter(
|
|
41414
|
+
(s) => s.line >= callee.startLine && s.line <= callee.endLine && (!fileCode || s.type === "interprocedural_param" || calleeSourceReachesReturn(graph, fileCode, callee.startLine, callee.endLine, s.line))
|
|
41415
|
+
);
|
|
41416
|
+
if (calleeSources.length === 0) continue;
|
|
41417
|
+
const assignee = graph.ir.dfg.defs.find(
|
|
41418
|
+
(d) => d.line === edge.callLine && d.kind === "local"
|
|
41419
|
+
);
|
|
41420
|
+
if (!assignee?.variable) continue;
|
|
41421
|
+
if (constProp && callArgumentIsConstPropTainted(graph, constProp, edge.callerMethod, edge.callLine) && assigneeIsConstPropClean(constProp, edge.callerMethod, assignee.variable) && !(fileCode && calleeReturnsTaintedContainer(constProp, fileCode, callee.startLine, callee.endLine))) {
|
|
41422
|
+
continue;
|
|
41423
|
+
}
|
|
41424
|
+
const derivedLocals = localsDerivedFromAssignee(
|
|
41425
|
+
graph,
|
|
41426
|
+
assignee.variable,
|
|
41427
|
+
assignee.line
|
|
41428
|
+
);
|
|
41429
|
+
for (const sink of sinks) {
|
|
41430
|
+
if (sink.type === "external_taint_escape") continue;
|
|
41431
|
+
if (sink.line <= edge.callLine) continue;
|
|
41432
|
+
if (enclosingMethodName(types, sink.line) !== edge.callerMethod) continue;
|
|
41433
|
+
const sinkVar = sinkVariableConsumingDerived(graph, sink, derivedLocals);
|
|
41434
|
+
if (!sinkVar) continue;
|
|
41435
|
+
if (additionalFlows.some((f) => f.source_line === calleeSources[0].line && f.sink_line === sink.line)) {
|
|
41436
|
+
continue;
|
|
41437
|
+
}
|
|
41438
|
+
const source = calleeSources[0];
|
|
41439
|
+
if (fileCode && source.type === "interprocedural_param" && source.variable && DEAD_BRANCH_CHECKED_SINKS.has(sink.type) && interproceduralParamReachIsDeadBranchOnly(
|
|
41440
|
+
fileCode,
|
|
41441
|
+
callee.startLine,
|
|
41442
|
+
callee.endLine,
|
|
41443
|
+
source.variable,
|
|
41444
|
+
assignee.variable
|
|
41445
|
+
)) {
|
|
41446
|
+
continue;
|
|
41447
|
+
}
|
|
41448
|
+
if (fileCode && HELPER_RETURN_GATED_SINKS.has(sink.type) && calleeSources.every(
|
|
41449
|
+
(s) => (s.type === "interprocedural_param" || s.type === "plugin_param") && !!s.variable
|
|
41450
|
+
) && !calleeReturnMayCarryParam(
|
|
41451
|
+
fileCode,
|
|
41452
|
+
callee.startLine,
|
|
41453
|
+
callee.endLine,
|
|
41454
|
+
calleeSources.map((s) => s.variable)
|
|
41455
|
+
)) {
|
|
41456
|
+
continue;
|
|
41457
|
+
}
|
|
41458
|
+
additionalFlows.push({
|
|
41459
|
+
source_line: source.line,
|
|
41460
|
+
sink_line: sink.line,
|
|
41461
|
+
source_type: source.type,
|
|
41462
|
+
sink_type: sink.type,
|
|
41463
|
+
path: [
|
|
41464
|
+
{ variable: source.location, line: source.line, type: "source" },
|
|
41465
|
+
{ variable: assignee.variable, line: edge.callLine, type: "use" },
|
|
41466
|
+
{ variable: sinkVar, line: sink.line, type: "sink" }
|
|
41467
|
+
],
|
|
41468
|
+
confidence: sink.confidence * source.confidence * 0.85,
|
|
41469
|
+
sanitized: false
|
|
41470
|
+
});
|
|
41471
|
+
}
|
|
41472
|
+
}
|
|
41473
|
+
}
|
|
41474
|
+
function interproceduralParamReachIsDeadBranchOnly(code, startLine, endLine, paramName, localName) {
|
|
41475
|
+
if (paramName === localName) return false;
|
|
41476
|
+
const lines = code.split("\n");
|
|
41477
|
+
const slice = lines.slice(startLine - 1, endLine);
|
|
41478
|
+
const sliceText = slice.join("\n");
|
|
41479
|
+
const dead = computeDeadJavaBranchLines(slice);
|
|
41480
|
+
const onLivePaths = buildJavaTaintedVars(sliceText, /* @__PURE__ */ new Set([paramName]), dead);
|
|
41481
|
+
const ifDeadLinesCount = buildJavaTaintedVars(sliceText, /* @__PURE__ */ new Set([paramName]), /* @__PURE__ */ new Set());
|
|
41482
|
+
return ifDeadLinesCount.has(localName) && !onLivePaths.has(localName);
|
|
41483
|
+
}
|
|
41484
|
+
var HELPER_RETURN_GATED_SINKS = /* @__PURE__ */ new Set([
|
|
41485
|
+
"sql_injection",
|
|
41486
|
+
"trust_boundary",
|
|
41487
|
+
"command_injection",
|
|
41488
|
+
"path_traversal",
|
|
41489
|
+
// #976: the same helper shapes feed LDAP and XPath sinks (OWASP ldapi/xpathi).
|
|
41490
|
+
"ldap_injection",
|
|
41491
|
+
"xpath_injection"
|
|
41492
|
+
]);
|
|
41493
|
+
function calleeReturnMayCarryParam(code, startLine, endLine, sourceVars) {
|
|
41494
|
+
const raw = code.split("\n").slice(startLine - 1, endLine);
|
|
41495
|
+
const paramNames = [...sourceVars, ...javaMethodParamNames(raw)];
|
|
41496
|
+
const dead = computeDeadJavaBranchLines(raw);
|
|
41497
|
+
const inlined = /* @__PURE__ */ new Set();
|
|
41498
|
+
const slice = joinWrappedStatements(raw, inlined);
|
|
41499
|
+
const text = slice.join("\n");
|
|
41500
|
+
const seeds = new Set(paramNames);
|
|
41501
|
+
const mutated = /* @__PURE__ */ new Set();
|
|
41502
|
+
let live = new Set(seeds);
|
|
41503
|
+
for (let round = 0; round < 8; round++) {
|
|
41504
|
+
live = /* @__PURE__ */ new Set([...seeds, ...buildJavaTaintedVars(text, seeds, dead, { trackSlots: true }).keys()]);
|
|
41505
|
+
let grew = false;
|
|
41506
|
+
for (let i2 = 0; i2 < slice.length; i2++) {
|
|
41507
|
+
if (dead.has(i2)) continue;
|
|
41508
|
+
for (const call of slice[i2].matchAll(/([A-Za-z_]\w*)\s*\.\s*[A-Za-z_]\w*\s*\(([^;]*)\)/g)) {
|
|
41509
|
+
if (live.has(call[1])) continue;
|
|
41510
|
+
const args2 = call[2].replace(/"(?:[^"\\]|\\.)*"/g, " ").match(/[\p{L}\p{N}_]+/gu) ?? [];
|
|
41511
|
+
if (args2.some((t) => live.has(t))) {
|
|
41512
|
+
seeds.add(call[1]);
|
|
41513
|
+
mutated.add(call[1]);
|
|
41514
|
+
grew = true;
|
|
41515
|
+
}
|
|
41516
|
+
}
|
|
41517
|
+
const store = /^\s*([A-Za-z_]\w*)\s*(?:(?:\[[^\]=]*\]\s*)+|\.\s*[A-Za-z_]\w*\s*)=(?!=)\s*(.*?);?\s*$/.exec(slice[i2]);
|
|
41518
|
+
if (store && store[1] !== "this" && !live.has(store[1])) {
|
|
41519
|
+
const rhs = store[2].replace(/"(?:[^"\\]|\\.)*"/g, " ").match(/[\p{L}\p{N}_]+/gu) ?? [];
|
|
41520
|
+
if (rhs.some((t) => live.has(t))) {
|
|
41521
|
+
seeds.add(store[1]);
|
|
41522
|
+
mutated.add(store[1]);
|
|
41523
|
+
grew = true;
|
|
41524
|
+
}
|
|
41525
|
+
}
|
|
41526
|
+
}
|
|
41527
|
+
if (!grew) break;
|
|
41528
|
+
}
|
|
41529
|
+
let sawReturn = false;
|
|
41530
|
+
for (let i2 = 0; i2 < slice.length; i2++) {
|
|
41531
|
+
if (dead.has(i2) || !/^\s*return\b/.test(slice[i2])) continue;
|
|
41532
|
+
const m = /^\s*return\b\s*(.*?)\s*;\s*(?:\/\/.*)?$/.exec(slice[i2]);
|
|
41533
|
+
if (!m) return true;
|
|
41534
|
+
sawReturn = true;
|
|
41535
|
+
const expr = m[1];
|
|
41536
|
+
const bare = /^[A-Za-z_]\w*$/.test(expr) ? expr : null;
|
|
41537
|
+
if (bare && mutated.has(bare)) return true;
|
|
41538
|
+
if (bare && live.has(bare)) {
|
|
41539
|
+
const def = mustReachingAssignment(slice, dead, i2, bare, inlined);
|
|
41540
|
+
if (!def) return true;
|
|
41541
|
+
const upTo = slice.slice(0, def.line);
|
|
41542
|
+
upTo.push(`__helperReturn = ${def.rhs};`);
|
|
41543
|
+
const atDef = buildJavaTaintedVars(upTo.join("\n"), seeds, dead, { trackSlots: true });
|
|
41544
|
+
if (atDef.has("__helperReturn")) return true;
|
|
41545
|
+
continue;
|
|
41546
|
+
}
|
|
41547
|
+
const idents = expr.replace(/"(?:[^"\\]|\\.)*"/g, " ").replace(/'(?:[^'\\]|\\.)'/g, " ").match(/[\p{L}\p{N}_]+/gu) ?? [];
|
|
41548
|
+
if (idents.some((t) => live.has(t))) return true;
|
|
41549
|
+
}
|
|
41550
|
+
return !sawReturn;
|
|
41551
|
+
}
|
|
41552
|
+
function javaMethodParamNames(lines) {
|
|
41553
|
+
const header = lines.join("\n").split("{")[0];
|
|
41554
|
+
const open = header.indexOf("(");
|
|
41555
|
+
if (open < 0) return [];
|
|
41556
|
+
let depth = 0;
|
|
41557
|
+
let end = -1;
|
|
41558
|
+
for (let k = open; k < header.length; k++) {
|
|
41559
|
+
if (header[k] === "(") depth++;
|
|
41560
|
+
else if (header[k] === ")" && --depth === 0) {
|
|
41561
|
+
end = k;
|
|
41562
|
+
break;
|
|
41563
|
+
}
|
|
41564
|
+
}
|
|
41565
|
+
if (end < 0) return [];
|
|
41566
|
+
const names = [];
|
|
41567
|
+
let angle = 0;
|
|
41568
|
+
let piece = "";
|
|
41569
|
+
for (const c of header.slice(open + 1, end) + ",") {
|
|
41570
|
+
if (c === "<") angle++;
|
|
41571
|
+
else if (c === ">") angle--;
|
|
41572
|
+
if (c === "," && angle === 0) {
|
|
41573
|
+
const m = /([A-Za-z_]\w*)\s*(?:\[\s*\])*\s*$/.exec(piece.trim());
|
|
41574
|
+
if (m) names.push(m[1]);
|
|
41575
|
+
piece = "";
|
|
41576
|
+
} else piece += c;
|
|
41577
|
+
}
|
|
41578
|
+
return names;
|
|
41579
|
+
}
|
|
41580
|
+
function joinWrappedStatements(lines, inlined) {
|
|
41581
|
+
const out2 = [...lines];
|
|
41582
|
+
const ends = /[;{}:]\s*(?:\/\/.*)?$/;
|
|
41583
|
+
const header = /^\s*(?:if|else|for|while|do|switch|case|default|try|catch|finally|synchronized)\b|^\s*@|^\s*(?:\/\/|\/?\*)/;
|
|
41584
|
+
for (let i2 = 0; i2 < out2.length; i2++) {
|
|
41585
|
+
const line = out2[i2];
|
|
41586
|
+
if (line.trim() === "" || ends.test(line) || header.test(line)) continue;
|
|
41587
|
+
let joined = line;
|
|
41588
|
+
let j = i2 + 1;
|
|
41589
|
+
for (; j < out2.length && j < i2 + 15; j++) {
|
|
41590
|
+
joined += " " + out2[j].trim();
|
|
41591
|
+
if (ends.test(out2[j])) break;
|
|
41592
|
+
}
|
|
41593
|
+
if (j >= out2.length || j >= i2 + 15) continue;
|
|
41594
|
+
out2[i2] = joined;
|
|
41595
|
+
for (let k = i2 + 1; k <= j; k++) out2[k] = "";
|
|
41596
|
+
i2 = j;
|
|
41597
|
+
}
|
|
41598
|
+
return out2.map((line, i2) => {
|
|
41599
|
+
const body2 = inlineControlBody(line);
|
|
41600
|
+
if (body2 !== line) inlined.add(i2);
|
|
41601
|
+
return body2;
|
|
41602
|
+
});
|
|
41603
|
+
}
|
|
41604
|
+
function inlineControlBody(line) {
|
|
41605
|
+
let rest = line;
|
|
41606
|
+
const indent = /^\s*/.exec(line)[0];
|
|
41607
|
+
for (let guard = 0; guard < 4; guard++) {
|
|
41608
|
+
const t = rest.trimStart();
|
|
41609
|
+
const elseM = /^(?:\}\s*)?else\b\s*/.exec(t);
|
|
41610
|
+
if (elseM && !/^(?:\}\s*)?else\s+if\b/.test(t)) {
|
|
41611
|
+
rest = t.slice(elseM[0].length);
|
|
41612
|
+
continue;
|
|
41613
|
+
}
|
|
41614
|
+
const head = /^(?:\}\s*)?(?:else\s+)?(?:if|for|while)\s*\(/.exec(t);
|
|
41615
|
+
if (!head) break;
|
|
41616
|
+
let depth = 1;
|
|
41617
|
+
let k = head[0].length;
|
|
41618
|
+
for (; k < t.length && depth > 0; k++) {
|
|
41619
|
+
if (t[k] === "(") depth++;
|
|
41620
|
+
else if (t[k] === ")") depth--;
|
|
41621
|
+
}
|
|
41622
|
+
if (depth !== 0) return line;
|
|
41623
|
+
rest = t.slice(k);
|
|
41624
|
+
}
|
|
41625
|
+
const body2 = rest.trim();
|
|
41626
|
+
if (body2 === "" || body2 === rest.trim() && body2 === line.trim() || body2.startsWith("{")) return line;
|
|
41627
|
+
return indent + body2;
|
|
41628
|
+
}
|
|
41629
|
+
function mustReachingAssignment(slice, dead, retLine, name2, inlined) {
|
|
41630
|
+
const depth = [];
|
|
41631
|
+
let d = 0;
|
|
41632
|
+
for (const line of slice) {
|
|
41633
|
+
depth.push(d);
|
|
41634
|
+
const code = line.replace(/"(?:[^"\\]|\\.)*"/g, '""').replace(/'(?:[^'\\]|\\.)'/g, "''").replace(/\/\/.*$/, "");
|
|
41635
|
+
for (const c of code) {
|
|
41636
|
+
if (c === "{") d++;
|
|
41637
|
+
else if (c === "}") d--;
|
|
41638
|
+
}
|
|
41639
|
+
}
|
|
41640
|
+
const target = depth[retLine];
|
|
41641
|
+
const esc = name2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
41642
|
+
const assign = new RegExp(`^\\s*(?:(?:final\\s+)?[A-Za-z_][\\w.]*(?:\\s*<[^>]*>)?(?:\\s*\\[\\s*\\])*\\s+)?${esc}\\s*=\\s*(.+?);\\s*(?://.*)?$`);
|
|
41643
|
+
const mentionsAssign = new RegExp(`(?<![\\w.])${esc}\\s*=(?!=)`);
|
|
41644
|
+
for (let j = retLine - 1; j >= 0; j--) {
|
|
41645
|
+
if (depth[j] < target) return null;
|
|
41646
|
+
if (dead.has(j)) continue;
|
|
41647
|
+
if (!mentionsAssign.test(slice[j])) continue;
|
|
41648
|
+
if (depth[j] > target || inlined.has(j)) return null;
|
|
41649
|
+
const m = assign.exec(slice[j]);
|
|
41650
|
+
if (!m) return null;
|
|
41651
|
+
return { line: j, rhs: m[1] };
|
|
41652
|
+
}
|
|
41653
|
+
return null;
|
|
41654
|
+
}
|
|
41655
|
+
function localsDerivedFromAssignee(graph, seedVar, seedLine) {
|
|
41656
|
+
const defs = graph.ir.dfg.defs;
|
|
41657
|
+
const chains = graph.ir.dfg.chains ?? [];
|
|
41658
|
+
const seedDefs = defs.filter(
|
|
41659
|
+
(d) => d.variable === seedVar && d.line === seedLine && d.kind === "local"
|
|
41660
|
+
);
|
|
41661
|
+
if (seedDefs.length === 0) return /* @__PURE__ */ new Set([seedVar]);
|
|
41662
|
+
const defIds = new Set(seedDefs.map((d) => d.id));
|
|
41663
|
+
const frontier = seedDefs.map((d) => d.id);
|
|
41664
|
+
const visited = new Set(frontier);
|
|
41665
|
+
while (frontier.length > 0) {
|
|
41666
|
+
const cur = frontier.shift();
|
|
41667
|
+
for (const ch of chains) {
|
|
41668
|
+
if (ch.from_def !== cur || visited.has(ch.to_def)) continue;
|
|
41669
|
+
visited.add(ch.to_def);
|
|
41670
|
+
defIds.add(ch.to_def);
|
|
41671
|
+
frontier.push(ch.to_def);
|
|
41672
|
+
}
|
|
41673
|
+
}
|
|
41674
|
+
const names = /* @__PURE__ */ new Set([seedVar]);
|
|
41675
|
+
for (const d of defs) {
|
|
41676
|
+
if (d.variable && defIds.has(d.id)) names.add(d.variable);
|
|
41677
|
+
}
|
|
41678
|
+
return names;
|
|
41679
|
+
}
|
|
41680
|
+
function sinkVariableConsumingDerived(graph, sink, derived) {
|
|
41681
|
+
for (const u of graph.usesAtLine(sink.line)) {
|
|
41682
|
+
if (u.variable && derived.has(u.variable)) return u.variable;
|
|
41683
|
+
}
|
|
41684
|
+
for (const call of graph.callsByLine.get(sink.line) ?? []) {
|
|
41685
|
+
for (const arg of call.arguments ?? []) {
|
|
41686
|
+
if (arg.variable && derived.has(arg.variable)) return arg.variable;
|
|
41687
|
+
if (arg.expression) {
|
|
41688
|
+
for (const v of derived) {
|
|
41689
|
+
const re = new RegExp(`\\b${v.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`);
|
|
41690
|
+
if (re.test(arg.expression)) return v;
|
|
41691
|
+
}
|
|
41692
|
+
}
|
|
41693
|
+
}
|
|
41694
|
+
}
|
|
41695
|
+
return void 0;
|
|
41696
|
+
}
|
|
41697
|
+
function enclosingMethodName(types, line) {
|
|
41698
|
+
for (const type of types) {
|
|
41699
|
+
for (const method of type.methods) {
|
|
41700
|
+
if (line >= method.start_line && line <= method.end_line) {
|
|
41701
|
+
return method.name;
|
|
41702
|
+
}
|
|
41703
|
+
}
|
|
41704
|
+
}
|
|
41705
|
+
return null;
|
|
41706
|
+
}
|
|
39465
41707
|
function csharpParamReachesSink(code, method, taintedArgs, sink, credit) {
|
|
39466
41708
|
const lines = code.split(/\r?\n/);
|
|
39467
41709
|
const params = taintedArgs.length > 0 ? method.parameters.filter((p) => taintedArgs.includes(p.position)) : method.parameters;
|
|
@@ -40202,12 +42444,26 @@ var ConstantPropagationPass = class {
|
|
|
40202
42444
|
}
|
|
40203
42445
|
}
|
|
40204
42446
|
}
|
|
40205
|
-
|
|
42447
|
+
const result = analyzeConstantPropagation(this.tree, code, {
|
|
40206
42448
|
sanitizerMethods: taintMatcher.sanitizerMethods,
|
|
40207
42449
|
taintedParameters
|
|
40208
42450
|
});
|
|
42451
|
+
if (ctx.language === "java") pruneDeadChains(ctx, result.unreachableLines);
|
|
42452
|
+
return result;
|
|
40209
42453
|
}
|
|
40210
42454
|
};
|
|
42455
|
+
function pruneDeadChains(ctx, unreachable) {
|
|
42456
|
+
if (unreachable.size === 0) return;
|
|
42457
|
+
const dfg = ctx.graph.ir.dfg;
|
|
42458
|
+
if (!dfg.chains || dfg.chains.length === 0) return;
|
|
42459
|
+
const lines = ctx.code.split("\n");
|
|
42460
|
+
const dead = /* @__PURE__ */ new Set();
|
|
42461
|
+
for (const d of dfg.defs) {
|
|
42462
|
+
if (unreachable.has(d.line) && !/\?[^?.:]*:/.test(lines[d.line - 1] ?? "")) dead.add(d.id);
|
|
42463
|
+
}
|
|
42464
|
+
if (dead.size === 0) return;
|
|
42465
|
+
dfg.chains = dfg.chains.filter((c) => !dead.has(c.from_def) && !dead.has(c.to_def));
|
|
42466
|
+
}
|
|
40211
42467
|
|
|
40212
42468
|
// src/analysis/passes/source-semantics-pass.ts
|
|
40213
42469
|
var DEMO_PATH_RE = /(?:^|\/)(?:demo|example|examples|samples|integration-tests|integration_tests)(?:\/|$)/i;
|
|
@@ -40694,13 +42950,123 @@ function argvFirstLiteralIsShell(lineText) {
|
|
|
40694
42950
|
const base = m[1].split(/[\\/]/).pop().replace(/\.exe$/i, "").toLowerCase();
|
|
40695
42951
|
return SHELL_PROGRAMS.has(base);
|
|
40696
42952
|
}
|
|
40697
|
-
function
|
|
42953
|
+
function javaLocalInitRhsBeforeLine(varName, beforeLine, methodStart, methodEnd, sourceLines) {
|
|
42954
|
+
const declAssign = new RegExp(
|
|
42955
|
+
`^\\s*(?:final\\s+)?[\\w<>,\\s\\[\\]]+\\s+${escapeReSf(varName)}\\s*=\\s*(.+?)\\s*;\\s*(?://.*)?$`
|
|
42956
|
+
);
|
|
42957
|
+
const reassign = new RegExp(`^\\s*${escapeReSf(varName)}\\s*=\\s*(.+?)\\s*;\\s*(?://.*)?$`);
|
|
42958
|
+
let lastRhs;
|
|
42959
|
+
for (let l = methodStart; l < beforeLine && l <= methodEnd; l++) {
|
|
42960
|
+
const line = (sourceLines[l - 1] ?? "").trim();
|
|
42961
|
+
const m = declAssign.exec(line) ?? reassign.exec(line);
|
|
42962
|
+
if (m) lastRhs = m[1].trim();
|
|
42963
|
+
}
|
|
42964
|
+
return lastRhs;
|
|
42965
|
+
}
|
|
42966
|
+
function callArgLooksLikeShellArgv(expr, probePrefix) {
|
|
42967
|
+
return argvFirstLiteralIsShell(`${probePrefix}${expr}`);
|
|
42968
|
+
}
|
|
42969
|
+
function parseSimpleJavaCall(expr) {
|
|
42970
|
+
const trimmed = expr.trim();
|
|
42971
|
+
const m = /^([\w][\w.]*)\s*\(/.exec(trimmed);
|
|
42972
|
+
if (!m) return null;
|
|
42973
|
+
const head = m[1];
|
|
42974
|
+
const dot = head.lastIndexOf(".");
|
|
42975
|
+
if (dot < 0) return { method: head };
|
|
42976
|
+
return { qualifier: head.slice(0, dot), method: head.slice(dot + 1) };
|
|
42977
|
+
}
|
|
42978
|
+
function javaMethodBodyReturnLooksLikeShellArgv(methodStart, methodEnd, sourceLines) {
|
|
42979
|
+
for (let l = methodStart; l <= methodEnd; l++) {
|
|
42980
|
+
const line = (sourceLines[l - 1] ?? "").trim();
|
|
42981
|
+
const ret = /\breturn\s+(.+?)\s*;/.exec(line);
|
|
42982
|
+
if (!ret) continue;
|
|
42983
|
+
if (callArgLooksLikeShellArgv(ret[1].trim(), "new ProcessBuilder(")) return true;
|
|
42984
|
+
}
|
|
42985
|
+
return false;
|
|
42986
|
+
}
|
|
42987
|
+
function javaResolveCalleeMethodLines(methodName, qualifier, refLine, types, sourceLines, ctx) {
|
|
42988
|
+
const enclosing = types.find((t) => refLine >= t.start_line && refLine <= t.end_line);
|
|
42989
|
+
const typeName = qualifier ? qualifier.includes(".") ? qualifier.split(".").pop() : qualifier : enclosing?.name;
|
|
42990
|
+
if (!typeName) return void 0;
|
|
42991
|
+
const tryType = (t, lines) => {
|
|
42992
|
+
const mm = t.methods.find((m) => m.name === methodName);
|
|
42993
|
+
if (!mm) return void 0;
|
|
42994
|
+
return { start: mm.start_line, end: mm.end_line, lines };
|
|
42995
|
+
};
|
|
42996
|
+
if (ctx?.projectGraph && ctx.sourceLinesByFile) {
|
|
42997
|
+
for (const fp of ctx.projectGraph.filePaths) {
|
|
42998
|
+
const ir = ctx.projectGraph.getIR(fp);
|
|
42999
|
+
if (!ir) continue;
|
|
43000
|
+
const t = ir.types.find((tt) => tt.name === typeName);
|
|
43001
|
+
if (!t) continue;
|
|
43002
|
+
const lines = ctx.sourceLinesByFile.get(fp);
|
|
43003
|
+
if (!lines) continue;
|
|
43004
|
+
const hit = tryType(t, lines);
|
|
43005
|
+
if (hit) return hit;
|
|
43006
|
+
}
|
|
43007
|
+
}
|
|
43008
|
+
const local = types.find((t) => t.name === typeName);
|
|
43009
|
+
if (local) return tryType(local, sourceLines);
|
|
43010
|
+
return void 0;
|
|
43011
|
+
}
|
|
43012
|
+
function javaExpressionLooksLikeShellArgv(expr, refLine, types, sourceLines, ctx) {
|
|
43013
|
+
if (callArgLooksLikeShellArgv(expr, "new ProcessBuilder(")) return true;
|
|
43014
|
+
const parsed = parseSimpleJavaCall(expr);
|
|
43015
|
+
if (!parsed) return false;
|
|
43016
|
+
const body2 = javaResolveCalleeMethodLines(
|
|
43017
|
+
parsed.method,
|
|
43018
|
+
parsed.qualifier,
|
|
43019
|
+
refLine,
|
|
43020
|
+
types,
|
|
43021
|
+
sourceLines,
|
|
43022
|
+
ctx
|
|
43023
|
+
);
|
|
43024
|
+
if (!body2) return false;
|
|
43025
|
+
return javaMethodBodyReturnLooksLikeShellArgv(body2.start, body2.end, body2.lines);
|
|
43026
|
+
}
|
|
43027
|
+
function javaFieldShellArgvWrittenBeforeLine(fieldName, beforeLine, methodStart, methodEnd, sourceLines, types, ctx) {
|
|
43028
|
+
const cap = fieldName.charAt(0).toUpperCase() + fieldName.slice(1);
|
|
43029
|
+
const beanSetter = new RegExp(`\\bset${escapeReSf(cap)}\\s*\\((.+)\\)\\s*;?\\s*$`);
|
|
43030
|
+
const fieldAssign = new RegExp(`^\\s*(?:this\\.)?${escapeReSf(fieldName)}\\s*=\\s*(.+?)\\s*;\\s*(?://.*)?$`);
|
|
43031
|
+
for (let l = methodStart; l < beforeLine && l <= methodEnd; l++) {
|
|
43032
|
+
const line = (sourceLines[l - 1] ?? "").trim();
|
|
43033
|
+
const sm = beanSetter.exec(line);
|
|
43034
|
+
if (sm && javaExpressionLooksLikeShellArgv(sm[1].trim(), l, types, sourceLines, ctx)) {
|
|
43035
|
+
return true;
|
|
43036
|
+
}
|
|
43037
|
+
const fm = fieldAssign.exec(line);
|
|
43038
|
+
if (fm && callArgLooksLikeShellArgv(fm[1].trim(), "new ProcessBuilder(")) return true;
|
|
43039
|
+
}
|
|
43040
|
+
return false;
|
|
43041
|
+
}
|
|
43042
|
+
function sameFileCallerPassesShellArgv(paramName, methodName, sinkLine, types, calls, sourceLines) {
|
|
40698
43043
|
if (!methodName) return false;
|
|
40699
43044
|
for (const t of types) {
|
|
40700
43045
|
const m = t.methods.find((mm) => mm.name === methodName && sinkLine >= mm.start_line && sinkLine <= mm.end_line);
|
|
40701
43046
|
if (!m) continue;
|
|
40702
43047
|
const pos = m.parameters.findIndex((p) => p.name === paramName);
|
|
40703
|
-
if (pos < 0)
|
|
43048
|
+
if (pos < 0) {
|
|
43049
|
+
const isField = t.fields?.some((f) => f.name === paramName);
|
|
43050
|
+
if (!isField) return false;
|
|
43051
|
+
return calls.some((c) => {
|
|
43052
|
+
if (c.method_name !== methodName) return false;
|
|
43053
|
+
if (c.receiver != null && c.receiver !== "this") return false;
|
|
43054
|
+
if (c.location.line < t.start_line || c.location.line > t.end_line) return false;
|
|
43055
|
+
if (c.arguments.length !== m.parameters.length) return false;
|
|
43056
|
+
const callerMethod = t.methods.find(
|
|
43057
|
+
(mm) => c.location.line >= mm.start_line && c.location.line <= mm.end_line
|
|
43058
|
+
);
|
|
43059
|
+
if (!callerMethod) return false;
|
|
43060
|
+
return javaFieldShellArgvWrittenBeforeLine(
|
|
43061
|
+
paramName,
|
|
43062
|
+
c.location.line,
|
|
43063
|
+
callerMethod.start_line,
|
|
43064
|
+
callerMethod.end_line,
|
|
43065
|
+
sourceLines,
|
|
43066
|
+
types
|
|
43067
|
+
);
|
|
43068
|
+
});
|
|
43069
|
+
}
|
|
40704
43070
|
return calls.some((c) => {
|
|
40705
43071
|
if (c.method_name !== methodName) return false;
|
|
40706
43072
|
if (c.receiver != null && c.receiver !== "this") return false;
|
|
@@ -40708,8 +43074,30 @@ function sameFileCallerPassesShellArgv(paramName, methodName, sinkLine, types, c
|
|
|
40708
43074
|
if (c.arguments.length !== m.parameters.length) return false;
|
|
40709
43075
|
const shadowed = types.some((u) => u !== t && u.start_line >= t.start_line && u.end_line <= t.end_line && (u.start_line > t.start_line || u.end_line < t.end_line) && c.location.line >= u.start_line && c.location.line <= u.end_line && u.methods.some((um) => um.name === methodName && um.parameters.length === c.arguments.length));
|
|
40710
43076
|
if (shadowed) return false;
|
|
40711
|
-
const expr = c.arguments[pos]?.expression;
|
|
40712
|
-
|
|
43077
|
+
const expr = (c.arguments[pos]?.expression ?? "").trim();
|
|
43078
|
+
if (!expr) return false;
|
|
43079
|
+
if (javaExpressionLooksLikeShellArgv(expr, c.location.line, types, sourceLines)) {
|
|
43080
|
+
return true;
|
|
43081
|
+
}
|
|
43082
|
+
const argVar = c.arguments[pos]?.variable;
|
|
43083
|
+
if (argVar && expr === argVar && !/[.(]/.test(expr)) {
|
|
43084
|
+
const callerMethod = t.methods.find(
|
|
43085
|
+
(mm) => c.location.line >= mm.start_line && c.location.line <= mm.end_line
|
|
43086
|
+
);
|
|
43087
|
+
if (callerMethod) {
|
|
43088
|
+
const rhs = javaLocalInitRhsBeforeLine(
|
|
43089
|
+
argVar,
|
|
43090
|
+
c.location.line,
|
|
43091
|
+
callerMethod.start_line,
|
|
43092
|
+
callerMethod.end_line,
|
|
43093
|
+
sourceLines
|
|
43094
|
+
);
|
|
43095
|
+
if (rhs && javaExpressionLooksLikeShellArgv(rhs, c.location.line, types, sourceLines)) {
|
|
43096
|
+
return true;
|
|
43097
|
+
}
|
|
43098
|
+
}
|
|
43099
|
+
}
|
|
43100
|
+
return false;
|
|
40713
43101
|
});
|
|
40714
43102
|
}
|
|
40715
43103
|
return false;
|
|
@@ -40764,12 +43152,42 @@ var FETCH_GLOBAL_RECEIVER_RE = /(?:^|[^.\w])(?:window|globalThis|self|global)\s*
|
|
|
40764
43152
|
var FETCH_MEMBER_RECEIVER_RE = /[\w$)\]]\s*\.\s*fetch\s*\(/;
|
|
40765
43153
|
var NOSQL_ARRAY_METHODS = /* @__PURE__ */ new Set(["find", "filter", "some", "every", "findIndex", "findLast"]);
|
|
40766
43154
|
var FUNCTION_FIRST_ARG_RE = /\.\s*(?:find|filter|some|every|findIndex|findLast)\s*\(\s*(?:async\s+)?(?:function\b|(?:\([^()]*\)|[A-Za-z_$][\w$]*)\s*=>)/;
|
|
43155
|
+
var NAMED_ARRAY_PREDICATE_RE = /([A-Za-z_$][\w$]*)\s*\.\s*(find|filter|some|every|findIndex|findLast)\s*\(\s*([A-Za-z_$][\w$]*)\s*\)/;
|
|
43156
|
+
function isDeclaredFunctionName(name2, lines) {
|
|
43157
|
+
const n = name2.replace(/[$]/g, "\\$");
|
|
43158
|
+
const decls = [
|
|
43159
|
+
new RegExp(`\\bfunction\\s+${n}\\s*[(<]`),
|
|
43160
|
+
new RegExp(`\\b(?:const|let|var)\\s+${n}\\s*(?::[^=]*)?=\\s*(?:async\\s+)?(?:function\\b|\\(|[A-Za-z_$][\\w$]*\\s*=>)`)
|
|
43161
|
+
];
|
|
43162
|
+
return lines.some((l) => decls.some((re) => re.test(l)));
|
|
43163
|
+
}
|
|
43164
|
+
function isDeclaredArrayName(name2, lines) {
|
|
43165
|
+
const n = name2.replace(/[$]/g, "\\$");
|
|
43166
|
+
const re = new RegExp(
|
|
43167
|
+
`\\b${n}\\s*(?:\\??\\s*:\\s*(?:readonly\\s+)?(?:[\\w$.<>,\\s]+\\[\\]|(?:Readonly)?Array\\s*<)|(?::[^=;]*)?=\\s*(?:\\[|new\\s+Array\\b|Array\\.(?:from|of)\\b))`
|
|
43168
|
+
);
|
|
43169
|
+
return lines.some((l) => re.test(l));
|
|
43170
|
+
}
|
|
40767
43171
|
var ORM_BUILDER_KEY_RE = /[{,]\s*(?:where|select|populate|include|orderBy|relations|attributes)\s*:/;
|
|
40768
43172
|
var BROWSER_COMPONENT_EXT_RE = /\.(?:jsx|tsx|vue|svelte)$/;
|
|
40769
43173
|
var CLIENT_SIGNAL_RE = /["']use client["']|\buse(?:State|Effect|Ref|Callback|Memo|Context|Reducer|LayoutEffect)\s*\(|\b(?:window|document|localStorage|sessionStorage|navigator)\s*\.|\son(?:Click|Change|Submit|Input|KeyDown|KeyUp|MouseOver|Focus|Blur)\s*=/;
|
|
40770
43174
|
var SERVER_SIGNAL_RE = /["']use server["']|\b(?:getServerSideProps|getStaticProps|getStaticPaths)\b|from\s+["']next\/server["']|\bNext(?:Api)?(?:Request|Response)\b|from\s+["'](?:node:)?(?:fs|child_process|http|https|net|dns|dgram)["']|\bexport\s+(?:default\s+)?(?:async\s+)?function\s+handler\s*\(/;
|
|
40771
43175
|
var FIXED_HOST_TEMPLATE_RE = /`https?:\/\/[^/${}\s`]+[/`]/;
|
|
40772
43176
|
var BASH_FIXED_HOST_URL_RE = /^(?:https?|ftp):\/\/[A-Za-z0-9.-]+(?::\d+)?\//;
|
|
43177
|
+
var RUST_FIXED_HOST_FORMAT_RE = /format!\(\s*"https?:\/\/[^"{}]+\/\{\}"/;
|
|
43178
|
+
function rustSsrfRequestUsesFixedHostFormat(code, sinkLine) {
|
|
43179
|
+
const lines = code.split("\n");
|
|
43180
|
+
const sinkText = lines[sinkLine - 1] ?? "";
|
|
43181
|
+
const argMatch = sinkText.match(/(?:reqwest::)?get\s*\(\s*&?(\w+)/);
|
|
43182
|
+
if (!argMatch) return false;
|
|
43183
|
+
const varName = argMatch[1];
|
|
43184
|
+
const assignRe = new RegExp(`\\blet\\s+${escapeRegex2(varName)}\\s*=\\s*format!`);
|
|
43185
|
+
for (let i2 = sinkLine - 2; i2 >= Math.max(0, sinkLine - 20); i2--) {
|
|
43186
|
+
const line = lines[i2] ?? "";
|
|
43187
|
+
if (assignRe.test(line) && RUST_FIXED_HOST_FORMAT_RE.test(line)) return true;
|
|
43188
|
+
}
|
|
43189
|
+
return false;
|
|
43190
|
+
}
|
|
40773
43191
|
function literalEndsInitializer(code, end) {
|
|
40774
43192
|
let i2 = end;
|
|
40775
43193
|
let crossedNewline = false;
|
|
@@ -40941,7 +43359,9 @@ function isUnwrittenLocalMapKeyRead(code, source) {
|
|
|
40941
43359
|
if (!read) return false;
|
|
40942
43360
|
const [, recv, key] = read;
|
|
40943
43361
|
const ctorRe = new RegExp(
|
|
40944
|
-
|
|
43362
|
+
// `new HashMap<>()` or the qualified `new java.util.HashMap<>()` (OWASP
|
|
43363
|
+
// BenchmarkJava writes the qualified form throughout).
|
|
43364
|
+
`\\b${recv}\\s*=\\s*new\\s+(?:java\\.util\\.(?:concurrent\\.)?)?(?:Hash|Tree|LinkedHash|ConcurrentHash|Concurrent|Identity|Weak)?Map\\s*(?:<[^>]*>)?\\s*\\(`
|
|
40945
43365
|
);
|
|
40946
43366
|
if (!lines.some((l) => ctorRe.test(l))) return false;
|
|
40947
43367
|
const escapeRe2 = new RegExp(`\\w\\s*\\([^)]*(?<![\\w.])${recv}(?![\\w.])[^)]*\\)`);
|
|
@@ -40951,26 +43371,46 @@ function isUnwrittenLocalMapKeyRead(code, source) {
|
|
|
40951
43371
|
if (escapeRe2.test(stripped)) return false;
|
|
40952
43372
|
}
|
|
40953
43373
|
const putRe = new RegExp(`\\b${recv}\\s*\\.\\s*put\\s*\\(\\s*"${key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}"`);
|
|
40954
|
-
if (lines.some((l) => putRe.test(l))) return
|
|
43374
|
+
if (lines.some((l) => putRe.test(l))) return isLiteralOnlyLocalMapKey(lines, recv, key);
|
|
40955
43375
|
return true;
|
|
40956
43376
|
}
|
|
40957
|
-
function
|
|
40958
|
-
const
|
|
40959
|
-
const literalPut = new RegExp(
|
|
40960
|
-
`\\b${recv}\\s*\\.\\s*put\\s*\\(\\s*(?:"(?:[^"\\\\]|\\\\.)*"|[A-Za-z_][\\w.]*)\\s*,\\s*"(?:[^"\\\\]|\\\\.)*"\\s*\\)`,
|
|
40961
|
-
"g"
|
|
40962
|
-
);
|
|
43377
|
+
function isLiteralOnlyLocalMapKey(lines, recv, key) {
|
|
43378
|
+
const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
40963
43379
|
const otherMutator = new RegExp(
|
|
40964
43380
|
`\\b${recv}\\s*\\.\\s*(?:putAll|putIfAbsent|compute|computeIfAbsent|computeIfPresent|merge|replace|replaceAll|entrySet|values)\\s*\\(`
|
|
40965
43381
|
);
|
|
40966
|
-
|
|
40967
|
-
|
|
43382
|
+
if (lines.some((l) => otherMutator.test(l))) return false;
|
|
43383
|
+
const keyPut = new RegExp(
|
|
43384
|
+
`\\b${recv}\\s*\\.\\s*put\\s*\\(\\s*"${escapedKey}"\\s*,\\s*"(?:[^"\\\\]|\\\\.)*"\\s*\\)`
|
|
43385
|
+
);
|
|
43386
|
+
const anyKeyPut = new RegExp(`\\b${recv}\\s*\\.\\s*put\\s*\\(\\s*"${escapedKey}"`);
|
|
43387
|
+
let putsForKey = 0;
|
|
43388
|
+
let literalPutsForKey = 0;
|
|
40968
43389
|
for (const l of lines) {
|
|
40969
|
-
|
|
40970
|
-
|
|
40971
|
-
literalPuts += (l.match(literalPut) ?? []).length;
|
|
43390
|
+
putsForKey += (l.match(anyKeyPut) ?? []).length;
|
|
43391
|
+
literalPutsForKey += (l.match(keyPut) ?? []).length;
|
|
40972
43392
|
}
|
|
40973
|
-
return
|
|
43393
|
+
return putsForKey > 0 && putsForKey === literalPutsForKey;
|
|
43394
|
+
}
|
|
43395
|
+
function isJsDisguisedCacheDbInputRead(code, source) {
|
|
43396
|
+
if (source.type !== "db_input") return false;
|
|
43397
|
+
const loc = source.location ?? "";
|
|
43398
|
+
if (!/\.cache\.get\s*\(/i.test(loc) && !/\bcache\.get\s*\(/i.test(loc)) return false;
|
|
43399
|
+
const lines = code.split("\n");
|
|
43400
|
+
const lineText = lines[source.line - 1] ?? "";
|
|
43401
|
+
if (/\w+\.cache\.get\s*\(/.test(lineText)) return true;
|
|
43402
|
+
const read = /\b([A-Za-z_]\w*)\s*\.\s*get\s*\(/.exec(lineText);
|
|
43403
|
+
if (!read) return false;
|
|
43404
|
+
const recv = read[1];
|
|
43405
|
+
if (recv !== "cache") return false;
|
|
43406
|
+
const mapCtor = new RegExp(
|
|
43407
|
+
`\\b${recv}\\s*=\\s*new\\s+(?:Hash|Tree|LinkedHash|ConcurrentHash|Concurrent|Identity|Weak)?Map\\s*(?:<[^>]*>)?\\s*\\(`
|
|
43408
|
+
);
|
|
43409
|
+
if (lines.some((l) => mapCtor.test(l))) return true;
|
|
43410
|
+
const redisClient = new RegExp(
|
|
43411
|
+
`\\b${recv}\\s*=\\s*(?:await\\s+)?(?:redis\\.)?createClient\\s*\\(`
|
|
43412
|
+
);
|
|
43413
|
+
return !lines.some((l) => redisClient.test(l));
|
|
40974
43414
|
}
|
|
40975
43415
|
function csharpContentServesHtml(call) {
|
|
40976
43416
|
if (call.receiver !== null && call.receiver !== "this" && call.receiver !== "base") return false;
|
|
@@ -40978,8 +43418,8 @@ function csharpContentServesHtml(call) {
|
|
|
40978
43418
|
if (contentType === void 0) return true;
|
|
40979
43419
|
const literal = /^@?"([^"]*)"$/.exec(contentType);
|
|
40980
43420
|
if (literal) return /^\s*(?:text\/html|text\/xml|application\/xml|[\w.-]+\/[\w.-]+\+xml)\b/i.test(literal[1]);
|
|
40981
|
-
const
|
|
40982
|
-
if (
|
|
43421
|
+
const named2 = /^(?:System\s*\.\s*Net\s*\.\s*Mime\s*\.\s*)?MediaTypeNames\s*\.\s*(Text|Application)\s*\.\s*(\w+)$/.exec(contentType);
|
|
43422
|
+
if (named2) return named2[2] === "Html" || named2[2] === "Xml";
|
|
40983
43423
|
return true;
|
|
40984
43424
|
}
|
|
40985
43425
|
function csharpContentCallInheritsHelper(call, types, sourceLines) {
|
|
@@ -41146,7 +43586,7 @@ function findEnclosingMethodFromIr(types, sinkLine) {
|
|
|
41146
43586
|
return null;
|
|
41147
43587
|
}
|
|
41148
43588
|
function splitJavaConcatTokens(rhs) {
|
|
41149
|
-
const
|
|
43589
|
+
const tokens2 = [];
|
|
41150
43590
|
let cur = "";
|
|
41151
43591
|
let depth = 0;
|
|
41152
43592
|
let inString = false;
|
|
@@ -41183,7 +43623,7 @@ function splitJavaConcatTokens(rhs) {
|
|
|
41183
43623
|
continue;
|
|
41184
43624
|
}
|
|
41185
43625
|
if (ch === "+" && depth === 0) {
|
|
41186
|
-
|
|
43626
|
+
tokens2.push(cur.trim());
|
|
41187
43627
|
cur = "";
|
|
41188
43628
|
i2++;
|
|
41189
43629
|
continue;
|
|
@@ -41191,8 +43631,8 @@ function splitJavaConcatTokens(rhs) {
|
|
|
41191
43631
|
cur += ch;
|
|
41192
43632
|
i2++;
|
|
41193
43633
|
}
|
|
41194
|
-
if (cur.trim())
|
|
41195
|
-
return
|
|
43634
|
+
if (cur.trim()) tokens2.push(cur.trim());
|
|
43635
|
+
return tokens2;
|
|
41196
43636
|
}
|
|
41197
43637
|
function isImplicitlyAnchoredAllowlistRegex(re) {
|
|
41198
43638
|
if (re === "") return false;
|
|
@@ -41297,7 +43737,7 @@ var SinkFilterPass = class {
|
|
|
41297
43737
|
const executableSources = ctx.code && ctx.language ? mergedSources.filter(
|
|
41298
43738
|
(s) => !isNonExecutableSourceLine(ctx.code, s.line, ctx.language)
|
|
41299
43739
|
) : mergedSources;
|
|
41300
|
-
const sources = ctx.code ? executableSources.filter((s) => !isUnwrittenLocalMapKeyRead(ctx.code, s)) : executableSources;
|
|
43740
|
+
const sources = ctx.code ? executableSources.filter((s) => !isUnwrittenLocalMapKeyRead(ctx.code, s)).filter((s) => !isJsDisguisedCacheDbInputRead(ctx.code, s)) : executableSources;
|
|
41301
43741
|
const sinks = [...taintMatcher.sinks];
|
|
41302
43742
|
for (const s of langSources.additionalSinks) {
|
|
41303
43743
|
const springReturn = s.type === "xss" && typeof s.location === "string" && s.location.startsWith("Spring handler returns HTML");
|
|
@@ -41605,7 +44045,7 @@ var SinkFilterPass = class {
|
|
|
41605
44045
|
const varName = arg0.variable ?? "";
|
|
41606
44046
|
if (varName !== "" && argExpr === varName && !/[(.]/.test(argExpr)) {
|
|
41607
44047
|
const t = resolveIdentifierType(varName, ctorCall.in_method, irTypes);
|
|
41608
|
-
if (isArgvContainerType(t) && !sameFileCallerPassesShellArgv(varName, ctorCall.in_method, ctorCall.location.line, irTypes, irCalls)) {
|
|
44048
|
+
if (isArgvContainerType(t) && !sameFileCallerPassesShellArgv(varName, ctorCall.in_method, ctorCall.location.line, irTypes, irCalls, sourceLines)) {
|
|
41609
44049
|
return false;
|
|
41610
44050
|
}
|
|
41611
44051
|
}
|
|
@@ -41788,11 +44228,11 @@ var SinkFilterPass = class {
|
|
|
41788
44228
|
rhs = sqlVar;
|
|
41789
44229
|
}
|
|
41790
44230
|
if (!rhs) return true;
|
|
41791
|
-
const
|
|
41792
|
-
if (
|
|
44231
|
+
const tokens2 = splitJavaConcatTokens(rhs);
|
|
44232
|
+
if (tokens2.length < 2) return true;
|
|
41793
44233
|
let hasPlaceholder = false;
|
|
41794
44234
|
const methodCallNames = [];
|
|
41795
|
-
for (const tk of
|
|
44235
|
+
for (const tk of tokens2) {
|
|
41796
44236
|
if (/^"(?:[^"\\]|\\.)*"$/.test(tk)) {
|
|
41797
44237
|
if (tk.includes("?")) hasPlaceholder = true;
|
|
41798
44238
|
continue;
|
|
@@ -41855,7 +44295,15 @@ var SinkFilterPass = class {
|
|
|
41855
44295
|
if (sink.type !== "nosql_injection") return true;
|
|
41856
44296
|
if (!NOSQL_ARRAY_METHODS.has(sink.method ?? "")) return true;
|
|
41857
44297
|
const sinkLineText = sourceLines[sink.line - 1] ?? "";
|
|
41858
|
-
|
|
44298
|
+
if (FUNCTION_FIRST_ARG_RE.test(sinkLineText)) return false;
|
|
44299
|
+
const named2 = NAMED_ARRAY_PREDICATE_RE.exec(sinkLineText);
|
|
44300
|
+
if (named2) {
|
|
44301
|
+
const [, receiver, method, arg] = named2;
|
|
44302
|
+
if (NOSQL_ARRAY_METHODS.has(method) && (isDeclaredFunctionName(arg, sourceLines) || isDeclaredArrayName(receiver, sourceLines))) {
|
|
44303
|
+
return false;
|
|
44304
|
+
}
|
|
44305
|
+
}
|
|
44306
|
+
return true;
|
|
41859
44307
|
});
|
|
41860
44308
|
}
|
|
41861
44309
|
if (["javascript", "typescript"].includes(language)) {
|
|
@@ -41972,6 +44420,12 @@ var SinkFilterPass = class {
|
|
|
41972
44420
|
return bodyCalls.length === 0 || !bodyCalls.every(rustBodyDeclaresNonHtmlType);
|
|
41973
44421
|
});
|
|
41974
44422
|
}
|
|
44423
|
+
if (language === "rust") {
|
|
44424
|
+
filtered = filtered.filter((sink) => {
|
|
44425
|
+
if (sink.type !== "ssrf") return true;
|
|
44426
|
+
return !rustSsrfRequestUsesFixedHostFormat(ctx.code, sink.line);
|
|
44427
|
+
});
|
|
44428
|
+
}
|
|
41975
44429
|
if (language === "bash") {
|
|
41976
44430
|
const sourceLines = ctx.code.split("\n");
|
|
41977
44431
|
filtered = filtered.filter((sink) => {
|
|
@@ -42494,7 +44948,9 @@ function filterCleanVariableSinks(sinks, calls, taintedVars, symbols, dfg, sanit
|
|
|
42494
44948
|
return sinks.filter((sink) => {
|
|
42495
44949
|
const callsAtSink = callsByLine.get(sink.line) ?? [];
|
|
42496
44950
|
const isInSynchronizedBlock = synchronizedLines?.has(sink.line) ?? false;
|
|
42497
|
-
const relevantCalls = sink.method ? callsAtSink.filter(
|
|
44951
|
+
const relevantCalls = sink.method ? callsAtSink.filter(
|
|
44952
|
+
(c) => c.method_name === sink.method && (sink.callColumn === void 0 || c.location.column === sink.callColumn)
|
|
44953
|
+
) : callsAtSink;
|
|
42498
44954
|
const trustArgPositions = language !== "bash" && language !== "shell";
|
|
42499
44955
|
for (const call of relevantCalls) {
|
|
42500
44956
|
let allArgsAreClean = true;
|
|
@@ -43259,19 +45715,59 @@ function rustAttributesAbove(lines, startLine) {
|
|
|
43259
45715
|
continue;
|
|
43260
45716
|
}
|
|
43261
45717
|
if (t === "" || t.startsWith("//")) continue;
|
|
45718
|
+
if (out2.length > 0) break;
|
|
43262
45719
|
break;
|
|
43263
45720
|
}
|
|
43264
45721
|
return out2;
|
|
43265
45722
|
}
|
|
45723
|
+
function isModuleLevelFunction(method, lines) {
|
|
45724
|
+
const first = lines[method.start_line - 1] ?? "";
|
|
45725
|
+
return !/^\s/.test(first);
|
|
45726
|
+
}
|
|
45727
|
+
function jsTsExportBinding(exportName, lines) {
|
|
45728
|
+
const esc = exportName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
45729
|
+
const text = lines.join("\n");
|
|
45730
|
+
const first = lines.find((l) => /^\s*(?:export|function|const|async)/.test(l)) ?? "";
|
|
45731
|
+
if (/^export\s+(?:default\s+)?(?:async\s+)?(?:function\b|(?:const|let|var)\s+)/.test(first.trimStart()) && new RegExp(`\\b${esc}\\b`).test(first)) {
|
|
45732
|
+
return true;
|
|
45733
|
+
}
|
|
45734
|
+
if (new RegExp(`(?:^|\\n)\\s*export\\s*\\{[^}]*\\b${esc}\\b`).test(text)) return true;
|
|
45735
|
+
if (new RegExp(`(?:^|[^\\w$.])(?:module\\s*\\.\\s*)?exports\\s*\\.\\s*${esc}\\s*=`).test(text)) return true;
|
|
45736
|
+
if (new RegExp(`module\\s*\\.\\s*exports\\s*=\\s*\\{[^}]*\\b${esc}\\b`).test(text)) return true;
|
|
45737
|
+
return false;
|
|
45738
|
+
}
|
|
45739
|
+
var CHANNEL_EXPORT_HANDLER_NAMES = /* @__PURE__ */ new Set([
|
|
45740
|
+
"handle",
|
|
45741
|
+
"getUser",
|
|
45742
|
+
"onMessage",
|
|
45743
|
+
"onWebhook",
|
|
45744
|
+
"resolveUser",
|
|
45745
|
+
"sse"
|
|
45746
|
+
]);
|
|
45747
|
+
var CHANNEL_HANDLER_PARAM = /^(event|req|request|ctx|context|message|payload|topic|id|user|_|args|info)$/;
|
|
45748
|
+
function hasChannelHandlerParamSignature(method) {
|
|
45749
|
+
if (method.parameters.length === 0) return false;
|
|
45750
|
+
return method.parameters.some((p) => CHANNEL_HANDLER_PARAM.test(p.name));
|
|
45751
|
+
}
|
|
45752
|
+
function isChannelExportedHandler(method, language, lines) {
|
|
45753
|
+
if (language !== "javascript" && language !== "typescript") return false;
|
|
45754
|
+
if (!isModuleLevelFunction(method, lines)) return false;
|
|
45755
|
+
if (!CHANNEL_EXPORT_HANDLER_NAMES.has(method.name)) return false;
|
|
45756
|
+
if (!hasChannelHandlerParamSignature(method)) return false;
|
|
45757
|
+
return jsTsExportBinding(method.name, lines);
|
|
45758
|
+
}
|
|
43266
45759
|
function isLambdaEntryPoint(method, language, lines) {
|
|
43267
45760
|
const names = method.parameters.map((p) => p.name);
|
|
43268
45761
|
const first = lines[method.start_line - 1] ?? "";
|
|
43269
|
-
if (
|
|
45762
|
+
if (!isModuleLevelFunction(method, lines)) return false;
|
|
43270
45763
|
if (language === "python") {
|
|
43271
45764
|
return method.name === "lambda_handler" && names.length === 2 && names[0] === "event" && names[1] === "context";
|
|
43272
45765
|
}
|
|
43273
45766
|
if (language !== "javascript" && language !== "typescript") return false;
|
|
43274
45767
|
if (method.name !== "handler") return false;
|
|
45768
|
+
if (names.length === 1 && names[0] === "event") {
|
|
45769
|
+
return jsTsExportBinding("handler", lines) || /^export\s+(?:default\s+)?(?:async\s+)?(?:function\b|(?:const|let|var)\s+handler\b)/.test(first);
|
|
45770
|
+
}
|
|
43275
45771
|
if (names.length < 2 || names.length > 3 || names[0] !== "event" || names[1] !== "context") return false;
|
|
43276
45772
|
if (names.length === 3 && names[2] !== "callback") return false;
|
|
43277
45773
|
if (/^export\s+(?:default\s+)?(?:async\s+)?(?:function\b|(?:const|let|var)\s+handler\b)/.test(first)) return true;
|
|
@@ -43281,6 +45777,7 @@ function isLambdaEntryPoint(method, language, lines) {
|
|
|
43281
45777
|
function isFrameworkHandler(method, language, lines) {
|
|
43282
45778
|
if (method.annotations.some((a) => ROUTE_ANNOTATION.test(a))) return true;
|
|
43283
45779
|
if (isLambdaEntryPoint(method, language, lines)) return true;
|
|
45780
|
+
if (isChannelExportedHandler(method, language, lines)) return true;
|
|
43284
45781
|
if (language === "rust") {
|
|
43285
45782
|
if (rustAttributesAbove(lines, method.start_line).some((a) => RUST_ROUTE_ATTR.test(a))) return true;
|
|
43286
45783
|
if (method.return_type && RUST_HANDLER_RETURN.test(method.return_type)) return true;
|
|
@@ -43296,6 +45793,20 @@ function isReactHtmlComponent(m, f, lines) {
|
|
|
43296
45793
|
const sig = lines[m.start_line - 1] ?? "";
|
|
43297
45794
|
return /\bfunction\s+[A-Z][A-Za-z0-9]*\s*\(\s*\{/.test(sig);
|
|
43298
45795
|
}
|
|
45796
|
+
function isRustDieselSqlParamFlow(f, lines) {
|
|
45797
|
+
if (f.source_type !== "interprocedural_param" || f.sink_type !== "sql_injection") return false;
|
|
45798
|
+
const sinkLine = lines[f.sink_line - 1] ?? "";
|
|
45799
|
+
return /\b(?:sql_query|raw_sql)\s*\(/.test(sinkLine);
|
|
45800
|
+
}
|
|
45801
|
+
function isPythonYamlLoadParamDeser(f, lines) {
|
|
45802
|
+
if (f.source_type !== "interprocedural_param" || f.sink_type !== "deserialization") return false;
|
|
45803
|
+
const head = f.path[0]?.variable;
|
|
45804
|
+
if (!head || f.path.length < 2) return false;
|
|
45805
|
+
const sinkLine = lines[f.sink_line - 1] ?? "";
|
|
45806
|
+
if (!/\byaml\.load\s*\(/.test(sinkLine) || /\bLoader\s*=\s*yaml\.SafeLoader/.test(sinkLine)) return false;
|
|
45807
|
+
const esc = head.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
45808
|
+
return new RegExp(`\\byaml\\.load\\s*\\(\\s*${esc}\\b`).test(sinkLine);
|
|
45809
|
+
}
|
|
43299
45810
|
var FIELD_CARRIED_SOURCE = /constructor-injected field|returns tainted field/;
|
|
43300
45811
|
var EXECUTOR_CALLEES = /* @__PURE__ */ new Map([
|
|
43301
45812
|
["submit", 0],
|
|
@@ -43416,8 +45927,8 @@ var ParamSourceFlowGatePass = class {
|
|
|
43416
45927
|
const viaReceiver = !isCtor && !!owner && owner.name !== "<module>";
|
|
43417
45928
|
const feeds = (pos) => fed.params.has(`${viaReceiver ? "m" : "f"}:${callee}:${pos}`);
|
|
43418
45929
|
const head = f.path[0]?.variable;
|
|
43419
|
-
const
|
|
43420
|
-
if (
|
|
45930
|
+
const named2 = m.parameters.findIndex((p) => p.name === head);
|
|
45931
|
+
if (named2 >= 0) return feeds(named2);
|
|
43421
45932
|
for (const s of sources) {
|
|
43422
45933
|
if (s.line !== f.source_line || s.type !== "interprocedural_param") continue;
|
|
43423
45934
|
if (FIELD_CARRIED_SOURCE.test(s.location ?? "")) {
|
|
@@ -43435,7 +45946,7 @@ var ParamSourceFlowGatePass = class {
|
|
|
43435
45946
|
let keep = true;
|
|
43436
45947
|
if (f.source_type === "interprocedural_param" && !realSourceLines.has(f.source_line)) {
|
|
43437
45948
|
const m = enclosingMethod(methods, f.source_line);
|
|
43438
|
-
keep = m !== void 0 && (isHandler(m) || fedByRealSource(f, m) || isReactHtmlComponent(m, f, lines));
|
|
45949
|
+
keep = m !== void 0 && (isHandler(m) || fedByRealSource(f, m) || isReactHtmlComponent(m, f, lines) || ctx.language === "python" && isPythonYamlLoadParamDeser(f, lines) || ctx.language === "rust" && isRustDieselSqlParamFlow(f, lines));
|
|
43439
45950
|
}
|
|
43440
45951
|
if (keep) flows[w++] = f;
|
|
43441
45952
|
else {
|
|
@@ -44068,6 +46579,32 @@ function javaReceiverChainDerivedVar(rhsByLocal, localVar, seedVar) {
|
|
|
44068
46579
|
const receiverOnRhs = new RegExp(`(?:^|[^\\w.$])${esc}\\s*\\.`, "u");
|
|
44069
46580
|
return rhsList.some((rhs) => receiverOnRhs.test(rhs));
|
|
44070
46581
|
}
|
|
46582
|
+
function defLineForUse(graph, varName, useLine) {
|
|
46583
|
+
const own = (graph.defsByLine.get(useLine) ?? []).find((d2) => d2.variable === varName);
|
|
46584
|
+
if (own) return own.line;
|
|
46585
|
+
const use = (graph.usesByLine.get(useLine) ?? []).find((u) => u.variable === varName && u.def_id != null);
|
|
46586
|
+
const d = use ? graph.defById.get(use.def_id) : void 0;
|
|
46587
|
+
return d?.line ?? useLine;
|
|
46588
|
+
}
|
|
46589
|
+
function constPropFlowLookup(language, method, varName, blockLine, constProp, preferMethodScoped = false) {
|
|
46590
|
+
if (language === "go") {
|
|
46591
|
+
return constPropScopedVar(method, varName, blockLine, constProp);
|
|
46592
|
+
}
|
|
46593
|
+
if (!method || varName.includes(":")) return varName;
|
|
46594
|
+
const blockScoped = constPropScopedVar(method, varName, blockLine, constProp);
|
|
46595
|
+
const hasTaintBinding = (name2) => constProp.tainted.has(name2) || constProp.sanitizedVars.has(name2);
|
|
46596
|
+
if (blockScoped.includes(":b") && hasTaintBinding(blockScoped)) {
|
|
46597
|
+
return blockScoped;
|
|
46598
|
+
}
|
|
46599
|
+
const methodScoped = `${method}:${varName}`;
|
|
46600
|
+
if (hasTaintBinding(methodScoped)) {
|
|
46601
|
+
return methodScoped;
|
|
46602
|
+
}
|
|
46603
|
+
if (preferMethodScoped && method) {
|
|
46604
|
+
return methodScoped;
|
|
46605
|
+
}
|
|
46606
|
+
return constProp.lineBlockScopes?.has(blockLine) ? blockScoped : varName;
|
|
46607
|
+
}
|
|
44071
46608
|
function indexJavaLocalAssignments(code) {
|
|
44072
46609
|
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*$/;
|
|
44073
46610
|
const assignRe = /^\s*([A-Za-z_]\w*)\s*=\s*(.+?);\s*$/;
|
|
@@ -44085,6 +46622,32 @@ function indexJavaLocalAssignments(code) {
|
|
|
44085
46622
|
}
|
|
44086
46623
|
return index;
|
|
44087
46624
|
}
|
|
46625
|
+
function goCalleeBaseName(callee) {
|
|
46626
|
+
const dot = callee.lastIndexOf(".");
|
|
46627
|
+
return dot === -1 ? callee : callee.slice(dot + 1);
|
|
46628
|
+
}
|
|
46629
|
+
function goConstantHelperCalleeOnLine(line) {
|
|
46630
|
+
const m = line.match(/:=\s*([\w.]+)\s*\(/) ?? line.match(/\bvar\s+\w+\s*=\s*([\w.]+)\s*\(/);
|
|
46631
|
+
if (!m) return null;
|
|
46632
|
+
return goCalleeBaseName(m[1]);
|
|
46633
|
+
}
|
|
46634
|
+
function goConstantHelperCalleeFromLines(lines, lineNumber) {
|
|
46635
|
+
const direct = goConstantHelperCalleeOnLine(lines[lineNumber - 1] ?? "");
|
|
46636
|
+
if (direct) return direct;
|
|
46637
|
+
const cur = lines[lineNumber - 1] ?? "";
|
|
46638
|
+
const partial = cur.match(/:=\s*([\w.]+)\s*$/) ?? cur.match(/\bvar\s+\w+\s*=\s*([\w.]+)\s*$/);
|
|
46639
|
+
if (partial) {
|
|
46640
|
+
const next = (lines[lineNumber] ?? "").trimStart();
|
|
46641
|
+
if (next.startsWith("(")) return goCalleeBaseName(partial[1]);
|
|
46642
|
+
}
|
|
46643
|
+
if (!cur.includes(":=") && !/\bvar\s+\w+\s*=/.test(cur)) return null;
|
|
46644
|
+
if (cur.includes("(")) return null;
|
|
46645
|
+
for (let i2 = lineNumber + 1; i2 <= Math.min(lines.length, lineNumber + 4); i2++) {
|
|
46646
|
+
const m = (lines[i2 - 1] ?? "").match(/^\s*([\w.]+)\s*\(/);
|
|
46647
|
+
if (m) return goCalleeBaseName(m[1]);
|
|
46648
|
+
}
|
|
46649
|
+
return null;
|
|
46650
|
+
}
|
|
44088
46651
|
function makeConstPropVeto(constProp, graph, sources, code, language, sanitizers) {
|
|
44089
46652
|
const lines = typeof code === "string" ? code.split("\n") : [];
|
|
44090
46653
|
const selfReassign = /* @__PURE__ */ new Map();
|
|
@@ -44113,10 +46676,24 @@ function makeConstPropVeto(constProp, graph, sources, code, language, sanitizers
|
|
|
44113
46676
|
};
|
|
44114
46677
|
return (step, sourceLine, sinkLine, sinkType) => {
|
|
44115
46678
|
const method = methodAtLine(graph, step.line);
|
|
44116
|
-
const
|
|
44117
|
-
const
|
|
46679
|
+
const blockLine = defLineForUse(graph, step.variable, step.line);
|
|
46680
|
+
const fpLookup = constPropFlowLookup(
|
|
46681
|
+
language,
|
|
46682
|
+
method,
|
|
46683
|
+
step.variable,
|
|
46684
|
+
blockLine,
|
|
46685
|
+
constProp
|
|
46686
|
+
);
|
|
46687
|
+
const r = isFalsePositive(constProp, step.line, fpLookup);
|
|
44118
46688
|
if (!r.isFalsePositive) return false;
|
|
44119
46689
|
if (r.reason !== "variable_not_tainted") return true;
|
|
46690
|
+
if (r.reason === "variable_not_tainted" && language === "java" && method && fpLookup === step.variable && !step.variable.includes(".")) {
|
|
46691
|
+
const v = step.variable;
|
|
46692
|
+
if (constProp.tainted.has(`${method}:${v}`)) return false;
|
|
46693
|
+
for (const t of constProp.tainted) {
|
|
46694
|
+
if (t.startsWith(`${method}:b`) && t.endsWith(`:${v}`)) return false;
|
|
46695
|
+
}
|
|
46696
|
+
}
|
|
44120
46697
|
if (sinkType !== void 0 && constProp.sanitizedVarUncovered) {
|
|
44121
46698
|
const unc = constProp.sanitizedVarUncovered;
|
|
44122
46699
|
if (step.variable.includes(":")) {
|
|
@@ -44124,7 +46701,9 @@ function makeConstPropVeto(constProp, graph, sources, code, language, sanitizers
|
|
|
44124
46701
|
} else {
|
|
44125
46702
|
const method2 = methodAtLine(graph, step.line);
|
|
44126
46703
|
if (method2) {
|
|
44127
|
-
if (unc.get(
|
|
46704
|
+
if (unc.get(fpLookup)?.has(sinkType)) {
|
|
46705
|
+
return false;
|
|
46706
|
+
}
|
|
44128
46707
|
} else if (unc.get(step.variable)?.has(sinkType)) {
|
|
44129
46708
|
return false;
|
|
44130
46709
|
}
|
|
@@ -44162,10 +46741,10 @@ function makeConstPropVeto(constProp, graph, sources, code, language, sanitizers
|
|
|
44162
46741
|
}
|
|
44163
46742
|
if (!d) return true;
|
|
44164
46743
|
if (d.line === sourceLine && sources.some((s) => s.line === sourceLine && !CONTAINER_READ_SOURCES.has(s.type))) {
|
|
44165
|
-
if (step.line === sinkLine && !constProp.tainted.has(
|
|
46744
|
+
if (step.line === sinkLine && !constProp.tainted.has(fpLookup)) {
|
|
44166
46745
|
for (let ln = sourceLine; ln <= sinkLine; ln++) {
|
|
44167
|
-
const
|
|
44168
|
-
if (
|
|
46746
|
+
const callee = goConstantHelperCalleeFromLines(lines, ln);
|
|
46747
|
+
if (callee && constProp.constantReturningMethods?.has(callee)) {
|
|
44169
46748
|
return true;
|
|
44170
46749
|
}
|
|
44171
46750
|
}
|
|
@@ -44341,7 +46920,15 @@ var TaintPropagationPass = class {
|
|
|
44341
46920
|
for (const f of arrayFlows) {
|
|
44342
46921
|
pushIfNew(f);
|
|
44343
46922
|
}
|
|
44344
|
-
const collectionFlows = detectCollectionFlows(
|
|
46923
|
+
const collectionFlows = detectCollectionFlows(
|
|
46924
|
+
calls,
|
|
46925
|
+
sources,
|
|
46926
|
+
sinks,
|
|
46927
|
+
constProp,
|
|
46928
|
+
ctx.code,
|
|
46929
|
+
types,
|
|
46930
|
+
graph
|
|
46931
|
+
) ?? [];
|
|
44345
46932
|
for (const f of collectionFlows) {
|
|
44346
46933
|
if (flowKeys.has(flowKey(f))) continue;
|
|
44347
46934
|
const flowForCheck = {
|
|
@@ -44360,7 +46947,17 @@ var TaintPropagationPass = class {
|
|
|
44360
46947
|
if (isFP) continue;
|
|
44361
46948
|
pushIfNew(f);
|
|
44362
46949
|
}
|
|
44363
|
-
const paramFlows = detectParameterSinkFlows(
|
|
46950
|
+
const paramFlows = detectParameterSinkFlows(
|
|
46951
|
+
types,
|
|
46952
|
+
calls,
|
|
46953
|
+
sources,
|
|
46954
|
+
sinks,
|
|
46955
|
+
constProp.unreachableLines,
|
|
46956
|
+
constProp.tainted,
|
|
46957
|
+
ctx.code,
|
|
46958
|
+
ctx.language,
|
|
46959
|
+
graph.ir.meta.file
|
|
46960
|
+
) ?? [];
|
|
44364
46961
|
for (const f of paramFlows) {
|
|
44365
46962
|
pushIfNew(f);
|
|
44366
46963
|
}
|
|
@@ -44395,6 +46992,17 @@ var TaintPropagationPass = class {
|
|
|
44395
46992
|
pushIfNew(f);
|
|
44396
46993
|
}
|
|
44397
46994
|
}
|
|
46995
|
+
if ((ctx.language === "javascript" || ctx.language === "typescript") && typeof ctx.code === "string") {
|
|
46996
|
+
const jsModuleFlows = detectJsModuleScopeFlows(
|
|
46997
|
+
ctx.code,
|
|
46998
|
+
sources,
|
|
46999
|
+
sinks,
|
|
47000
|
+
constProp.unreachableLines
|
|
47001
|
+
) ?? [];
|
|
47002
|
+
for (const f of jsModuleFlows) {
|
|
47003
|
+
pushIfNew(f);
|
|
47004
|
+
}
|
|
47005
|
+
}
|
|
44398
47006
|
const sanitizedNames = constProp.sanitizedVars;
|
|
44399
47007
|
const sanitizedUncovered = constProp.sanitizedVarUncovered;
|
|
44400
47008
|
let finalFlows = sanitizedNames.size === 0 ? flows : flows.filter((f) => {
|
|
@@ -44424,6 +47032,22 @@ var TaintPropagationPass = class {
|
|
|
44424
47032
|
if (sanitizedNames.has(sourceVar) && coversFlow(sourceVar)) return false;
|
|
44425
47033
|
return true;
|
|
44426
47034
|
});
|
|
47035
|
+
if (ctx.language === "csharp") {
|
|
47036
|
+
finalFlows = finalFlows.filter(
|
|
47037
|
+
(f) => !shouldGateCSharpPatPermsLibraryParamFlow(
|
|
47038
|
+
f,
|
|
47039
|
+
sources,
|
|
47040
|
+
sinks,
|
|
47041
|
+
types,
|
|
47042
|
+
{
|
|
47043
|
+
language: ctx.language,
|
|
47044
|
+
types,
|
|
47045
|
+
filePath: graph.ir.meta.file,
|
|
47046
|
+
calls
|
|
47047
|
+
}
|
|
47048
|
+
)
|
|
47049
|
+
);
|
|
47050
|
+
}
|
|
44427
47051
|
if (sanitizers && sanitizers.length > 0) {
|
|
44428
47052
|
const sanitizersByLine = /* @__PURE__ */ new Map();
|
|
44429
47053
|
for (const san of sanitizers) {
|
|
@@ -44808,6 +47432,87 @@ function detectGoPackageGlobalFlows(code, _calls, sources, sinks, unreachableLin
|
|
|
44808
47432
|
}
|
|
44809
47433
|
return flows;
|
|
44810
47434
|
}
|
|
47435
|
+
function detectJsModuleScopeFlows(code, sources, sinks, unreachableLines) {
|
|
47436
|
+
const flows = [];
|
|
47437
|
+
if (!code || sinks.length === 0) return flows;
|
|
47438
|
+
const lines = code.split("\n");
|
|
47439
|
+
const moduleVars = /* @__PURE__ */ new Set();
|
|
47440
|
+
for (const line of lines) {
|
|
47441
|
+
const stripped = line.replace(/\s+$/, "");
|
|
47442
|
+
if (!stripped || /^[ \t]/.test(line)) continue;
|
|
47443
|
+
const m = stripped.match(/^(?:export\s+)?(?:let|var|const)\s+([A-Za-z_$][\w$]*)\b/);
|
|
47444
|
+
if (m) moduleVars.add(m[1]);
|
|
47445
|
+
}
|
|
47446
|
+
if (moduleVars.size === 0) return flows;
|
|
47447
|
+
const sourceVarNames = /* @__PURE__ */ new Set();
|
|
47448
|
+
for (const s of sources) {
|
|
47449
|
+
if (typeof s.variable === "string" && s.variable.length > 0) {
|
|
47450
|
+
sourceVarNames.add(s.variable);
|
|
47451
|
+
}
|
|
47452
|
+
}
|
|
47453
|
+
const JS_HTTP_SOURCE_RE = /\b(?:req|request)\.(?:body|query|params|headers|cookies|get)\b|\breq\.(?:param|query|body)\(/;
|
|
47454
|
+
const writes = [];
|
|
47455
|
+
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
47456
|
+
const line = lines[i2];
|
|
47457
|
+
if (unreachableLines.has(i2 + 1)) continue;
|
|
47458
|
+
for (const lhs of moduleVars) {
|
|
47459
|
+
const declRe = new RegExp(
|
|
47460
|
+
`\\b(?:export\\s+)?(?:let|const|var)\\s+${lhs.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`
|
|
47461
|
+
);
|
|
47462
|
+
if (declRe.test(line)) continue;
|
|
47463
|
+
const m = line.match(
|
|
47464
|
+
new RegExp(`\\b${lhs.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\s*=\\s*([^;]+)`)
|
|
47465
|
+
);
|
|
47466
|
+
if (!m) continue;
|
|
47467
|
+
const rhs = m[1];
|
|
47468
|
+
let taintedBy = null;
|
|
47469
|
+
const onLine = sources.filter((s) => s.line === i2 + 1);
|
|
47470
|
+
if (onLine.length > 0) {
|
|
47471
|
+
taintedBy = { line: onLine[0].line, type: onLine[0].type };
|
|
47472
|
+
} else if (JS_HTTP_SOURCE_RE.test(rhs)) {
|
|
47473
|
+
taintedBy = { line: i2 + 1, type: "http_body" };
|
|
47474
|
+
} else {
|
|
47475
|
+
for (const v of sourceVarNames) {
|
|
47476
|
+
const re = new RegExp(`(?<![A-Za-z0-9_$])${v.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?![A-Za-z0-9_$])`);
|
|
47477
|
+
if (re.test(rhs)) {
|
|
47478
|
+
const src = sources.find((s) => s.variable === v);
|
|
47479
|
+
if (src) {
|
|
47480
|
+
taintedBy = { line: src.line, type: src.type };
|
|
47481
|
+
break;
|
|
47482
|
+
}
|
|
47483
|
+
}
|
|
47484
|
+
}
|
|
47485
|
+
}
|
|
47486
|
+
if (!taintedBy) continue;
|
|
47487
|
+
writes.push({ varName: lhs, line: i2 + 1, sourceLine: taintedBy.line, sourceType: taintedBy.type });
|
|
47488
|
+
}
|
|
47489
|
+
}
|
|
47490
|
+
if (writes.length === 0) return flows;
|
|
47491
|
+
for (const sink of sinks) {
|
|
47492
|
+
if (unreachableLines.has(sink.line)) continue;
|
|
47493
|
+
const sinkLineText = lines[sink.line - 1] ?? "";
|
|
47494
|
+
if (!sinkLineText) continue;
|
|
47495
|
+
for (const w of writes) {
|
|
47496
|
+
if (w.line >= sink.line) continue;
|
|
47497
|
+
const re = new RegExp(`(?<![A-Za-z0-9_$])${w.varName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?![A-Za-z0-9_$])`);
|
|
47498
|
+
if (!re.test(sinkLineText)) continue;
|
|
47499
|
+
flows.push({
|
|
47500
|
+
source_line: w.sourceLine,
|
|
47501
|
+
sink_line: sink.line,
|
|
47502
|
+
source_type: w.sourceType,
|
|
47503
|
+
sink_type: sink.type,
|
|
47504
|
+
path: [
|
|
47505
|
+
{ variable: w.varName, line: w.sourceLine, type: "source" },
|
|
47506
|
+
{ variable: w.varName, line: w.line, type: "assignment" },
|
|
47507
|
+
{ variable: w.varName, line: sink.line, type: "sink" }
|
|
47508
|
+
],
|
|
47509
|
+
confidence: 0.9,
|
|
47510
|
+
sanitized: false
|
|
47511
|
+
});
|
|
47512
|
+
}
|
|
47513
|
+
}
|
|
47514
|
+
return flows;
|
|
47515
|
+
}
|
|
44811
47516
|
function methodHasLocalDecl(code, methodName, varName, atLine) {
|
|
44812
47517
|
if (!code || !methodName) return false;
|
|
44813
47518
|
const esc = methodName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -44875,7 +47580,19 @@ function isTaintedInMethod(taintedVars, varName, scopedName, fieldNames, isField
|
|
|
44875
47580
|
}
|
|
44876
47581
|
return true;
|
|
44877
47582
|
}
|
|
44878
|
-
function
|
|
47583
|
+
function mapGetLiteralKeyFromExpr(expr) {
|
|
47584
|
+
const m = expr.match(/\b([A-Za-z_]\w*)\s*\.\s*get\s*\(\s*"([^"]*)"\s*\)/);
|
|
47585
|
+
return m ? { map: m[1], key: m[2] } : null;
|
|
47586
|
+
}
|
|
47587
|
+
function isMapLiteralKeyTainted(constProp, mapName, key, inMethod) {
|
|
47588
|
+
const scoped = inMethod ? `${inMethod}:${mapName}` : mapName;
|
|
47589
|
+
const keys = constProp.taintedCollections.get(mapName) ?? constProp.taintedCollections.get(scoped);
|
|
47590
|
+
if (!keys || keys.size === 0) return false;
|
|
47591
|
+
return keys.has(key) || keys.has("*");
|
|
47592
|
+
}
|
|
47593
|
+
function detectCollectionFlows(calls, sources, sinks, constProp, code, types, graph) {
|
|
47594
|
+
const taintedVars = constProp.tainted;
|
|
47595
|
+
const unreachableLines = constProp.unreachableLines;
|
|
44879
47596
|
const flows = [];
|
|
44880
47597
|
const fieldNames = /* @__PURE__ */ new Set();
|
|
44881
47598
|
for (const t of types ?? []) for (const f of t.fields ?? []) fieldNames.add(f.name);
|
|
@@ -44895,7 +47612,18 @@ function detectCollectionFlows(calls, sources, sinks, taintedVars, unreachableLi
|
|
|
44895
47612
|
}
|
|
44896
47613
|
if (arg.variable) {
|
|
44897
47614
|
const varName = arg.variable;
|
|
44898
|
-
|
|
47615
|
+
let scopedName = varName;
|
|
47616
|
+
if (call.in_method) {
|
|
47617
|
+
const blockLine = graph ? defLineForUse(graph, varName, call.location.line) : call.location.line;
|
|
47618
|
+
scopedName = constPropFlowLookup(
|
|
47619
|
+
"java",
|
|
47620
|
+
call.in_method,
|
|
47621
|
+
varName,
|
|
47622
|
+
blockLine,
|
|
47623
|
+
constProp,
|
|
47624
|
+
true
|
|
47625
|
+
);
|
|
47626
|
+
}
|
|
44899
47627
|
const expr = arg.expression ?? "";
|
|
44900
47628
|
const explicitField = isFieldUseExpression(
|
|
44901
47629
|
expr,
|
|
@@ -44904,7 +47632,23 @@ function detectCollectionFlows(calls, sources, sinks, taintedVars, unreachableLi
|
|
|
44904
47632
|
);
|
|
44905
47633
|
const bareFieldRead = fieldNames.has(varName) && expr === varName && !methodHasLocalDecl(code, call.in_method, varName, call.location.line);
|
|
44906
47634
|
const isFieldUse = explicitField || bareFieldRead;
|
|
44907
|
-
|
|
47635
|
+
const mapKeyRead = mapGetLiteralKeyFromExpr(expr);
|
|
47636
|
+
let argTainted = isTaintedInMethod(
|
|
47637
|
+
taintedVars,
|
|
47638
|
+
varName,
|
|
47639
|
+
scopedName,
|
|
47640
|
+
fieldNames,
|
|
47641
|
+
isFieldUse
|
|
47642
|
+
);
|
|
47643
|
+
if (mapKeyRead && mapKeyRead.map === varName) {
|
|
47644
|
+
argTainted = isMapLiteralKeyTainted(
|
|
47645
|
+
constProp,
|
|
47646
|
+
varName,
|
|
47647
|
+
mapKeyRead.key,
|
|
47648
|
+
call.in_method
|
|
47649
|
+
);
|
|
47650
|
+
}
|
|
47651
|
+
if (argTainted) {
|
|
44908
47652
|
const source = pickScopedSource(sources, sink.line, call.in_method ?? null, types, varName);
|
|
44909
47653
|
if (source) {
|
|
44910
47654
|
if (source.variable && source.variable !== varName && source.in_method && call.in_method && source.in_method !== call.in_method) {
|
|
@@ -44939,12 +47683,23 @@ function detectCollectionFlows(calls, sources, sinks, taintedVars, unreachableLi
|
|
|
44939
47683
|
{ method: "peek", re: /(\w+)\.peek\(/ },
|
|
44940
47684
|
{ method: "toArray", re: /(\w+)\.toArray\(/ }
|
|
44941
47685
|
];
|
|
44942
|
-
for (const { re } of collectionPatterns) {
|
|
47686
|
+
for (const { method, re } of collectionPatterns) {
|
|
44943
47687
|
const match = expr.match(re);
|
|
44944
47688
|
if (match) {
|
|
44945
47689
|
const collectionVar = match[1];
|
|
44946
47690
|
const scopedCollection = call.in_method ? `${call.in_method}:${collectionVar}` : collectionVar;
|
|
44947
|
-
|
|
47691
|
+
const mapKey = method === "get" ? mapGetLiteralKeyFromExpr(expr) : null;
|
|
47692
|
+
let collectionTainted = taintedVars.has(collectionVar) || taintedVars.has(scopedCollection);
|
|
47693
|
+
if (mapKey && mapKey.map === collectionVar) {
|
|
47694
|
+
collectionTainted = isMapLiteralKeyTainted(
|
|
47695
|
+
constProp,
|
|
47696
|
+
collectionVar,
|
|
47697
|
+
mapKey.key,
|
|
47698
|
+
call.in_method
|
|
47699
|
+
);
|
|
47700
|
+
}
|
|
47701
|
+
if (!collectionTainted) continue;
|
|
47702
|
+
{
|
|
44948
47703
|
const source = pickScopedSource(sources, sink.line, call.in_method ?? null, types, collectionVar);
|
|
44949
47704
|
if (source) {
|
|
44950
47705
|
if (source.variable && source.variable !== collectionVar && source.in_method && call.in_method && source.in_method !== call.in_method) {
|
|
@@ -45021,6 +47776,39 @@ function detectArrayElementFlows(calls, sources, sinks, taintedArrayElements, un
|
|
|
45021
47776
|
}
|
|
45022
47777
|
return flows;
|
|
45023
47778
|
}
|
|
47779
|
+
function csharpPatPermsDirectParamSink(sink, paramVar) {
|
|
47780
|
+
const method = (sink.method ?? "").replace(/^.*\./, "");
|
|
47781
|
+
if (method !== "FileStream") return false;
|
|
47782
|
+
const code = sink.code ?? "";
|
|
47783
|
+
if (!code) return true;
|
|
47784
|
+
const escaped = paramVar.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
47785
|
+
return new RegExp(`\\bFileStream\\s*\\(\\s*${escaped}\\b`).test(code);
|
|
47786
|
+
}
|
|
47787
|
+
function shouldGateCSharpPatPermsLibraryParamFlow(flow, sources, sinks, types, ctx) {
|
|
47788
|
+
if (flow.source_type !== "interprocedural_param" || flow.sink_type !== "path_traversal") {
|
|
47789
|
+
return false;
|
|
47790
|
+
}
|
|
47791
|
+
const srcVar = flow.path?.[0]?.variable;
|
|
47792
|
+
const sinkVar = flow.path?.[flow.path.length - 1]?.variable;
|
|
47793
|
+
if (!srcVar || srcVar !== sinkVar) return false;
|
|
47794
|
+
const sink = sinks.find(
|
|
47795
|
+
(s) => s.line === flow.sink_line && s.type === "path_traversal"
|
|
47796
|
+
);
|
|
47797
|
+
if (!sink || !csharpPatPermsDirectParamSink(sink, srcVar)) return false;
|
|
47798
|
+
const src = sources.find(
|
|
47799
|
+
(s) => s.line === flow.source_line && s.type === "interprocedural_param"
|
|
47800
|
+
);
|
|
47801
|
+
const methodName = src?.in_method;
|
|
47802
|
+
if (!methodName) return false;
|
|
47803
|
+
const enclosingType = types.find((t) => t.methods.some((m) => m.name === methodName));
|
|
47804
|
+
const enclosingMethod2 = enclosingType?.methods.find((m) => m.name === methodName);
|
|
47805
|
+
return shouldGateInterproceduralParam(
|
|
47806
|
+
flow.source_type,
|
|
47807
|
+
enclosingMethod2,
|
|
47808
|
+
enclosingType,
|
|
47809
|
+
ctx
|
|
47810
|
+
);
|
|
47811
|
+
}
|
|
45024
47812
|
function compoundArgParamNames(expression, paramNames) {
|
|
45025
47813
|
const stripped = expression.replace(/(['"`])(?:\\.|(?!\1).)*\1/g, (m) => " ".repeat(m.length));
|
|
45026
47814
|
const matched = [];
|
|
@@ -45034,7 +47822,7 @@ function compoundArgParamNames(expression, paramNames) {
|
|
|
45034
47822
|
}
|
|
45035
47823
|
return matched;
|
|
45036
47824
|
}
|
|
45037
|
-
function detectParameterSinkFlows(types, calls, sources, sinks, unreachableLines, tainted, code) {
|
|
47825
|
+
function detectParameterSinkFlows(types, calls, sources, sinks, unreachableLines, tainted, code, language, filePath) {
|
|
45038
47826
|
const flows = [];
|
|
45039
47827
|
const paramSourcesByMethod = /* @__PURE__ */ new Map();
|
|
45040
47828
|
for (const source of sources) {
|
|
@@ -45075,6 +47863,20 @@ function detectParameterSinkFlows(types, calls, sources, sinks, unreachableLines
|
|
|
45075
47863
|
for (const varName of varNames) {
|
|
45076
47864
|
const paramSource = methodParamSources.get(varName);
|
|
45077
47865
|
if (paramSource) {
|
|
47866
|
+
if (language === "csharp" && paramSource.type === "interprocedural_param" && sink.type === "path_traversal" && csharpPatPermsDirectParamSink(sink, varName)) {
|
|
47867
|
+
const enclosingType = types.find(
|
|
47868
|
+
(t) => t.methods.some((m) => m.name === methodName)
|
|
47869
|
+
);
|
|
47870
|
+
const enclosingMethod2 = enclosingType?.methods.find((m) => m.name === methodName);
|
|
47871
|
+
if (shouldGateInterproceduralParam(
|
|
47872
|
+
paramSource.type,
|
|
47873
|
+
enclosingMethod2,
|
|
47874
|
+
enclosingType,
|
|
47875
|
+
{ language, types, filePath, calls }
|
|
47876
|
+
)) {
|
|
47877
|
+
continue;
|
|
47878
|
+
}
|
|
47879
|
+
}
|
|
45078
47880
|
const exists = flows.some((f) => f.source_line === paramSource.line && f.sink_line === sink.line && f.sink_type === sink.type);
|
|
45079
47881
|
if (!exists) {
|
|
45080
47882
|
if (typeof code === "string" && isReassignedToLiteralBetween(code, varName, paramSource.line, sink.line, unreachableLines)) {
|
|
@@ -46069,9 +48871,18 @@ function detectExpressionScanFlows(calls, sources, sinks, sanitizers, unreachabl
|
|
|
46069
48871
|
if (!prev || s.line < prev.line) realSourceByVar.set(s.variable, s);
|
|
46070
48872
|
}
|
|
46071
48873
|
for (const [varName, derivedLine] of derived) {
|
|
46072
|
-
if (!varName
|
|
48874
|
+
if (!varName) continue;
|
|
46073
48875
|
const owner = methodAt(derivedLine);
|
|
46074
48876
|
const fieldAlias = isDeclaredField(derivedLine, varName);
|
|
48877
|
+
if (existingVars.has(varName)) {
|
|
48878
|
+
if (!fieldAlias) continue;
|
|
48879
|
+
const stubIdx = sourcesWithVar.findIndex(
|
|
48880
|
+
(s) => s.variable === varName && s.type === "interprocedural_param"
|
|
48881
|
+
);
|
|
48882
|
+
if (stubIdx < 0) continue;
|
|
48883
|
+
sourcesWithVar.splice(stubIdx, 1);
|
|
48884
|
+
existingVars.delete(varName);
|
|
48885
|
+
}
|
|
46075
48886
|
let scopedAnchor = anchor;
|
|
46076
48887
|
if (owner) {
|
|
46077
48888
|
let best;
|
|
@@ -51430,10 +54241,28 @@ var InsecureCookiePass = class {
|
|
|
51430
54241
|
* unassigned `new Cookie(...)` only looks at that statement, so another
|
|
51431
54242
|
* cookie's setter cannot cover it.
|
|
51432
54243
|
*/
|
|
54244
|
+
/** Cookie variable assigned to this `new Cookie(...)` call site (per-cookie, #595). */
|
|
54245
|
+
javaCookieVarName(code, call) {
|
|
54246
|
+
const lines = code.split("\n");
|
|
54247
|
+
const idx = (call.location.line ?? 1) - 1;
|
|
54248
|
+
const line = lines[idx] ?? "";
|
|
54249
|
+
const sameLine = /\b([A-Za-z_]\w*)\s*=\s*new\s+(?:[\w.]+\.)?Cookie\b/.exec(line);
|
|
54250
|
+
if (sameLine?.[1]) return sameLine[1];
|
|
54251
|
+
if (/\bnew\s+(?:[\w.]+\.)?Cookie\s*\(/.test(line)) {
|
|
54252
|
+
for (let i2 = idx - 1; i2 >= Math.max(0, idx - 4); i2--) {
|
|
54253
|
+
const prev = (lines[i2] ?? "").trimEnd();
|
|
54254
|
+
const splitAssign = /\b([A-Za-z_]\w*)\s*=\s*$/.exec(prev);
|
|
54255
|
+
if (splitAssign?.[1]) return splitAssign[1];
|
|
54256
|
+
const prevSame = /\b([A-Za-z_]\w*)\s*=\s*new\s+(?:[\w.]+\.)?Cookie\b/.exec(prev);
|
|
54257
|
+
if (prevSame?.[1]) return prevSame[1];
|
|
54258
|
+
}
|
|
54259
|
+
}
|
|
54260
|
+
return void 0;
|
|
54261
|
+
}
|
|
51433
54262
|
javaCookieSetterTrue(code, call, setter) {
|
|
51434
|
-
const
|
|
51435
|
-
const
|
|
51436
|
-
const varName =
|
|
54263
|
+
const lines = code.split("\n");
|
|
54264
|
+
const line = lines[(call.location.line ?? 1) - 1] ?? "";
|
|
54265
|
+
const varName = this.javaCookieVarName(code, call);
|
|
51437
54266
|
const re = varName ? new RegExp(`\\b${varName}\\.${setter}\\s*\\(\\s*true\\s*\\)`) : new RegExp(`\\.${setter}\\s*\\(\\s*true\\s*\\)`);
|
|
51438
54267
|
return re.test(varName ? code : line);
|
|
51439
54268
|
}
|
|
@@ -52516,6 +55345,9 @@ var WeakRandomPass = class {
|
|
|
52516
55345
|
if (method === "random" && (receiver === "Math" || receiver.endsWith(".Math"))) {
|
|
52517
55346
|
return "Math.random";
|
|
52518
55347
|
}
|
|
55348
|
+
if (this.jsIsModuleScopedCryptoRandomUuid(call, ctx)) {
|
|
55349
|
+
return "crypto.randomUUID (module scope)";
|
|
55350
|
+
}
|
|
52519
55351
|
return null;
|
|
52520
55352
|
}
|
|
52521
55353
|
if (language === "go") {
|
|
@@ -52553,6 +55385,26 @@ var WeakRandomPass = class {
|
|
|
52553
55385
|
* file (i.e. `math/rand` imported unaliased; `crypto/rand` is either not
|
|
52554
55386
|
* imported or imported under an alias).
|
|
52555
55387
|
*/
|
|
55388
|
+
/**
|
|
55389
|
+
* Module-scoped `crypto.randomUUID()` / `randomUUID()` — entropy fixed at load.
|
|
55390
|
+
*/
|
|
55391
|
+
jsIsModuleScopedCryptoRandomUuid(call, ctx) {
|
|
55392
|
+
if (call.method_name !== "randomUUID") return false;
|
|
55393
|
+
if (call.in_method != null && call.in_method !== "") return false;
|
|
55394
|
+
const receiver = call.receiver ?? "";
|
|
55395
|
+
if (receiver === "crypto" || receiver.endsWith(".crypto")) return true;
|
|
55396
|
+
if (!receiver) {
|
|
55397
|
+
const imports = ctx.graph.ir.imports ?? [];
|
|
55398
|
+
return imports.some((imp) => {
|
|
55399
|
+
const pkg = imp.from_package ?? "";
|
|
55400
|
+
if (pkg !== "crypto" && pkg !== "node:crypto") return false;
|
|
55401
|
+
const name2 = imp.imported_name ?? imp.alias ?? "";
|
|
55402
|
+
const alias = imp.alias ?? imp.imported_name ?? "";
|
|
55403
|
+
return name2 === "randomUUID" || alias === "randomUUID";
|
|
55404
|
+
});
|
|
55405
|
+
}
|
|
55406
|
+
return false;
|
|
55407
|
+
}
|
|
52556
55408
|
goMathRandIsActive(ctx) {
|
|
52557
55409
|
const imports = ctx.graph.ir.imports ?? [];
|
|
52558
55410
|
let mathRandUnaliased = false;
|
|
@@ -53864,6 +56716,13 @@ var PY_HTTP_RECEIVERS = /* @__PURE__ */ new Set([
|
|
|
53864
56716
|
var VERIFY_FALSE_RE = /\bverify\s*=\s*False\b/;
|
|
53865
56717
|
var REJECT_UNAUTHORIZED_FALSE_RE = /\brejectUnauthorized\s*:\s*false\b/;
|
|
53866
56718
|
var INSECURE_SKIP_VERIFY_TRUE_RE = /\bInsecureSkipVerify\s*:\s*true\b/;
|
|
56719
|
+
var GO_X509_VERIFY_CALL_RE = /\.Verify\s*\(/;
|
|
56720
|
+
var GO_INTERMEDIATE_DENYLIST_GUARD_RE = /\b(denylist|denyList|deny_list|blocklist|blockList|blockedIntermediate|blocked_intermediate|deniedIntermediate|deniedIntermediates|denied_intermediate|isDeniedIntermediate|intermediateDenylist|intermediate_denylist)\b/i;
|
|
56721
|
+
function goSourceWithoutComments(code) {
|
|
56722
|
+
let s = code.replace(/\/\*[\s\S]*?\*\//g, " ");
|
|
56723
|
+
s = s.replace(/\/\/[^\n]*/g, " ");
|
|
56724
|
+
return s;
|
|
56725
|
+
}
|
|
53867
56726
|
var HOSTNAME_LAMBDA_TRUE_RE = /\(\s*\w+\s*,\s*\w+\s*\)\s*->\s*true\b/;
|
|
53868
56727
|
var CS_CERT_CALLBACK_TRUE_RE = /\b(ServerCertificateValidationCallback|ServerCertificateCustomValidationCallback|RemoteCertificateValidationCallback)\s*(?:\+?=|\()\s*(?:\([^)]*\)|\w+)\s*=>\s*(?:true\b|\{\s*return\s+true\b)/;
|
|
53869
56728
|
var CS_DANGEROUS_ACCEPT_RE = /\bDangerousAcceptAnyServerCertificateValidator\b/;
|
|
@@ -53872,6 +56731,14 @@ var ALLOW_ALL_HOSTNAME_VERIFIERS = /* @__PURE__ */ new Set([
|
|
|
53872
56731
|
"new AllowAllHostnameVerifier()",
|
|
53873
56732
|
"new NoopHostnameVerifier()"
|
|
53874
56733
|
]);
|
|
56734
|
+
var JS_CHECK_SERVER_IDENTITY_NOP_RE = /\bcheckServerIdentity\s*:\s*(?:async\s*)?\([^)]*\)\s*=>\s*(?:undefined|null)\b/;
|
|
56735
|
+
var JS_CHECK_SERVER_IDENTITY_NOP_FN_RE = /\bcheckServerIdentity\s*:\s*function\s*\([^)]*\)\s*\{\s*return\s+(?:undefined|null)\s*;?\s*\}/;
|
|
56736
|
+
var JS_CHECK_SERVER_IDENTITY_DECL_RE = /\bfunction\s+checkServerIdentity\s*\([^)]*\)\s*\{/g;
|
|
56737
|
+
var JS_LOOSE_IP_HOST_STRIP_RE = /\.replace\s*\(\s*\/\[\^\\d\.\]\\]\//;
|
|
56738
|
+
var JS_CHECK_SERVER_IDENTITY_LOOSE_BODY_RE = /(?:looseIp\s*\(|\.replace\s*\(\s*\/\[\^\\d\.\]\\]\/)[\s\S]*?return\s+undefined\b/;
|
|
56739
|
+
var JS_NFKC_NORMALIZE_RE = /\.normalize\s*\(\s*['"]NFKC['"]\s*\)/i;
|
|
56740
|
+
var JS_SUBJECT_ALT_NAME_RE = /\bsubjectaltname\b|\bsubjectAltName\b|\baltNames\b|\bDNS:\b|\bIP Address:/i;
|
|
56741
|
+
var JS_ISSUER_CHAIN_WALK_RE = /\bissuerCertificate\b/;
|
|
53875
56742
|
var TlsVerifyDisabledPass = class {
|
|
53876
56743
|
name = "tls-verify-disabled";
|
|
53877
56744
|
category = "security";
|
|
@@ -53986,6 +56853,18 @@ var TlsVerifyDisabledPass = class {
|
|
|
53986
56853
|
});
|
|
53987
56854
|
}
|
|
53988
56855
|
}
|
|
56856
|
+
if (/\bx509\./.test(code) && /\bVerifyOptions\b/.test(code) && GO_X509_VERIFY_CALL_RE.test(code) && !GO_INTERMEDIATE_DENYLIST_GUARD_RE.test(goSourceWithoutComments(code))) {
|
|
56857
|
+
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
56858
|
+
if (GO_X509_VERIFY_CALL_RE.test(lines[i2])) {
|
|
56859
|
+
out2.push({
|
|
56860
|
+
line: i2 + 1,
|
|
56861
|
+
pattern: "x509.Verify without intermediate denylist",
|
|
56862
|
+
api: "x509.Certificate.Verify"
|
|
56863
|
+
});
|
|
56864
|
+
break;
|
|
56865
|
+
}
|
|
56866
|
+
}
|
|
56867
|
+
}
|
|
53989
56868
|
}
|
|
53990
56869
|
if (language === "python") {
|
|
53991
56870
|
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
@@ -54009,6 +56888,35 @@ var TlsVerifyDisabledPass = class {
|
|
|
54009
56888
|
api: "process.env"
|
|
54010
56889
|
});
|
|
54011
56890
|
}
|
|
56891
|
+
if (JS_CHECK_SERVER_IDENTITY_NOP_RE.test(l) || JS_CHECK_SERVER_IDENTITY_NOP_FN_RE.test(l)) {
|
|
56892
|
+
out2.push({
|
|
56893
|
+
line: i2 + 1,
|
|
56894
|
+
pattern: "checkServerIdentity => undefined",
|
|
56895
|
+
api: "tls.connect / https.request options"
|
|
56896
|
+
});
|
|
56897
|
+
}
|
|
56898
|
+
}
|
|
56899
|
+
const m = JS_CHECK_SERVER_IDENTITY_NOP_RE.exec(code) ?? JS_CHECK_SERVER_IDENTITY_NOP_FN_RE.exec(code);
|
|
56900
|
+
if (m && m.index !== void 0) {
|
|
56901
|
+
const line = code.slice(0, m.index).split("\n").length;
|
|
56902
|
+
const dupKey = `${line}-checkServerIdentity => undefined`;
|
|
56903
|
+
if (!out2.some((o) => `${o.line}-${o.pattern}` === dupKey)) {
|
|
56904
|
+
out2.push({
|
|
56905
|
+
line,
|
|
56906
|
+
pattern: "checkServerIdentity => undefined",
|
|
56907
|
+
api: "tls.connect / https.request options"
|
|
56908
|
+
});
|
|
56909
|
+
}
|
|
56910
|
+
}
|
|
56911
|
+
for (const weak of this.detectWeakCheckServerIdentityCallbacks(code)) {
|
|
56912
|
+
const dupKey = `${weak.line}-${weak.pattern}`;
|
|
56913
|
+
if (!out2.some((o) => `${o.line}-${o.pattern}` === dupKey)) {
|
|
56914
|
+
out2.push({
|
|
56915
|
+
line: weak.line,
|
|
56916
|
+
pattern: weak.pattern,
|
|
56917
|
+
api: "checkServerIdentity (custom)"
|
|
56918
|
+
});
|
|
56919
|
+
}
|
|
54012
56920
|
}
|
|
54013
56921
|
}
|
|
54014
56922
|
if (language === "csharp") {
|
|
@@ -54028,16 +56936,90 @@ var TlsVerifyDisabledPass = class {
|
|
|
54028
56936
|
}
|
|
54029
56937
|
return out2;
|
|
54030
56938
|
}
|
|
56939
|
+
/** Standalone and inline `checkServerIdentity` with weak identity checks. */
|
|
56940
|
+
detectWeakCheckServerIdentityCallbacks(code) {
|
|
56941
|
+
const hits = [];
|
|
56942
|
+
const fileHasDenylist = GO_INTERMEDIATE_DENYLIST_GUARD_RE.test(code);
|
|
56943
|
+
const declRe = new RegExp(JS_CHECK_SERVER_IDENTITY_DECL_RE.source, "g");
|
|
56944
|
+
let match;
|
|
56945
|
+
while ((match = declRe.exec(code)) !== null) {
|
|
56946
|
+
const openBrace = code.indexOf("{", match.index);
|
|
56947
|
+
if (openBrace < 0) continue;
|
|
56948
|
+
const body2 = this.sliceBalancedBraces(code, openBrace);
|
|
56949
|
+
if (!body2) continue;
|
|
56950
|
+
const prelude = code.slice(Math.max(0, match.index - 800), match.index);
|
|
56951
|
+
const line = code.slice(0, match.index).split("\n").length;
|
|
56952
|
+
const weak = this.classifyWeakCheckServerIdentityBody(
|
|
56953
|
+
body2,
|
|
56954
|
+
prelude,
|
|
56955
|
+
fileHasDenylist
|
|
56956
|
+
);
|
|
56957
|
+
if (weak) hits.push({ line, pattern: weak });
|
|
56958
|
+
}
|
|
56959
|
+
const inlineRe = /\bcheckServerIdentity\s*:\s*(?:async\s*)?function\s*\([^)]*\)\s*\{/g;
|
|
56960
|
+
while ((match = inlineRe.exec(code)) !== null) {
|
|
56961
|
+
const openBrace = code.indexOf("{", match.index);
|
|
56962
|
+
if (openBrace < 0) continue;
|
|
56963
|
+
const body2 = this.sliceBalancedBraces(code, openBrace);
|
|
56964
|
+
if (!body2) continue;
|
|
56965
|
+
const prelude = code.slice(Math.max(0, match.index - 800), match.index);
|
|
56966
|
+
const line = code.slice(0, match.index).split("\n").length;
|
|
56967
|
+
const weak = this.classifyWeakCheckServerIdentityBody(
|
|
56968
|
+
body2,
|
|
56969
|
+
prelude,
|
|
56970
|
+
fileHasDenylist
|
|
56971
|
+
);
|
|
56972
|
+
if (weak) hits.push({ line, pattern: weak });
|
|
56973
|
+
}
|
|
56974
|
+
return hits;
|
|
56975
|
+
}
|
|
56976
|
+
classifyWeakCheckServerIdentityBody(body2, prelude, fileHasDenylist) {
|
|
56977
|
+
if (!/\breturn\s+undefined\b/.test(body2)) return null;
|
|
56978
|
+
const loose = JS_LOOSE_IP_HOST_STRIP_RE.test(body2) || JS_LOOSE_IP_HOST_STRIP_RE.test(prelude) || /\blooseIp\s*\(/.test(body2);
|
|
56979
|
+
if (loose && JS_CHECK_SERVER_IDENTITY_LOOSE_BODY_RE.test(body2)) {
|
|
56980
|
+
return "checkServerIdentity loose host parse => undefined";
|
|
56981
|
+
}
|
|
56982
|
+
const nfkc = JS_NFKC_NORMALIZE_RE.test(body2) || JS_NFKC_NORMALIZE_RE.test(prelude);
|
|
56983
|
+
if (nfkc && /\breturn\s+undefined\b/.test(body2)) {
|
|
56984
|
+
return "checkServerIdentity NFKC host normalize => undefined";
|
|
56985
|
+
}
|
|
56986
|
+
if (JS_SUBJECT_ALT_NAME_RE.test(body2) && /\breturn\s+undefined\b/.test(body2)) {
|
|
56987
|
+
return "checkServerIdentity DNS SAN / IP match => undefined";
|
|
56988
|
+
}
|
|
56989
|
+
if (JS_ISSUER_CHAIN_WALK_RE.test(body2) && !fileHasDenylist && /\breturn\s+undefined\b/.test(body2)) {
|
|
56990
|
+
return "checkServerIdentity issuer chain without denylist => undefined";
|
|
56991
|
+
}
|
|
56992
|
+
return null;
|
|
56993
|
+
}
|
|
56994
|
+
sliceBalancedBraces(code, openIndex) {
|
|
56995
|
+
if (code[openIndex] !== "{") return null;
|
|
56996
|
+
let depth = 0;
|
|
56997
|
+
for (let i2 = openIndex; i2 < code.length; i2++) {
|
|
56998
|
+
const ch = code[i2];
|
|
56999
|
+
if (ch === "{") depth++;
|
|
57000
|
+
else if (ch === "}") {
|
|
57001
|
+
depth--;
|
|
57002
|
+
if (depth === 0) return code.slice(openIndex, i2 + 1);
|
|
57003
|
+
}
|
|
57004
|
+
}
|
|
57005
|
+
return null;
|
|
57006
|
+
}
|
|
54031
57007
|
fixFor(language, pattern) {
|
|
54032
57008
|
if (pattern.includes("InsecureSkipVerify")) {
|
|
54033
57009
|
return "Remove `InsecureSkipVerify: true` \u2014 let Go verify the cert. If you need to trust a private CA, set `RootCAs` to a `*x509.CertPool` containing that CA.";
|
|
54034
57010
|
}
|
|
57011
|
+
if (pattern.includes("x509.Verify without intermediate denylist")) {
|
|
57012
|
+
return "Before calling `x509.Certificate.Verify`, check the chain (including intermediates) against your blocked-CA / denylist policy. Relying on PKIX verification alone accepts chains that include revoked or blocked intermediates.";
|
|
57013
|
+
}
|
|
54035
57014
|
if (pattern.includes("verify=False")) {
|
|
54036
57015
|
return "Remove `verify=False`. To trust a private CA, pass `verify='/path/to/ca.pem'`.";
|
|
54037
57016
|
}
|
|
54038
57017
|
if (pattern.includes("rejectUnauthorized")) {
|
|
54039
57018
|
return "Remove `rejectUnauthorized: false`. To trust a private CA, set the `ca` option to the CA cert(s). Never disable TLS verification globally.";
|
|
54040
57019
|
}
|
|
57020
|
+
if (pattern.includes("checkServerIdentity")) {
|
|
57021
|
+
return "Implement `checkServerIdentity` to compare the certificate to the intended hostname (or remove the override). Returning `undefined` accepts the connection; loose host/IP folding before that check enables MITM.";
|
|
57022
|
+
}
|
|
54041
57023
|
if (pattern.includes("NODE_TLS_REJECT_UNAUTHORIZED")) {
|
|
54042
57024
|
return "Remove the `NODE_TLS_REJECT_UNAUTHORIZED=0` assignment \u2014 it globally disables TLS verification for every outbound HTTPS request.";
|
|
54043
57025
|
}
|
|
@@ -56009,6 +58991,36 @@ function boundedMakeDefIds(defs, code, language) {
|
|
|
56009
58991
|
return out2;
|
|
56010
58992
|
}
|
|
56011
58993
|
|
|
58994
|
+
// src/analysis/comment-lines.ts
|
|
58995
|
+
function makeCommentOnlyLineTest(root, code) {
|
|
58996
|
+
const lines = code.split("\n");
|
|
58997
|
+
const cache = /* @__PURE__ */ new Map();
|
|
58998
|
+
return (line) => {
|
|
58999
|
+
const hit = cache.get(line);
|
|
59000
|
+
if (hit !== void 0) return hit;
|
|
59001
|
+
let result = false;
|
|
59002
|
+
const text = lines[line - 1];
|
|
59003
|
+
if (text !== void 0) {
|
|
59004
|
+
const first = text.search(/\S/);
|
|
59005
|
+
if (first >= 0) {
|
|
59006
|
+
const end = text.trimEnd().length;
|
|
59007
|
+
const node = root.descendantForPosition({ row: line - 1, column: first });
|
|
59008
|
+
let n = node;
|
|
59009
|
+
while (n && !/comment/.test(n.type) && n.startPosition.row === line - 1 && n.startPosition.column === first && n.parent) {
|
|
59010
|
+
n = n.parent;
|
|
59011
|
+
}
|
|
59012
|
+
if (n && /comment/.test(n.type)) {
|
|
59013
|
+
const startsBefore = n.startPosition.row < line - 1 || n.startPosition.row === line - 1 && n.startPosition.column <= first;
|
|
59014
|
+
const endsAfter = n.endPosition.row > line - 1 || n.endPosition.row === line - 1 && n.endPosition.column >= end;
|
|
59015
|
+
result = startsBefore && endsAfter;
|
|
59016
|
+
}
|
|
59017
|
+
}
|
|
59018
|
+
}
|
|
59019
|
+
cache.set(line, result);
|
|
59020
|
+
return result;
|
|
59021
|
+
};
|
|
59022
|
+
}
|
|
59023
|
+
|
|
56012
59024
|
// src/analyzer.ts
|
|
56013
59025
|
var initialized = false;
|
|
56014
59026
|
async function initAnalyzer(options = {}) {
|
|
@@ -56232,10 +59244,24 @@ function neutralizeOverlongLines(code) {
|
|
|
56232
59244
|
}
|
|
56233
59245
|
return { code: out2, dropped };
|
|
56234
59246
|
}
|
|
59247
|
+
var AnalysisTimeoutError = class extends Error {
|
|
59248
|
+
file;
|
|
59249
|
+
elapsedMs;
|
|
59250
|
+
phase;
|
|
59251
|
+
constructor(file, elapsedMs, phase) {
|
|
59252
|
+
super(`analysis of ${file} timed out after ${elapsedMs}ms (at ${phase})`);
|
|
59253
|
+
this.name = "AnalysisTimeoutError";
|
|
59254
|
+
this.file = file;
|
|
59255
|
+
this.elapsedMs = elapsedMs;
|
|
59256
|
+
this.phase = phase;
|
|
59257
|
+
}
|
|
59258
|
+
};
|
|
56235
59259
|
async function analyze(code, filePath, language, options = {}) {
|
|
56236
59260
|
if (!initialized) {
|
|
56237
59261
|
await initAnalyzer(options);
|
|
56238
59262
|
}
|
|
59263
|
+
const fileTimeoutMs = options.fileTimeoutMs ?? 0;
|
|
59264
|
+
const fileDeadline = fileTimeoutMs > 0 ? Date.now() + fileTimeoutMs : 0;
|
|
56239
59265
|
if (language === "html" || language === "vue") {
|
|
56240
59266
|
return analyzeMarkupFile(code, filePath, options, language);
|
|
56241
59267
|
}
|
|
@@ -56270,6 +59296,9 @@ async function analyze(code, filePath, language, options = {}) {
|
|
|
56270
59296
|
const tree = await parse(code, parseGrammar);
|
|
56271
59297
|
phaseEnd("parse", tParse);
|
|
56272
59298
|
try {
|
|
59299
|
+
if (fileDeadline > 0 && Date.now() > fileDeadline) {
|
|
59300
|
+
throw new AnalysisTimeoutError(filePath, Date.now() - (fileDeadline - fileTimeoutMs), "parse");
|
|
59301
|
+
}
|
|
56273
59302
|
logger.trace("Parsed AST", { rootNodeType: tree.rootNode.type });
|
|
56274
59303
|
const tParseStatus = phaseStart();
|
|
56275
59304
|
const parseStatus = extractParseStatus(tree);
|
|
@@ -56322,7 +59351,8 @@ async function analyze(code, filePath, language, options = {}) {
|
|
|
56322
59351
|
const imports = extractImports(tree, language, nodeCache);
|
|
56323
59352
|
phaseEnd("extractImports", tImports);
|
|
56324
59353
|
const tExports = phaseStart();
|
|
56325
|
-
|
|
59354
|
+
if (options.declarationDetail && (language === "typescript" || language === "javascript")) enrichTsDeclarations(tree, types);
|
|
59355
|
+
const exports = (options.declarationDetail && (language === "typescript" || language === "javascript") ? extractTsExports(types) : null) ?? extractExports(types);
|
|
56326
59356
|
phaseEnd("extractExports", tExports);
|
|
56327
59357
|
const tCFG = phaseStart();
|
|
56328
59358
|
const cfg = buildCFG(tree, language, nodeCache);
|
|
@@ -56443,7 +59473,14 @@ async function analyze(code, filePath, language, options = {}) {
|
|
|
56443
59473
|
if (!disabledPasses.has("unrestricted-file-upload")) pipeline.add(new UnrestrictedFileUploadPass());
|
|
56444
59474
|
if (!disabledPasses.has("missing-sanitizer-gate")) pipeline.add(new MissingSanitizerGatePass());
|
|
56445
59475
|
if (!disabledPasses.has("insecure-deserialization-config")) pipeline.add(new InsecureDeserializationConfigPass());
|
|
56446
|
-
const { results, findings } = pipeline.run(
|
|
59476
|
+
const { results, findings } = pipeline.run(
|
|
59477
|
+
graph,
|
|
59478
|
+
code,
|
|
59479
|
+
language,
|
|
59480
|
+
config,
|
|
59481
|
+
options.resources,
|
|
59482
|
+
fileDeadline > 0 ? { deadline: fileDeadline, onExpired: (phase) => new AnalysisTimeoutError(filePath, Date.now() - (fileDeadline - fileTimeoutMs), phase) } : void 0
|
|
59483
|
+
);
|
|
56447
59484
|
const sinkFilter = results.get("sink-filter");
|
|
56448
59485
|
const interProc = results.get("interprocedural");
|
|
56449
59486
|
const taint = {
|
|
@@ -56464,6 +59501,12 @@ async function analyze(code, filePath, language, options = {}) {
|
|
|
56464
59501
|
taint.flows = [...taint.flows ?? [], ...constructionFlows];
|
|
56465
59502
|
}
|
|
56466
59503
|
}
|
|
59504
|
+
const isCommentOnlyLine = makeCommentOnlyLineTest(tree.rootNode, code);
|
|
59505
|
+
taint.sinks = taint.sinks.filter((s) => !isCommentOnlyLine(s.line));
|
|
59506
|
+
taint.sources = taint.sources.filter((s) => !isCommentOnlyLine(s.line));
|
|
59507
|
+
if (taint.flows) {
|
|
59508
|
+
taint.flows = taint.flows.filter((f) => !isCommentOnlyLine(f.sink_line) && !isCommentOnlyLine(f.source_line));
|
|
59509
|
+
}
|
|
56467
59510
|
if (taint.flows && taint.flows.length > 0 && taint.sinks.length > 0) {
|
|
56468
59511
|
const sinkTagsByKey = /* @__PURE__ */ new Map();
|
|
56469
59512
|
for (const s of taint.sinks) {
|
|
@@ -56492,7 +59535,10 @@ async function analyze(code, filePath, language, options = {}) {
|
|
|
56492
59535
|
unresolvedItems: unresolved.length
|
|
56493
59536
|
});
|
|
56494
59537
|
emitFindingsInstrumentation(filePath, findings, taint);
|
|
56495
|
-
const
|
|
59538
|
+
const codeFindings = findings.filter(
|
|
59539
|
+
(f) => f.category !== "security" || f.line <= 1 || !isCommentOnlyLine(f.line)
|
|
59540
|
+
);
|
|
59541
|
+
const verifiedFindings = applyConfidenceFilter(codeFindings, options.includeSpeculative === true);
|
|
56496
59542
|
const downgradedFindings = applyLibraryApiSurfaceDowngrade(verifiedFindings);
|
|
56497
59543
|
const profiledFindings = applyProjectProfileTransform(
|
|
56498
59544
|
downgradedFindings,
|