circle-ir 4.17.0 → 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/propagator.d.ts +4 -0
- package/dist/analysis/constant-propagation/propagator.d.ts.map +1 -1
- package/dist/analysis/constant-propagation/propagator.js +99 -0
- package/dist/analysis/constant-propagation/propagator.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 +81 -1
- 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 +1057 -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 +66 -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 +133 -10
- 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/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 +225 -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 +2936 -291
- package/dist/core/circle-ir-core.cjs +634 -203
- package/dist/core/circle-ir-core.js +634 -203
- 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/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 +51 -1
- package/dist/navigation/navigation-index.d.ts.map +1 -1
- package/dist/navigation/navigation-index.js +72 -0
- 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 +9 -0
- package/dist/resolution/cross-file.d.ts.map +1 -1
- package/dist/resolution/cross-file.js +60 -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 +35 -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
|
}
|
|
@@ -10410,6 +10410,79 @@ function processStatement2(stmt, startDefId, startUseId, scopeStack, isJavaScrip
|
|
|
10410
10410
|
carryBranchDefsOut(popped, scopeStack, alwaysRuns);
|
|
10411
10411
|
break;
|
|
10412
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
|
+
}
|
|
10413
10486
|
case "else_clause": {
|
|
10414
10487
|
for (let i2 = 0; i2 < stmt.childCount; i2++) {
|
|
10415
10488
|
const arm = stmt.child(i2);
|
|
@@ -11025,6 +11098,42 @@ function buildBashDFG(tree) {
|
|
|
11025
11098
|
defs.push(def);
|
|
11026
11099
|
currentScope(scopeStack).set(p, def.id);
|
|
11027
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
|
+
};
|
|
11028
11137
|
walkTree(tree.rootNode, (node) => {
|
|
11029
11138
|
if (node.type === "variable_assignment") {
|
|
11030
11139
|
const nameNode = node.childForFieldName("name");
|
|
@@ -11077,7 +11186,7 @@ function buildBashDFG(tree) {
|
|
|
11077
11186
|
if (varNameNode) {
|
|
11078
11187
|
const varName = getNodeText(varNameNode);
|
|
11079
11188
|
if (varName && !varName.startsWith("?") && !varName.startsWith("#")) {
|
|
11080
|
-
let reachingDef = findReachingDef(varName, scopeStack);
|
|
11189
|
+
let reachingDef = positionalDef(varName, node, findReachingDef(varName, scopeStack));
|
|
11081
11190
|
if (reachingDef === null && !positionalParams.includes(varName)) {
|
|
11082
11191
|
const def = {
|
|
11083
11192
|
id: defIdCounter++,
|
|
@@ -11101,7 +11210,7 @@ function buildBashDFG(tree) {
|
|
|
11101
11210
|
const varNameNode = node.namedChildCount > 0 ? node.namedChild(0) : null;
|
|
11102
11211
|
if (varNameNode && varNameNode.type === "variable_name") {
|
|
11103
11212
|
const varName = getNodeText(varNameNode);
|
|
11104
|
-
let reachingDef = findReachingDef(varName, scopeStack);
|
|
11213
|
+
let reachingDef = positionalDef(varName, node, findReachingDef(varName, scopeStack));
|
|
11105
11214
|
if (reachingDef === null && !positionalParams.includes(varName)) {
|
|
11106
11215
|
const def = {
|
|
11107
11216
|
id: defIdCounter++,
|
|
@@ -11123,8 +11232,56 @@ function buildBashDFG(tree) {
|
|
|
11123
11232
|
}
|
|
11124
11233
|
});
|
|
11125
11234
|
const chains = computeChains(defs, uses);
|
|
11235
|
+
chains.push(...bashCallArgChains(tree, bashFnDefs, uses, positionalParams));
|
|
11126
11236
|
return { defs, uses, chains };
|
|
11127
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
|
+
}
|
|
11128
11285
|
function buildRustDFG(tree, cache) {
|
|
11129
11286
|
const defs = [];
|
|
11130
11287
|
const uses = [];
|
|
@@ -12360,6 +12517,195 @@ function parseDistributedSliceAttribute(attrItem) {
|
|
|
12360
12517
|
};
|
|
12361
12518
|
}
|
|
12362
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
|
+
|
|
12363
12709
|
// src/analysis/config-loader.ts
|
|
12364
12710
|
var DEFAULT_SOURCES = [
|
|
12365
12711
|
// HTTP Sources (Servlet API)
|
|
@@ -13061,6 +13407,10 @@ var DEFAULT_SOURCES = [
|
|
|
13061
13407
|
{ method: "get", class: "Redis", type: "db_input", severity: "medium", return_tainted: true, languages: ["javascript", "typescript"] },
|
|
13062
13408
|
{ method: "hget", class: "Redis", type: "db_input", severity: "medium", return_tainted: true, languages: ["javascript", "typescript"] },
|
|
13063
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"] },
|
|
13064
13414
|
{ method: "get", class: "Jedis", type: "db_input", severity: "medium", return_tainted: true, languages: ["java"] },
|
|
13065
13415
|
{ method: "hget", class: "Jedis", type: "db_input", severity: "medium", return_tainted: true, languages: ["java"] },
|
|
13066
13416
|
{ method: "mget", class: "Jedis", type: "db_input", severity: "medium", return_tainted: true, languages: ["java"] },
|
|
@@ -13240,7 +13590,9 @@ var TRUST_BOUNDARY_FRAMEWORK_SINKS = [
|
|
|
13240
13590
|
// on arg[1] (the *Cookie) catches struct-literal composite tainting.
|
|
13241
13591
|
{ method: "SetCookie", class: "http", type: "trust_boundary", cwe: "CWE-501", severity: "medium", arg_positions: [1], languages: ["go"] },
|
|
13242
13592
|
// gin: c.SetCookie(name, value, maxAge, path, domain, secure, httpOnly).
|
|
13243
|
-
{ 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"] }
|
|
13244
13596
|
];
|
|
13245
13597
|
var DEFAULT_SINKS = [
|
|
13246
13598
|
// SQL Injection (CWE-89)
|
|
@@ -13901,7 +14253,9 @@ var DEFAULT_SINKS = [
|
|
|
13901
14253
|
{ method: "addParametersToContext", class: "StaticParametersInterceptor", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
|
|
13902
14254
|
{ method: "build", class: "HttpParameters", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
|
|
13903
14255
|
// Cron expression parsing (DoS/ReDoS)
|
|
13904
|
-
|
|
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"] },
|
|
13905
14259
|
{ method: "isValid", class: "CronValidator", type: "code_injection", cwe: "CWE-94", severity: "high", arg_positions: [0] },
|
|
13906
14260
|
// ff4j feature flags
|
|
13907
14261
|
{ method: "check", class: "FF4j", type: "code_injection", cwe: "CWE-94", severity: "high", arg_positions: [0] },
|
|
@@ -13998,9 +14352,13 @@ var DEFAULT_SINKS = [
|
|
|
13998
14352
|
{ method: "selectNodes", class: "Node", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0] },
|
|
13999
14353
|
{ method: "selectSingleNode", class: "Node", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0] },
|
|
14000
14354
|
// XXE - XML External Entity (CWE-611)
|
|
14001
|
-
|
|
14002
|
-
|
|
14003
|
-
|
|
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"] },
|
|
14004
14362
|
{ method: "unmarshal", class: "Unmarshaller", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0] },
|
|
14005
14363
|
{ method: "newTransformer", class: "TransformerFactory", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0] },
|
|
14006
14364
|
{ method: "transform", class: "Transformer", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0] },
|
|
@@ -14255,6 +14613,46 @@ var DEFAULT_SINKS = [
|
|
|
14255
14613
|
{ method: "each", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], allow_unresolved_receiver: true },
|
|
14256
14614
|
{ method: "get", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], allow_unresolved_receiver: true },
|
|
14257
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"] },
|
|
14258
14656
|
// TypeORM QueryBuilder raw fragments (CWE-89). TypeORM's raw `.query(sql)`
|
|
14259
14657
|
// escape hatch is already covered by the classless `query` sink in the JS
|
|
14260
14658
|
// plugin (getBuiltinSinks), so it is NOT re-registered here — the dedup
|
|
@@ -14365,6 +14763,8 @@ var DEFAULT_SINKS = [
|
|
|
14365
14763
|
{ method: "get", class: "http", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
|
|
14366
14764
|
{ method: "request", class: "https", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
|
|
14367
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"] },
|
|
14368
14768
|
// needle library (used in NodeGoat)
|
|
14369
14769
|
{ method: "get", class: "needle", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
|
|
14370
14770
|
{ method: "post", class: "needle", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
|
|
@@ -14859,7 +15259,7 @@ var DEFAULT_SINKS = [
|
|
|
14859
15259
|
// receiver-taint or operator-LHS-taint tracking — the format string is the
|
|
14860
15260
|
// receiver, not an argument. Deferred to Sprint 6 (#86 follow-up).
|
|
14861
15261
|
// C-style: printf / fprintf / sprintf / snprintf via ctypes/cffi.
|
|
14862
|
-
{ 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"] },
|
|
14863
15263
|
{ method: "fprintf", type: "format_string", cwe: "CWE-134", severity: "high", arg_positions: [1], languages: ["python"] },
|
|
14864
15264
|
// Go: fmt.Sprintf/Printf/Fprintf/Errorf — format string is first/second arg
|
|
14865
15265
|
{ method: "Sprintf", class: "fmt", type: "format_string", cwe: "CWE-134", severity: "medium", arg_positions: [0], languages: ["go"] },
|
|
@@ -17117,6 +17517,198 @@ function constTableLookupDefIds(defs, code, language) {
|
|
|
17117
17517
|
return out2;
|
|
17118
17518
|
}
|
|
17119
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
|
+
|
|
17120
17712
|
// src/analysis/passes/language-sources-pass.ts
|
|
17121
17713
|
var JS_DOM_XSS_SINKS = [
|
|
17122
17714
|
{ pattern: /\.innerHTML\s*=/, type: "xss", cwe: "CWE-79", severity: "critical" },
|
|
@@ -17368,6 +17960,7 @@ var LanguageSourcesPass = class {
|
|
|
17368
17960
|
}
|
|
17369
17961
|
additionalSanitizers.push(...findBashRegexAllowlistSanitizers(code));
|
|
17370
17962
|
additionalSanitizers.push(...findBashRealpathPrefixGuardSanitizers(code));
|
|
17963
|
+
additionalSanitizers.push(...findBashVerifiedPackageDownloadSanitizers(code));
|
|
17371
17964
|
additionalSanitizers.push(...findBashShellQuoteSanitizers(code));
|
|
17372
17965
|
}
|
|
17373
17966
|
if (language === "go") {
|
|
@@ -17483,8 +18076,13 @@ var LanguageSourcesPass = class {
|
|
|
17483
18076
|
}
|
|
17484
18077
|
}
|
|
17485
18078
|
if (language === "rust") {
|
|
18079
|
+
additionalSanitizers.push(...findRustConstSliceMembershipGuardSanitizers(code));
|
|
17486
18080
|
additionalSanitizers.push(...findRustSetAllowlistGuardSanitizers(code));
|
|
17487
18081
|
additionalSanitizers.push(...findRustCanonicalizeGuardSanitizers(code));
|
|
18082
|
+
additionalSanitizers.push(
|
|
18083
|
+
...findRustNumericRangeCheckGuardSanitizers(code)
|
|
18084
|
+
);
|
|
18085
|
+
additionalSanitizers.push(...findRustPatInfoHttpOnlyResponseSanitizers(code));
|
|
17488
18086
|
additionalSanitizers.push(...findRustLiteralEqualityGuardSanitizers(code));
|
|
17489
18087
|
additionalSanitizers.push(...findRustArgvCommandSanitizers(code));
|
|
17490
18088
|
const rustMisconfigFindings = findRustPatternFindings(code, graph.ir.meta.file);
|
|
@@ -17542,6 +18140,9 @@ var LanguageSourcesPass = class {
|
|
|
17542
18140
|
additionalSanitizers.push(
|
|
17543
18141
|
...findJsCommandAllowlistGuardSanitizers(code)
|
|
17544
18142
|
);
|
|
18143
|
+
additionalSanitizers.push(
|
|
18144
|
+
...findJsNumericRangeCheckGuardSanitizers(code)
|
|
18145
|
+
);
|
|
17545
18146
|
for (const finding of findJsPatternFindings(code, graph.ir.meta.file)) {
|
|
17546
18147
|
ctx.addFinding(finding);
|
|
17547
18148
|
}
|
|
@@ -17575,6 +18176,12 @@ var LanguageSourcesPass = class {
|
|
|
17575
18176
|
)) {
|
|
17576
18177
|
ctx.addFinding(finding);
|
|
17577
18178
|
}
|
|
18179
|
+
for (const finding of findJsZipHeaderFailopenFindings(
|
|
18180
|
+
code,
|
|
18181
|
+
graph.ir.meta.file
|
|
18182
|
+
)) {
|
|
18183
|
+
ctx.addFinding(finding);
|
|
18184
|
+
}
|
|
17578
18185
|
for (const finding of findJsJsonParseBodyFindings(
|
|
17579
18186
|
code,
|
|
17580
18187
|
graph.ir.meta.file
|
|
@@ -17624,6 +18231,7 @@ var LanguageSourcesPass = class {
|
|
|
17624
18231
|
}
|
|
17625
18232
|
additionalSanitizers.push(...findJavaInlineCrlfStripLogSanitizers(code));
|
|
17626
18233
|
additionalSanitizers.push(...findCrlfWrapperFunctionSanitizers(code, "java"));
|
|
18234
|
+
additionalSanitizers.push(...findJavaTemplateDelimiterWrapperSanitizers(code));
|
|
17627
18235
|
additionalSanitizers.push(...findJavaArgvFormExecSanitizers(code));
|
|
17628
18236
|
for (const finding of findJavaPatternFindings(code, graph.ir.meta.file)) {
|
|
17629
18237
|
ctx.addFinding(finding);
|
|
@@ -18833,9 +19441,9 @@ function findGraphqlResolverArgSources(sourceCode) {
|
|
|
18833
19441
|
maps.push(open + inline.index + inline[0].length - 1);
|
|
18834
19442
|
continue;
|
|
18835
19443
|
}
|
|
18836
|
-
const
|
|
18837
|
-
if (!
|
|
18838
|
-
const ident =
|
|
19444
|
+
const named2 = opts.match(/\bresolvers\s*(?::\s*(\w+))?\s*[,}]/);
|
|
19445
|
+
if (!named2) continue;
|
|
19446
|
+
const ident = named2[1] ?? "resolvers";
|
|
18839
19447
|
const decl = new RegExp(
|
|
18840
19448
|
`\\b(?:const|let|var)\\s+${ident}\\s*(?::\\s*[A-Za-z_$][\\w$]*(?:\\s*\\.\\s*[A-Za-z_$][\\w$]*)*(?:\\s*<[^<>=]*(?:<[^<>=]*>[^<>=]*)*>)?\\s*)?=\\s*\\{`
|
|
18841
19449
|
).exec(sourceCode);
|
|
@@ -18998,15 +19606,15 @@ function evaluatePythonConstExpression(expr, consts) {
|
|
|
18998
19606
|
if (parts2.length !== 1) return void 0;
|
|
18999
19607
|
const matched = expr.match(/\d+|[A-Za-z_][A-Za-z0-9_]*|\/\/|[-+*/%()]|\S/g);
|
|
19000
19608
|
if (!matched) return void 0;
|
|
19001
|
-
const
|
|
19609
|
+
const tokens2 = matched;
|
|
19002
19610
|
let pos = 0;
|
|
19003
|
-
const peek = () =>
|
|
19611
|
+
const peek = () => tokens2[pos];
|
|
19004
19612
|
const parseFactor = () => {
|
|
19005
|
-
const tok =
|
|
19613
|
+
const tok = tokens2[pos++];
|
|
19006
19614
|
if (tok === void 0) return void 0;
|
|
19007
19615
|
if (tok === "(") {
|
|
19008
19616
|
const inner = parseSum();
|
|
19009
|
-
if (
|
|
19617
|
+
if (tokens2[pos++] !== ")") return void 0;
|
|
19010
19618
|
return inner;
|
|
19011
19619
|
}
|
|
19012
19620
|
if (tok === "-") {
|
|
@@ -19021,7 +19629,7 @@ function evaluatePythonConstExpression(expr, consts) {
|
|
|
19021
19629
|
let value = parseFactor();
|
|
19022
19630
|
if (value === void 0) return void 0;
|
|
19023
19631
|
while (peek() === "*" || peek() === "/" || peek() === "//" || peek() === "%") {
|
|
19024
|
-
const op =
|
|
19632
|
+
const op = tokens2[pos++];
|
|
19025
19633
|
const rhs = parseFactor();
|
|
19026
19634
|
if (rhs === void 0) return void 0;
|
|
19027
19635
|
if ((op === "/" || op === "//" || op === "%") && rhs === 0) return void 0;
|
|
@@ -19036,7 +19644,7 @@ function evaluatePythonConstExpression(expr, consts) {
|
|
|
19036
19644
|
let value = parseTerm();
|
|
19037
19645
|
if (value === void 0) return void 0;
|
|
19038
19646
|
while (peek() === "+" || peek() === "-") {
|
|
19039
|
-
const op =
|
|
19647
|
+
const op = tokens2[pos++];
|
|
19040
19648
|
const rhs = parseTerm();
|
|
19041
19649
|
if (rhs === void 0) return void 0;
|
|
19042
19650
|
value = op === "+" ? value + rhs : value - rhs;
|
|
@@ -19044,7 +19652,7 @@ function evaluatePythonConstExpression(expr, consts) {
|
|
|
19044
19652
|
return value;
|
|
19045
19653
|
}
|
|
19046
19654
|
const result = parseSum();
|
|
19047
|
-
return pos ===
|
|
19655
|
+
return pos === tokens2.length ? result : void 0;
|
|
19048
19656
|
}
|
|
19049
19657
|
function normalizeListIndex(index, length) {
|
|
19050
19658
|
const resolved = index < 0 ? length + index : index;
|
|
@@ -19092,6 +19700,140 @@ function computeDeadPythonBranchLines(lines) {
|
|
|
19092
19700
|
}
|
|
19093
19701
|
return dead;
|
|
19094
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
|
+
}
|
|
19095
19837
|
function buildPythonTaintedVars(sourceCode) {
|
|
19096
19838
|
const tainted = /* @__PURE__ */ new Map();
|
|
19097
19839
|
const containerTainted = /* @__PURE__ */ new Map();
|
|
@@ -19811,6 +20553,82 @@ function buildRustTaintedVars(sourceCode, seedVars) {
|
|
|
19811
20553
|
}
|
|
19812
20554
|
return derived;
|
|
19813
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
|
+
}
|
|
19814
20632
|
function joinWrappedJavaStatement(lines, i2) {
|
|
19815
20633
|
const strip = (l) => l.replace(/"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)'/g, '""').replace(/\/\/.*$/, "");
|
|
19816
20634
|
const first = strip(lines[i2]);
|
|
@@ -19832,12 +20650,19 @@ function joinWrappedJavaStatement(lines, i2) {
|
|
|
19832
20650
|
}
|
|
19833
20651
|
return null;
|
|
19834
20652
|
}
|
|
19835
|
-
function buildJavaTaintedVars(sourceCode, seedVars) {
|
|
20653
|
+
function buildJavaTaintedVars(sourceCode, seedVars, deadBranchLines, options) {
|
|
19836
20654
|
const derived = /* @__PURE__ */ new Map();
|
|
19837
20655
|
const knownTainted = new Set(seedVars);
|
|
19838
|
-
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();
|
|
19839
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*$/;
|
|
19840
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*$/;
|
|
19841
20666
|
const JAVA_KEYWORDS = /* @__PURE__ */ new Set([
|
|
19842
20667
|
"if",
|
|
19843
20668
|
"else",
|
|
@@ -19873,10 +20698,23 @@ function buildJavaTaintedVars(sourceCode, seedVars) {
|
|
|
19873
20698
|
while (changed && guard < lines.length + 2) {
|
|
19874
20699
|
changed = false;
|
|
19875
20700
|
guard++;
|
|
20701
|
+
listElems.clear();
|
|
20702
|
+
mapSlots.clear();
|
|
20703
|
+
intConsts.clear();
|
|
19876
20704
|
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
20705
|
+
if (deadBranchLines !== void 0 && dead.has(i2)) continue;
|
|
19877
20706
|
const line = lines[i2];
|
|
19878
20707
|
const trimmed = line.trimStart();
|
|
19879
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
|
+
}
|
|
19880
20718
|
const arrayCopyRe = /\bSystem\.arraycopy\s*\(\s*([A-Za-z_]\w*)\s*,\s*[^,]+\s*,\s*([A-Za-z_]\w*)\s*,/;
|
|
19881
20719
|
const acMatch = arrayCopyRe.exec(line);
|
|
19882
20720
|
if (acMatch) {
|
|
@@ -19906,17 +20744,79 @@ function buildJavaTaintedVars(sourceCode, seedVars) {
|
|
|
19906
20744
|
let assignMatch = !declMatch ? assignRe.exec(statement) : null;
|
|
19907
20745
|
if (!declMatch && !assignMatch) {
|
|
19908
20746
|
const joined = joinWrappedJavaStatement(lines, i2);
|
|
19909
|
-
if (joined
|
|
19910
|
-
|
|
19911
|
-
|
|
19912
|
-
|
|
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;
|
|
19913
20796
|
}
|
|
19914
20797
|
const m = declMatch ?? assignMatch;
|
|
19915
20798
|
if (!m) continue;
|
|
19916
20799
|
const lhs = m[1];
|
|
19917
|
-
|
|
20800
|
+
let rhs = m[2];
|
|
19918
20801
|
if (JAVA_KEYWORDS.has(lhs)) continue;
|
|
19919
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
|
+
}
|
|
19920
20820
|
const rhsCode = rhs.replace(/\$@?"(?:[^"\\]|\\.)*"/g, (lit) => " " + [...lit.matchAll(/\{([^}]*)\}/g)].map((x) => x[1]).join(" ") + " ").replace(/@?"(?:[^"\\]|\\.)*"/g, " ").replace(/'(?:[^'\\]|\\.)'/g, " ");
|
|
19921
20821
|
const rhsIdents = rhsCode.match(/[\p{L}\p{N}_]+/gu);
|
|
19922
20822
|
const ref = rhsIdents !== null && rhsIdents.some((t) => knownTainted.has(t)) || complexTaintedRes.length > 0 && complexTaintedRes.some((re) => re.test(rhsCode));
|
|
@@ -19968,8 +20868,11 @@ function findBashTaintSources(sourceCode, dfg) {
|
|
|
19968
20868
|
const insideFunction = braceDepth > 0;
|
|
19969
20869
|
if (!insideFunction) {
|
|
19970
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, "''");
|
|
19971
20874
|
let m;
|
|
19972
|
-
while ((m = positionalRe.exec(
|
|
20875
|
+
while ((m = positionalRe.exec(topLevelText)) !== null) {
|
|
19973
20876
|
const param = m[1] ?? m[2];
|
|
19974
20877
|
const alreadyExists = sources.some((s) => s.line === lineNumber && s.variable === param);
|
|
19975
20878
|
if (!alreadyExists) {
|
|
@@ -20025,10 +20928,10 @@ function findBashTaintSources(sourceCode, dfg) {
|
|
|
20025
20928
|
if (readMatch) {
|
|
20026
20929
|
const argStr = readMatch[1].trim();
|
|
20027
20930
|
const noRedirect = argStr.replace(/\s*<[^<].*$/, "").trim();
|
|
20028
|
-
const
|
|
20931
|
+
const tokens2 = noRedirect.split(/\s+/);
|
|
20029
20932
|
const varNames = [];
|
|
20030
|
-
for (let ti = 0; ti <
|
|
20031
|
-
const tok =
|
|
20933
|
+
for (let ti = 0; ti < tokens2.length; ti++) {
|
|
20934
|
+
const tok = tokens2[ti];
|
|
20032
20935
|
if (!tok) continue;
|
|
20033
20936
|
if (tok.startsWith("-")) {
|
|
20034
20937
|
if (/^-[antpidu]$|^-N$/.test(tok)) ti++;
|
|
@@ -20053,10 +20956,10 @@ function findBashTaintSources(sourceCode, dfg) {
|
|
|
20053
20956
|
const mapfileMatch = /^(?:mapfile|readarray)\s*\(/.test(trimmed) ? null : trimmed.match(/^(?:mapfile|readarray)\b([^#(]*)$/);
|
|
20054
20957
|
if (mapfileMatch) {
|
|
20055
20958
|
const argStr = mapfileMatch[1].trim();
|
|
20056
|
-
const
|
|
20959
|
+
const tokens2 = argStr.split(/\s+/);
|
|
20057
20960
|
const varNames = [];
|
|
20058
|
-
for (let ti = 0; ti <
|
|
20059
|
-
const tok =
|
|
20961
|
+
for (let ti = 0; ti < tokens2.length; ti++) {
|
|
20962
|
+
const tok = tokens2[ti];
|
|
20060
20963
|
if (!tok) continue;
|
|
20061
20964
|
if (tok.startsWith("-")) {
|
|
20062
20965
|
if (/^-[cCnOsud]$/.test(tok)) ti++;
|
|
@@ -20441,6 +21344,30 @@ function findBashRealpathPrefixGuardSanitizers(code) {
|
|
|
20441
21344
|
}
|
|
20442
21345
|
return sanitizers;
|
|
20443
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
|
+
}
|
|
20444
21371
|
function findGoMapAllowlistGuardSanitizers(code) {
|
|
20445
21372
|
const sanitizers = [];
|
|
20446
21373
|
const lines = code.split("\n");
|
|
@@ -20596,6 +21523,160 @@ function findPythonNetlocAllowlistGuardSanitizers(code) {
|
|
|
20596
21523
|
}
|
|
20597
21524
|
return sanitizers;
|
|
20598
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
|
+
}
|
|
20599
21680
|
function findPythonRangeCheckGuardSanitizers(code) {
|
|
20600
21681
|
const sanitizers = [];
|
|
20601
21682
|
const lines = code.split("\n");
|
|
@@ -21182,6 +22263,127 @@ function findPythonDefusedXmlSanitizers(code) {
|
|
|
21182
22263
|
}
|
|
21183
22264
|
return sanitizers;
|
|
21184
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
|
+
}
|
|
21185
22387
|
function findRustSetAllowlistGuardSanitizers(code) {
|
|
21186
22388
|
const sanitizers = [];
|
|
21187
22389
|
const lines = code.split("\n");
|
|
@@ -21338,6 +22540,13 @@ function findJsCsvFormulaPrefixSanitizers(code) {
|
|
|
21338
22540
|
}
|
|
21339
22541
|
return sanitizers;
|
|
21340
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
|
+
}
|
|
21341
22550
|
function findJsWrapperFunctionSanitizers(code) {
|
|
21342
22551
|
const sanitizers = [];
|
|
21343
22552
|
const lines = code.split("\n");
|
|
@@ -21357,8 +22566,12 @@ function findJsWrapperFunctionSanitizers(code) {
|
|
|
21357
22566
|
kinds.add("external_taint_escape");
|
|
21358
22567
|
}
|
|
21359
22568
|
if (crlfCharClass.test(body2)) {
|
|
21360
|
-
|
|
21361
|
-
|
|
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
|
+
}
|
|
21362
22575
|
}
|
|
21363
22576
|
if (kinds.size > 0) wrappers.set(name2, kinds);
|
|
21364
22577
|
}
|
|
@@ -22160,6 +23373,74 @@ function javaBodyStripsCrAndLf(body2) {
|
|
|
22160
23373
|
}
|
|
22161
23374
|
return cr && lf;
|
|
22162
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
|
+
}
|
|
22163
23444
|
function findCrlfWrapperFunctionSanitizers(code, language) {
|
|
22164
23445
|
const sanitizers = [];
|
|
22165
23446
|
const lines = code.split("\n");
|
|
@@ -24575,6 +25856,79 @@ function findJsLdapInjectionFindings(code, file) {
|
|
|
24575
25856
|
snippet: line.trim()
|
|
24576
25857
|
});
|
|
24577
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
|
+
}
|
|
24578
25932
|
return findings;
|
|
24579
25933
|
}
|
|
24580
25934
|
function findGoLdapInjectionFindings(code, file) {
|
|
@@ -25077,6 +26431,40 @@ function findGoGobDeserializationFindings(code, file) {
|
|
|
25077
26431
|
}
|
|
25078
26432
|
return findings;
|
|
25079
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
|
+
}
|
|
25080
26468
|
function findJsJsonParseBodyFindings(code, file) {
|
|
25081
26469
|
const findings = [];
|
|
25082
26470
|
if (typeof code !== "string" || code.length === 0) return findings;
|
|
@@ -25110,12 +26498,13 @@ function findJsJsonParseBodyFindings(code, file) {
|
|
|
25110
26498
|
}
|
|
25111
26499
|
const callRe = /\bJSON\s*\.\s*parse\s*\(\s*([^)]+?)\s*\)/g;
|
|
25112
26500
|
const seen = /* @__PURE__ */ new Set();
|
|
26501
|
+
const codeLines = maskJsLiteralsAndComments(code).split("\n");
|
|
25113
26502
|
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
25114
26503
|
const line = lines[i2];
|
|
25115
|
-
|
|
26504
|
+
const codeLine = codeLines[i2] ?? "";
|
|
25116
26505
|
callRe.lastIndex = 0;
|
|
25117
26506
|
let m;
|
|
25118
|
-
while ((m = callRe.exec(
|
|
26507
|
+
while ((m = callRe.exec(codeLine)) !== null) {
|
|
25119
26508
|
const arg = m[1];
|
|
25120
26509
|
let tainted = reqBodyRe.test(arg);
|
|
25121
26510
|
if (!tainted) {
|
|
@@ -25535,198 +26924,6 @@ function findJavaSpringReturnXSSSinks(code, types, sources, xssSanitizerLines) {
|
|
|
25535
26924
|
return out2;
|
|
25536
26925
|
}
|
|
25537
26926
|
|
|
25538
|
-
// src/analysis/js-crlf-strip.ts
|
|
25539
|
-
var MAX_PATTERN_LENGTH = 200;
|
|
25540
|
-
var NEUTRAL_AFTER_REPLACE = /* @__PURE__ */ new Set([
|
|
25541
|
-
"replace",
|
|
25542
|
-
"replaceAll",
|
|
25543
|
-
"trim",
|
|
25544
|
-
"trimStart",
|
|
25545
|
-
"trimEnd",
|
|
25546
|
-
"toString",
|
|
25547
|
-
"toLowerCase",
|
|
25548
|
-
"toUpperCase",
|
|
25549
|
-
"slice",
|
|
25550
|
-
"substring"
|
|
25551
|
-
]);
|
|
25552
|
-
var PROBE = "a\r\rb\n\nc\r\nd\n\re x ";
|
|
25553
|
-
function readQuoted(s, i2) {
|
|
25554
|
-
const q = s[i2];
|
|
25555
|
-
if (q !== '"' && q !== "'" && q !== "`") return null;
|
|
25556
|
-
let out2 = "";
|
|
25557
|
-
for (let j = i2 + 1; j < s.length; j++) {
|
|
25558
|
-
const c = s[j];
|
|
25559
|
-
if (c === q) return { value: out2, end: j + 1 };
|
|
25560
|
-
if (q === "`" && c === "$" && s[j + 1] === "{") return null;
|
|
25561
|
-
if (c === "\\") {
|
|
25562
|
-
const n = s[j + 1];
|
|
25563
|
-
if (n === void 0) return null;
|
|
25564
|
-
switch (n) {
|
|
25565
|
-
case "n":
|
|
25566
|
-
out2 += "\n";
|
|
25567
|
-
j++;
|
|
25568
|
-
continue;
|
|
25569
|
-
case "r":
|
|
25570
|
-
out2 += "\r";
|
|
25571
|
-
j++;
|
|
25572
|
-
continue;
|
|
25573
|
-
case "t":
|
|
25574
|
-
out2 += " ";
|
|
25575
|
-
j++;
|
|
25576
|
-
continue;
|
|
25577
|
-
case "0":
|
|
25578
|
-
out2 += "\0";
|
|
25579
|
-
j++;
|
|
25580
|
-
continue;
|
|
25581
|
-
case "x": {
|
|
25582
|
-
const h = /^[0-9a-fA-F]{2}/.exec(s.slice(j + 2, j + 4));
|
|
25583
|
-
if (!h) return null;
|
|
25584
|
-
out2 += String.fromCharCode(parseInt(h[0], 16));
|
|
25585
|
-
j += 3;
|
|
25586
|
-
continue;
|
|
25587
|
-
}
|
|
25588
|
-
case "u": {
|
|
25589
|
-
const h = /^[0-9a-fA-F]{4}/.exec(s.slice(j + 2, j + 6));
|
|
25590
|
-
if (!h) return null;
|
|
25591
|
-
out2 += String.fromCharCode(parseInt(h[0], 16));
|
|
25592
|
-
j += 5;
|
|
25593
|
-
continue;
|
|
25594
|
-
}
|
|
25595
|
-
default:
|
|
25596
|
-
out2 += n;
|
|
25597
|
-
j++;
|
|
25598
|
-
continue;
|
|
25599
|
-
}
|
|
25600
|
-
}
|
|
25601
|
-
if (c === "\n" && q !== "`") return null;
|
|
25602
|
-
out2 += c;
|
|
25603
|
-
}
|
|
25604
|
-
return null;
|
|
25605
|
-
}
|
|
25606
|
-
function readRegexLiteral(s, i2) {
|
|
25607
|
-
if (s[i2] !== "/") return null;
|
|
25608
|
-
let inClass = false;
|
|
25609
|
-
for (let j = i2 + 1; j < s.length; j++) {
|
|
25610
|
-
const c = s[j];
|
|
25611
|
-
if (c === "\n") return null;
|
|
25612
|
-
if (c === "\\") {
|
|
25613
|
-
j++;
|
|
25614
|
-
continue;
|
|
25615
|
-
}
|
|
25616
|
-
if (inClass) {
|
|
25617
|
-
if (c === "]") inClass = false;
|
|
25618
|
-
continue;
|
|
25619
|
-
}
|
|
25620
|
-
if (c === "[") {
|
|
25621
|
-
inClass = true;
|
|
25622
|
-
continue;
|
|
25623
|
-
}
|
|
25624
|
-
if (c === "/") {
|
|
25625
|
-
const flags2 = /^[dgimsuvy]*/.exec(s.slice(j + 1))[0];
|
|
25626
|
-
return { source: s.slice(i2 + 1, j), flags: flags2, end: j + 1 + flags2.length };
|
|
25627
|
-
}
|
|
25628
|
-
}
|
|
25629
|
-
return null;
|
|
25630
|
-
}
|
|
25631
|
-
var skipWs = (s, i2) => {
|
|
25632
|
-
while (i2 < s.length && /\s/.test(s[i2])) i2++;
|
|
25633
|
-
return i2;
|
|
25634
|
-
};
|
|
25635
|
-
function parseSteps(chainText) {
|
|
25636
|
-
const steps = [];
|
|
25637
|
-
const callRe = /\.\s*(replaceAll|replace)\s*\(/g;
|
|
25638
|
-
let m;
|
|
25639
|
-
while ((m = callRe.exec(chainText)) !== null) {
|
|
25640
|
-
const kind = m[1];
|
|
25641
|
-
let i2 = skipWs(chainText, m.index + m[0].length);
|
|
25642
|
-
let pattern;
|
|
25643
|
-
const rx = readRegexLiteral(chainText, i2);
|
|
25644
|
-
if (rx) {
|
|
25645
|
-
if (rx.source.length > MAX_PATTERN_LENGTH) return null;
|
|
25646
|
-
try {
|
|
25647
|
-
pattern = new RegExp(rx.source, rx.flags);
|
|
25648
|
-
} catch {
|
|
25649
|
-
return null;
|
|
25650
|
-
}
|
|
25651
|
-
i2 = rx.end;
|
|
25652
|
-
} else {
|
|
25653
|
-
const str = readQuoted(chainText, i2);
|
|
25654
|
-
if (!str) return null;
|
|
25655
|
-
pattern = str.value;
|
|
25656
|
-
i2 = str.end;
|
|
25657
|
-
}
|
|
25658
|
-
i2 = skipWs(chainText, i2);
|
|
25659
|
-
if (chainText[i2] !== ",") return null;
|
|
25660
|
-
i2 = skipWs(chainText, i2 + 1);
|
|
25661
|
-
const rep = readQuoted(chainText, i2);
|
|
25662
|
-
if (!rep) return null;
|
|
25663
|
-
i2 = skipWs(chainText, rep.end);
|
|
25664
|
-
if (chainText[i2] !== ")") return null;
|
|
25665
|
-
steps.push({ kind, pattern, replacement: rep.value });
|
|
25666
|
-
callRe.lastIndex = i2 + 1;
|
|
25667
|
-
}
|
|
25668
|
-
return steps;
|
|
25669
|
-
}
|
|
25670
|
-
function run2(steps, input) {
|
|
25671
|
-
let s = input;
|
|
25672
|
-
try {
|
|
25673
|
-
for (const st of steps) {
|
|
25674
|
-
if (st.kind === "replaceAll" && st.pattern instanceof RegExp && !st.pattern.global) return null;
|
|
25675
|
-
s = st.kind === "replaceAll" ? s.replaceAll(st.pattern, st.replacement) : s.replace(st.pattern, st.replacement);
|
|
25676
|
-
}
|
|
25677
|
-
} catch {
|
|
25678
|
-
return null;
|
|
25679
|
-
}
|
|
25680
|
-
return s;
|
|
25681
|
-
}
|
|
25682
|
-
function jsReplaceChainVerdict(chainText) {
|
|
25683
|
-
const steps = parseSteps(chainText);
|
|
25684
|
-
if (!steps || steps.length === 0) return "undecided";
|
|
25685
|
-
const first = chainText.search(/\.\s*(?:replaceAll|replace)\s*\(/);
|
|
25686
|
-
for (const seg of chainText.slice(first + 1).matchAll(/\.\s*([A-Za-z_$][\w$]*)\s*\(/g)) {
|
|
25687
|
-
if (!NEUTRAL_AFTER_REPLACE.has(seg[1])) return "undecided";
|
|
25688
|
-
}
|
|
25689
|
-
const out2 = run2(steps, PROBE);
|
|
25690
|
-
if (out2 === null) return "undecided";
|
|
25691
|
-
return out2.includes("\r") || out2.includes("\n") ? "leaves-crlf" : "strips-crlf";
|
|
25692
|
-
}
|
|
25693
|
-
function jsChainTextAt(sourceLines, line, column) {
|
|
25694
|
-
const text = [(sourceLines[line - 1] ?? "").slice(column), ...sourceLines.slice(line, line + 7)].join("\n");
|
|
25695
|
-
let depth = 0;
|
|
25696
|
-
let prevSig = "";
|
|
25697
|
-
let i2 = 0;
|
|
25698
|
-
for (; i2 < text.length; i2++) {
|
|
25699
|
-
const c = text[i2];
|
|
25700
|
-
if (c === '"' || c === "'" || c === "`") {
|
|
25701
|
-
for (i2++; i2 < text.length && text[i2] !== c; i2++) if (text[i2] === "\\") i2++;
|
|
25702
|
-
prevSig = c;
|
|
25703
|
-
continue;
|
|
25704
|
-
}
|
|
25705
|
-
if (c === "/" && depth > 0 && (prevSig === "(" || prevSig === ",")) {
|
|
25706
|
-
const rx = readRegexLiteral(text, i2);
|
|
25707
|
-
if (rx) {
|
|
25708
|
-
i2 = rx.end - 1;
|
|
25709
|
-
prevSig = "/";
|
|
25710
|
-
continue;
|
|
25711
|
-
}
|
|
25712
|
-
}
|
|
25713
|
-
if (c === "(" || c === "[" || c === "{") {
|
|
25714
|
-
depth++;
|
|
25715
|
-
prevSig = c;
|
|
25716
|
-
continue;
|
|
25717
|
-
}
|
|
25718
|
-
if (c === ")" || c === "]" || c === "}") {
|
|
25719
|
-
if (depth === 0) break;
|
|
25720
|
-
depth--;
|
|
25721
|
-
prevSig = c;
|
|
25722
|
-
continue;
|
|
25723
|
-
}
|
|
25724
|
-
if (depth === 0 && !/[\w$.\s]/.test(c)) break;
|
|
25725
|
-
if (!/\s/.test(c)) prevSig = c;
|
|
25726
|
-
}
|
|
25727
|
-
return text.slice(0, i2);
|
|
25728
|
-
}
|
|
25729
|
-
|
|
25730
26927
|
// src/analysis/constant-propagation/patterns.ts
|
|
25731
26928
|
var TAINT_PATTERNS = [
|
|
25732
26929
|
// HTTP Sources (Servlet API)
|
|
@@ -26124,13 +27321,35 @@ function dropCSharpObjectCarriedWaypoints(sinks, calls) {
|
|
|
26124
27321
|
function analyzeTaint(calls, types, config = getDefaultConfig(), typeHierarchy, language, code, imports) {
|
|
26125
27322
|
const sourceLines = code !== void 0 ? code.split("\n") : void 0;
|
|
26126
27323
|
const sources = findSources(calls, types, config.sources, sourceLines, language, imports);
|
|
26127
|
-
let sinkPatterns =
|
|
27324
|
+
let sinkPatterns = config.sinks.filter((p) => sinkPatternImportApplies(p, imports));
|
|
27325
|
+
sinkPatterns = expandPromisifyAliases(sinkPatterns, sourceLines, language);
|
|
26128
27326
|
sinkPatterns = expandIndirectEvalAliases(sinkPatterns, sourceLines, language);
|
|
26129
27327
|
const sinks = findSinks(calls, sinkPatterns, typeHierarchy, language, sourceLines, types);
|
|
26130
27328
|
const gatedSinks = language === "csharp" ? dropCSharpObjectCarriedWaypoints(sinks, calls) : sinks;
|
|
26131
27329
|
const sanitizers = findSanitizers(calls, types, config.sanitizers, sourceLines, language);
|
|
26132
27330
|
return { sources, sinks: gatedSinks, sanitizers };
|
|
26133
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
|
+
}
|
|
26134
27353
|
function sinkPatternAppliesTo(pattern, language) {
|
|
26135
27354
|
if (language === void 0) return true;
|
|
26136
27355
|
if (pattern.languages && pattern.languages.length > 0 && !pattern.languages.includes(language)) {
|
|
@@ -26548,6 +27767,46 @@ function isInterproceduralTaintableType(typeName, language) {
|
|
|
26548
27767
|
const leaf = baseType.split(".").pop() ?? baseType;
|
|
26549
27768
|
if (csharpTaintable.includes(baseType) || csharpTaintable.includes(leaf)) return true;
|
|
26550
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
|
+
}
|
|
26551
27810
|
if (typeName.endsWith("[]")) {
|
|
26552
27811
|
const elementType = typeName.slice(0, -2);
|
|
26553
27812
|
if (elementType === "String" || elementType === "Object" || elementType === "byte") {
|
|
@@ -26662,6 +27921,31 @@ function isSafeGoExecCommandCall(call, pattern, language) {
|
|
|
26662
27921
|
return false;
|
|
26663
27922
|
}
|
|
26664
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
|
+
}
|
|
26665
27949
|
return true;
|
|
26666
27950
|
}
|
|
26667
27951
|
function isSafeJSChildProcessCall(call, pattern, language) {
|
|
@@ -27065,6 +28349,29 @@ function isRegexReceiver(receiver, sourceLines) {
|
|
|
27065
28349
|
const decl = new RegExp(`(?:const|let|var)\\s+${escapeRe(r)}\\s*=\\s*(?:/|(?:new\\s+)?RegExp\\s*\\()`);
|
|
27066
28350
|
return sourceLines.some((l) => decl.test(l));
|
|
27067
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
|
+
}
|
|
27068
28375
|
function isDeclaredFunctionReference(arg, sourceLines) {
|
|
27069
28376
|
if (arg.literal !== null && arg.literal !== void 0) return false;
|
|
27070
28377
|
if (!sourceLines) return false;
|
|
@@ -27269,6 +28576,12 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types)
|
|
|
27269
28576
|
if (isParameterizedQueryCall(call, pattern)) {
|
|
27270
28577
|
continue;
|
|
27271
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
|
+
}
|
|
27272
28585
|
if (isSafePythonSubprocessCall(call, pattern, language)) {
|
|
27273
28586
|
continue;
|
|
27274
28587
|
}
|
|
@@ -27326,9 +28639,15 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types)
|
|
|
27326
28639
|
if (isArgumentlessJsResponseWriter(call, pattern, language)) {
|
|
27327
28640
|
continue;
|
|
27328
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
|
+
}
|
|
27329
28645
|
if ((pattern.type === "command_injection" || pattern.type === "sql_injection") && call.method_name === "exec" && (language === "javascript" || language === "typescript") && isRegexReceiver(call.receiver, sourceLines)) {
|
|
27330
28646
|
continue;
|
|
27331
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
|
+
}
|
|
27332
28651
|
if (pattern.type === "code_injection" && language === "python" && call.method_name === "compile" && call.receiver && call.receiver !== "builtins") {
|
|
27333
28652
|
continue;
|
|
27334
28653
|
}
|
|
@@ -27345,7 +28664,7 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types)
|
|
|
27345
28664
|
continue;
|
|
27346
28665
|
}
|
|
27347
28666
|
const location = formatCallLocation(call);
|
|
27348
|
-
const key = `${location}:${call.location.line}:${pattern.cwe}`;
|
|
28667
|
+
const key = `${location}:${call.location.line}:${call.location.column}:${pattern.cwe}`;
|
|
27349
28668
|
const confidence = calculateSinkConfidence(call, pattern);
|
|
27350
28669
|
const existing = sinkMap.get(key);
|
|
27351
28670
|
if (!existing || confidence > existing.confidence) {
|
|
@@ -27356,6 +28675,7 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types)
|
|
|
27356
28675
|
cwe: pattern.cwe,
|
|
27357
28676
|
location,
|
|
27358
28677
|
line: call.location.line,
|
|
28678
|
+
callColumn: call.location.column,
|
|
27359
28679
|
confidence,
|
|
27360
28680
|
method: call.method_name,
|
|
27361
28681
|
argPositions: pattern.arg_positions,
|
|
@@ -27393,6 +28713,15 @@ function matchesSourcePattern(call, pattern, language, types, imports) {
|
|
|
27393
28713
|
return false;
|
|
27394
28714
|
}
|
|
27395
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
|
+
}
|
|
27396
28725
|
return pattern.return_tainted === true;
|
|
27397
28726
|
}
|
|
27398
28727
|
return false;
|
|
@@ -27486,6 +28815,7 @@ var SAFE_RECEIVERS_BY_METHOD = {
|
|
|
27486
28815
|
"database",
|
|
27487
28816
|
"sqlite",
|
|
27488
28817
|
"conn",
|
|
28818
|
+
"con",
|
|
27489
28819
|
"connection",
|
|
27490
28820
|
"client",
|
|
27491
28821
|
"pool",
|
|
@@ -27762,7 +29092,7 @@ function matchesSinkPattern(call, pattern, typeHierarchy, language) {
|
|
|
27762
29092
|
}
|
|
27763
29093
|
}
|
|
27764
29094
|
}
|
|
27765
|
-
if (!pattern.class && call.receiver) {
|
|
29095
|
+
if (!pattern.class && call.receiver && !pattern.requires_import) {
|
|
27766
29096
|
if (isKnownSafeReceiverForMethod(call.receiver, call.method_name, pattern.type)) {
|
|
27767
29097
|
return false;
|
|
27768
29098
|
}
|
|
@@ -28495,6 +29825,8 @@ function javaReplaceChainCredit(chainText) {
|
|
|
28495
29825
|
let lf = false;
|
|
28496
29826
|
let lt = false;
|
|
28497
29827
|
let gt = false;
|
|
29828
|
+
let openBrace = false;
|
|
29829
|
+
let closeBrace = false;
|
|
28498
29830
|
for (const m of chainText.matchAll(JAVA_REPLACE_ARGS_RE)) {
|
|
28499
29831
|
const kind = m[1];
|
|
28500
29832
|
const rawFrom = m[2] ?? m[3] ?? "";
|
|
@@ -28505,10 +29837,13 @@ function javaReplaceChainCredit(chainText) {
|
|
|
28505
29837
|
if (cov.lf) lf = true;
|
|
28506
29838
|
if (from === "<" && to.startsWith("&")) lt = true;
|
|
28507
29839
|
if (from === ">" && to.startsWith("&")) gt = true;
|
|
29840
|
+
if (from === "{") openBrace = true;
|
|
29841
|
+
if (from === "}") closeBrace = true;
|
|
28508
29842
|
}
|
|
28509
29843
|
const out2 = [];
|
|
28510
29844
|
if (cr && lf) out2.push("log_injection");
|
|
28511
29845
|
if (lt && gt) out2.push("xss");
|
|
29846
|
+
if (openBrace && closeBrace) out2.push("code_injection");
|
|
28512
29847
|
return out2;
|
|
28513
29848
|
}
|
|
28514
29849
|
function matchesSanitizerPattern(call, pattern) {
|
|
@@ -30220,7 +31555,7 @@ var AnalysisPipeline = class {
|
|
|
30220
31555
|
this.passes.push(pass);
|
|
30221
31556
|
return this;
|
|
30222
31557
|
}
|
|
30223
|
-
run(graph, code, language, config, resources) {
|
|
31558
|
+
run(graph, code, language, config, resources, limit) {
|
|
30224
31559
|
const results = /* @__PURE__ */ new Map();
|
|
30225
31560
|
const findings = [];
|
|
30226
31561
|
const context = {
|
|
@@ -30249,6 +31584,7 @@ var AnalysisPipeline = class {
|
|
|
30249
31584
|
};
|
|
30250
31585
|
const timingEnabled = globalThis.__circleIrPassTiming === true;
|
|
30251
31586
|
for (const pass of this.passes) {
|
|
31587
|
+
if (limit && Date.now() > limit.deadline) throw limit.onExpired(pass.name);
|
|
30252
31588
|
if (timingEnabled) {
|
|
30253
31589
|
console.error(`[pass-start] ${pass.name} file=${graph.ir.meta.file}`);
|
|
30254
31590
|
}
|
|
@@ -31111,10 +32447,49 @@ var JAVA_LANG_EXCEPTION_TYPES = /* @__PURE__ */ new Set([
|
|
|
31111
32447
|
"Error",
|
|
31112
32448
|
"AssertionError"
|
|
31113
32449
|
]);
|
|
32450
|
+
var JAVA_LANG_STRING_ACCUMULATOR_TYPES = /* @__PURE__ */ new Set(["StringBuilder", "StringBuffer"]);
|
|
31114
32451
|
var JAVA_FORMATTER_CLASSES = /* @__PURE__ */ new Map([
|
|
31115
32452
|
["String", "java.lang"],
|
|
31116
32453
|
["MessageFormat", "java.text"]
|
|
31117
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
|
+
]);
|
|
31118
32493
|
function isJavaStaticFormatterCall(call, declaredTypeNames, imports, calls) {
|
|
31119
32494
|
if (call.method_name !== "format" || !call.receiver) return false;
|
|
31120
32495
|
const needle = `${call.receiver}.format(`;
|
|
@@ -31124,6 +32499,10 @@ function isJavaStaticFormatterCall(call, declaredTypeNames, imports, calls) {
|
|
|
31124
32499
|
}
|
|
31125
32500
|
return isJdkClassReceiver(call.receiver, JAVA_FORMATTER_CLASSES, declaredTypeNames, imports);
|
|
31126
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
|
+
}
|
|
31127
32506
|
function isJdkClassReceiver(receiver, classes, declaredTypeNames, imports) {
|
|
31128
32507
|
const dot = receiver.lastIndexOf(".");
|
|
31129
32508
|
const simple = dot < 0 ? receiver : receiver.slice(dot + 1);
|
|
@@ -31166,12 +32545,18 @@ var JAVA_CHARACTER_PREDICATES = /* @__PURE__ */ new Set([
|
|
|
31166
32545
|
"isBmpCodePoint",
|
|
31167
32546
|
"isSupplementaryCodePoint"
|
|
31168
32547
|
]);
|
|
32548
|
+
var JAVA_CHARACTER_METADATA = /* @__PURE__ */ new Set(["getName", "getType", "getNumericValue"]);
|
|
31169
32549
|
function isJavaCharacterPredicateCall(call, declaredTypeNames, imports) {
|
|
31170
|
-
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
|
+
}
|
|
31171
32554
|
return isJdkClassReceiver(call.receiver, JAVA_CHARACTER_CLASS, declaredTypeNames, imports);
|
|
31172
32555
|
}
|
|
31173
32556
|
function isJavaNonEscapeCall(call, declaredTypeNames, imports, calls) {
|
|
31174
32557
|
if (isJavaStaticFormatterCall(call, declaredTypeNames, imports, calls)) return true;
|
|
32558
|
+
if (isJavaObjectsNullCheckCall(call, declaredTypeNames, imports)) return true;
|
|
32559
|
+
if (isJavaLoggerCall(call)) return true;
|
|
31175
32560
|
if (isJavaCharacterPredicateCall(call, declaredTypeNames, imports)) return true;
|
|
31176
32561
|
if (call.receiver) return false;
|
|
31177
32562
|
const isCtor = call.is_constructor === true || (call.resolution?.target ?? "").endsWith(".<init>");
|
|
@@ -31179,11 +32564,11 @@ function isJavaNonEscapeCall(call, declaredTypeNames, imports, calls) {
|
|
|
31179
32564
|
const written = call.method_name;
|
|
31180
32565
|
const simple = written.split(".").pop() ?? written;
|
|
31181
32566
|
if (written.includes(".")) {
|
|
31182
|
-
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));
|
|
31183
32568
|
}
|
|
31184
32569
|
if (call.receiver_type_fqn && !call.receiver_type_fqn.startsWith("java.lang.")) return false;
|
|
31185
32570
|
if (declaredTypeNames.has(simple)) return false;
|
|
31186
|
-
return JAVA_LANG_EXCEPTION_TYPES.has(simple);
|
|
32571
|
+
return JAVA_LANG_EXCEPTION_TYPES.has(simple) || JAVA_LANG_STRING_ACCUMULATOR_TYPES.has(simple);
|
|
31187
32572
|
}
|
|
31188
32573
|
function isGoConstRegexMatchCall(call, graph, calls) {
|
|
31189
32574
|
if (call.method_name !== "MatchString" || call.arguments.length !== 1) return false;
|
|
@@ -31444,6 +32829,14 @@ function analyzeInterprocedural(graphOrTypes, callsOrSources, dfgOrSinks, source
|
|
|
31444
32829
|
sanitizerMethods.add(match[1]);
|
|
31445
32830
|
}
|
|
31446
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
|
+
}
|
|
31447
32840
|
for (const call of calls) {
|
|
31448
32841
|
const taintedArgPositions = [];
|
|
31449
32842
|
const taintedArgVars = [];
|
|
@@ -31467,7 +32860,8 @@ function analyzeInterprocedural(graphOrTypes, callsOrSources, dfgOrSinks, source
|
|
|
31467
32860
|
}
|
|
31468
32861
|
}
|
|
31469
32862
|
}
|
|
31470
|
-
const
|
|
32863
|
+
const resolvedCalleeFqn = resolveMethodCall2(call, methodNodes, types);
|
|
32864
|
+
const targetMethod = resolvedCalleeFqn ? methodNodes.byFqn.get(resolvedCalleeFqn) : getMethodNode(methodNodes, call.method_name);
|
|
31471
32865
|
if (!targetMethod) {
|
|
31472
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)) {
|
|
31473
32867
|
const isBash = graph.ir.meta.language === "bash";
|
|
@@ -31501,6 +32895,9 @@ function analyzeInterprocedural(graphOrTypes, callsOrSources, dfgOrSinks, source
|
|
|
31501
32895
|
if (isBash && bashSafeBuiltins.has(call.method_name)) {
|
|
31502
32896
|
continue;
|
|
31503
32897
|
}
|
|
32898
|
+
if (isBash && bashScriptFunctions.has(call.method_name)) {
|
|
32899
|
+
continue;
|
|
32900
|
+
}
|
|
31504
32901
|
if (isBash && taintedArgPositions.length > 0) {
|
|
31505
32902
|
const terminatorPos = call.arguments.filter((a) => a.expression === "--").map((a) => a.position).reduce((min, p) => min === null || p < min ? p : min, null);
|
|
31506
32903
|
const earliestTainted = Math.min(...taintedArgPositions);
|
|
@@ -31558,6 +32955,12 @@ function analyzeInterprocedural(graphOrTypes, callsOrSources, dfgOrSinks, source
|
|
|
31558
32955
|
if (pos < targetMethod.parameters.length) {
|
|
31559
32956
|
targetMethod.parameters[pos].isTainted = true;
|
|
31560
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
|
+
}
|
|
31561
32964
|
}
|
|
31562
32965
|
}
|
|
31563
32966
|
taintedMethods.add(targetMethod.fqn);
|
|
@@ -32942,6 +34345,9 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
32942
34345
|
case "assignment_statement":
|
|
32943
34346
|
this.handleGoAssignment(node);
|
|
32944
34347
|
return false;
|
|
34348
|
+
case "var_declaration":
|
|
34349
|
+
this.handleGoVarDeclaration(node);
|
|
34350
|
+
return false;
|
|
32945
34351
|
case "update_expression":
|
|
32946
34352
|
this.handleUpdateExpression(node);
|
|
32947
34353
|
return false;
|
|
@@ -33349,6 +34755,64 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
33349
34755
|
* helper that always returns a constant — clears the variable. Anything else stops being tracked here, which is what Go
|
|
33350
34756
|
* assignments got before this handler existed: the DFG decides.
|
|
33351
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
|
+
}
|
|
33352
34816
|
handleGoAssignment(node) {
|
|
33353
34817
|
const left = node.childForFieldName("left");
|
|
33354
34818
|
const right = node.childForFieldName("right");
|
|
@@ -34165,6 +35629,24 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
34165
35629
|
cache.set(rootId, false);
|
|
34166
35630
|
return false;
|
|
34167
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
|
+
}
|
|
34168
35650
|
isTaintedExpressionStep(node) {
|
|
34169
35651
|
const text = getNodeText2(node, this.source);
|
|
34170
35652
|
if (node.type === "method_invocation") {
|
|
@@ -34172,6 +35654,9 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
34172
35654
|
const objectNode = node.childForFieldName("object");
|
|
34173
35655
|
if (nameNode) {
|
|
34174
35656
|
const methodName = getNodeText2(nameNode, this.source);
|
|
35657
|
+
if (ANTI_SANITIZER_METHODS.has(methodName) && this.argIsSanitizedTaint(node)) {
|
|
35658
|
+
return true;
|
|
35659
|
+
}
|
|
34175
35660
|
if (SANITIZER_METHODS.has(methodName) && !this.localNonSanitizing.has(methodName)) {
|
|
34176
35661
|
return false;
|
|
34177
35662
|
}
|
|
@@ -35147,6 +36632,8 @@ function classifyEntryPointTier(method, enclosingType, ctx) {
|
|
|
35147
36632
|
return classifyJsTsEntryPoint(method, enclosingType, ctx);
|
|
35148
36633
|
case "go":
|
|
35149
36634
|
return classifyGoEntryPoint(method, enclosingType, ctx);
|
|
36635
|
+
case "csharp":
|
|
36636
|
+
return classifyCSharpEntryPoint(method, enclosingType, ctx);
|
|
35150
36637
|
case "bash":
|
|
35151
36638
|
case "shell":
|
|
35152
36639
|
return classifyBashEntryPoint(method, enclosingType, ctx);
|
|
@@ -35399,6 +36886,84 @@ function fileHasJsTsFrameworkRegistration(regs) {
|
|
|
35399
36886
|
}
|
|
35400
36887
|
return false;
|
|
35401
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
|
+
}
|
|
35402
36967
|
var GO_HTTP_REGISTRAR_METHODS = /* @__PURE__ */ new Set([
|
|
35403
36968
|
// net/http
|
|
35404
36969
|
"HandleFunc",
|
|
@@ -39007,6 +40572,10 @@ var CSharpCredit = class {
|
|
|
39007
40572
|
if (type === "path_traversal") {
|
|
39008
40573
|
if (/\b([A-Za-z_]\w*)\s*\.\s*StartsWith\s*\([^)]*\)\s*\?\s*\1\s*:\s*null\b/.test(text)) return true;
|
|
39009
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
|
+
}
|
|
39010
40579
|
return false;
|
|
39011
40580
|
}
|
|
39012
40581
|
/**
|
|
@@ -39020,6 +40589,10 @@ var CSharpCredit = class {
|
|
|
39020
40589
|
const set = /* @__PURE__ */ new Set();
|
|
39021
40590
|
this.helperCache.set(type, set);
|
|
39022
40591
|
for (const m of this.methods) {
|
|
40592
|
+
if (type === "open_redirect" && this.openRedirectHelperMethod(m)) {
|
|
40593
|
+
set.add(m.name);
|
|
40594
|
+
continue;
|
|
40595
|
+
}
|
|
39023
40596
|
const returns = this.returnExprs(m);
|
|
39024
40597
|
if (returns.length === 0) continue;
|
|
39025
40598
|
const ok = returns.every((r) => {
|
|
@@ -39031,6 +40604,23 @@ var CSharpCredit = class {
|
|
|
39031
40604
|
}
|
|
39032
40605
|
return set;
|
|
39033
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
|
+
}
|
|
39034
40624
|
/** Every returned expression of a method: `return e;` and expression bodies. */
|
|
39035
40625
|
returnExprs(m) {
|
|
39036
40626
|
const out2 = [];
|
|
@@ -39263,6 +40853,14 @@ var CSharpCredit = class {
|
|
|
39263
40853
|
}
|
|
39264
40854
|
return out2;
|
|
39265
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
|
+
}
|
|
39266
40864
|
/** Variables whose host a condition compares against a constant or an allowlist. */
|
|
39267
40865
|
hostVars(cond, hostOf) {
|
|
39268
40866
|
const out2 = [];
|
|
@@ -39304,7 +40902,7 @@ var CSharpCredit = class {
|
|
|
39304
40902
|
return map;
|
|
39305
40903
|
}
|
|
39306
40904
|
provenBy(kind, cond, parsedFrom, hostOf, withHelpers) {
|
|
39307
|
-
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);
|
|
39308
40906
|
if (withHelpers) {
|
|
39309
40907
|
for (const [name2, pos] of this.guardHelpers(kind)) {
|
|
39310
40908
|
for (const call of cond.matchAll(new RegExp(`(?<![\\w.])${escapeRegex(name2)}\\s*\\(`, "g"))) {
|
|
@@ -39334,6 +40932,7 @@ var CSharpCredit = class {
|
|
|
39334
40932
|
const kinds = [];
|
|
39335
40933
|
if (INERT_WHEN_ALPHANUMERIC.has(type)) kinds.push("allowlist", "numeric");
|
|
39336
40934
|
if (type === "ssrf") kinds.push("host");
|
|
40935
|
+
if (type === "open_redirect") kinds.push("localUrl");
|
|
39337
40936
|
const proven = /* @__PURE__ */ new Set();
|
|
39338
40937
|
if (kinds.length === 0) return proven;
|
|
39339
40938
|
const scopeStart = this.methodAt(sinkLine)?.start_line ?? 1;
|
|
@@ -39576,6 +41175,15 @@ var InterproceduralPass = class {
|
|
|
39576
41175
|
}
|
|
39577
41176
|
}
|
|
39578
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
|
+
);
|
|
39579
41187
|
const taintBridges = findTaintBridges(interProc);
|
|
39580
41188
|
interprocedural = {
|
|
39581
41189
|
tainted_methods: Array.from(interProc.taintedMethods),
|
|
@@ -39701,6 +41309,401 @@ var InterproceduralPass = class {
|
|
|
39701
41309
|
return { additionalSinks: filteredAdditionalSinks, additionalFlows: filteredAdditionalFlows, interprocedural };
|
|
39702
41310
|
}
|
|
39703
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
|
+
}
|
|
39704
41707
|
function csharpParamReachesSink(code, method, taintedArgs, sink, credit) {
|
|
39705
41708
|
const lines = code.split(/\r?\n/);
|
|
39706
41709
|
const params = taintedArgs.length > 0 ? method.parameters.filter((p) => taintedArgs.includes(p.position)) : method.parameters;
|
|
@@ -40441,12 +42444,26 @@ var ConstantPropagationPass = class {
|
|
|
40441
42444
|
}
|
|
40442
42445
|
}
|
|
40443
42446
|
}
|
|
40444
|
-
|
|
42447
|
+
const result = analyzeConstantPropagation(this.tree, code, {
|
|
40445
42448
|
sanitizerMethods: taintMatcher.sanitizerMethods,
|
|
40446
42449
|
taintedParameters
|
|
40447
42450
|
});
|
|
42451
|
+
if (ctx.language === "java") pruneDeadChains(ctx, result.unreachableLines);
|
|
42452
|
+
return result;
|
|
40448
42453
|
}
|
|
40449
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
|
+
}
|
|
40450
42467
|
|
|
40451
42468
|
// src/analysis/passes/source-semantics-pass.ts
|
|
40452
42469
|
var DEMO_PATH_RE = /(?:^|\/)(?:demo|example|examples|samples|integration-tests|integration_tests)(?:\/|$)/i;
|
|
@@ -40933,13 +42950,123 @@ function argvFirstLiteralIsShell(lineText) {
|
|
|
40933
42950
|
const base = m[1].split(/[\\/]/).pop().replace(/\.exe$/i, "").toLowerCase();
|
|
40934
42951
|
return SHELL_PROGRAMS.has(base);
|
|
40935
42952
|
}
|
|
40936
|
-
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) {
|
|
40937
43043
|
if (!methodName) return false;
|
|
40938
43044
|
for (const t of types) {
|
|
40939
43045
|
const m = t.methods.find((mm) => mm.name === methodName && sinkLine >= mm.start_line && sinkLine <= mm.end_line);
|
|
40940
43046
|
if (!m) continue;
|
|
40941
43047
|
const pos = m.parameters.findIndex((p) => p.name === paramName);
|
|
40942
|
-
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
|
+
}
|
|
40943
43070
|
return calls.some((c) => {
|
|
40944
43071
|
if (c.method_name !== methodName) return false;
|
|
40945
43072
|
if (c.receiver != null && c.receiver !== "this") return false;
|
|
@@ -40947,8 +43074,30 @@ function sameFileCallerPassesShellArgv(paramName, methodName, sinkLine, types, c
|
|
|
40947
43074
|
if (c.arguments.length !== m.parameters.length) return false;
|
|
40948
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));
|
|
40949
43076
|
if (shadowed) return false;
|
|
40950
|
-
const expr = c.arguments[pos]?.expression;
|
|
40951
|
-
|
|
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;
|
|
40952
43101
|
});
|
|
40953
43102
|
}
|
|
40954
43103
|
return false;
|
|
@@ -41003,12 +43152,42 @@ var FETCH_GLOBAL_RECEIVER_RE = /(?:^|[^.\w])(?:window|globalThis|self|global)\s*
|
|
|
41003
43152
|
var FETCH_MEMBER_RECEIVER_RE = /[\w$)\]]\s*\.\s*fetch\s*\(/;
|
|
41004
43153
|
var NOSQL_ARRAY_METHODS = /* @__PURE__ */ new Set(["find", "filter", "some", "every", "findIndex", "findLast"]);
|
|
41005
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
|
+
}
|
|
41006
43171
|
var ORM_BUILDER_KEY_RE = /[{,]\s*(?:where|select|populate|include|orderBy|relations|attributes)\s*:/;
|
|
41007
43172
|
var BROWSER_COMPONENT_EXT_RE = /\.(?:jsx|tsx|vue|svelte)$/;
|
|
41008
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*=/;
|
|
41009
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*\(/;
|
|
41010
43175
|
var FIXED_HOST_TEMPLATE_RE = /`https?:\/\/[^/${}\s`]+[/`]/;
|
|
41011
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
|
+
}
|
|
41012
43191
|
function literalEndsInitializer(code, end) {
|
|
41013
43192
|
let i2 = end;
|
|
41014
43193
|
let crossedNewline = false;
|
|
@@ -41180,7 +43359,9 @@ function isUnwrittenLocalMapKeyRead(code, source) {
|
|
|
41180
43359
|
if (!read) return false;
|
|
41181
43360
|
const [, recv, key] = read;
|
|
41182
43361
|
const ctorRe = new RegExp(
|
|
41183
|
-
|
|
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*\\(`
|
|
41184
43365
|
);
|
|
41185
43366
|
if (!lines.some((l) => ctorRe.test(l))) return false;
|
|
41186
43367
|
const escapeRe2 = new RegExp(`\\w\\s*\\([^)]*(?<![\\w.])${recv}(?![\\w.])[^)]*\\)`);
|
|
@@ -41190,26 +43371,46 @@ function isUnwrittenLocalMapKeyRead(code, source) {
|
|
|
41190
43371
|
if (escapeRe2.test(stripped)) return false;
|
|
41191
43372
|
}
|
|
41192
43373
|
const putRe = new RegExp(`\\b${recv}\\s*\\.\\s*put\\s*\\(\\s*"${key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}"`);
|
|
41193
|
-
if (lines.some((l) => putRe.test(l))) return
|
|
43374
|
+
if (lines.some((l) => putRe.test(l))) return isLiteralOnlyLocalMapKey(lines, recv, key);
|
|
41194
43375
|
return true;
|
|
41195
43376
|
}
|
|
41196
|
-
function
|
|
41197
|
-
const
|
|
41198
|
-
const literalPut = new RegExp(
|
|
41199
|
-
`\\b${recv}\\s*\\.\\s*put\\s*\\(\\s*(?:"(?:[^"\\\\]|\\\\.)*"|[A-Za-z_][\\w.]*)\\s*,\\s*"(?:[^"\\\\]|\\\\.)*"\\s*\\)`,
|
|
41200
|
-
"g"
|
|
41201
|
-
);
|
|
43377
|
+
function isLiteralOnlyLocalMapKey(lines, recv, key) {
|
|
43378
|
+
const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
41202
43379
|
const otherMutator = new RegExp(
|
|
41203
43380
|
`\\b${recv}\\s*\\.\\s*(?:putAll|putIfAbsent|compute|computeIfAbsent|computeIfPresent|merge|replace|replaceAll|entrySet|values)\\s*\\(`
|
|
41204
43381
|
);
|
|
41205
|
-
|
|
41206
|
-
|
|
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;
|
|
41207
43389
|
for (const l of lines) {
|
|
41208
|
-
|
|
41209
|
-
|
|
41210
|
-
literalPuts += (l.match(literalPut) ?? []).length;
|
|
43390
|
+
putsForKey += (l.match(anyKeyPut) ?? []).length;
|
|
43391
|
+
literalPutsForKey += (l.match(keyPut) ?? []).length;
|
|
41211
43392
|
}
|
|
41212
|
-
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));
|
|
41213
43414
|
}
|
|
41214
43415
|
function csharpContentServesHtml(call) {
|
|
41215
43416
|
if (call.receiver !== null && call.receiver !== "this" && call.receiver !== "base") return false;
|
|
@@ -41217,8 +43418,8 @@ function csharpContentServesHtml(call) {
|
|
|
41217
43418
|
if (contentType === void 0) return true;
|
|
41218
43419
|
const literal = /^@?"([^"]*)"$/.exec(contentType);
|
|
41219
43420
|
if (literal) return /^\s*(?:text\/html|text\/xml|application\/xml|[\w.-]+\/[\w.-]+\+xml)\b/i.test(literal[1]);
|
|
41220
|
-
const
|
|
41221
|
-
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";
|
|
41222
43423
|
return true;
|
|
41223
43424
|
}
|
|
41224
43425
|
function csharpContentCallInheritsHelper(call, types, sourceLines) {
|
|
@@ -41385,7 +43586,7 @@ function findEnclosingMethodFromIr(types, sinkLine) {
|
|
|
41385
43586
|
return null;
|
|
41386
43587
|
}
|
|
41387
43588
|
function splitJavaConcatTokens(rhs) {
|
|
41388
|
-
const
|
|
43589
|
+
const tokens2 = [];
|
|
41389
43590
|
let cur = "";
|
|
41390
43591
|
let depth = 0;
|
|
41391
43592
|
let inString = false;
|
|
@@ -41422,7 +43623,7 @@ function splitJavaConcatTokens(rhs) {
|
|
|
41422
43623
|
continue;
|
|
41423
43624
|
}
|
|
41424
43625
|
if (ch === "+" && depth === 0) {
|
|
41425
|
-
|
|
43626
|
+
tokens2.push(cur.trim());
|
|
41426
43627
|
cur = "";
|
|
41427
43628
|
i2++;
|
|
41428
43629
|
continue;
|
|
@@ -41430,8 +43631,8 @@ function splitJavaConcatTokens(rhs) {
|
|
|
41430
43631
|
cur += ch;
|
|
41431
43632
|
i2++;
|
|
41432
43633
|
}
|
|
41433
|
-
if (cur.trim())
|
|
41434
|
-
return
|
|
43634
|
+
if (cur.trim()) tokens2.push(cur.trim());
|
|
43635
|
+
return tokens2;
|
|
41435
43636
|
}
|
|
41436
43637
|
function isImplicitlyAnchoredAllowlistRegex(re) {
|
|
41437
43638
|
if (re === "") return false;
|
|
@@ -41536,7 +43737,7 @@ var SinkFilterPass = class {
|
|
|
41536
43737
|
const executableSources = ctx.code && ctx.language ? mergedSources.filter(
|
|
41537
43738
|
(s) => !isNonExecutableSourceLine(ctx.code, s.line, ctx.language)
|
|
41538
43739
|
) : mergedSources;
|
|
41539
|
-
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;
|
|
41540
43741
|
const sinks = [...taintMatcher.sinks];
|
|
41541
43742
|
for (const s of langSources.additionalSinks) {
|
|
41542
43743
|
const springReturn = s.type === "xss" && typeof s.location === "string" && s.location.startsWith("Spring handler returns HTML");
|
|
@@ -41844,7 +44045,7 @@ var SinkFilterPass = class {
|
|
|
41844
44045
|
const varName = arg0.variable ?? "";
|
|
41845
44046
|
if (varName !== "" && argExpr === varName && !/[(.]/.test(argExpr)) {
|
|
41846
44047
|
const t = resolveIdentifierType(varName, ctorCall.in_method, irTypes);
|
|
41847
|
-
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)) {
|
|
41848
44049
|
return false;
|
|
41849
44050
|
}
|
|
41850
44051
|
}
|
|
@@ -42027,11 +44228,11 @@ var SinkFilterPass = class {
|
|
|
42027
44228
|
rhs = sqlVar;
|
|
42028
44229
|
}
|
|
42029
44230
|
if (!rhs) return true;
|
|
42030
|
-
const
|
|
42031
|
-
if (
|
|
44231
|
+
const tokens2 = splitJavaConcatTokens(rhs);
|
|
44232
|
+
if (tokens2.length < 2) return true;
|
|
42032
44233
|
let hasPlaceholder = false;
|
|
42033
44234
|
const methodCallNames = [];
|
|
42034
|
-
for (const tk of
|
|
44235
|
+
for (const tk of tokens2) {
|
|
42035
44236
|
if (/^"(?:[^"\\]|\\.)*"$/.test(tk)) {
|
|
42036
44237
|
if (tk.includes("?")) hasPlaceholder = true;
|
|
42037
44238
|
continue;
|
|
@@ -42094,7 +44295,15 @@ var SinkFilterPass = class {
|
|
|
42094
44295
|
if (sink.type !== "nosql_injection") return true;
|
|
42095
44296
|
if (!NOSQL_ARRAY_METHODS.has(sink.method ?? "")) return true;
|
|
42096
44297
|
const sinkLineText = sourceLines[sink.line - 1] ?? "";
|
|
42097
|
-
|
|
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;
|
|
42098
44307
|
});
|
|
42099
44308
|
}
|
|
42100
44309
|
if (["javascript", "typescript"].includes(language)) {
|
|
@@ -42211,6 +44420,12 @@ var SinkFilterPass = class {
|
|
|
42211
44420
|
return bodyCalls.length === 0 || !bodyCalls.every(rustBodyDeclaresNonHtmlType);
|
|
42212
44421
|
});
|
|
42213
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
|
+
}
|
|
42214
44429
|
if (language === "bash") {
|
|
42215
44430
|
const sourceLines = ctx.code.split("\n");
|
|
42216
44431
|
filtered = filtered.filter((sink) => {
|
|
@@ -42733,7 +44948,9 @@ function filterCleanVariableSinks(sinks, calls, taintedVars, symbols, dfg, sanit
|
|
|
42733
44948
|
return sinks.filter((sink) => {
|
|
42734
44949
|
const callsAtSink = callsByLine.get(sink.line) ?? [];
|
|
42735
44950
|
const isInSynchronizedBlock = synchronizedLines?.has(sink.line) ?? false;
|
|
42736
|
-
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;
|
|
42737
44954
|
const trustArgPositions = language !== "bash" && language !== "shell";
|
|
42738
44955
|
for (const call of relevantCalls) {
|
|
42739
44956
|
let allArgsAreClean = true;
|
|
@@ -43498,19 +45715,59 @@ function rustAttributesAbove(lines, startLine) {
|
|
|
43498
45715
|
continue;
|
|
43499
45716
|
}
|
|
43500
45717
|
if (t === "" || t.startsWith("//")) continue;
|
|
45718
|
+
if (out2.length > 0) break;
|
|
43501
45719
|
break;
|
|
43502
45720
|
}
|
|
43503
45721
|
return out2;
|
|
43504
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
|
+
}
|
|
43505
45759
|
function isLambdaEntryPoint(method, language, lines) {
|
|
43506
45760
|
const names = method.parameters.map((p) => p.name);
|
|
43507
45761
|
const first = lines[method.start_line - 1] ?? "";
|
|
43508
|
-
if (
|
|
45762
|
+
if (!isModuleLevelFunction(method, lines)) return false;
|
|
43509
45763
|
if (language === "python") {
|
|
43510
45764
|
return method.name === "lambda_handler" && names.length === 2 && names[0] === "event" && names[1] === "context";
|
|
43511
45765
|
}
|
|
43512
45766
|
if (language !== "javascript" && language !== "typescript") return false;
|
|
43513
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
|
+
}
|
|
43514
45771
|
if (names.length < 2 || names.length > 3 || names[0] !== "event" || names[1] !== "context") return false;
|
|
43515
45772
|
if (names.length === 3 && names[2] !== "callback") return false;
|
|
43516
45773
|
if (/^export\s+(?:default\s+)?(?:async\s+)?(?:function\b|(?:const|let|var)\s+handler\b)/.test(first)) return true;
|
|
@@ -43520,6 +45777,7 @@ function isLambdaEntryPoint(method, language, lines) {
|
|
|
43520
45777
|
function isFrameworkHandler(method, language, lines) {
|
|
43521
45778
|
if (method.annotations.some((a) => ROUTE_ANNOTATION.test(a))) return true;
|
|
43522
45779
|
if (isLambdaEntryPoint(method, language, lines)) return true;
|
|
45780
|
+
if (isChannelExportedHandler(method, language, lines)) return true;
|
|
43523
45781
|
if (language === "rust") {
|
|
43524
45782
|
if (rustAttributesAbove(lines, method.start_line).some((a) => RUST_ROUTE_ATTR.test(a))) return true;
|
|
43525
45783
|
if (method.return_type && RUST_HANDLER_RETURN.test(method.return_type)) return true;
|
|
@@ -43535,6 +45793,11 @@ function isReactHtmlComponent(m, f, lines) {
|
|
|
43535
45793
|
const sig = lines[m.start_line - 1] ?? "";
|
|
43536
45794
|
return /\bfunction\s+[A-Z][A-Za-z0-9]*\s*\(\s*\{/.test(sig);
|
|
43537
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
|
+
}
|
|
43538
45801
|
function isPythonYamlLoadParamDeser(f, lines) {
|
|
43539
45802
|
if (f.source_type !== "interprocedural_param" || f.sink_type !== "deserialization") return false;
|
|
43540
45803
|
const head = f.path[0]?.variable;
|
|
@@ -43664,8 +45927,8 @@ var ParamSourceFlowGatePass = class {
|
|
|
43664
45927
|
const viaReceiver = !isCtor && !!owner && owner.name !== "<module>";
|
|
43665
45928
|
const feeds = (pos) => fed.params.has(`${viaReceiver ? "m" : "f"}:${callee}:${pos}`);
|
|
43666
45929
|
const head = f.path[0]?.variable;
|
|
43667
|
-
const
|
|
43668
|
-
if (
|
|
45930
|
+
const named2 = m.parameters.findIndex((p) => p.name === head);
|
|
45931
|
+
if (named2 >= 0) return feeds(named2);
|
|
43669
45932
|
for (const s of sources) {
|
|
43670
45933
|
if (s.line !== f.source_line || s.type !== "interprocedural_param") continue;
|
|
43671
45934
|
if (FIELD_CARRIED_SOURCE.test(s.location ?? "")) {
|
|
@@ -43683,7 +45946,7 @@ var ParamSourceFlowGatePass = class {
|
|
|
43683
45946
|
let keep = true;
|
|
43684
45947
|
if (f.source_type === "interprocedural_param" && !realSourceLines.has(f.source_line)) {
|
|
43685
45948
|
const m = enclosingMethod(methods, f.source_line);
|
|
43686
|
-
keep = m !== void 0 && (isHandler(m) || fedByRealSource(f, m) || isReactHtmlComponent(m, f, lines) || ctx.language === "python" && isPythonYamlLoadParamDeser(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));
|
|
43687
45950
|
}
|
|
43688
45951
|
if (keep) flows[w++] = f;
|
|
43689
45952
|
else {
|
|
@@ -44359,6 +46622,32 @@ function indexJavaLocalAssignments(code) {
|
|
|
44359
46622
|
}
|
|
44360
46623
|
return index;
|
|
44361
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
|
+
}
|
|
44362
46651
|
function makeConstPropVeto(constProp, graph, sources, code, language, sanitizers) {
|
|
44363
46652
|
const lines = typeof code === "string" ? code.split("\n") : [];
|
|
44364
46653
|
const selfReassign = /* @__PURE__ */ new Map();
|
|
@@ -44454,8 +46743,8 @@ function makeConstPropVeto(constProp, graph, sources, code, language, sanitizers
|
|
|
44454
46743
|
if (d.line === sourceLine && sources.some((s) => s.line === sourceLine && !CONTAINER_READ_SOURCES.has(s.type))) {
|
|
44455
46744
|
if (step.line === sinkLine && !constProp.tainted.has(fpLookup)) {
|
|
44456
46745
|
for (let ln = sourceLine; ln <= sinkLine; ln++) {
|
|
44457
|
-
const
|
|
44458
|
-
if (
|
|
46746
|
+
const callee = goConstantHelperCalleeFromLines(lines, ln);
|
|
46747
|
+
if (callee && constProp.constantReturningMethods?.has(callee)) {
|
|
44459
46748
|
return true;
|
|
44460
46749
|
}
|
|
44461
46750
|
}
|
|
@@ -44658,7 +46947,17 @@ var TaintPropagationPass = class {
|
|
|
44658
46947
|
if (isFP) continue;
|
|
44659
46948
|
pushIfNew(f);
|
|
44660
46949
|
}
|
|
44661
|
-
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
|
+
) ?? [];
|
|
44662
46961
|
for (const f of paramFlows) {
|
|
44663
46962
|
pushIfNew(f);
|
|
44664
46963
|
}
|
|
@@ -44733,6 +47032,22 @@ var TaintPropagationPass = class {
|
|
|
44733
47032
|
if (sanitizedNames.has(sourceVar) && coversFlow(sourceVar)) return false;
|
|
44734
47033
|
return true;
|
|
44735
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
|
+
}
|
|
44736
47051
|
if (sanitizers && sanitizers.length > 0) {
|
|
44737
47052
|
const sanitizersByLine = /* @__PURE__ */ new Map();
|
|
44738
47053
|
for (const san of sanitizers) {
|
|
@@ -45125,7 +47440,7 @@ function detectJsModuleScopeFlows(code, sources, sinks, unreachableLines) {
|
|
|
45125
47440
|
for (const line of lines) {
|
|
45126
47441
|
const stripped = line.replace(/\s+$/, "");
|
|
45127
47442
|
if (!stripped || /^[ \t]/.test(line)) continue;
|
|
45128
|
-
const m = stripped.match(/^(?:let|var|const)\s+([A-Za-z_$][\w$]*)\b/);
|
|
47443
|
+
const m = stripped.match(/^(?:export\s+)?(?:let|var|const)\s+([A-Za-z_$][\w$]*)\b/);
|
|
45129
47444
|
if (m) moduleVars.add(m[1]);
|
|
45130
47445
|
}
|
|
45131
47446
|
if (moduleVars.size === 0) return flows;
|
|
@@ -45141,7 +47456,9 @@ function detectJsModuleScopeFlows(code, sources, sinks, unreachableLines) {
|
|
|
45141
47456
|
const line = lines[i2];
|
|
45142
47457
|
if (unreachableLines.has(i2 + 1)) continue;
|
|
45143
47458
|
for (const lhs of moduleVars) {
|
|
45144
|
-
const declRe = new RegExp(
|
|
47459
|
+
const declRe = new RegExp(
|
|
47460
|
+
`\\b(?:export\\s+)?(?:let|const|var)\\s+${lhs.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`
|
|
47461
|
+
);
|
|
45145
47462
|
if (declRe.test(line)) continue;
|
|
45146
47463
|
const m = line.match(
|
|
45147
47464
|
new RegExp(`\\b${lhs.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\s*=\\s*([^;]+)`)
|
|
@@ -45263,6 +47580,16 @@ function isTaintedInMethod(taintedVars, varName, scopedName, fieldNames, isField
|
|
|
45263
47580
|
}
|
|
45264
47581
|
return true;
|
|
45265
47582
|
}
|
|
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
|
+
}
|
|
45266
47593
|
function detectCollectionFlows(calls, sources, sinks, constProp, code, types, graph) {
|
|
45267
47594
|
const taintedVars = constProp.tainted;
|
|
45268
47595
|
const unreachableLines = constProp.unreachableLines;
|
|
@@ -45305,7 +47632,23 @@ function detectCollectionFlows(calls, sources, sinks, constProp, code, types, gr
|
|
|
45305
47632
|
);
|
|
45306
47633
|
const bareFieldRead = fieldNames.has(varName) && expr === varName && !methodHasLocalDecl(code, call.in_method, varName, call.location.line);
|
|
45307
47634
|
const isFieldUse = explicitField || bareFieldRead;
|
|
45308
|
-
|
|
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) {
|
|
45309
47652
|
const source = pickScopedSource(sources, sink.line, call.in_method ?? null, types, varName);
|
|
45310
47653
|
if (source) {
|
|
45311
47654
|
if (source.variable && source.variable !== varName && source.in_method && call.in_method && source.in_method !== call.in_method) {
|
|
@@ -45340,12 +47683,23 @@ function detectCollectionFlows(calls, sources, sinks, constProp, code, types, gr
|
|
|
45340
47683
|
{ method: "peek", re: /(\w+)\.peek\(/ },
|
|
45341
47684
|
{ method: "toArray", re: /(\w+)\.toArray\(/ }
|
|
45342
47685
|
];
|
|
45343
|
-
for (const { re } of collectionPatterns) {
|
|
47686
|
+
for (const { method, re } of collectionPatterns) {
|
|
45344
47687
|
const match = expr.match(re);
|
|
45345
47688
|
if (match) {
|
|
45346
47689
|
const collectionVar = match[1];
|
|
45347
47690
|
const scopedCollection = call.in_method ? `${call.in_method}:${collectionVar}` : collectionVar;
|
|
45348
|
-
|
|
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
|
+
{
|
|
45349
47703
|
const source = pickScopedSource(sources, sink.line, call.in_method ?? null, types, collectionVar);
|
|
45350
47704
|
if (source) {
|
|
45351
47705
|
if (source.variable && source.variable !== collectionVar && source.in_method && call.in_method && source.in_method !== call.in_method) {
|
|
@@ -45422,6 +47776,39 @@ function detectArrayElementFlows(calls, sources, sinks, taintedArrayElements, un
|
|
|
45422
47776
|
}
|
|
45423
47777
|
return flows;
|
|
45424
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
|
+
}
|
|
45425
47812
|
function compoundArgParamNames(expression, paramNames) {
|
|
45426
47813
|
const stripped = expression.replace(/(['"`])(?:\\.|(?!\1).)*\1/g, (m) => " ".repeat(m.length));
|
|
45427
47814
|
const matched = [];
|
|
@@ -45435,7 +47822,7 @@ function compoundArgParamNames(expression, paramNames) {
|
|
|
45435
47822
|
}
|
|
45436
47823
|
return matched;
|
|
45437
47824
|
}
|
|
45438
|
-
function detectParameterSinkFlows(types, calls, sources, sinks, unreachableLines, tainted, code) {
|
|
47825
|
+
function detectParameterSinkFlows(types, calls, sources, sinks, unreachableLines, tainted, code, language, filePath) {
|
|
45439
47826
|
const flows = [];
|
|
45440
47827
|
const paramSourcesByMethod = /* @__PURE__ */ new Map();
|
|
45441
47828
|
for (const source of sources) {
|
|
@@ -45476,6 +47863,20 @@ function detectParameterSinkFlows(types, calls, sources, sinks, unreachableLines
|
|
|
45476
47863
|
for (const varName of varNames) {
|
|
45477
47864
|
const paramSource = methodParamSources.get(varName);
|
|
45478
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
|
+
}
|
|
45479
47880
|
const exists = flows.some((f) => f.source_line === paramSource.line && f.sink_line === sink.line && f.sink_type === sink.type);
|
|
45480
47881
|
if (!exists) {
|
|
45481
47882
|
if (typeof code === "string" && isReassignedToLiteralBetween(code, varName, paramSource.line, sink.line, unreachableLines)) {
|
|
@@ -46470,9 +48871,18 @@ function detectExpressionScanFlows(calls, sources, sinks, sanitizers, unreachabl
|
|
|
46470
48871
|
if (!prev || s.line < prev.line) realSourceByVar.set(s.variable, s);
|
|
46471
48872
|
}
|
|
46472
48873
|
for (const [varName, derivedLine] of derived) {
|
|
46473
|
-
if (!varName
|
|
48874
|
+
if (!varName) continue;
|
|
46474
48875
|
const owner = methodAt(derivedLine);
|
|
46475
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
|
+
}
|
|
46476
48886
|
let scopedAnchor = anchor;
|
|
46477
48887
|
if (owner) {
|
|
46478
48888
|
let best;
|
|
@@ -51831,10 +54241,28 @@ var InsecureCookiePass = class {
|
|
|
51831
54241
|
* unassigned `new Cookie(...)` only looks at that statement, so another
|
|
51832
54242
|
* cookie's setter cannot cover it.
|
|
51833
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
|
+
}
|
|
51834
54262
|
javaCookieSetterTrue(code, call, setter) {
|
|
51835
|
-
const
|
|
51836
|
-
const
|
|
51837
|
-
const varName =
|
|
54263
|
+
const lines = code.split("\n");
|
|
54264
|
+
const line = lines[(call.location.line ?? 1) - 1] ?? "";
|
|
54265
|
+
const varName = this.javaCookieVarName(code, call);
|
|
51838
54266
|
const re = varName ? new RegExp(`\\b${varName}\\.${setter}\\s*\\(\\s*true\\s*\\)`) : new RegExp(`\\.${setter}\\s*\\(\\s*true\\s*\\)`);
|
|
51839
54267
|
return re.test(varName ? code : line);
|
|
51840
54268
|
}
|
|
@@ -52917,6 +55345,9 @@ var WeakRandomPass = class {
|
|
|
52917
55345
|
if (method === "random" && (receiver === "Math" || receiver.endsWith(".Math"))) {
|
|
52918
55346
|
return "Math.random";
|
|
52919
55347
|
}
|
|
55348
|
+
if (this.jsIsModuleScopedCryptoRandomUuid(call, ctx)) {
|
|
55349
|
+
return "crypto.randomUUID (module scope)";
|
|
55350
|
+
}
|
|
52920
55351
|
return null;
|
|
52921
55352
|
}
|
|
52922
55353
|
if (language === "go") {
|
|
@@ -52954,6 +55385,26 @@ var WeakRandomPass = class {
|
|
|
52954
55385
|
* file (i.e. `math/rand` imported unaliased; `crypto/rand` is either not
|
|
52955
55386
|
* imported or imported under an alias).
|
|
52956
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
|
+
}
|
|
52957
55408
|
goMathRandIsActive(ctx) {
|
|
52958
55409
|
const imports = ctx.graph.ir.imports ?? [];
|
|
52959
55410
|
let mathRandUnaliased = false;
|
|
@@ -54265,6 +56716,13 @@ var PY_HTTP_RECEIVERS = /* @__PURE__ */ new Set([
|
|
|
54265
56716
|
var VERIFY_FALSE_RE = /\bverify\s*=\s*False\b/;
|
|
54266
56717
|
var REJECT_UNAUTHORIZED_FALSE_RE = /\brejectUnauthorized\s*:\s*false\b/;
|
|
54267
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
|
+
}
|
|
54268
56726
|
var HOSTNAME_LAMBDA_TRUE_RE = /\(\s*\w+\s*,\s*\w+\s*\)\s*->\s*true\b/;
|
|
54269
56727
|
var CS_CERT_CALLBACK_TRUE_RE = /\b(ServerCertificateValidationCallback|ServerCertificateCustomValidationCallback|RemoteCertificateValidationCallback)\s*(?:\+?=|\()\s*(?:\([^)]*\)|\w+)\s*=>\s*(?:true\b|\{\s*return\s+true\b)/;
|
|
54270
56728
|
var CS_DANGEROUS_ACCEPT_RE = /\bDangerousAcceptAnyServerCertificateValidator\b/;
|
|
@@ -54273,6 +56731,14 @@ var ALLOW_ALL_HOSTNAME_VERIFIERS = /* @__PURE__ */ new Set([
|
|
|
54273
56731
|
"new AllowAllHostnameVerifier()",
|
|
54274
56732
|
"new NoopHostnameVerifier()"
|
|
54275
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/;
|
|
54276
56742
|
var TlsVerifyDisabledPass = class {
|
|
54277
56743
|
name = "tls-verify-disabled";
|
|
54278
56744
|
category = "security";
|
|
@@ -54387,6 +56853,18 @@ var TlsVerifyDisabledPass = class {
|
|
|
54387
56853
|
});
|
|
54388
56854
|
}
|
|
54389
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
|
+
}
|
|
54390
56868
|
}
|
|
54391
56869
|
if (language === "python") {
|
|
54392
56870
|
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
@@ -54410,6 +56888,35 @@ var TlsVerifyDisabledPass = class {
|
|
|
54410
56888
|
api: "process.env"
|
|
54411
56889
|
});
|
|
54412
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
|
+
}
|
|
54413
56920
|
}
|
|
54414
56921
|
}
|
|
54415
56922
|
if (language === "csharp") {
|
|
@@ -54429,16 +56936,90 @@ var TlsVerifyDisabledPass = class {
|
|
|
54429
56936
|
}
|
|
54430
56937
|
return out2;
|
|
54431
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
|
+
}
|
|
54432
57007
|
fixFor(language, pattern) {
|
|
54433
57008
|
if (pattern.includes("InsecureSkipVerify")) {
|
|
54434
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.";
|
|
54435
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
|
+
}
|
|
54436
57014
|
if (pattern.includes("verify=False")) {
|
|
54437
57015
|
return "Remove `verify=False`. To trust a private CA, pass `verify='/path/to/ca.pem'`.";
|
|
54438
57016
|
}
|
|
54439
57017
|
if (pattern.includes("rejectUnauthorized")) {
|
|
54440
57018
|
return "Remove `rejectUnauthorized: false`. To trust a private CA, set the `ca` option to the CA cert(s). Never disable TLS verification globally.";
|
|
54441
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
|
+
}
|
|
54442
57023
|
if (pattern.includes("NODE_TLS_REJECT_UNAUTHORIZED")) {
|
|
54443
57024
|
return "Remove the `NODE_TLS_REJECT_UNAUTHORIZED=0` assignment \u2014 it globally disables TLS verification for every outbound HTTPS request.";
|
|
54444
57025
|
}
|
|
@@ -56410,6 +58991,36 @@ function boundedMakeDefIds(defs, code, language) {
|
|
|
56410
58991
|
return out2;
|
|
56411
58992
|
}
|
|
56412
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
|
+
|
|
56413
59024
|
// src/analyzer.ts
|
|
56414
59025
|
var initialized = false;
|
|
56415
59026
|
async function initAnalyzer(options = {}) {
|
|
@@ -56633,10 +59244,24 @@ function neutralizeOverlongLines(code) {
|
|
|
56633
59244
|
}
|
|
56634
59245
|
return { code: out2, dropped };
|
|
56635
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
|
+
};
|
|
56636
59259
|
async function analyze(code, filePath, language, options = {}) {
|
|
56637
59260
|
if (!initialized) {
|
|
56638
59261
|
await initAnalyzer(options);
|
|
56639
59262
|
}
|
|
59263
|
+
const fileTimeoutMs = options.fileTimeoutMs ?? 0;
|
|
59264
|
+
const fileDeadline = fileTimeoutMs > 0 ? Date.now() + fileTimeoutMs : 0;
|
|
56640
59265
|
if (language === "html" || language === "vue") {
|
|
56641
59266
|
return analyzeMarkupFile(code, filePath, options, language);
|
|
56642
59267
|
}
|
|
@@ -56671,6 +59296,9 @@ async function analyze(code, filePath, language, options = {}) {
|
|
|
56671
59296
|
const tree = await parse(code, parseGrammar);
|
|
56672
59297
|
phaseEnd("parse", tParse);
|
|
56673
59298
|
try {
|
|
59299
|
+
if (fileDeadline > 0 && Date.now() > fileDeadline) {
|
|
59300
|
+
throw new AnalysisTimeoutError(filePath, Date.now() - (fileDeadline - fileTimeoutMs), "parse");
|
|
59301
|
+
}
|
|
56674
59302
|
logger.trace("Parsed AST", { rootNodeType: tree.rootNode.type });
|
|
56675
59303
|
const tParseStatus = phaseStart();
|
|
56676
59304
|
const parseStatus = extractParseStatus(tree);
|
|
@@ -56723,7 +59351,8 @@ async function analyze(code, filePath, language, options = {}) {
|
|
|
56723
59351
|
const imports = extractImports(tree, language, nodeCache);
|
|
56724
59352
|
phaseEnd("extractImports", tImports);
|
|
56725
59353
|
const tExports = phaseStart();
|
|
56726
|
-
|
|
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);
|
|
56727
59356
|
phaseEnd("extractExports", tExports);
|
|
56728
59357
|
const tCFG = phaseStart();
|
|
56729
59358
|
const cfg = buildCFG(tree, language, nodeCache);
|
|
@@ -56844,7 +59473,14 @@ async function analyze(code, filePath, language, options = {}) {
|
|
|
56844
59473
|
if (!disabledPasses.has("unrestricted-file-upload")) pipeline.add(new UnrestrictedFileUploadPass());
|
|
56845
59474
|
if (!disabledPasses.has("missing-sanitizer-gate")) pipeline.add(new MissingSanitizerGatePass());
|
|
56846
59475
|
if (!disabledPasses.has("insecure-deserialization-config")) pipeline.add(new InsecureDeserializationConfigPass());
|
|
56847
|
-
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
|
+
);
|
|
56848
59484
|
const sinkFilter = results.get("sink-filter");
|
|
56849
59485
|
const interProc = results.get("interprocedural");
|
|
56850
59486
|
const taint = {
|
|
@@ -56865,6 +59501,12 @@ async function analyze(code, filePath, language, options = {}) {
|
|
|
56865
59501
|
taint.flows = [...taint.flows ?? [], ...constructionFlows];
|
|
56866
59502
|
}
|
|
56867
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
|
+
}
|
|
56868
59510
|
if (taint.flows && taint.flows.length > 0 && taint.sinks.length > 0) {
|
|
56869
59511
|
const sinkTagsByKey = /* @__PURE__ */ new Map();
|
|
56870
59512
|
for (const s of taint.sinks) {
|
|
@@ -56893,7 +59535,10 @@ async function analyze(code, filePath, language, options = {}) {
|
|
|
56893
59535
|
unresolvedItems: unresolved.length
|
|
56894
59536
|
});
|
|
56895
59537
|
emitFindingsInstrumentation(filePath, findings, taint);
|
|
56896
|
-
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);
|
|
56897
59542
|
const downgradedFindings = applyLibraryApiSurfaceDowngrade(verifiedFindings);
|
|
56898
59543
|
const profiledFindings = applyProjectProfileTransform(
|
|
56899
59544
|
downgradedFindings,
|