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
|
@@ -10456,6 +10456,79 @@ function processStatement2(stmt, startDefId, startUseId, scopeStack, isJavaScrip
|
|
|
10456
10456
|
carryBranchDefsOut(popped, scopeStack, alwaysRuns);
|
|
10457
10457
|
break;
|
|
10458
10458
|
}
|
|
10459
|
+
case "try_statement":
|
|
10460
|
+
case "try_with_resources_statement": {
|
|
10461
|
+
if (isJavaScript) {
|
|
10462
|
+
const stmtUses = extractUses(stmt, useId, scopeStack, isJavaScript);
|
|
10463
|
+
uses.push(...stmtUses.uses);
|
|
10464
|
+
useId = stmtUses.nextId;
|
|
10465
|
+
break;
|
|
10466
|
+
}
|
|
10467
|
+
scopeStack.push(/* @__PURE__ */ new Map());
|
|
10468
|
+
const resources = stmt.childForFieldName("resources");
|
|
10469
|
+
for (const res of resources?.namedChildren ?? []) {
|
|
10470
|
+
if (!res || res.type !== "resource") continue;
|
|
10471
|
+
const nameNode = res.childForFieldName("name");
|
|
10472
|
+
const value = res.childForFieldName("value");
|
|
10473
|
+
if (value) {
|
|
10474
|
+
const valueUses = extractUses(value, useId, scopeStack, isJavaScript);
|
|
10475
|
+
uses.push(...valueUses.uses);
|
|
10476
|
+
useId = valueUses.nextId;
|
|
10477
|
+
}
|
|
10478
|
+
if (nameNode && nameNode.type === "identifier") {
|
|
10479
|
+
const def = { id: defId++, variable: getNodeText(nameNode), line: nameNode.startPosition.row + 1, column: nameNode.startPosition.column, kind: "local" };
|
|
10480
|
+
defs.push(def);
|
|
10481
|
+
currentScope(scopeStack).set(def.variable, def.id);
|
|
10482
|
+
markDeclared(currentScope(scopeStack), def.variable);
|
|
10483
|
+
} else if (!value) {
|
|
10484
|
+
const resUses = extractUses(res, useId, scopeStack, isJavaScript);
|
|
10485
|
+
uses.push(...resUses.uses);
|
|
10486
|
+
useId = resUses.nextId;
|
|
10487
|
+
}
|
|
10488
|
+
}
|
|
10489
|
+
for (const part of stmt.namedChildren) {
|
|
10490
|
+
if (!part) continue;
|
|
10491
|
+
if (part.type === "block") {
|
|
10492
|
+
const r = processStatement2(part, defId, useId, scopeStack, isJavaScript);
|
|
10493
|
+
defs.push(...r.defs);
|
|
10494
|
+
uses.push(...r.uses);
|
|
10495
|
+
defId = r.nextDefId;
|
|
10496
|
+
useId = r.nextUseId;
|
|
10497
|
+
} else if (part.type === "catch_clause") {
|
|
10498
|
+
scopeStack.push(/* @__PURE__ */ new Map());
|
|
10499
|
+
for (const param of part.namedChildren) {
|
|
10500
|
+
if (!param || param.type !== "catch_formal_parameter") continue;
|
|
10501
|
+
const pname = param.childForFieldName("name");
|
|
10502
|
+
if (pname) {
|
|
10503
|
+
const def = { id: defId++, variable: getNodeText(pname), line: pname.startPosition.row + 1, column: pname.startPosition.column, kind: "local" };
|
|
10504
|
+
defs.push(def);
|
|
10505
|
+
currentScope(scopeStack).set(def.variable, def.id);
|
|
10506
|
+
markDeclared(currentScope(scopeStack), def.variable);
|
|
10507
|
+
}
|
|
10508
|
+
}
|
|
10509
|
+
const body2 = part.childForFieldName("body");
|
|
10510
|
+
if (body2) {
|
|
10511
|
+
const r = processStatement2(body2, defId, useId, scopeStack, isJavaScript);
|
|
10512
|
+
defs.push(...r.defs);
|
|
10513
|
+
uses.push(...r.uses);
|
|
10514
|
+
defId = r.nextDefId;
|
|
10515
|
+
useId = r.nextUseId;
|
|
10516
|
+
}
|
|
10517
|
+
carryBranchDefsOut(scopeStack.pop(), scopeStack, false);
|
|
10518
|
+
} else if (part.type === "finally_clause") {
|
|
10519
|
+
for (const b of part.namedChildren) {
|
|
10520
|
+
if (!b || b.type !== "block") continue;
|
|
10521
|
+
const r = processStatement2(b, defId, useId, scopeStack, isJavaScript);
|
|
10522
|
+
defs.push(...r.defs);
|
|
10523
|
+
uses.push(...r.uses);
|
|
10524
|
+
defId = r.nextDefId;
|
|
10525
|
+
useId = r.nextUseId;
|
|
10526
|
+
}
|
|
10527
|
+
}
|
|
10528
|
+
}
|
|
10529
|
+
carryBranchDefsOut(scopeStack.pop(), scopeStack, false);
|
|
10530
|
+
break;
|
|
10531
|
+
}
|
|
10459
10532
|
case "else_clause": {
|
|
10460
10533
|
for (let i2 = 0; i2 < stmt.childCount; i2++) {
|
|
10461
10534
|
const arm = stmt.child(i2);
|
|
@@ -11071,6 +11144,42 @@ function buildBashDFG(tree) {
|
|
|
11071
11144
|
defs.push(def);
|
|
11072
11145
|
currentScope(scopeStack).set(p, def.id);
|
|
11073
11146
|
}
|
|
11147
|
+
const bashFnDefs = /* @__PURE__ */ new Map();
|
|
11148
|
+
const bashFnByNodeId = /* @__PURE__ */ new Map();
|
|
11149
|
+
for (const fn of findNodes(tree.rootNode, "function_definition")) {
|
|
11150
|
+
const nameNode = fn.childForFieldName("name");
|
|
11151
|
+
if (!nameNode) continue;
|
|
11152
|
+
const fnName = getNodeText(nameNode);
|
|
11153
|
+
if (!fnName || bashFnDefs.has(fnName)) continue;
|
|
11154
|
+
const m = /* @__PURE__ */ new Map();
|
|
11155
|
+
for (const p of positionalParams) {
|
|
11156
|
+
const def = {
|
|
11157
|
+
id: defIdCounter++,
|
|
11158
|
+
variable: p,
|
|
11159
|
+
line: fn.startPosition.row + 1,
|
|
11160
|
+
kind: "param",
|
|
11161
|
+
expression: `bash-fn:${fnName}`
|
|
11162
|
+
};
|
|
11163
|
+
defs.push(def);
|
|
11164
|
+
m.set(p, def.id);
|
|
11165
|
+
}
|
|
11166
|
+
bashFnDefs.set(fnName, m);
|
|
11167
|
+
bashFnByNodeId.set(fn.id, fnName);
|
|
11168
|
+
}
|
|
11169
|
+
const enclosingBashFn = (node) => {
|
|
11170
|
+
let cur = node.parent;
|
|
11171
|
+
while (cur) {
|
|
11172
|
+
if (cur.type === "function_definition") return bashFnByNodeId.get(cur.id) ?? null;
|
|
11173
|
+
cur = cur.parent;
|
|
11174
|
+
}
|
|
11175
|
+
return null;
|
|
11176
|
+
};
|
|
11177
|
+
const positionalDef = (varName, node, fallback) => {
|
|
11178
|
+
if (!positionalParams.includes(varName)) return fallback;
|
|
11179
|
+
const fnName = enclosingBashFn(node);
|
|
11180
|
+
if (!fnName) return fallback;
|
|
11181
|
+
return bashFnDefs.get(fnName)?.get(varName) ?? fallback;
|
|
11182
|
+
};
|
|
11074
11183
|
walkTree(tree.rootNode, (node) => {
|
|
11075
11184
|
if (node.type === "variable_assignment") {
|
|
11076
11185
|
const nameNode = node.childForFieldName("name");
|
|
@@ -11123,7 +11232,7 @@ function buildBashDFG(tree) {
|
|
|
11123
11232
|
if (varNameNode) {
|
|
11124
11233
|
const varName = getNodeText(varNameNode);
|
|
11125
11234
|
if (varName && !varName.startsWith("?") && !varName.startsWith("#")) {
|
|
11126
|
-
let reachingDef = findReachingDef(varName, scopeStack);
|
|
11235
|
+
let reachingDef = positionalDef(varName, node, findReachingDef(varName, scopeStack));
|
|
11127
11236
|
if (reachingDef === null && !positionalParams.includes(varName)) {
|
|
11128
11237
|
const def = {
|
|
11129
11238
|
id: defIdCounter++,
|
|
@@ -11147,7 +11256,7 @@ function buildBashDFG(tree) {
|
|
|
11147
11256
|
const varNameNode = node.namedChildCount > 0 ? node.namedChild(0) : null;
|
|
11148
11257
|
if (varNameNode && varNameNode.type === "variable_name") {
|
|
11149
11258
|
const varName = getNodeText(varNameNode);
|
|
11150
|
-
let reachingDef = findReachingDef(varName, scopeStack);
|
|
11259
|
+
let reachingDef = positionalDef(varName, node, findReachingDef(varName, scopeStack));
|
|
11151
11260
|
if (reachingDef === null && !positionalParams.includes(varName)) {
|
|
11152
11261
|
const def = {
|
|
11153
11262
|
id: defIdCounter++,
|
|
@@ -11169,8 +11278,56 @@ function buildBashDFG(tree) {
|
|
|
11169
11278
|
}
|
|
11170
11279
|
});
|
|
11171
11280
|
const chains = computeChains(defs, uses);
|
|
11281
|
+
chains.push(...bashCallArgChains(tree, bashFnDefs, uses, positionalParams));
|
|
11172
11282
|
return { defs, uses, chains };
|
|
11173
11283
|
}
|
|
11284
|
+
function bashCallArgChains(tree, bashFnDefs, uses, positionalParams) {
|
|
11285
|
+
const chains = [];
|
|
11286
|
+
if (bashFnDefs.size === 0) return chains;
|
|
11287
|
+
const renumbers = /* @__PURE__ */ new Set();
|
|
11288
|
+
for (const fn of findNodes(tree.rootNode, "function_definition")) {
|
|
11289
|
+
const nameNode = fn.childForFieldName("name");
|
|
11290
|
+
if (!nameNode) continue;
|
|
11291
|
+
if (/\bshift\b|\bset\s+--/.test(getNodeText(fn))) renumbers.add(getNodeText(nameNode));
|
|
11292
|
+
}
|
|
11293
|
+
const usesByLine = /* @__PURE__ */ new Map();
|
|
11294
|
+
for (const u of uses) {
|
|
11295
|
+
const l = usesByLine.get(u.line) ?? [];
|
|
11296
|
+
l.push(u);
|
|
11297
|
+
usesByLine.set(u.line, l);
|
|
11298
|
+
}
|
|
11299
|
+
for (const cmd of findNodes(tree.rootNode, "command")) {
|
|
11300
|
+
const nameNode = cmd.childForFieldName("name");
|
|
11301
|
+
if (!nameNode) continue;
|
|
11302
|
+
const fnDefs = bashFnDefs.get(getNodeText(nameNode));
|
|
11303
|
+
if (!fnDefs) continue;
|
|
11304
|
+
const fnName = getNodeText(nameNode);
|
|
11305
|
+
let position = 0;
|
|
11306
|
+
for (let i2 = 0; i2 < cmd.childCount; i2++) {
|
|
11307
|
+
const arg = cmd.child(i2);
|
|
11308
|
+
if (!arg || arg.id === nameNode.id) continue;
|
|
11309
|
+
if (arg.type.includes("redirect") || arg.type === "heredoc_body" || arg.type === "file_descriptor") continue;
|
|
11310
|
+
const text = getNodeText(arg);
|
|
11311
|
+
if (!text.trim()) continue;
|
|
11312
|
+
position++;
|
|
11313
|
+
const names = /* @__PURE__ */ new Set();
|
|
11314
|
+
const re = /\$\{?([A-Za-z_][A-Za-z0-9_]*|[0-9@*])/g;
|
|
11315
|
+
let m;
|
|
11316
|
+
while ((m = re.exec(text)) !== null) names.add(m[1]);
|
|
11317
|
+
if (names.size === 0) continue;
|
|
11318
|
+
const spreads = /\$\{?[@*]/.test(text);
|
|
11319
|
+
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);
|
|
11320
|
+
const lineUses = usesByLine.get(arg.startPosition.row + 1) ?? [];
|
|
11321
|
+
for (const name2 of names) {
|
|
11322
|
+
for (const u of lineUses) {
|
|
11323
|
+
if (u.variable !== name2 || u.def_id === null) continue;
|
|
11324
|
+
for (const t of targets) chains.push({ from_def: u.def_id, to_def: t, via: name2 });
|
|
11325
|
+
}
|
|
11326
|
+
}
|
|
11327
|
+
}
|
|
11328
|
+
}
|
|
11329
|
+
return chains;
|
|
11330
|
+
}
|
|
11174
11331
|
function buildRustDFG(tree, cache) {
|
|
11175
11332
|
const defs = [];
|
|
11176
11333
|
const uses = [];
|
|
@@ -11880,6 +12037,198 @@ function javaReplaceCrLfCoverage(method, rawFrom) {
|
|
|
11880
12037
|
return { cr, lf };
|
|
11881
12038
|
}
|
|
11882
12039
|
|
|
12040
|
+
// src/analysis/js-crlf-strip.ts
|
|
12041
|
+
var MAX_PATTERN_LENGTH = 200;
|
|
12042
|
+
var NEUTRAL_AFTER_REPLACE = /* @__PURE__ */ new Set([
|
|
12043
|
+
"replace",
|
|
12044
|
+
"replaceAll",
|
|
12045
|
+
"trim",
|
|
12046
|
+
"trimStart",
|
|
12047
|
+
"trimEnd",
|
|
12048
|
+
"toString",
|
|
12049
|
+
"toLowerCase",
|
|
12050
|
+
"toUpperCase",
|
|
12051
|
+
"slice",
|
|
12052
|
+
"substring"
|
|
12053
|
+
]);
|
|
12054
|
+
var PROBE = "a\r\rb\n\nc\r\nd\n\re x ";
|
|
12055
|
+
function readQuoted(s, i2) {
|
|
12056
|
+
const q = s[i2];
|
|
12057
|
+
if (q !== '"' && q !== "'" && q !== "`") return null;
|
|
12058
|
+
let out2 = "";
|
|
12059
|
+
for (let j = i2 + 1; j < s.length; j++) {
|
|
12060
|
+
const c = s[j];
|
|
12061
|
+
if (c === q) return { value: out2, end: j + 1 };
|
|
12062
|
+
if (q === "`" && c === "$" && s[j + 1] === "{") return null;
|
|
12063
|
+
if (c === "\\") {
|
|
12064
|
+
const n = s[j + 1];
|
|
12065
|
+
if (n === void 0) return null;
|
|
12066
|
+
switch (n) {
|
|
12067
|
+
case "n":
|
|
12068
|
+
out2 += "\n";
|
|
12069
|
+
j++;
|
|
12070
|
+
continue;
|
|
12071
|
+
case "r":
|
|
12072
|
+
out2 += "\r";
|
|
12073
|
+
j++;
|
|
12074
|
+
continue;
|
|
12075
|
+
case "t":
|
|
12076
|
+
out2 += " ";
|
|
12077
|
+
j++;
|
|
12078
|
+
continue;
|
|
12079
|
+
case "0":
|
|
12080
|
+
out2 += "\0";
|
|
12081
|
+
j++;
|
|
12082
|
+
continue;
|
|
12083
|
+
case "x": {
|
|
12084
|
+
const h = /^[0-9a-fA-F]{2}/.exec(s.slice(j + 2, j + 4));
|
|
12085
|
+
if (!h) return null;
|
|
12086
|
+
out2 += String.fromCharCode(parseInt(h[0], 16));
|
|
12087
|
+
j += 3;
|
|
12088
|
+
continue;
|
|
12089
|
+
}
|
|
12090
|
+
case "u": {
|
|
12091
|
+
const h = /^[0-9a-fA-F]{4}/.exec(s.slice(j + 2, j + 6));
|
|
12092
|
+
if (!h) return null;
|
|
12093
|
+
out2 += String.fromCharCode(parseInt(h[0], 16));
|
|
12094
|
+
j += 5;
|
|
12095
|
+
continue;
|
|
12096
|
+
}
|
|
12097
|
+
default:
|
|
12098
|
+
out2 += n;
|
|
12099
|
+
j++;
|
|
12100
|
+
continue;
|
|
12101
|
+
}
|
|
12102
|
+
}
|
|
12103
|
+
if (c === "\n" && q !== "`") return null;
|
|
12104
|
+
out2 += c;
|
|
12105
|
+
}
|
|
12106
|
+
return null;
|
|
12107
|
+
}
|
|
12108
|
+
function readRegexLiteral(s, i2) {
|
|
12109
|
+
if (s[i2] !== "/") return null;
|
|
12110
|
+
let inClass = false;
|
|
12111
|
+
for (let j = i2 + 1; j < s.length; j++) {
|
|
12112
|
+
const c = s[j];
|
|
12113
|
+
if (c === "\n") return null;
|
|
12114
|
+
if (c === "\\") {
|
|
12115
|
+
j++;
|
|
12116
|
+
continue;
|
|
12117
|
+
}
|
|
12118
|
+
if (inClass) {
|
|
12119
|
+
if (c === "]") inClass = false;
|
|
12120
|
+
continue;
|
|
12121
|
+
}
|
|
12122
|
+
if (c === "[") {
|
|
12123
|
+
inClass = true;
|
|
12124
|
+
continue;
|
|
12125
|
+
}
|
|
12126
|
+
if (c === "/") {
|
|
12127
|
+
const flags2 = /^[dgimsuvy]*/.exec(s.slice(j + 1))[0];
|
|
12128
|
+
return { source: s.slice(i2 + 1, j), flags: flags2, end: j + 1 + flags2.length };
|
|
12129
|
+
}
|
|
12130
|
+
}
|
|
12131
|
+
return null;
|
|
12132
|
+
}
|
|
12133
|
+
var skipWs = (s, i2) => {
|
|
12134
|
+
while (i2 < s.length && /\s/.test(s[i2])) i2++;
|
|
12135
|
+
return i2;
|
|
12136
|
+
};
|
|
12137
|
+
function parseSteps(chainText) {
|
|
12138
|
+
const steps = [];
|
|
12139
|
+
const callRe = /\.\s*(replaceAll|replace)\s*\(/g;
|
|
12140
|
+
let m;
|
|
12141
|
+
while ((m = callRe.exec(chainText)) !== null) {
|
|
12142
|
+
const kind = m[1];
|
|
12143
|
+
let i2 = skipWs(chainText, m.index + m[0].length);
|
|
12144
|
+
let pattern;
|
|
12145
|
+
const rx = readRegexLiteral(chainText, i2);
|
|
12146
|
+
if (rx) {
|
|
12147
|
+
if (rx.source.length > MAX_PATTERN_LENGTH) return null;
|
|
12148
|
+
try {
|
|
12149
|
+
pattern = new RegExp(rx.source, rx.flags);
|
|
12150
|
+
} catch {
|
|
12151
|
+
return null;
|
|
12152
|
+
}
|
|
12153
|
+
i2 = rx.end;
|
|
12154
|
+
} else {
|
|
12155
|
+
const str = readQuoted(chainText, i2);
|
|
12156
|
+
if (!str) return null;
|
|
12157
|
+
pattern = str.value;
|
|
12158
|
+
i2 = str.end;
|
|
12159
|
+
}
|
|
12160
|
+
i2 = skipWs(chainText, i2);
|
|
12161
|
+
if (chainText[i2] !== ",") return null;
|
|
12162
|
+
i2 = skipWs(chainText, i2 + 1);
|
|
12163
|
+
const rep = readQuoted(chainText, i2);
|
|
12164
|
+
if (!rep) return null;
|
|
12165
|
+
i2 = skipWs(chainText, rep.end);
|
|
12166
|
+
if (chainText[i2] !== ")") return null;
|
|
12167
|
+
steps.push({ kind, pattern, replacement: rep.value });
|
|
12168
|
+
callRe.lastIndex = i2 + 1;
|
|
12169
|
+
}
|
|
12170
|
+
return steps;
|
|
12171
|
+
}
|
|
12172
|
+
function run2(steps, input) {
|
|
12173
|
+
let s = input;
|
|
12174
|
+
try {
|
|
12175
|
+
for (const st of steps) {
|
|
12176
|
+
if (st.kind === "replaceAll" && st.pattern instanceof RegExp && !st.pattern.global) return null;
|
|
12177
|
+
s = st.kind === "replaceAll" ? s.replaceAll(st.pattern, st.replacement) : s.replace(st.pattern, st.replacement);
|
|
12178
|
+
}
|
|
12179
|
+
} catch {
|
|
12180
|
+
return null;
|
|
12181
|
+
}
|
|
12182
|
+
return s;
|
|
12183
|
+
}
|
|
12184
|
+
function jsReplaceChainVerdict(chainText) {
|
|
12185
|
+
const steps = parseSteps(chainText);
|
|
12186
|
+
if (!steps || steps.length === 0) return "undecided";
|
|
12187
|
+
const first = chainText.search(/\.\s*(?:replaceAll|replace)\s*\(/);
|
|
12188
|
+
for (const seg of chainText.slice(first + 1).matchAll(/\.\s*([A-Za-z_$][\w$]*)\s*\(/g)) {
|
|
12189
|
+
if (!NEUTRAL_AFTER_REPLACE.has(seg[1])) return "undecided";
|
|
12190
|
+
}
|
|
12191
|
+
const out2 = run2(steps, PROBE);
|
|
12192
|
+
if (out2 === null) return "undecided";
|
|
12193
|
+
return out2.includes("\r") || out2.includes("\n") ? "leaves-crlf" : "strips-crlf";
|
|
12194
|
+
}
|
|
12195
|
+
function jsChainTextAt(sourceLines, line, column) {
|
|
12196
|
+
const text = [(sourceLines[line - 1] ?? "").slice(column), ...sourceLines.slice(line, line + 7)].join("\n");
|
|
12197
|
+
let depth = 0;
|
|
12198
|
+
let prevSig = "";
|
|
12199
|
+
let i2 = 0;
|
|
12200
|
+
for (; i2 < text.length; i2++) {
|
|
12201
|
+
const c = text[i2];
|
|
12202
|
+
if (c === '"' || c === "'" || c === "`") {
|
|
12203
|
+
for (i2++; i2 < text.length && text[i2] !== c; i2++) if (text[i2] === "\\") i2++;
|
|
12204
|
+
prevSig = c;
|
|
12205
|
+
continue;
|
|
12206
|
+
}
|
|
12207
|
+
if (c === "/" && depth > 0 && (prevSig === "(" || prevSig === ",")) {
|
|
12208
|
+
const rx = readRegexLiteral(text, i2);
|
|
12209
|
+
if (rx) {
|
|
12210
|
+
i2 = rx.end - 1;
|
|
12211
|
+
prevSig = "/";
|
|
12212
|
+
continue;
|
|
12213
|
+
}
|
|
12214
|
+
}
|
|
12215
|
+
if (c === "(" || c === "[" || c === "{") {
|
|
12216
|
+
depth++;
|
|
12217
|
+
prevSig = c;
|
|
12218
|
+
continue;
|
|
12219
|
+
}
|
|
12220
|
+
if (c === ")" || c === "]" || c === "}") {
|
|
12221
|
+
if (depth === 0) break;
|
|
12222
|
+
depth--;
|
|
12223
|
+
prevSig = c;
|
|
12224
|
+
continue;
|
|
12225
|
+
}
|
|
12226
|
+
if (depth === 0 && !/[\w$.\s]/.test(c)) break;
|
|
12227
|
+
if (!/\s/.test(c)) prevSig = c;
|
|
12228
|
+
}
|
|
12229
|
+
return text.slice(0, i2);
|
|
12230
|
+
}
|
|
12231
|
+
|
|
11883
12232
|
// src/analysis/passes/language-sources-pass.ts
|
|
11884
12233
|
var JINJA_LITERAL_ARG_RE_FRAG = `(?:"[^"\\\\]*"|'[^'\\\\]*')`;
|
|
11885
12234
|
var JINJA_SAFE_RTS_RE = new RegExp(
|
|
@@ -12663,6 +13012,10 @@ var DEFAULT_SOURCES = [
|
|
|
12663
13012
|
{ method: "get", class: "Redis", type: "db_input", severity: "medium", return_tainted: true, languages: ["javascript", "typescript"] },
|
|
12664
13013
|
{ method: "hget", class: "Redis", type: "db_input", severity: "medium", return_tainted: true, languages: ["javascript", "typescript"] },
|
|
12665
13014
|
{ method: "mget", class: "Redis", type: "db_input", severity: "medium", return_tainted: true, languages: ["javascript", "typescript"] },
|
|
13015
|
+
// Node redis clients are often bound to `cache` from `createClient()`; class
|
|
13016
|
+
// `cache` is scoped to JS/TS (Python Django cache above) and Map.get is
|
|
13017
|
+
// filtered in sink-filter-pass (#818 src14).
|
|
13018
|
+
{ method: "get", class: "cache", type: "db_input", severity: "medium", return_tainted: true, languages: ["javascript", "typescript"] },
|
|
12666
13019
|
{ method: "get", class: "Jedis", type: "db_input", severity: "medium", return_tainted: true, languages: ["java"] },
|
|
12667
13020
|
{ method: "hget", class: "Jedis", type: "db_input", severity: "medium", return_tainted: true, languages: ["java"] },
|
|
12668
13021
|
{ method: "mget", class: "Jedis", type: "db_input", severity: "medium", return_tainted: true, languages: ["java"] },
|
|
@@ -12842,7 +13195,9 @@ var TRUST_BOUNDARY_FRAMEWORK_SINKS = [
|
|
|
12842
13195
|
// on arg[1] (the *Cookie) catches struct-literal composite tainting.
|
|
12843
13196
|
{ method: "SetCookie", class: "http", type: "trust_boundary", cwe: "CWE-501", severity: "medium", arg_positions: [1], languages: ["go"] },
|
|
12844
13197
|
// gin: c.SetCookie(name, value, maxAge, path, domain, secure, httpOnly).
|
|
12845
|
-
{ method: "SetCookie", class: "Context", type: "trust_boundary", cwe: "CWE-501", severity: "medium", arg_positions: [1], languages: ["go"] }
|
|
13198
|
+
{ method: "SetCookie", class: "Context", type: "trust_boundary", cwe: "CWE-501", severity: "medium", arg_positions: [1], languages: ["go"] },
|
|
13199
|
+
// --- Rust: actix-session / tower-sessions write -------------------------
|
|
13200
|
+
{ method: "insert", class: "Session", type: "trust_boundary", cwe: "CWE-501", severity: "medium", arg_positions: [1], languages: ["rust"] }
|
|
12846
13201
|
];
|
|
12847
13202
|
var DEFAULT_SINKS = [
|
|
12848
13203
|
// SQL Injection (CWE-89)
|
|
@@ -13503,7 +13858,9 @@ var DEFAULT_SINKS = [
|
|
|
13503
13858
|
{ method: "addParametersToContext", class: "StaticParametersInterceptor", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
|
|
13504
13859
|
{ method: "build", class: "HttpParameters", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
|
|
13505
13860
|
// Cron expression parsing (DoS/ReDoS)
|
|
13506
|
-
|
|
13861
|
+
// cognium-dev#998 — Java cron-utils `CronParser`; scoped to Java so a JS/TS
|
|
13862
|
+
// `parser.parse(...)` (tree-sitter, a lockfile parser) never matches it by name.
|
|
13863
|
+
{ method: "parse", class: "CronParser", type: "code_injection", cwe: "CWE-94", severity: "high", arg_positions: [0], languages: ["java"] },
|
|
13507
13864
|
{ method: "isValid", class: "CronValidator", type: "code_injection", cwe: "CWE-94", severity: "high", arg_positions: [0] },
|
|
13508
13865
|
// ff4j feature flags
|
|
13509
13866
|
{ method: "check", class: "FF4j", type: "code_injection", cwe: "CWE-94", severity: "high", arg_positions: [0] },
|
|
@@ -13600,9 +13957,13 @@ var DEFAULT_SINKS = [
|
|
|
13600
13957
|
{ method: "selectNodes", class: "Node", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0] },
|
|
13601
13958
|
{ method: "selectSingleNode", class: "Node", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0] },
|
|
13602
13959
|
// XXE - XML External Entity (CWE-611)
|
|
13603
|
-
|
|
13604
|
-
|
|
13605
|
-
|
|
13960
|
+
// cognium-dev#998 — JAXP parsers are JVM classes. Scoped to Java so the
|
|
13961
|
+
// receiver-name heuristic (`parser` → SAXParser) cannot turn a JS/TS
|
|
13962
|
+
// `parser.parse(text)` into an XXE sink. JS/TS XML parsers (sax, xml2js,
|
|
13963
|
+
// libxmljs, DOMParser) have their own entries.
|
|
13964
|
+
{ method: "parse", class: "DocumentBuilder", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0], languages: ["java"] },
|
|
13965
|
+
{ method: "parse", class: "SAXParser", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0], languages: ["java"] },
|
|
13966
|
+
{ method: "parse", class: "XMLReader", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0], languages: ["java"] },
|
|
13606
13967
|
{ method: "unmarshal", class: "Unmarshaller", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0] },
|
|
13607
13968
|
{ method: "newTransformer", class: "TransformerFactory", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0] },
|
|
13608
13969
|
{ method: "transform", class: "Transformer", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0] },
|
|
@@ -13857,6 +14218,46 @@ var DEFAULT_SINKS = [
|
|
|
13857
14218
|
{ method: "each", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], allow_unresolved_receiver: true },
|
|
13858
14219
|
{ method: "get", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], allow_unresolved_receiver: true },
|
|
13859
14220
|
{ method: "exec", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], allow_unresolved_receiver: true },
|
|
14221
|
+
// DuckDB from JS/TS: `@duckdb/node-api` (DuckDBConnection), the classic
|
|
14222
|
+
// `duckdb` / `duckdb-async` bindings (Database / Connection) and
|
|
14223
|
+
// `react-native-duckdb` (Database / Transaction). The method names are
|
|
14224
|
+
// generic and the receivers come from factories (`await instance.connect()`,
|
|
14225
|
+
// `open({...})`), so the receiver cannot be typed: the import is the gate
|
|
14226
|
+
// (`requires_import`). Only the SQL text (arg 0) is a sink — values passed
|
|
14227
|
+
// as bound parameters (`run(sql, [v])`, `execute(sql, params)`,
|
|
14228
|
+
// `executeNamed(sql, {k: v})`, `stmt.bind(...)`) are parameterised and do
|
|
14229
|
+
// not reach it.
|
|
14230
|
+
{ method: "run", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["@duckdb/node-api", "duckdb", "duckdb-async"] },
|
|
14231
|
+
{ 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"] },
|
|
14232
|
+
{ 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"] },
|
|
14233
|
+
{ method: "runAndRead", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["@duckdb/node-api"] },
|
|
14234
|
+
{ method: "runAndReadAll", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["@duckdb/node-api"] },
|
|
14235
|
+
{ method: "runAndReadUntil", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["@duckdb/node-api"] },
|
|
14236
|
+
{ method: "streamAndRead", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["@duckdb/node-api"] },
|
|
14237
|
+
{ method: "streamAndReadAll", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["@duckdb/node-api"] },
|
|
14238
|
+
{ method: "streamAndReadUntil", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["@duckdb/node-api"] },
|
|
14239
|
+
{ method: "start", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["@duckdb/node-api"] },
|
|
14240
|
+
{ method: "startStream", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["@duckdb/node-api"] },
|
|
14241
|
+
{ method: "extractStatements", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["@duckdb/node-api"] },
|
|
14242
|
+
{ method: "all", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["duckdb", "duckdb-async"] },
|
|
14243
|
+
{ method: "each", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["duckdb", "duckdb-async"] },
|
|
14244
|
+
{ method: "exec", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["duckdb", "duckdb-async"] },
|
|
14245
|
+
{ method: "arrowIPCAll", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["duckdb", "duckdb-async"] },
|
|
14246
|
+
{ method: "arrowIPCStream", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["duckdb", "duckdb-async"] },
|
|
14247
|
+
{ method: "execute", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["react-native-duckdb"] },
|
|
14248
|
+
{ method: "executeSync", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["react-native-duckdb"] },
|
|
14249
|
+
{ method: "executeNamed", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["react-native-duckdb"] },
|
|
14250
|
+
{ method: "executeSyncNamed", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["react-native-duckdb"] },
|
|
14251
|
+
{ method: "streamNamed", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["react-native-duckdb"] },
|
|
14252
|
+
// RxDB (Mango queries, any storage incl. SQLite): a selector or query
|
|
14253
|
+
// object, a field name passed to the query builder, or a `$regex` pattern
|
|
14254
|
+
// under caller control is NoSQL operator injection (CWE-943). Gated on the
|
|
14255
|
+
// `rxdb` import for the same reason as DuckDB above. `find` is already a
|
|
14256
|
+
// classless JS NoSQL sink in the JavaScript plugin.
|
|
14257
|
+
{ method: "findOne", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["rxdb"] },
|
|
14258
|
+
{ method: "count", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["rxdb"] },
|
|
14259
|
+
{ method: "where", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["rxdb"] },
|
|
14260
|
+
{ method: "regex", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["rxdb"] },
|
|
13860
14261
|
// TypeORM QueryBuilder raw fragments (CWE-89). TypeORM's raw `.query(sql)`
|
|
13861
14262
|
// escape hatch is already covered by the classless `query` sink in the JS
|
|
13862
14263
|
// plugin (getBuiltinSinks), so it is NOT re-registered here — the dedup
|
|
@@ -13967,6 +14368,8 @@ var DEFAULT_SINKS = [
|
|
|
13967
14368
|
{ method: "get", class: "http", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
|
|
13968
14369
|
{ method: "request", class: "https", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
|
|
13969
14370
|
{ method: "get", class: "https", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
|
|
14371
|
+
// Node.js dns.promises (CWE-918 / NUL-in-name SSRF class — #895)
|
|
14372
|
+
{ method: "resolve", class: "dns.promises", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
13970
14373
|
// needle library (used in NodeGoat)
|
|
13971
14374
|
{ method: "get", class: "needle", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
|
|
13972
14375
|
{ method: "post", class: "needle", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
|
|
@@ -14461,7 +14864,7 @@ var DEFAULT_SINKS = [
|
|
|
14461
14864
|
// receiver-taint or operator-LHS-taint tracking — the format string is the
|
|
14462
14865
|
// receiver, not an argument. Deferred to Sprint 6 (#86 follow-up).
|
|
14463
14866
|
// C-style: printf / fprintf / sprintf / snprintf via ctypes/cffi.
|
|
14464
|
-
{ method: "printf", type: "format_string", cwe: "CWE-134", severity: "high", arg_positions: [0], languages: ["python"] },
|
|
14867
|
+
{ method: "printf", type: "format_string", cwe: "CWE-134", severity: "high", arg_positions: [0], languages: ["python", "rust"] },
|
|
14465
14868
|
{ method: "fprintf", type: "format_string", cwe: "CWE-134", severity: "high", arg_positions: [1], languages: ["python"] },
|
|
14466
14869
|
// Go: fmt.Sprintf/Printf/Fprintf/Errorf — format string is first/second arg
|
|
14467
14870
|
{ method: "Sprintf", class: "fmt", type: "format_string", cwe: "CWE-134", severity: "medium", arg_positions: [0], languages: ["go"] },
|
|
@@ -15530,198 +15933,6 @@ function getDefaultConfig() {
|
|
|
15530
15933
|
};
|
|
15531
15934
|
}
|
|
15532
15935
|
|
|
15533
|
-
// src/analysis/js-crlf-strip.ts
|
|
15534
|
-
var MAX_PATTERN_LENGTH = 200;
|
|
15535
|
-
var NEUTRAL_AFTER_REPLACE = /* @__PURE__ */ new Set([
|
|
15536
|
-
"replace",
|
|
15537
|
-
"replaceAll",
|
|
15538
|
-
"trim",
|
|
15539
|
-
"trimStart",
|
|
15540
|
-
"trimEnd",
|
|
15541
|
-
"toString",
|
|
15542
|
-
"toLowerCase",
|
|
15543
|
-
"toUpperCase",
|
|
15544
|
-
"slice",
|
|
15545
|
-
"substring"
|
|
15546
|
-
]);
|
|
15547
|
-
var PROBE = "a\r\rb\n\nc\r\nd\n\re x ";
|
|
15548
|
-
function readQuoted(s, i2) {
|
|
15549
|
-
const q = s[i2];
|
|
15550
|
-
if (q !== '"' && q !== "'" && q !== "`") return null;
|
|
15551
|
-
let out2 = "";
|
|
15552
|
-
for (let j = i2 + 1; j < s.length; j++) {
|
|
15553
|
-
const c = s[j];
|
|
15554
|
-
if (c === q) return { value: out2, end: j + 1 };
|
|
15555
|
-
if (q === "`" && c === "$" && s[j + 1] === "{") return null;
|
|
15556
|
-
if (c === "\\") {
|
|
15557
|
-
const n = s[j + 1];
|
|
15558
|
-
if (n === void 0) return null;
|
|
15559
|
-
switch (n) {
|
|
15560
|
-
case "n":
|
|
15561
|
-
out2 += "\n";
|
|
15562
|
-
j++;
|
|
15563
|
-
continue;
|
|
15564
|
-
case "r":
|
|
15565
|
-
out2 += "\r";
|
|
15566
|
-
j++;
|
|
15567
|
-
continue;
|
|
15568
|
-
case "t":
|
|
15569
|
-
out2 += " ";
|
|
15570
|
-
j++;
|
|
15571
|
-
continue;
|
|
15572
|
-
case "0":
|
|
15573
|
-
out2 += "\0";
|
|
15574
|
-
j++;
|
|
15575
|
-
continue;
|
|
15576
|
-
case "x": {
|
|
15577
|
-
const h = /^[0-9a-fA-F]{2}/.exec(s.slice(j + 2, j + 4));
|
|
15578
|
-
if (!h) return null;
|
|
15579
|
-
out2 += String.fromCharCode(parseInt(h[0], 16));
|
|
15580
|
-
j += 3;
|
|
15581
|
-
continue;
|
|
15582
|
-
}
|
|
15583
|
-
case "u": {
|
|
15584
|
-
const h = /^[0-9a-fA-F]{4}/.exec(s.slice(j + 2, j + 6));
|
|
15585
|
-
if (!h) return null;
|
|
15586
|
-
out2 += String.fromCharCode(parseInt(h[0], 16));
|
|
15587
|
-
j += 5;
|
|
15588
|
-
continue;
|
|
15589
|
-
}
|
|
15590
|
-
default:
|
|
15591
|
-
out2 += n;
|
|
15592
|
-
j++;
|
|
15593
|
-
continue;
|
|
15594
|
-
}
|
|
15595
|
-
}
|
|
15596
|
-
if (c === "\n" && q !== "`") return null;
|
|
15597
|
-
out2 += c;
|
|
15598
|
-
}
|
|
15599
|
-
return null;
|
|
15600
|
-
}
|
|
15601
|
-
function readRegexLiteral(s, i2) {
|
|
15602
|
-
if (s[i2] !== "/") return null;
|
|
15603
|
-
let inClass = false;
|
|
15604
|
-
for (let j = i2 + 1; j < s.length; j++) {
|
|
15605
|
-
const c = s[j];
|
|
15606
|
-
if (c === "\n") return null;
|
|
15607
|
-
if (c === "\\") {
|
|
15608
|
-
j++;
|
|
15609
|
-
continue;
|
|
15610
|
-
}
|
|
15611
|
-
if (inClass) {
|
|
15612
|
-
if (c === "]") inClass = false;
|
|
15613
|
-
continue;
|
|
15614
|
-
}
|
|
15615
|
-
if (c === "[") {
|
|
15616
|
-
inClass = true;
|
|
15617
|
-
continue;
|
|
15618
|
-
}
|
|
15619
|
-
if (c === "/") {
|
|
15620
|
-
const flags2 = /^[dgimsuvy]*/.exec(s.slice(j + 1))[0];
|
|
15621
|
-
return { source: s.slice(i2 + 1, j), flags: flags2, end: j + 1 + flags2.length };
|
|
15622
|
-
}
|
|
15623
|
-
}
|
|
15624
|
-
return null;
|
|
15625
|
-
}
|
|
15626
|
-
var skipWs = (s, i2) => {
|
|
15627
|
-
while (i2 < s.length && /\s/.test(s[i2])) i2++;
|
|
15628
|
-
return i2;
|
|
15629
|
-
};
|
|
15630
|
-
function parseSteps(chainText) {
|
|
15631
|
-
const steps = [];
|
|
15632
|
-
const callRe = /\.\s*(replaceAll|replace)\s*\(/g;
|
|
15633
|
-
let m;
|
|
15634
|
-
while ((m = callRe.exec(chainText)) !== null) {
|
|
15635
|
-
const kind = m[1];
|
|
15636
|
-
let i2 = skipWs(chainText, m.index + m[0].length);
|
|
15637
|
-
let pattern;
|
|
15638
|
-
const rx = readRegexLiteral(chainText, i2);
|
|
15639
|
-
if (rx) {
|
|
15640
|
-
if (rx.source.length > MAX_PATTERN_LENGTH) return null;
|
|
15641
|
-
try {
|
|
15642
|
-
pattern = new RegExp(rx.source, rx.flags);
|
|
15643
|
-
} catch {
|
|
15644
|
-
return null;
|
|
15645
|
-
}
|
|
15646
|
-
i2 = rx.end;
|
|
15647
|
-
} else {
|
|
15648
|
-
const str = readQuoted(chainText, i2);
|
|
15649
|
-
if (!str) return null;
|
|
15650
|
-
pattern = str.value;
|
|
15651
|
-
i2 = str.end;
|
|
15652
|
-
}
|
|
15653
|
-
i2 = skipWs(chainText, i2);
|
|
15654
|
-
if (chainText[i2] !== ",") return null;
|
|
15655
|
-
i2 = skipWs(chainText, i2 + 1);
|
|
15656
|
-
const rep = readQuoted(chainText, i2);
|
|
15657
|
-
if (!rep) return null;
|
|
15658
|
-
i2 = skipWs(chainText, rep.end);
|
|
15659
|
-
if (chainText[i2] !== ")") return null;
|
|
15660
|
-
steps.push({ kind, pattern, replacement: rep.value });
|
|
15661
|
-
callRe.lastIndex = i2 + 1;
|
|
15662
|
-
}
|
|
15663
|
-
return steps;
|
|
15664
|
-
}
|
|
15665
|
-
function run2(steps, input) {
|
|
15666
|
-
let s = input;
|
|
15667
|
-
try {
|
|
15668
|
-
for (const st of steps) {
|
|
15669
|
-
if (st.kind === "replaceAll" && st.pattern instanceof RegExp && !st.pattern.global) return null;
|
|
15670
|
-
s = st.kind === "replaceAll" ? s.replaceAll(st.pattern, st.replacement) : s.replace(st.pattern, st.replacement);
|
|
15671
|
-
}
|
|
15672
|
-
} catch {
|
|
15673
|
-
return null;
|
|
15674
|
-
}
|
|
15675
|
-
return s;
|
|
15676
|
-
}
|
|
15677
|
-
function jsReplaceChainVerdict(chainText) {
|
|
15678
|
-
const steps = parseSteps(chainText);
|
|
15679
|
-
if (!steps || steps.length === 0) return "undecided";
|
|
15680
|
-
const first = chainText.search(/\.\s*(?:replaceAll|replace)\s*\(/);
|
|
15681
|
-
for (const seg of chainText.slice(first + 1).matchAll(/\.\s*([A-Za-z_$][\w$]*)\s*\(/g)) {
|
|
15682
|
-
if (!NEUTRAL_AFTER_REPLACE.has(seg[1])) return "undecided";
|
|
15683
|
-
}
|
|
15684
|
-
const out2 = run2(steps, PROBE);
|
|
15685
|
-
if (out2 === null) return "undecided";
|
|
15686
|
-
return out2.includes("\r") || out2.includes("\n") ? "leaves-crlf" : "strips-crlf";
|
|
15687
|
-
}
|
|
15688
|
-
function jsChainTextAt(sourceLines, line, column) {
|
|
15689
|
-
const text = [(sourceLines[line - 1] ?? "").slice(column), ...sourceLines.slice(line, line + 7)].join("\n");
|
|
15690
|
-
let depth = 0;
|
|
15691
|
-
let prevSig = "";
|
|
15692
|
-
let i2 = 0;
|
|
15693
|
-
for (; i2 < text.length; i2++) {
|
|
15694
|
-
const c = text[i2];
|
|
15695
|
-
if (c === '"' || c === "'" || c === "`") {
|
|
15696
|
-
for (i2++; i2 < text.length && text[i2] !== c; i2++) if (text[i2] === "\\") i2++;
|
|
15697
|
-
prevSig = c;
|
|
15698
|
-
continue;
|
|
15699
|
-
}
|
|
15700
|
-
if (c === "/" && depth > 0 && (prevSig === "(" || prevSig === ",")) {
|
|
15701
|
-
const rx = readRegexLiteral(text, i2);
|
|
15702
|
-
if (rx) {
|
|
15703
|
-
i2 = rx.end - 1;
|
|
15704
|
-
prevSig = "/";
|
|
15705
|
-
continue;
|
|
15706
|
-
}
|
|
15707
|
-
}
|
|
15708
|
-
if (c === "(" || c === "[" || c === "{") {
|
|
15709
|
-
depth++;
|
|
15710
|
-
prevSig = c;
|
|
15711
|
-
continue;
|
|
15712
|
-
}
|
|
15713
|
-
if (c === ")" || c === "]" || c === "}") {
|
|
15714
|
-
if (depth === 0) break;
|
|
15715
|
-
depth--;
|
|
15716
|
-
prevSig = c;
|
|
15717
|
-
continue;
|
|
15718
|
-
}
|
|
15719
|
-
if (depth === 0 && !/[\w$.\s]/.test(c)) break;
|
|
15720
|
-
if (!/\s/.test(c)) prevSig = c;
|
|
15721
|
-
}
|
|
15722
|
-
return text.slice(0, i2);
|
|
15723
|
-
}
|
|
15724
|
-
|
|
15725
15936
|
// src/analysis/constant-propagation/patterns.ts
|
|
15726
15937
|
var TAINT_PATTERNS = [
|
|
15727
15938
|
// HTTP Sources (Servlet API)
|
|
@@ -16111,13 +16322,35 @@ function dropCSharpObjectCarriedWaypoints(sinks, calls) {
|
|
|
16111
16322
|
function analyzeTaint(calls, types, config = getDefaultConfig(), typeHierarchy, language, code, imports) {
|
|
16112
16323
|
const sourceLines = code !== void 0 ? code.split("\n") : void 0;
|
|
16113
16324
|
const sources = findSources(calls, types, config.sources, sourceLines, language, imports);
|
|
16114
|
-
let sinkPatterns =
|
|
16325
|
+
let sinkPatterns = config.sinks.filter((p) => sinkPatternImportApplies(p, imports));
|
|
16326
|
+
sinkPatterns = expandPromisifyAliases(sinkPatterns, sourceLines, language);
|
|
16115
16327
|
sinkPatterns = expandIndirectEvalAliases(sinkPatterns, sourceLines, language);
|
|
16116
16328
|
const sinks = findSinks(calls, sinkPatterns, typeHierarchy, language, sourceLines, types);
|
|
16117
16329
|
const gatedSinks = language === "csharp" ? dropCSharpObjectCarriedWaypoints(sinks, calls) : sinks;
|
|
16118
16330
|
const sanitizers = findSanitizers(calls, types, config.sanitizers, sourceLines, language);
|
|
16119
16331
|
return { sources, sinks: gatedSinks, sanitizers };
|
|
16120
16332
|
}
|
|
16333
|
+
function isBoundParameterCall(call, pattern, sourceLines) {
|
|
16334
|
+
if (!pattern.requires_import || pattern.type !== "sql_injection") return false;
|
|
16335
|
+
const first = call.arguments[0]?.expression?.trim() ?? "";
|
|
16336
|
+
if (first.startsWith("[") || first.startsWith("{")) return true;
|
|
16337
|
+
const receiver = call.receiver;
|
|
16338
|
+
if (!receiver || !sourceLines || !/^[A-Za-z_$][\w$]*$/.test(receiver)) return false;
|
|
16339
|
+
const assignedFromPrepare = new RegExp(
|
|
16340
|
+
`\\b${receiver.replace(/\$/g, "\\$")}\\s*(?::[^=]+)?=\\s*(?:await\\s+)?[\\w$.]+\\.prepare\\s*\\(`
|
|
16341
|
+
);
|
|
16342
|
+
return sourceLines.some((line) => assignedFromPrepare.test(line));
|
|
16343
|
+
}
|
|
16344
|
+
function sinkPatternImportApplies(pattern, imports) {
|
|
16345
|
+
const required = pattern.requires_import;
|
|
16346
|
+
if (!required || required.length === 0) return true;
|
|
16347
|
+
if (!imports || imports.length === 0) return false;
|
|
16348
|
+
return imports.some((imp) => {
|
|
16349
|
+
const from = imp.from_package;
|
|
16350
|
+
if (!from) return false;
|
|
16351
|
+
return required.some((m) => from === m || from.startsWith(m + "/"));
|
|
16352
|
+
});
|
|
16353
|
+
}
|
|
16121
16354
|
function sinkPatternAppliesTo(pattern, language) {
|
|
16122
16355
|
if (language === void 0) return true;
|
|
16123
16356
|
if (pattern.languages && pattern.languages.length > 0 && !pattern.languages.includes(language)) {
|
|
@@ -16535,6 +16768,46 @@ function isInterproceduralTaintableType(typeName, language) {
|
|
|
16535
16768
|
const leaf = baseType.split(".").pop() ?? baseType;
|
|
16536
16769
|
if (csharpTaintable.includes(baseType) || csharpTaintable.includes(leaf)) return true;
|
|
16537
16770
|
}
|
|
16771
|
+
if (language === "rust") {
|
|
16772
|
+
const trimmed = typeName.trim();
|
|
16773
|
+
const unref = trimmed.replace(/^&mut\s+/, "").replace(/^&\s*/, "");
|
|
16774
|
+
const rustBase = unref.split("<")[0].trim();
|
|
16775
|
+
const rustScalars = [
|
|
16776
|
+
"String",
|
|
16777
|
+
"str",
|
|
16778
|
+
"bool",
|
|
16779
|
+
"char",
|
|
16780
|
+
"i8",
|
|
16781
|
+
"i16",
|
|
16782
|
+
"i32",
|
|
16783
|
+
"i64",
|
|
16784
|
+
"i128",
|
|
16785
|
+
"isize",
|
|
16786
|
+
"u8",
|
|
16787
|
+
"u16",
|
|
16788
|
+
"u32",
|
|
16789
|
+
"u64",
|
|
16790
|
+
"u128",
|
|
16791
|
+
"usize",
|
|
16792
|
+
"f32",
|
|
16793
|
+
"f64"
|
|
16794
|
+
];
|
|
16795
|
+
if (rustScalars.includes(rustBase)) return true;
|
|
16796
|
+
if (rustBase === "()" || rustBase === "!") return false;
|
|
16797
|
+
if (unref.startsWith("Vec<") || unref.startsWith("Option<") || unref.startsWith("Result<")) {
|
|
16798
|
+
return true;
|
|
16799
|
+
}
|
|
16800
|
+
if (/^[A-Z][A-Za-z0-9_]*$/.test(rustBase)) return true;
|
|
16801
|
+
}
|
|
16802
|
+
if (language === "javascript" || language === "typescript") {
|
|
16803
|
+
const trimmed = typeName.trim();
|
|
16804
|
+
const jsScalars = ["string", "number", "boolean", "bigint", "any", "unknown", "object"];
|
|
16805
|
+
if (jsScalars.includes(baseType)) return true;
|
|
16806
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[") || trimmed.startsWith("(")) return true;
|
|
16807
|
+
if (/\bRecord\s*</.test(trimmed)) return true;
|
|
16808
|
+
if (["void", "undefined", "null", "never"].includes(baseType)) return false;
|
|
16809
|
+
if (/^[A-Za-z_][\w.$]*$/.test(baseType)) return true;
|
|
16810
|
+
}
|
|
16538
16811
|
if (typeName.endsWith("[]")) {
|
|
16539
16812
|
const elementType = typeName.slice(0, -2);
|
|
16540
16813
|
if (elementType === "String" || elementType === "Object" || elementType === "byte") {
|
|
@@ -16649,6 +16922,31 @@ function isSafeGoExecCommandCall(call, pattern, language) {
|
|
|
16649
16922
|
return false;
|
|
16650
16923
|
}
|
|
16651
16924
|
}
|
|
16925
|
+
if (program === "git") {
|
|
16926
|
+
const subcommandArgPos = pattern.method === "CommandContext" ? 2 : 1;
|
|
16927
|
+
const subcommandArg = call.arguments.find((a) => a.position === subcommandArgPos);
|
|
16928
|
+
let subcommand;
|
|
16929
|
+
if (subcommandArg?.literal !== null && subcommandArg?.literal !== void 0) {
|
|
16930
|
+
subcommand = String(subcommandArg.literal);
|
|
16931
|
+
} else {
|
|
16932
|
+
const expr = (subcommandArg?.expression ?? "").trim();
|
|
16933
|
+
if (expr.startsWith('"') || expr.startsWith("`") || expr.startsWith("'")) {
|
|
16934
|
+
subcommand = expr.slice(1, -1);
|
|
16935
|
+
}
|
|
16936
|
+
}
|
|
16937
|
+
if (subcommand === "ls-remote") {
|
|
16938
|
+
const hasEndOfOptions = call.arguments.some((a) => {
|
|
16939
|
+
if (a.literal !== null && a.literal !== void 0) {
|
|
16940
|
+
return String(a.literal) === "--";
|
|
16941
|
+
}
|
|
16942
|
+
const expr = (a.expression ?? "").trim();
|
|
16943
|
+
return expr === '"--"' || expr === "'--'" || expr === "`--`";
|
|
16944
|
+
});
|
|
16945
|
+
if (!hasEndOfOptions) {
|
|
16946
|
+
return false;
|
|
16947
|
+
}
|
|
16948
|
+
}
|
|
16949
|
+
}
|
|
16652
16950
|
return true;
|
|
16653
16951
|
}
|
|
16654
16952
|
function isSafeJSChildProcessCall(call, pattern, language) {
|
|
@@ -17052,6 +17350,29 @@ function isRegexReceiver(receiver, sourceLines) {
|
|
|
17052
17350
|
const decl = new RegExp(`(?:const|let|var)\\s+${escapeRe(r)}\\s*=\\s*(?:/|(?:new\\s+)?RegExp\\s*\\()`);
|
|
17053
17351
|
return sourceLines.some((l) => decl.test(l));
|
|
17054
17352
|
}
|
|
17353
|
+
function isNavigationReceiverName(receiver) {
|
|
17354
|
+
const r = (receiver ?? "").trim();
|
|
17355
|
+
if (!r) return false;
|
|
17356
|
+
const last = r.slice(r.lastIndexOf(".") + 1).replace(/\(\)$/, "");
|
|
17357
|
+
return /router|history|navigat|^nav$/i.test(last);
|
|
17358
|
+
}
|
|
17359
|
+
function isInMemoryMapGetReceiver(receiver, sourceLines) {
|
|
17360
|
+
const raw = (receiver ?? "").trim();
|
|
17361
|
+
if (!raw || !sourceLines) return false;
|
|
17362
|
+
if (/\.properties$/i.test(raw)) return true;
|
|
17363
|
+
const lastDot = raw.lastIndexOf(".");
|
|
17364
|
+
const r = (lastDot >= 0 ? raw.slice(lastDot + 1) : raw).trim();
|
|
17365
|
+
if (/^(properties|memoryCache)$/i.test(r)) return true;
|
|
17366
|
+
if (!/^[A-Za-z_$][\w$]*$/.test(r)) return false;
|
|
17367
|
+
const n = escapeRe(r);
|
|
17368
|
+
const mapCtor = new RegExp(
|
|
17369
|
+
`\\b${n}\\s*(?::[^=;]*)?=\\s*new\\s+(?:Weak)?Map\\s*(?:<.*>)?\\s*\\(`
|
|
17370
|
+
);
|
|
17371
|
+
const mapType = new RegExp(
|
|
17372
|
+
`\\b${n}\\s*\\??\\s*:\\s*(?:Readonly)?(?:Weak)?Map\\s*<`
|
|
17373
|
+
);
|
|
17374
|
+
return sourceLines.some((l) => mapCtor.test(l) || mapType.test(l));
|
|
17375
|
+
}
|
|
17055
17376
|
function isDeclaredFunctionReference(arg, sourceLines) {
|
|
17056
17377
|
if (arg.literal !== null && arg.literal !== void 0) return false;
|
|
17057
17378
|
if (!sourceLines) return false;
|
|
@@ -17256,6 +17577,12 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types)
|
|
|
17256
17577
|
if (isParameterizedQueryCall(call, pattern)) {
|
|
17257
17578
|
continue;
|
|
17258
17579
|
}
|
|
17580
|
+
if ((language === "javascript" || language === "typescript") && (pattern.type === "sql_injection" || pattern.type === "nosql_injection") && call.arguments.length === 0 && pattern.arg_positions.every((p) => p >= 0)) {
|
|
17581
|
+
continue;
|
|
17582
|
+
}
|
|
17583
|
+
if (isBoundParameterCall(call, pattern, sourceLines)) {
|
|
17584
|
+
continue;
|
|
17585
|
+
}
|
|
17259
17586
|
if (isSafePythonSubprocessCall(call, pattern, language)) {
|
|
17260
17587
|
continue;
|
|
17261
17588
|
}
|
|
@@ -17313,9 +17640,15 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types)
|
|
|
17313
17640
|
if (isArgumentlessJsResponseWriter(call, pattern, language)) {
|
|
17314
17641
|
continue;
|
|
17315
17642
|
}
|
|
17643
|
+
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)) {
|
|
17644
|
+
continue;
|
|
17645
|
+
}
|
|
17316
17646
|
if ((pattern.type === "command_injection" || pattern.type === "sql_injection") && call.method_name === "exec" && (language === "javascript" || language === "typescript") && isRegexReceiver(call.receiver, sourceLines)) {
|
|
17317
17647
|
continue;
|
|
17318
17648
|
}
|
|
17649
|
+
if (pattern.type === "sql_injection" && call.method_name === "get" && pattern.class === "Connection" && (language === "javascript" || language === "typescript") && isInMemoryMapGetReceiver(call.receiver, sourceLines)) {
|
|
17650
|
+
continue;
|
|
17651
|
+
}
|
|
17319
17652
|
if (pattern.type === "code_injection" && language === "python" && call.method_name === "compile" && call.receiver && call.receiver !== "builtins") {
|
|
17320
17653
|
continue;
|
|
17321
17654
|
}
|
|
@@ -17332,7 +17665,7 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types)
|
|
|
17332
17665
|
continue;
|
|
17333
17666
|
}
|
|
17334
17667
|
const location = formatCallLocation(call);
|
|
17335
|
-
const key = `${location}:${call.location.line}:${pattern.cwe}`;
|
|
17668
|
+
const key = `${location}:${call.location.line}:${call.location.column}:${pattern.cwe}`;
|
|
17336
17669
|
const confidence = calculateSinkConfidence(call, pattern);
|
|
17337
17670
|
const existing = sinkMap.get(key);
|
|
17338
17671
|
if (!existing || confidence > existing.confidence) {
|
|
@@ -17343,6 +17676,7 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types)
|
|
|
17343
17676
|
cwe: pattern.cwe,
|
|
17344
17677
|
location,
|
|
17345
17678
|
line: call.location.line,
|
|
17679
|
+
callColumn: call.location.column,
|
|
17346
17680
|
confidence,
|
|
17347
17681
|
method: call.method_name,
|
|
17348
17682
|
argPositions: pattern.arg_positions,
|
|
@@ -17380,6 +17714,15 @@ function matchesSourcePattern(call, pattern, language, types, imports) {
|
|
|
17380
17714
|
return false;
|
|
17381
17715
|
}
|
|
17382
17716
|
}
|
|
17717
|
+
if (!pattern.class && pattern.return_tainted && pattern.method && (language === "javascript" || language === "typescript")) {
|
|
17718
|
+
const expressRequestAccessors = /* @__PURE__ */ new Set(["query", "body", "params", "headers", "cookies"]);
|
|
17719
|
+
if (expressRequestAccessors.has(pattern.method)) {
|
|
17720
|
+
const recv = call.receiver?.toLowerCase() ?? "";
|
|
17721
|
+
if (recv !== "req" && recv !== "request" && recv !== "ctx") {
|
|
17722
|
+
return false;
|
|
17723
|
+
}
|
|
17724
|
+
}
|
|
17725
|
+
}
|
|
17383
17726
|
return pattern.return_tainted === true;
|
|
17384
17727
|
}
|
|
17385
17728
|
return false;
|
|
@@ -17473,6 +17816,7 @@ var SAFE_RECEIVERS_BY_METHOD = {
|
|
|
17473
17816
|
"database",
|
|
17474
17817
|
"sqlite",
|
|
17475
17818
|
"conn",
|
|
17819
|
+
"con",
|
|
17476
17820
|
"connection",
|
|
17477
17821
|
"client",
|
|
17478
17822
|
"pool",
|
|
@@ -17749,7 +18093,7 @@ function matchesSinkPattern(call, pattern, typeHierarchy, language) {
|
|
|
17749
18093
|
}
|
|
17750
18094
|
}
|
|
17751
18095
|
}
|
|
17752
|
-
if (!pattern.class && call.receiver) {
|
|
18096
|
+
if (!pattern.class && call.receiver && !pattern.requires_import) {
|
|
17753
18097
|
if (isKnownSafeReceiverForMethod(call.receiver, call.method_name, pattern.type)) {
|
|
17754
18098
|
return false;
|
|
17755
18099
|
}
|
|
@@ -18482,6 +18826,8 @@ function javaReplaceChainCredit(chainText) {
|
|
|
18482
18826
|
let lf = false;
|
|
18483
18827
|
let lt = false;
|
|
18484
18828
|
let gt = false;
|
|
18829
|
+
let openBrace = false;
|
|
18830
|
+
let closeBrace = false;
|
|
18485
18831
|
for (const m of chainText.matchAll(JAVA_REPLACE_ARGS_RE)) {
|
|
18486
18832
|
const kind = m[1];
|
|
18487
18833
|
const rawFrom = m[2] ?? m[3] ?? "";
|
|
@@ -18492,10 +18838,13 @@ function javaReplaceChainCredit(chainText) {
|
|
|
18492
18838
|
if (cov.lf) lf = true;
|
|
18493
18839
|
if (from === "<" && to.startsWith("&")) lt = true;
|
|
18494
18840
|
if (from === ">" && to.startsWith("&")) gt = true;
|
|
18841
|
+
if (from === "{") openBrace = true;
|
|
18842
|
+
if (from === "}") closeBrace = true;
|
|
18495
18843
|
}
|
|
18496
18844
|
const out2 = [];
|
|
18497
18845
|
if (cr && lf) out2.push("log_injection");
|
|
18498
18846
|
if (lt && gt) out2.push("xss");
|
|
18847
|
+
if (openBrace && closeBrace) out2.push("code_injection");
|
|
18499
18848
|
return out2;
|
|
18500
18849
|
}
|
|
18501
18850
|
function matchesSanitizerPattern(call, pattern) {
|
|
@@ -20609,6 +20958,9 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
20609
20958
|
case "assignment_statement":
|
|
20610
20959
|
this.handleGoAssignment(node);
|
|
20611
20960
|
return false;
|
|
20961
|
+
case "var_declaration":
|
|
20962
|
+
this.handleGoVarDeclaration(node);
|
|
20963
|
+
return false;
|
|
20612
20964
|
case "update_expression":
|
|
20613
20965
|
this.handleUpdateExpression(node);
|
|
20614
20966
|
return false;
|
|
@@ -21016,6 +21368,64 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
21016
21368
|
* helper that always returns a constant — clears the variable. Anything else stops being tracked here, which is what Go
|
|
21017
21369
|
* assignments got before this handler existed: the DFG decides.
|
|
21018
21370
|
*/
|
|
21371
|
+
/** `var x = expr` / `var x T = expr` — same clean/taint rules as `:=` (#783). */
|
|
21372
|
+
handleGoVarDeclaration(node) {
|
|
21373
|
+
for (let i2 = 0; i2 < node.childCount; i2++) {
|
|
21374
|
+
const spec = node.child(i2);
|
|
21375
|
+
if (!spec || spec.type !== "var_spec") continue;
|
|
21376
|
+
const nameNode = spec.childForFieldName("name");
|
|
21377
|
+
let valueNode = spec.childForFieldName("value");
|
|
21378
|
+
if (!nameNode || !valueNode) continue;
|
|
21379
|
+
const names = [];
|
|
21380
|
+
if (nameNode.type === "identifier") {
|
|
21381
|
+
names.push(getNodeText2(nameNode, this.source));
|
|
21382
|
+
} else if (nameNode.type === "identifier_list") {
|
|
21383
|
+
for (const c of nameNode.children) {
|
|
21384
|
+
if (c.type === "identifier") names.push(getNodeText2(c, this.source));
|
|
21385
|
+
}
|
|
21386
|
+
}
|
|
21387
|
+
if (valueNode.type === "expression_list") {
|
|
21388
|
+
valueNode = valueNode.children.find((c) => c.type !== "," && c.type !== "comment") ?? valueNode;
|
|
21389
|
+
}
|
|
21390
|
+
if (names.length === 0) continue;
|
|
21391
|
+
const line = getNodeLine(node);
|
|
21392
|
+
for (const varName of names) {
|
|
21393
|
+
if (varName === "_") continue;
|
|
21394
|
+
const scopedName = this.getScopedName(varName);
|
|
21395
|
+
this.goScopedNames.add(scopedName);
|
|
21396
|
+
if (this.isTaintedExpression(valueNode)) {
|
|
21397
|
+
this.goUnknownVars.add(scopedName);
|
|
21398
|
+
this.tainted.add(scopedName);
|
|
21399
|
+
this.sanitizedVars.delete(scopedName);
|
|
21400
|
+
this.sanitizedVarUncovered.delete(scopedName);
|
|
21401
|
+
this.definitionNodes.set(scopedName, valueNode);
|
|
21402
|
+
this.symbols.set(scopedName, createUnknown(line));
|
|
21403
|
+
continue;
|
|
21404
|
+
}
|
|
21405
|
+
if (!this.isGoKnownCleanRhs(valueNode) || this.goUnknownVars.has(scopedName)) {
|
|
21406
|
+
this.goUnknownVars.add(scopedName);
|
|
21407
|
+
this.symbols.delete(scopedName);
|
|
21408
|
+
this.sanitizedVars.delete(scopedName);
|
|
21409
|
+
this.sanitizedVarUncovered.delete(scopedName);
|
|
21410
|
+
if (!this.inConditionalBranch) {
|
|
21411
|
+
this.tainted.delete(scopedName);
|
|
21412
|
+
this.definitionNodes.delete(scopedName);
|
|
21413
|
+
}
|
|
21414
|
+
continue;
|
|
21415
|
+
}
|
|
21416
|
+
if (this.inConditionalBranch) {
|
|
21417
|
+
this.goUnknownVars.add(scopedName);
|
|
21418
|
+
this.symbols.delete(scopedName);
|
|
21419
|
+
continue;
|
|
21420
|
+
}
|
|
21421
|
+
this.tainted.delete(scopedName);
|
|
21422
|
+
this.definitionNodes.delete(scopedName);
|
|
21423
|
+
this.symbols.set(scopedName, this.evaluateExpression(valueNode));
|
|
21424
|
+
this.sanitizedVars.delete(scopedName);
|
|
21425
|
+
this.sanitizedVarUncovered.delete(scopedName);
|
|
21426
|
+
}
|
|
21427
|
+
}
|
|
21428
|
+
}
|
|
21019
21429
|
handleGoAssignment(node) {
|
|
21020
21430
|
const left = node.childForFieldName("left");
|
|
21021
21431
|
const right = node.childForFieldName("right");
|
|
@@ -21832,6 +22242,24 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
21832
22242
|
cache.set(rootId, false);
|
|
21833
22243
|
return false;
|
|
21834
22244
|
}
|
|
22245
|
+
/** Is an argument of `call` an inline sanitizer call over tainted input (itself possibly nested)? */
|
|
22246
|
+
argIsSanitizedTaint(call) {
|
|
22247
|
+
const argsNode = call.childForFieldName("arguments");
|
|
22248
|
+
if (!argsNode) return false;
|
|
22249
|
+
for (const arg of argsNode.children) {
|
|
22250
|
+
if (arg.type !== "method_invocation") continue;
|
|
22251
|
+
const name2 = arg.childForFieldName("name");
|
|
22252
|
+
if (!name2 || !SANITIZER_METHODS.has(getNodeText2(name2, this.source))) continue;
|
|
22253
|
+
const inner = arg.childForFieldName("arguments");
|
|
22254
|
+
if (!inner) continue;
|
|
22255
|
+
for (const a of inner.children) {
|
|
22256
|
+
if (a.type === "(" || a.type === ")" || a.type === ",") continue;
|
|
22257
|
+
if (this.isTaintedExpression(a)) return true;
|
|
22258
|
+
}
|
|
22259
|
+
if (this.argIsSanitizedTaint(arg)) return true;
|
|
22260
|
+
}
|
|
22261
|
+
return false;
|
|
22262
|
+
}
|
|
21835
22263
|
isTaintedExpressionStep(node) {
|
|
21836
22264
|
const text = getNodeText2(node, this.source);
|
|
21837
22265
|
if (node.type === "method_invocation") {
|
|
@@ -21839,6 +22267,9 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
21839
22267
|
const objectNode = node.childForFieldName("object");
|
|
21840
22268
|
if (nameNode) {
|
|
21841
22269
|
const methodName = getNodeText2(nameNode, this.source);
|
|
22270
|
+
if (ANTI_SANITIZER_METHODS.has(methodName) && this.argIsSanitizedTaint(node)) {
|
|
22271
|
+
return true;
|
|
22272
|
+
}
|
|
21842
22273
|
if (SANITIZER_METHODS.has(methodName) && !this.localNonSanitizing.has(methodName)) {
|
|
21843
22274
|
return false;
|
|
21844
22275
|
}
|