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
|
@@ -10390,6 +10390,79 @@ function processStatement2(stmt, startDefId, startUseId, scopeStack, isJavaScrip
|
|
|
10390
10390
|
carryBranchDefsOut(popped, scopeStack, alwaysRuns);
|
|
10391
10391
|
break;
|
|
10392
10392
|
}
|
|
10393
|
+
case "try_statement":
|
|
10394
|
+
case "try_with_resources_statement": {
|
|
10395
|
+
if (isJavaScript) {
|
|
10396
|
+
const stmtUses = extractUses(stmt, useId, scopeStack, isJavaScript);
|
|
10397
|
+
uses.push(...stmtUses.uses);
|
|
10398
|
+
useId = stmtUses.nextId;
|
|
10399
|
+
break;
|
|
10400
|
+
}
|
|
10401
|
+
scopeStack.push(/* @__PURE__ */ new Map());
|
|
10402
|
+
const resources = stmt.childForFieldName("resources");
|
|
10403
|
+
for (const res of resources?.namedChildren ?? []) {
|
|
10404
|
+
if (!res || res.type !== "resource") continue;
|
|
10405
|
+
const nameNode = res.childForFieldName("name");
|
|
10406
|
+
const value = res.childForFieldName("value");
|
|
10407
|
+
if (value) {
|
|
10408
|
+
const valueUses = extractUses(value, useId, scopeStack, isJavaScript);
|
|
10409
|
+
uses.push(...valueUses.uses);
|
|
10410
|
+
useId = valueUses.nextId;
|
|
10411
|
+
}
|
|
10412
|
+
if (nameNode && nameNode.type === "identifier") {
|
|
10413
|
+
const def = { id: defId++, variable: getNodeText(nameNode), line: nameNode.startPosition.row + 1, column: nameNode.startPosition.column, kind: "local" };
|
|
10414
|
+
defs.push(def);
|
|
10415
|
+
currentScope(scopeStack).set(def.variable, def.id);
|
|
10416
|
+
markDeclared(currentScope(scopeStack), def.variable);
|
|
10417
|
+
} else if (!value) {
|
|
10418
|
+
const resUses = extractUses(res, useId, scopeStack, isJavaScript);
|
|
10419
|
+
uses.push(...resUses.uses);
|
|
10420
|
+
useId = resUses.nextId;
|
|
10421
|
+
}
|
|
10422
|
+
}
|
|
10423
|
+
for (const part of stmt.namedChildren) {
|
|
10424
|
+
if (!part) continue;
|
|
10425
|
+
if (part.type === "block") {
|
|
10426
|
+
const r = processStatement2(part, defId, useId, scopeStack, isJavaScript);
|
|
10427
|
+
defs.push(...r.defs);
|
|
10428
|
+
uses.push(...r.uses);
|
|
10429
|
+
defId = r.nextDefId;
|
|
10430
|
+
useId = r.nextUseId;
|
|
10431
|
+
} else if (part.type === "catch_clause") {
|
|
10432
|
+
scopeStack.push(/* @__PURE__ */ new Map());
|
|
10433
|
+
for (const param of part.namedChildren) {
|
|
10434
|
+
if (!param || param.type !== "catch_formal_parameter") continue;
|
|
10435
|
+
const pname = param.childForFieldName("name");
|
|
10436
|
+
if (pname) {
|
|
10437
|
+
const def = { id: defId++, variable: getNodeText(pname), line: pname.startPosition.row + 1, column: pname.startPosition.column, kind: "local" };
|
|
10438
|
+
defs.push(def);
|
|
10439
|
+
currentScope(scopeStack).set(def.variable, def.id);
|
|
10440
|
+
markDeclared(currentScope(scopeStack), def.variable);
|
|
10441
|
+
}
|
|
10442
|
+
}
|
|
10443
|
+
const body2 = part.childForFieldName("body");
|
|
10444
|
+
if (body2) {
|
|
10445
|
+
const r = processStatement2(body2, defId, useId, scopeStack, isJavaScript);
|
|
10446
|
+
defs.push(...r.defs);
|
|
10447
|
+
uses.push(...r.uses);
|
|
10448
|
+
defId = r.nextDefId;
|
|
10449
|
+
useId = r.nextUseId;
|
|
10450
|
+
}
|
|
10451
|
+
carryBranchDefsOut(scopeStack.pop(), scopeStack, false);
|
|
10452
|
+
} else if (part.type === "finally_clause") {
|
|
10453
|
+
for (const b of part.namedChildren) {
|
|
10454
|
+
if (!b || b.type !== "block") continue;
|
|
10455
|
+
const r = processStatement2(b, defId, useId, scopeStack, isJavaScript);
|
|
10456
|
+
defs.push(...r.defs);
|
|
10457
|
+
uses.push(...r.uses);
|
|
10458
|
+
defId = r.nextDefId;
|
|
10459
|
+
useId = r.nextUseId;
|
|
10460
|
+
}
|
|
10461
|
+
}
|
|
10462
|
+
}
|
|
10463
|
+
carryBranchDefsOut(scopeStack.pop(), scopeStack, false);
|
|
10464
|
+
break;
|
|
10465
|
+
}
|
|
10393
10466
|
case "else_clause": {
|
|
10394
10467
|
for (let i2 = 0; i2 < stmt.childCount; i2++) {
|
|
10395
10468
|
const arm = stmt.child(i2);
|
|
@@ -11005,6 +11078,42 @@ function buildBashDFG(tree) {
|
|
|
11005
11078
|
defs.push(def);
|
|
11006
11079
|
currentScope(scopeStack).set(p, def.id);
|
|
11007
11080
|
}
|
|
11081
|
+
const bashFnDefs = /* @__PURE__ */ new Map();
|
|
11082
|
+
const bashFnByNodeId = /* @__PURE__ */ new Map();
|
|
11083
|
+
for (const fn of findNodes(tree.rootNode, "function_definition")) {
|
|
11084
|
+
const nameNode = fn.childForFieldName("name");
|
|
11085
|
+
if (!nameNode) continue;
|
|
11086
|
+
const fnName = getNodeText(nameNode);
|
|
11087
|
+
if (!fnName || bashFnDefs.has(fnName)) continue;
|
|
11088
|
+
const m = /* @__PURE__ */ new Map();
|
|
11089
|
+
for (const p of positionalParams) {
|
|
11090
|
+
const def = {
|
|
11091
|
+
id: defIdCounter++,
|
|
11092
|
+
variable: p,
|
|
11093
|
+
line: fn.startPosition.row + 1,
|
|
11094
|
+
kind: "param",
|
|
11095
|
+
expression: `bash-fn:${fnName}`
|
|
11096
|
+
};
|
|
11097
|
+
defs.push(def);
|
|
11098
|
+
m.set(p, def.id);
|
|
11099
|
+
}
|
|
11100
|
+
bashFnDefs.set(fnName, m);
|
|
11101
|
+
bashFnByNodeId.set(fn.id, fnName);
|
|
11102
|
+
}
|
|
11103
|
+
const enclosingBashFn = (node) => {
|
|
11104
|
+
let cur = node.parent;
|
|
11105
|
+
while (cur) {
|
|
11106
|
+
if (cur.type === "function_definition") return bashFnByNodeId.get(cur.id) ?? null;
|
|
11107
|
+
cur = cur.parent;
|
|
11108
|
+
}
|
|
11109
|
+
return null;
|
|
11110
|
+
};
|
|
11111
|
+
const positionalDef = (varName, node, fallback) => {
|
|
11112
|
+
if (!positionalParams.includes(varName)) return fallback;
|
|
11113
|
+
const fnName = enclosingBashFn(node);
|
|
11114
|
+
if (!fnName) return fallback;
|
|
11115
|
+
return bashFnDefs.get(fnName)?.get(varName) ?? fallback;
|
|
11116
|
+
};
|
|
11008
11117
|
walkTree(tree.rootNode, (node) => {
|
|
11009
11118
|
if (node.type === "variable_assignment") {
|
|
11010
11119
|
const nameNode = node.childForFieldName("name");
|
|
@@ -11057,7 +11166,7 @@ function buildBashDFG(tree) {
|
|
|
11057
11166
|
if (varNameNode) {
|
|
11058
11167
|
const varName = getNodeText(varNameNode);
|
|
11059
11168
|
if (varName && !varName.startsWith("?") && !varName.startsWith("#")) {
|
|
11060
|
-
let reachingDef = findReachingDef(varName, scopeStack);
|
|
11169
|
+
let reachingDef = positionalDef(varName, node, findReachingDef(varName, scopeStack));
|
|
11061
11170
|
if (reachingDef === null && !positionalParams.includes(varName)) {
|
|
11062
11171
|
const def = {
|
|
11063
11172
|
id: defIdCounter++,
|
|
@@ -11081,7 +11190,7 @@ function buildBashDFG(tree) {
|
|
|
11081
11190
|
const varNameNode = node.namedChildCount > 0 ? node.namedChild(0) : null;
|
|
11082
11191
|
if (varNameNode && varNameNode.type === "variable_name") {
|
|
11083
11192
|
const varName = getNodeText(varNameNode);
|
|
11084
|
-
let reachingDef = findReachingDef(varName, scopeStack);
|
|
11193
|
+
let reachingDef = positionalDef(varName, node, findReachingDef(varName, scopeStack));
|
|
11085
11194
|
if (reachingDef === null && !positionalParams.includes(varName)) {
|
|
11086
11195
|
const def = {
|
|
11087
11196
|
id: defIdCounter++,
|
|
@@ -11103,8 +11212,56 @@ function buildBashDFG(tree) {
|
|
|
11103
11212
|
}
|
|
11104
11213
|
});
|
|
11105
11214
|
const chains = computeChains(defs, uses);
|
|
11215
|
+
chains.push(...bashCallArgChains(tree, bashFnDefs, uses, positionalParams));
|
|
11106
11216
|
return { defs, uses, chains };
|
|
11107
11217
|
}
|
|
11218
|
+
function bashCallArgChains(tree, bashFnDefs, uses, positionalParams) {
|
|
11219
|
+
const chains = [];
|
|
11220
|
+
if (bashFnDefs.size === 0) return chains;
|
|
11221
|
+
const renumbers = /* @__PURE__ */ new Set();
|
|
11222
|
+
for (const fn of findNodes(tree.rootNode, "function_definition")) {
|
|
11223
|
+
const nameNode = fn.childForFieldName("name");
|
|
11224
|
+
if (!nameNode) continue;
|
|
11225
|
+
if (/\bshift\b|\bset\s+--/.test(getNodeText(fn))) renumbers.add(getNodeText(nameNode));
|
|
11226
|
+
}
|
|
11227
|
+
const usesByLine = /* @__PURE__ */ new Map();
|
|
11228
|
+
for (const u of uses) {
|
|
11229
|
+
const l = usesByLine.get(u.line) ?? [];
|
|
11230
|
+
l.push(u);
|
|
11231
|
+
usesByLine.set(u.line, l);
|
|
11232
|
+
}
|
|
11233
|
+
for (const cmd of findNodes(tree.rootNode, "command")) {
|
|
11234
|
+
const nameNode = cmd.childForFieldName("name");
|
|
11235
|
+
if (!nameNode) continue;
|
|
11236
|
+
const fnDefs = bashFnDefs.get(getNodeText(nameNode));
|
|
11237
|
+
if (!fnDefs) continue;
|
|
11238
|
+
const fnName = getNodeText(nameNode);
|
|
11239
|
+
let position = 0;
|
|
11240
|
+
for (let i2 = 0; i2 < cmd.childCount; i2++) {
|
|
11241
|
+
const arg = cmd.child(i2);
|
|
11242
|
+
if (!arg || arg.id === nameNode.id) continue;
|
|
11243
|
+
if (arg.type.includes("redirect") || arg.type === "heredoc_body" || arg.type === "file_descriptor") continue;
|
|
11244
|
+
const text = getNodeText(arg);
|
|
11245
|
+
if (!text.trim()) continue;
|
|
11246
|
+
position++;
|
|
11247
|
+
const names = /* @__PURE__ */ new Set();
|
|
11248
|
+
const re = /\$\{?([A-Za-z_][A-Za-z0-9_]*|[0-9@*])/g;
|
|
11249
|
+
let m;
|
|
11250
|
+
while ((m = re.exec(text)) !== null) names.add(m[1]);
|
|
11251
|
+
if (names.size === 0) continue;
|
|
11252
|
+
const spreads = /\$\{?[@*]/.test(text);
|
|
11253
|
+
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);
|
|
11254
|
+
const lineUses = usesByLine.get(arg.startPosition.row + 1) ?? [];
|
|
11255
|
+
for (const name2 of names) {
|
|
11256
|
+
for (const u of lineUses) {
|
|
11257
|
+
if (u.variable !== name2 || u.def_id === null) continue;
|
|
11258
|
+
for (const t of targets) chains.push({ from_def: u.def_id, to_def: t, via: name2 });
|
|
11259
|
+
}
|
|
11260
|
+
}
|
|
11261
|
+
}
|
|
11262
|
+
}
|
|
11263
|
+
return chains;
|
|
11264
|
+
}
|
|
11108
11265
|
function buildRustDFG(tree, cache) {
|
|
11109
11266
|
const defs = [];
|
|
11110
11267
|
const uses = [];
|
|
@@ -11814,6 +11971,198 @@ function javaReplaceCrLfCoverage(method, rawFrom) {
|
|
|
11814
11971
|
return { cr, lf };
|
|
11815
11972
|
}
|
|
11816
11973
|
|
|
11974
|
+
// src/analysis/js-crlf-strip.ts
|
|
11975
|
+
var MAX_PATTERN_LENGTH = 200;
|
|
11976
|
+
var NEUTRAL_AFTER_REPLACE = /* @__PURE__ */ new Set([
|
|
11977
|
+
"replace",
|
|
11978
|
+
"replaceAll",
|
|
11979
|
+
"trim",
|
|
11980
|
+
"trimStart",
|
|
11981
|
+
"trimEnd",
|
|
11982
|
+
"toString",
|
|
11983
|
+
"toLowerCase",
|
|
11984
|
+
"toUpperCase",
|
|
11985
|
+
"slice",
|
|
11986
|
+
"substring"
|
|
11987
|
+
]);
|
|
11988
|
+
var PROBE = "a\r\rb\n\nc\r\nd\n\re x ";
|
|
11989
|
+
function readQuoted(s, i2) {
|
|
11990
|
+
const q = s[i2];
|
|
11991
|
+
if (q !== '"' && q !== "'" && q !== "`") return null;
|
|
11992
|
+
let out2 = "";
|
|
11993
|
+
for (let j = i2 + 1; j < s.length; j++) {
|
|
11994
|
+
const c = s[j];
|
|
11995
|
+
if (c === q) return { value: out2, end: j + 1 };
|
|
11996
|
+
if (q === "`" && c === "$" && s[j + 1] === "{") return null;
|
|
11997
|
+
if (c === "\\") {
|
|
11998
|
+
const n = s[j + 1];
|
|
11999
|
+
if (n === void 0) return null;
|
|
12000
|
+
switch (n) {
|
|
12001
|
+
case "n":
|
|
12002
|
+
out2 += "\n";
|
|
12003
|
+
j++;
|
|
12004
|
+
continue;
|
|
12005
|
+
case "r":
|
|
12006
|
+
out2 += "\r";
|
|
12007
|
+
j++;
|
|
12008
|
+
continue;
|
|
12009
|
+
case "t":
|
|
12010
|
+
out2 += " ";
|
|
12011
|
+
j++;
|
|
12012
|
+
continue;
|
|
12013
|
+
case "0":
|
|
12014
|
+
out2 += "\0";
|
|
12015
|
+
j++;
|
|
12016
|
+
continue;
|
|
12017
|
+
case "x": {
|
|
12018
|
+
const h = /^[0-9a-fA-F]{2}/.exec(s.slice(j + 2, j + 4));
|
|
12019
|
+
if (!h) return null;
|
|
12020
|
+
out2 += String.fromCharCode(parseInt(h[0], 16));
|
|
12021
|
+
j += 3;
|
|
12022
|
+
continue;
|
|
12023
|
+
}
|
|
12024
|
+
case "u": {
|
|
12025
|
+
const h = /^[0-9a-fA-F]{4}/.exec(s.slice(j + 2, j + 6));
|
|
12026
|
+
if (!h) return null;
|
|
12027
|
+
out2 += String.fromCharCode(parseInt(h[0], 16));
|
|
12028
|
+
j += 5;
|
|
12029
|
+
continue;
|
|
12030
|
+
}
|
|
12031
|
+
default:
|
|
12032
|
+
out2 += n;
|
|
12033
|
+
j++;
|
|
12034
|
+
continue;
|
|
12035
|
+
}
|
|
12036
|
+
}
|
|
12037
|
+
if (c === "\n" && q !== "`") return null;
|
|
12038
|
+
out2 += c;
|
|
12039
|
+
}
|
|
12040
|
+
return null;
|
|
12041
|
+
}
|
|
12042
|
+
function readRegexLiteral(s, i2) {
|
|
12043
|
+
if (s[i2] !== "/") return null;
|
|
12044
|
+
let inClass = false;
|
|
12045
|
+
for (let j = i2 + 1; j < s.length; j++) {
|
|
12046
|
+
const c = s[j];
|
|
12047
|
+
if (c === "\n") return null;
|
|
12048
|
+
if (c === "\\") {
|
|
12049
|
+
j++;
|
|
12050
|
+
continue;
|
|
12051
|
+
}
|
|
12052
|
+
if (inClass) {
|
|
12053
|
+
if (c === "]") inClass = false;
|
|
12054
|
+
continue;
|
|
12055
|
+
}
|
|
12056
|
+
if (c === "[") {
|
|
12057
|
+
inClass = true;
|
|
12058
|
+
continue;
|
|
12059
|
+
}
|
|
12060
|
+
if (c === "/") {
|
|
12061
|
+
const flags2 = /^[dgimsuvy]*/.exec(s.slice(j + 1))[0];
|
|
12062
|
+
return { source: s.slice(i2 + 1, j), flags: flags2, end: j + 1 + flags2.length };
|
|
12063
|
+
}
|
|
12064
|
+
}
|
|
12065
|
+
return null;
|
|
12066
|
+
}
|
|
12067
|
+
var skipWs = (s, i2) => {
|
|
12068
|
+
while (i2 < s.length && /\s/.test(s[i2])) i2++;
|
|
12069
|
+
return i2;
|
|
12070
|
+
};
|
|
12071
|
+
function parseSteps(chainText) {
|
|
12072
|
+
const steps = [];
|
|
12073
|
+
const callRe = /\.\s*(replaceAll|replace)\s*\(/g;
|
|
12074
|
+
let m;
|
|
12075
|
+
while ((m = callRe.exec(chainText)) !== null) {
|
|
12076
|
+
const kind = m[1];
|
|
12077
|
+
let i2 = skipWs(chainText, m.index + m[0].length);
|
|
12078
|
+
let pattern;
|
|
12079
|
+
const rx = readRegexLiteral(chainText, i2);
|
|
12080
|
+
if (rx) {
|
|
12081
|
+
if (rx.source.length > MAX_PATTERN_LENGTH) return null;
|
|
12082
|
+
try {
|
|
12083
|
+
pattern = new RegExp(rx.source, rx.flags);
|
|
12084
|
+
} catch {
|
|
12085
|
+
return null;
|
|
12086
|
+
}
|
|
12087
|
+
i2 = rx.end;
|
|
12088
|
+
} else {
|
|
12089
|
+
const str = readQuoted(chainText, i2);
|
|
12090
|
+
if (!str) return null;
|
|
12091
|
+
pattern = str.value;
|
|
12092
|
+
i2 = str.end;
|
|
12093
|
+
}
|
|
12094
|
+
i2 = skipWs(chainText, i2);
|
|
12095
|
+
if (chainText[i2] !== ",") return null;
|
|
12096
|
+
i2 = skipWs(chainText, i2 + 1);
|
|
12097
|
+
const rep = readQuoted(chainText, i2);
|
|
12098
|
+
if (!rep) return null;
|
|
12099
|
+
i2 = skipWs(chainText, rep.end);
|
|
12100
|
+
if (chainText[i2] !== ")") return null;
|
|
12101
|
+
steps.push({ kind, pattern, replacement: rep.value });
|
|
12102
|
+
callRe.lastIndex = i2 + 1;
|
|
12103
|
+
}
|
|
12104
|
+
return steps;
|
|
12105
|
+
}
|
|
12106
|
+
function run2(steps, input) {
|
|
12107
|
+
let s = input;
|
|
12108
|
+
try {
|
|
12109
|
+
for (const st of steps) {
|
|
12110
|
+
if (st.kind === "replaceAll" && st.pattern instanceof RegExp && !st.pattern.global) return null;
|
|
12111
|
+
s = st.kind === "replaceAll" ? s.replaceAll(st.pattern, st.replacement) : s.replace(st.pattern, st.replacement);
|
|
12112
|
+
}
|
|
12113
|
+
} catch {
|
|
12114
|
+
return null;
|
|
12115
|
+
}
|
|
12116
|
+
return s;
|
|
12117
|
+
}
|
|
12118
|
+
function jsReplaceChainVerdict(chainText) {
|
|
12119
|
+
const steps = parseSteps(chainText);
|
|
12120
|
+
if (!steps || steps.length === 0) return "undecided";
|
|
12121
|
+
const first = chainText.search(/\.\s*(?:replaceAll|replace)\s*\(/);
|
|
12122
|
+
for (const seg of chainText.slice(first + 1).matchAll(/\.\s*([A-Za-z_$][\w$]*)\s*\(/g)) {
|
|
12123
|
+
if (!NEUTRAL_AFTER_REPLACE.has(seg[1])) return "undecided";
|
|
12124
|
+
}
|
|
12125
|
+
const out2 = run2(steps, PROBE);
|
|
12126
|
+
if (out2 === null) return "undecided";
|
|
12127
|
+
return out2.includes("\r") || out2.includes("\n") ? "leaves-crlf" : "strips-crlf";
|
|
12128
|
+
}
|
|
12129
|
+
function jsChainTextAt(sourceLines, line, column) {
|
|
12130
|
+
const text = [(sourceLines[line - 1] ?? "").slice(column), ...sourceLines.slice(line, line + 7)].join("\n");
|
|
12131
|
+
let depth = 0;
|
|
12132
|
+
let prevSig = "";
|
|
12133
|
+
let i2 = 0;
|
|
12134
|
+
for (; i2 < text.length; i2++) {
|
|
12135
|
+
const c = text[i2];
|
|
12136
|
+
if (c === '"' || c === "'" || c === "`") {
|
|
12137
|
+
for (i2++; i2 < text.length && text[i2] !== c; i2++) if (text[i2] === "\\") i2++;
|
|
12138
|
+
prevSig = c;
|
|
12139
|
+
continue;
|
|
12140
|
+
}
|
|
12141
|
+
if (c === "/" && depth > 0 && (prevSig === "(" || prevSig === ",")) {
|
|
12142
|
+
const rx = readRegexLiteral(text, i2);
|
|
12143
|
+
if (rx) {
|
|
12144
|
+
i2 = rx.end - 1;
|
|
12145
|
+
prevSig = "/";
|
|
12146
|
+
continue;
|
|
12147
|
+
}
|
|
12148
|
+
}
|
|
12149
|
+
if (c === "(" || c === "[" || c === "{") {
|
|
12150
|
+
depth++;
|
|
12151
|
+
prevSig = c;
|
|
12152
|
+
continue;
|
|
12153
|
+
}
|
|
12154
|
+
if (c === ")" || c === "]" || c === "}") {
|
|
12155
|
+
if (depth === 0) break;
|
|
12156
|
+
depth--;
|
|
12157
|
+
prevSig = c;
|
|
12158
|
+
continue;
|
|
12159
|
+
}
|
|
12160
|
+
if (depth === 0 && !/[\w$.\s]/.test(c)) break;
|
|
12161
|
+
if (!/\s/.test(c)) prevSig = c;
|
|
12162
|
+
}
|
|
12163
|
+
return text.slice(0, i2);
|
|
12164
|
+
}
|
|
12165
|
+
|
|
11817
12166
|
// src/analysis/passes/language-sources-pass.ts
|
|
11818
12167
|
var JINJA_LITERAL_ARG_RE_FRAG = `(?:"[^"\\\\]*"|'[^'\\\\]*')`;
|
|
11819
12168
|
var JINJA_SAFE_RTS_RE = new RegExp(
|
|
@@ -12597,6 +12946,10 @@ var DEFAULT_SOURCES = [
|
|
|
12597
12946
|
{ method: "get", class: "Redis", type: "db_input", severity: "medium", return_tainted: true, languages: ["javascript", "typescript"] },
|
|
12598
12947
|
{ method: "hget", class: "Redis", type: "db_input", severity: "medium", return_tainted: true, languages: ["javascript", "typescript"] },
|
|
12599
12948
|
{ method: "mget", class: "Redis", type: "db_input", severity: "medium", return_tainted: true, languages: ["javascript", "typescript"] },
|
|
12949
|
+
// Node redis clients are often bound to `cache` from `createClient()`; class
|
|
12950
|
+
// `cache` is scoped to JS/TS (Python Django cache above) and Map.get is
|
|
12951
|
+
// filtered in sink-filter-pass (#818 src14).
|
|
12952
|
+
{ method: "get", class: "cache", type: "db_input", severity: "medium", return_tainted: true, languages: ["javascript", "typescript"] },
|
|
12600
12953
|
{ method: "get", class: "Jedis", type: "db_input", severity: "medium", return_tainted: true, languages: ["java"] },
|
|
12601
12954
|
{ method: "hget", class: "Jedis", type: "db_input", severity: "medium", return_tainted: true, languages: ["java"] },
|
|
12602
12955
|
{ method: "mget", class: "Jedis", type: "db_input", severity: "medium", return_tainted: true, languages: ["java"] },
|
|
@@ -12776,7 +13129,9 @@ var TRUST_BOUNDARY_FRAMEWORK_SINKS = [
|
|
|
12776
13129
|
// on arg[1] (the *Cookie) catches struct-literal composite tainting.
|
|
12777
13130
|
{ method: "SetCookie", class: "http", type: "trust_boundary", cwe: "CWE-501", severity: "medium", arg_positions: [1], languages: ["go"] },
|
|
12778
13131
|
// gin: c.SetCookie(name, value, maxAge, path, domain, secure, httpOnly).
|
|
12779
|
-
{ method: "SetCookie", class: "Context", type: "trust_boundary", cwe: "CWE-501", severity: "medium", arg_positions: [1], languages: ["go"] }
|
|
13132
|
+
{ method: "SetCookie", class: "Context", type: "trust_boundary", cwe: "CWE-501", severity: "medium", arg_positions: [1], languages: ["go"] },
|
|
13133
|
+
// --- Rust: actix-session / tower-sessions write -------------------------
|
|
13134
|
+
{ method: "insert", class: "Session", type: "trust_boundary", cwe: "CWE-501", severity: "medium", arg_positions: [1], languages: ["rust"] }
|
|
12780
13135
|
];
|
|
12781
13136
|
var DEFAULT_SINKS = [
|
|
12782
13137
|
// SQL Injection (CWE-89)
|
|
@@ -13437,7 +13792,9 @@ var DEFAULT_SINKS = [
|
|
|
13437
13792
|
{ method: "addParametersToContext", class: "StaticParametersInterceptor", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
|
|
13438
13793
|
{ method: "build", class: "HttpParameters", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
|
|
13439
13794
|
// Cron expression parsing (DoS/ReDoS)
|
|
13440
|
-
|
|
13795
|
+
// cognium-dev#998 — Java cron-utils `CronParser`; scoped to Java so a JS/TS
|
|
13796
|
+
// `parser.parse(...)` (tree-sitter, a lockfile parser) never matches it by name.
|
|
13797
|
+
{ method: "parse", class: "CronParser", type: "code_injection", cwe: "CWE-94", severity: "high", arg_positions: [0], languages: ["java"] },
|
|
13441
13798
|
{ method: "isValid", class: "CronValidator", type: "code_injection", cwe: "CWE-94", severity: "high", arg_positions: [0] },
|
|
13442
13799
|
// ff4j feature flags
|
|
13443
13800
|
{ method: "check", class: "FF4j", type: "code_injection", cwe: "CWE-94", severity: "high", arg_positions: [0] },
|
|
@@ -13534,9 +13891,13 @@ var DEFAULT_SINKS = [
|
|
|
13534
13891
|
{ method: "selectNodes", class: "Node", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0] },
|
|
13535
13892
|
{ method: "selectSingleNode", class: "Node", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0] },
|
|
13536
13893
|
// XXE - XML External Entity (CWE-611)
|
|
13537
|
-
|
|
13538
|
-
|
|
13539
|
-
|
|
13894
|
+
// cognium-dev#998 — JAXP parsers are JVM classes. Scoped to Java so the
|
|
13895
|
+
// receiver-name heuristic (`parser` → SAXParser) cannot turn a JS/TS
|
|
13896
|
+
// `parser.parse(text)` into an XXE sink. JS/TS XML parsers (sax, xml2js,
|
|
13897
|
+
// libxmljs, DOMParser) have their own entries.
|
|
13898
|
+
{ method: "parse", class: "DocumentBuilder", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0], languages: ["java"] },
|
|
13899
|
+
{ method: "parse", class: "SAXParser", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0], languages: ["java"] },
|
|
13900
|
+
{ method: "parse", class: "XMLReader", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0], languages: ["java"] },
|
|
13540
13901
|
{ method: "unmarshal", class: "Unmarshaller", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0] },
|
|
13541
13902
|
{ method: "newTransformer", class: "TransformerFactory", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0] },
|
|
13542
13903
|
{ method: "transform", class: "Transformer", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0] },
|
|
@@ -13791,6 +14152,46 @@ var DEFAULT_SINKS = [
|
|
|
13791
14152
|
{ method: "each", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], allow_unresolved_receiver: true },
|
|
13792
14153
|
{ method: "get", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], allow_unresolved_receiver: true },
|
|
13793
14154
|
{ method: "exec", class: "Connection", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], allow_unresolved_receiver: true },
|
|
14155
|
+
// DuckDB from JS/TS: `@duckdb/node-api` (DuckDBConnection), the classic
|
|
14156
|
+
// `duckdb` / `duckdb-async` bindings (Database / Connection) and
|
|
14157
|
+
// `react-native-duckdb` (Database / Transaction). The method names are
|
|
14158
|
+
// generic and the receivers come from factories (`await instance.connect()`,
|
|
14159
|
+
// `open({...})`), so the receiver cannot be typed: the import is the gate
|
|
14160
|
+
// (`requires_import`). Only the SQL text (arg 0) is a sink — values passed
|
|
14161
|
+
// as bound parameters (`run(sql, [v])`, `execute(sql, params)`,
|
|
14162
|
+
// `executeNamed(sql, {k: v})`, `stmt.bind(...)`) are parameterised and do
|
|
14163
|
+
// not reach it.
|
|
14164
|
+
{ method: "run", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["@duckdb/node-api", "duckdb", "duckdb-async"] },
|
|
14165
|
+
{ 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"] },
|
|
14166
|
+
{ 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"] },
|
|
14167
|
+
{ method: "runAndRead", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["@duckdb/node-api"] },
|
|
14168
|
+
{ method: "runAndReadAll", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["@duckdb/node-api"] },
|
|
14169
|
+
{ method: "runAndReadUntil", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["@duckdb/node-api"] },
|
|
14170
|
+
{ method: "streamAndRead", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["@duckdb/node-api"] },
|
|
14171
|
+
{ method: "streamAndReadAll", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["@duckdb/node-api"] },
|
|
14172
|
+
{ method: "streamAndReadUntil", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["@duckdb/node-api"] },
|
|
14173
|
+
{ method: "start", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["@duckdb/node-api"] },
|
|
14174
|
+
{ method: "startStream", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["@duckdb/node-api"] },
|
|
14175
|
+
{ method: "extractStatements", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["@duckdb/node-api"] },
|
|
14176
|
+
{ method: "all", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["duckdb", "duckdb-async"] },
|
|
14177
|
+
{ method: "each", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["duckdb", "duckdb-async"] },
|
|
14178
|
+
{ method: "exec", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["duckdb", "duckdb-async"] },
|
|
14179
|
+
{ method: "arrowIPCAll", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["duckdb", "duckdb-async"] },
|
|
14180
|
+
{ method: "arrowIPCStream", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["duckdb", "duckdb-async"] },
|
|
14181
|
+
{ method: "execute", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["react-native-duckdb"] },
|
|
14182
|
+
{ method: "executeSync", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["react-native-duckdb"] },
|
|
14183
|
+
{ method: "executeNamed", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["react-native-duckdb"] },
|
|
14184
|
+
{ method: "executeSyncNamed", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["react-native-duckdb"] },
|
|
14185
|
+
{ method: "streamNamed", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["react-native-duckdb"] },
|
|
14186
|
+
// RxDB (Mango queries, any storage incl. SQLite): a selector or query
|
|
14187
|
+
// object, a field name passed to the query builder, or a `$regex` pattern
|
|
14188
|
+
// under caller control is NoSQL operator injection (CWE-943). Gated on the
|
|
14189
|
+
// `rxdb` import for the same reason as DuckDB above. `find` is already a
|
|
14190
|
+
// classless JS NoSQL sink in the JavaScript plugin.
|
|
14191
|
+
{ method: "findOne", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["rxdb"] },
|
|
14192
|
+
{ method: "count", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["rxdb"] },
|
|
14193
|
+
{ method: "where", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["rxdb"] },
|
|
14194
|
+
{ method: "regex", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"], requires_import: ["rxdb"] },
|
|
13794
14195
|
// TypeORM QueryBuilder raw fragments (CWE-89). TypeORM's raw `.query(sql)`
|
|
13795
14196
|
// escape hatch is already covered by the classless `query` sink in the JS
|
|
13796
14197
|
// plugin (getBuiltinSinks), so it is NOT re-registered here — the dedup
|
|
@@ -13901,6 +14302,8 @@ var DEFAULT_SINKS = [
|
|
|
13901
14302
|
{ method: "get", class: "http", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
|
|
13902
14303
|
{ method: "request", class: "https", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
|
|
13903
14304
|
{ method: "get", class: "https", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
|
|
14305
|
+
// Node.js dns.promises (CWE-918 / NUL-in-name SSRF class — #895)
|
|
14306
|
+
{ method: "resolve", class: "dns.promises", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
|
|
13904
14307
|
// needle library (used in NodeGoat)
|
|
13905
14308
|
{ method: "get", class: "needle", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
|
|
13906
14309
|
{ method: "post", class: "needle", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
|
|
@@ -14395,7 +14798,7 @@ var DEFAULT_SINKS = [
|
|
|
14395
14798
|
// receiver-taint or operator-LHS-taint tracking — the format string is the
|
|
14396
14799
|
// receiver, not an argument. Deferred to Sprint 6 (#86 follow-up).
|
|
14397
14800
|
// C-style: printf / fprintf / sprintf / snprintf via ctypes/cffi.
|
|
14398
|
-
{ method: "printf", type: "format_string", cwe: "CWE-134", severity: "high", arg_positions: [0], languages: ["python"] },
|
|
14801
|
+
{ method: "printf", type: "format_string", cwe: "CWE-134", severity: "high", arg_positions: [0], languages: ["python", "rust"] },
|
|
14399
14802
|
{ method: "fprintf", type: "format_string", cwe: "CWE-134", severity: "high", arg_positions: [1], languages: ["python"] },
|
|
14400
14803
|
// Go: fmt.Sprintf/Printf/Fprintf/Errorf — format string is first/second arg
|
|
14401
14804
|
{ method: "Sprintf", class: "fmt", type: "format_string", cwe: "CWE-134", severity: "medium", arg_positions: [0], languages: ["go"] },
|
|
@@ -15464,198 +15867,6 @@ function getDefaultConfig() {
|
|
|
15464
15867
|
};
|
|
15465
15868
|
}
|
|
15466
15869
|
|
|
15467
|
-
// src/analysis/js-crlf-strip.ts
|
|
15468
|
-
var MAX_PATTERN_LENGTH = 200;
|
|
15469
|
-
var NEUTRAL_AFTER_REPLACE = /* @__PURE__ */ new Set([
|
|
15470
|
-
"replace",
|
|
15471
|
-
"replaceAll",
|
|
15472
|
-
"trim",
|
|
15473
|
-
"trimStart",
|
|
15474
|
-
"trimEnd",
|
|
15475
|
-
"toString",
|
|
15476
|
-
"toLowerCase",
|
|
15477
|
-
"toUpperCase",
|
|
15478
|
-
"slice",
|
|
15479
|
-
"substring"
|
|
15480
|
-
]);
|
|
15481
|
-
var PROBE = "a\r\rb\n\nc\r\nd\n\re x ";
|
|
15482
|
-
function readQuoted(s, i2) {
|
|
15483
|
-
const q = s[i2];
|
|
15484
|
-
if (q !== '"' && q !== "'" && q !== "`") return null;
|
|
15485
|
-
let out2 = "";
|
|
15486
|
-
for (let j = i2 + 1; j < s.length; j++) {
|
|
15487
|
-
const c = s[j];
|
|
15488
|
-
if (c === q) return { value: out2, end: j + 1 };
|
|
15489
|
-
if (q === "`" && c === "$" && s[j + 1] === "{") return null;
|
|
15490
|
-
if (c === "\\") {
|
|
15491
|
-
const n = s[j + 1];
|
|
15492
|
-
if (n === void 0) return null;
|
|
15493
|
-
switch (n) {
|
|
15494
|
-
case "n":
|
|
15495
|
-
out2 += "\n";
|
|
15496
|
-
j++;
|
|
15497
|
-
continue;
|
|
15498
|
-
case "r":
|
|
15499
|
-
out2 += "\r";
|
|
15500
|
-
j++;
|
|
15501
|
-
continue;
|
|
15502
|
-
case "t":
|
|
15503
|
-
out2 += " ";
|
|
15504
|
-
j++;
|
|
15505
|
-
continue;
|
|
15506
|
-
case "0":
|
|
15507
|
-
out2 += "\0";
|
|
15508
|
-
j++;
|
|
15509
|
-
continue;
|
|
15510
|
-
case "x": {
|
|
15511
|
-
const h = /^[0-9a-fA-F]{2}/.exec(s.slice(j + 2, j + 4));
|
|
15512
|
-
if (!h) return null;
|
|
15513
|
-
out2 += String.fromCharCode(parseInt(h[0], 16));
|
|
15514
|
-
j += 3;
|
|
15515
|
-
continue;
|
|
15516
|
-
}
|
|
15517
|
-
case "u": {
|
|
15518
|
-
const h = /^[0-9a-fA-F]{4}/.exec(s.slice(j + 2, j + 6));
|
|
15519
|
-
if (!h) return null;
|
|
15520
|
-
out2 += String.fromCharCode(parseInt(h[0], 16));
|
|
15521
|
-
j += 5;
|
|
15522
|
-
continue;
|
|
15523
|
-
}
|
|
15524
|
-
default:
|
|
15525
|
-
out2 += n;
|
|
15526
|
-
j++;
|
|
15527
|
-
continue;
|
|
15528
|
-
}
|
|
15529
|
-
}
|
|
15530
|
-
if (c === "\n" && q !== "`") return null;
|
|
15531
|
-
out2 += c;
|
|
15532
|
-
}
|
|
15533
|
-
return null;
|
|
15534
|
-
}
|
|
15535
|
-
function readRegexLiteral(s, i2) {
|
|
15536
|
-
if (s[i2] !== "/") return null;
|
|
15537
|
-
let inClass = false;
|
|
15538
|
-
for (let j = i2 + 1; j < s.length; j++) {
|
|
15539
|
-
const c = s[j];
|
|
15540
|
-
if (c === "\n") return null;
|
|
15541
|
-
if (c === "\\") {
|
|
15542
|
-
j++;
|
|
15543
|
-
continue;
|
|
15544
|
-
}
|
|
15545
|
-
if (inClass) {
|
|
15546
|
-
if (c === "]") inClass = false;
|
|
15547
|
-
continue;
|
|
15548
|
-
}
|
|
15549
|
-
if (c === "[") {
|
|
15550
|
-
inClass = true;
|
|
15551
|
-
continue;
|
|
15552
|
-
}
|
|
15553
|
-
if (c === "/") {
|
|
15554
|
-
const flags2 = /^[dgimsuvy]*/.exec(s.slice(j + 1))[0];
|
|
15555
|
-
return { source: s.slice(i2 + 1, j), flags: flags2, end: j + 1 + flags2.length };
|
|
15556
|
-
}
|
|
15557
|
-
}
|
|
15558
|
-
return null;
|
|
15559
|
-
}
|
|
15560
|
-
var skipWs = (s, i2) => {
|
|
15561
|
-
while (i2 < s.length && /\s/.test(s[i2])) i2++;
|
|
15562
|
-
return i2;
|
|
15563
|
-
};
|
|
15564
|
-
function parseSteps(chainText) {
|
|
15565
|
-
const steps = [];
|
|
15566
|
-
const callRe = /\.\s*(replaceAll|replace)\s*\(/g;
|
|
15567
|
-
let m;
|
|
15568
|
-
while ((m = callRe.exec(chainText)) !== null) {
|
|
15569
|
-
const kind = m[1];
|
|
15570
|
-
let i2 = skipWs(chainText, m.index + m[0].length);
|
|
15571
|
-
let pattern;
|
|
15572
|
-
const rx = readRegexLiteral(chainText, i2);
|
|
15573
|
-
if (rx) {
|
|
15574
|
-
if (rx.source.length > MAX_PATTERN_LENGTH) return null;
|
|
15575
|
-
try {
|
|
15576
|
-
pattern = new RegExp(rx.source, rx.flags);
|
|
15577
|
-
} catch {
|
|
15578
|
-
return null;
|
|
15579
|
-
}
|
|
15580
|
-
i2 = rx.end;
|
|
15581
|
-
} else {
|
|
15582
|
-
const str = readQuoted(chainText, i2);
|
|
15583
|
-
if (!str) return null;
|
|
15584
|
-
pattern = str.value;
|
|
15585
|
-
i2 = str.end;
|
|
15586
|
-
}
|
|
15587
|
-
i2 = skipWs(chainText, i2);
|
|
15588
|
-
if (chainText[i2] !== ",") return null;
|
|
15589
|
-
i2 = skipWs(chainText, i2 + 1);
|
|
15590
|
-
const rep = readQuoted(chainText, i2);
|
|
15591
|
-
if (!rep) return null;
|
|
15592
|
-
i2 = skipWs(chainText, rep.end);
|
|
15593
|
-
if (chainText[i2] !== ")") return null;
|
|
15594
|
-
steps.push({ kind, pattern, replacement: rep.value });
|
|
15595
|
-
callRe.lastIndex = i2 + 1;
|
|
15596
|
-
}
|
|
15597
|
-
return steps;
|
|
15598
|
-
}
|
|
15599
|
-
function run2(steps, input) {
|
|
15600
|
-
let s = input;
|
|
15601
|
-
try {
|
|
15602
|
-
for (const st of steps) {
|
|
15603
|
-
if (st.kind === "replaceAll" && st.pattern instanceof RegExp && !st.pattern.global) return null;
|
|
15604
|
-
s = st.kind === "replaceAll" ? s.replaceAll(st.pattern, st.replacement) : s.replace(st.pattern, st.replacement);
|
|
15605
|
-
}
|
|
15606
|
-
} catch {
|
|
15607
|
-
return null;
|
|
15608
|
-
}
|
|
15609
|
-
return s;
|
|
15610
|
-
}
|
|
15611
|
-
function jsReplaceChainVerdict(chainText) {
|
|
15612
|
-
const steps = parseSteps(chainText);
|
|
15613
|
-
if (!steps || steps.length === 0) return "undecided";
|
|
15614
|
-
const first = chainText.search(/\.\s*(?:replaceAll|replace)\s*\(/);
|
|
15615
|
-
for (const seg of chainText.slice(first + 1).matchAll(/\.\s*([A-Za-z_$][\w$]*)\s*\(/g)) {
|
|
15616
|
-
if (!NEUTRAL_AFTER_REPLACE.has(seg[1])) return "undecided";
|
|
15617
|
-
}
|
|
15618
|
-
const out2 = run2(steps, PROBE);
|
|
15619
|
-
if (out2 === null) return "undecided";
|
|
15620
|
-
return out2.includes("\r") || out2.includes("\n") ? "leaves-crlf" : "strips-crlf";
|
|
15621
|
-
}
|
|
15622
|
-
function jsChainTextAt(sourceLines, line, column) {
|
|
15623
|
-
const text = [(sourceLines[line - 1] ?? "").slice(column), ...sourceLines.slice(line, line + 7)].join("\n");
|
|
15624
|
-
let depth = 0;
|
|
15625
|
-
let prevSig = "";
|
|
15626
|
-
let i2 = 0;
|
|
15627
|
-
for (; i2 < text.length; i2++) {
|
|
15628
|
-
const c = text[i2];
|
|
15629
|
-
if (c === '"' || c === "'" || c === "`") {
|
|
15630
|
-
for (i2++; i2 < text.length && text[i2] !== c; i2++) if (text[i2] === "\\") i2++;
|
|
15631
|
-
prevSig = c;
|
|
15632
|
-
continue;
|
|
15633
|
-
}
|
|
15634
|
-
if (c === "/" && depth > 0 && (prevSig === "(" || prevSig === ",")) {
|
|
15635
|
-
const rx = readRegexLiteral(text, i2);
|
|
15636
|
-
if (rx) {
|
|
15637
|
-
i2 = rx.end - 1;
|
|
15638
|
-
prevSig = "/";
|
|
15639
|
-
continue;
|
|
15640
|
-
}
|
|
15641
|
-
}
|
|
15642
|
-
if (c === "(" || c === "[" || c === "{") {
|
|
15643
|
-
depth++;
|
|
15644
|
-
prevSig = c;
|
|
15645
|
-
continue;
|
|
15646
|
-
}
|
|
15647
|
-
if (c === ")" || c === "]" || c === "}") {
|
|
15648
|
-
if (depth === 0) break;
|
|
15649
|
-
depth--;
|
|
15650
|
-
prevSig = c;
|
|
15651
|
-
continue;
|
|
15652
|
-
}
|
|
15653
|
-
if (depth === 0 && !/[\w$.\s]/.test(c)) break;
|
|
15654
|
-
if (!/\s/.test(c)) prevSig = c;
|
|
15655
|
-
}
|
|
15656
|
-
return text.slice(0, i2);
|
|
15657
|
-
}
|
|
15658
|
-
|
|
15659
15870
|
// src/analysis/constant-propagation/patterns.ts
|
|
15660
15871
|
var TAINT_PATTERNS = [
|
|
15661
15872
|
// HTTP Sources (Servlet API)
|
|
@@ -16045,13 +16256,35 @@ function dropCSharpObjectCarriedWaypoints(sinks, calls) {
|
|
|
16045
16256
|
function analyzeTaint(calls, types, config = getDefaultConfig(), typeHierarchy, language, code, imports) {
|
|
16046
16257
|
const sourceLines = code !== void 0 ? code.split("\n") : void 0;
|
|
16047
16258
|
const sources = findSources(calls, types, config.sources, sourceLines, language, imports);
|
|
16048
|
-
let sinkPatterns =
|
|
16259
|
+
let sinkPatterns = config.sinks.filter((p) => sinkPatternImportApplies(p, imports));
|
|
16260
|
+
sinkPatterns = expandPromisifyAliases(sinkPatterns, sourceLines, language);
|
|
16049
16261
|
sinkPatterns = expandIndirectEvalAliases(sinkPatterns, sourceLines, language);
|
|
16050
16262
|
const sinks = findSinks(calls, sinkPatterns, typeHierarchy, language, sourceLines, types);
|
|
16051
16263
|
const gatedSinks = language === "csharp" ? dropCSharpObjectCarriedWaypoints(sinks, calls) : sinks;
|
|
16052
16264
|
const sanitizers = findSanitizers(calls, types, config.sanitizers, sourceLines, language);
|
|
16053
16265
|
return { sources, sinks: gatedSinks, sanitizers };
|
|
16054
16266
|
}
|
|
16267
|
+
function isBoundParameterCall(call, pattern, sourceLines) {
|
|
16268
|
+
if (!pattern.requires_import || pattern.type !== "sql_injection") return false;
|
|
16269
|
+
const first = call.arguments[0]?.expression?.trim() ?? "";
|
|
16270
|
+
if (first.startsWith("[") || first.startsWith("{")) return true;
|
|
16271
|
+
const receiver = call.receiver;
|
|
16272
|
+
if (!receiver || !sourceLines || !/^[A-Za-z_$][\w$]*$/.test(receiver)) return false;
|
|
16273
|
+
const assignedFromPrepare = new RegExp(
|
|
16274
|
+
`\\b${receiver.replace(/\$/g, "\\$")}\\s*(?::[^=]+)?=\\s*(?:await\\s+)?[\\w$.]+\\.prepare\\s*\\(`
|
|
16275
|
+
);
|
|
16276
|
+
return sourceLines.some((line) => assignedFromPrepare.test(line));
|
|
16277
|
+
}
|
|
16278
|
+
function sinkPatternImportApplies(pattern, imports) {
|
|
16279
|
+
const required = pattern.requires_import;
|
|
16280
|
+
if (!required || required.length === 0) return true;
|
|
16281
|
+
if (!imports || imports.length === 0) return false;
|
|
16282
|
+
return imports.some((imp) => {
|
|
16283
|
+
const from = imp.from_package;
|
|
16284
|
+
if (!from) return false;
|
|
16285
|
+
return required.some((m) => from === m || from.startsWith(m + "/"));
|
|
16286
|
+
});
|
|
16287
|
+
}
|
|
16055
16288
|
function sinkPatternAppliesTo(pattern, language) {
|
|
16056
16289
|
if (language === void 0) return true;
|
|
16057
16290
|
if (pattern.languages && pattern.languages.length > 0 && !pattern.languages.includes(language)) {
|
|
@@ -16469,6 +16702,46 @@ function isInterproceduralTaintableType(typeName, language) {
|
|
|
16469
16702
|
const leaf = baseType.split(".").pop() ?? baseType;
|
|
16470
16703
|
if (csharpTaintable.includes(baseType) || csharpTaintable.includes(leaf)) return true;
|
|
16471
16704
|
}
|
|
16705
|
+
if (language === "rust") {
|
|
16706
|
+
const trimmed = typeName.trim();
|
|
16707
|
+
const unref = trimmed.replace(/^&mut\s+/, "").replace(/^&\s*/, "");
|
|
16708
|
+
const rustBase = unref.split("<")[0].trim();
|
|
16709
|
+
const rustScalars = [
|
|
16710
|
+
"String",
|
|
16711
|
+
"str",
|
|
16712
|
+
"bool",
|
|
16713
|
+
"char",
|
|
16714
|
+
"i8",
|
|
16715
|
+
"i16",
|
|
16716
|
+
"i32",
|
|
16717
|
+
"i64",
|
|
16718
|
+
"i128",
|
|
16719
|
+
"isize",
|
|
16720
|
+
"u8",
|
|
16721
|
+
"u16",
|
|
16722
|
+
"u32",
|
|
16723
|
+
"u64",
|
|
16724
|
+
"u128",
|
|
16725
|
+
"usize",
|
|
16726
|
+
"f32",
|
|
16727
|
+
"f64"
|
|
16728
|
+
];
|
|
16729
|
+
if (rustScalars.includes(rustBase)) return true;
|
|
16730
|
+
if (rustBase === "()" || rustBase === "!") return false;
|
|
16731
|
+
if (unref.startsWith("Vec<") || unref.startsWith("Option<") || unref.startsWith("Result<")) {
|
|
16732
|
+
return true;
|
|
16733
|
+
}
|
|
16734
|
+
if (/^[A-Z][A-Za-z0-9_]*$/.test(rustBase)) return true;
|
|
16735
|
+
}
|
|
16736
|
+
if (language === "javascript" || language === "typescript") {
|
|
16737
|
+
const trimmed = typeName.trim();
|
|
16738
|
+
const jsScalars = ["string", "number", "boolean", "bigint", "any", "unknown", "object"];
|
|
16739
|
+
if (jsScalars.includes(baseType)) return true;
|
|
16740
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[") || trimmed.startsWith("(")) return true;
|
|
16741
|
+
if (/\bRecord\s*</.test(trimmed)) return true;
|
|
16742
|
+
if (["void", "undefined", "null", "never"].includes(baseType)) return false;
|
|
16743
|
+
if (/^[A-Za-z_][\w.$]*$/.test(baseType)) return true;
|
|
16744
|
+
}
|
|
16472
16745
|
if (typeName.endsWith("[]")) {
|
|
16473
16746
|
const elementType = typeName.slice(0, -2);
|
|
16474
16747
|
if (elementType === "String" || elementType === "Object" || elementType === "byte") {
|
|
@@ -16583,6 +16856,31 @@ function isSafeGoExecCommandCall(call, pattern, language) {
|
|
|
16583
16856
|
return false;
|
|
16584
16857
|
}
|
|
16585
16858
|
}
|
|
16859
|
+
if (program === "git") {
|
|
16860
|
+
const subcommandArgPos = pattern.method === "CommandContext" ? 2 : 1;
|
|
16861
|
+
const subcommandArg = call.arguments.find((a) => a.position === subcommandArgPos);
|
|
16862
|
+
let subcommand;
|
|
16863
|
+
if (subcommandArg?.literal !== null && subcommandArg?.literal !== void 0) {
|
|
16864
|
+
subcommand = String(subcommandArg.literal);
|
|
16865
|
+
} else {
|
|
16866
|
+
const expr = (subcommandArg?.expression ?? "").trim();
|
|
16867
|
+
if (expr.startsWith('"') || expr.startsWith("`") || expr.startsWith("'")) {
|
|
16868
|
+
subcommand = expr.slice(1, -1);
|
|
16869
|
+
}
|
|
16870
|
+
}
|
|
16871
|
+
if (subcommand === "ls-remote") {
|
|
16872
|
+
const hasEndOfOptions = call.arguments.some((a) => {
|
|
16873
|
+
if (a.literal !== null && a.literal !== void 0) {
|
|
16874
|
+
return String(a.literal) === "--";
|
|
16875
|
+
}
|
|
16876
|
+
const expr = (a.expression ?? "").trim();
|
|
16877
|
+
return expr === '"--"' || expr === "'--'" || expr === "`--`";
|
|
16878
|
+
});
|
|
16879
|
+
if (!hasEndOfOptions) {
|
|
16880
|
+
return false;
|
|
16881
|
+
}
|
|
16882
|
+
}
|
|
16883
|
+
}
|
|
16586
16884
|
return true;
|
|
16587
16885
|
}
|
|
16588
16886
|
function isSafeJSChildProcessCall(call, pattern, language) {
|
|
@@ -16986,6 +17284,29 @@ function isRegexReceiver(receiver, sourceLines) {
|
|
|
16986
17284
|
const decl = new RegExp(`(?:const|let|var)\\s+${escapeRe(r)}\\s*=\\s*(?:/|(?:new\\s+)?RegExp\\s*\\()`);
|
|
16987
17285
|
return sourceLines.some((l) => decl.test(l));
|
|
16988
17286
|
}
|
|
17287
|
+
function isNavigationReceiverName(receiver) {
|
|
17288
|
+
const r = (receiver ?? "").trim();
|
|
17289
|
+
if (!r) return false;
|
|
17290
|
+
const last = r.slice(r.lastIndexOf(".") + 1).replace(/\(\)$/, "");
|
|
17291
|
+
return /router|history|navigat|^nav$/i.test(last);
|
|
17292
|
+
}
|
|
17293
|
+
function isInMemoryMapGetReceiver(receiver, sourceLines) {
|
|
17294
|
+
const raw = (receiver ?? "").trim();
|
|
17295
|
+
if (!raw || !sourceLines) return false;
|
|
17296
|
+
if (/\.properties$/i.test(raw)) return true;
|
|
17297
|
+
const lastDot = raw.lastIndexOf(".");
|
|
17298
|
+
const r = (lastDot >= 0 ? raw.slice(lastDot + 1) : raw).trim();
|
|
17299
|
+
if (/^(properties|memoryCache)$/i.test(r)) return true;
|
|
17300
|
+
if (!/^[A-Za-z_$][\w$]*$/.test(r)) return false;
|
|
17301
|
+
const n = escapeRe(r);
|
|
17302
|
+
const mapCtor = new RegExp(
|
|
17303
|
+
`\\b${n}\\s*(?::[^=;]*)?=\\s*new\\s+(?:Weak)?Map\\s*(?:<.*>)?\\s*\\(`
|
|
17304
|
+
);
|
|
17305
|
+
const mapType = new RegExp(
|
|
17306
|
+
`\\b${n}\\s*\\??\\s*:\\s*(?:Readonly)?(?:Weak)?Map\\s*<`
|
|
17307
|
+
);
|
|
17308
|
+
return sourceLines.some((l) => mapCtor.test(l) || mapType.test(l));
|
|
17309
|
+
}
|
|
16989
17310
|
function isDeclaredFunctionReference(arg, sourceLines) {
|
|
16990
17311
|
if (arg.literal !== null && arg.literal !== void 0) return false;
|
|
16991
17312
|
if (!sourceLines) return false;
|
|
@@ -17190,6 +17511,12 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types)
|
|
|
17190
17511
|
if (isParameterizedQueryCall(call, pattern)) {
|
|
17191
17512
|
continue;
|
|
17192
17513
|
}
|
|
17514
|
+
if ((language === "javascript" || language === "typescript") && (pattern.type === "sql_injection" || pattern.type === "nosql_injection") && call.arguments.length === 0 && pattern.arg_positions.every((p) => p >= 0)) {
|
|
17515
|
+
continue;
|
|
17516
|
+
}
|
|
17517
|
+
if (isBoundParameterCall(call, pattern, sourceLines)) {
|
|
17518
|
+
continue;
|
|
17519
|
+
}
|
|
17193
17520
|
if (isSafePythonSubprocessCall(call, pattern, language)) {
|
|
17194
17521
|
continue;
|
|
17195
17522
|
}
|
|
@@ -17247,9 +17574,15 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types)
|
|
|
17247
17574
|
if (isArgumentlessJsResponseWriter(call, pattern, language)) {
|
|
17248
17575
|
continue;
|
|
17249
17576
|
}
|
|
17577
|
+
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)) {
|
|
17578
|
+
continue;
|
|
17579
|
+
}
|
|
17250
17580
|
if ((pattern.type === "command_injection" || pattern.type === "sql_injection") && call.method_name === "exec" && (language === "javascript" || language === "typescript") && isRegexReceiver(call.receiver, sourceLines)) {
|
|
17251
17581
|
continue;
|
|
17252
17582
|
}
|
|
17583
|
+
if (pattern.type === "sql_injection" && call.method_name === "get" && pattern.class === "Connection" && (language === "javascript" || language === "typescript") && isInMemoryMapGetReceiver(call.receiver, sourceLines)) {
|
|
17584
|
+
continue;
|
|
17585
|
+
}
|
|
17253
17586
|
if (pattern.type === "code_injection" && language === "python" && call.method_name === "compile" && call.receiver && call.receiver !== "builtins") {
|
|
17254
17587
|
continue;
|
|
17255
17588
|
}
|
|
@@ -17266,7 +17599,7 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types)
|
|
|
17266
17599
|
continue;
|
|
17267
17600
|
}
|
|
17268
17601
|
const location = formatCallLocation(call);
|
|
17269
|
-
const key = `${location}:${call.location.line}:${pattern.cwe}`;
|
|
17602
|
+
const key = `${location}:${call.location.line}:${call.location.column}:${pattern.cwe}`;
|
|
17270
17603
|
const confidence = calculateSinkConfidence(call, pattern);
|
|
17271
17604
|
const existing = sinkMap.get(key);
|
|
17272
17605
|
if (!existing || confidence > existing.confidence) {
|
|
@@ -17277,6 +17610,7 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types)
|
|
|
17277
17610
|
cwe: pattern.cwe,
|
|
17278
17611
|
location,
|
|
17279
17612
|
line: call.location.line,
|
|
17613
|
+
callColumn: call.location.column,
|
|
17280
17614
|
confidence,
|
|
17281
17615
|
method: call.method_name,
|
|
17282
17616
|
argPositions: pattern.arg_positions,
|
|
@@ -17314,6 +17648,15 @@ function matchesSourcePattern(call, pattern, language, types, imports) {
|
|
|
17314
17648
|
return false;
|
|
17315
17649
|
}
|
|
17316
17650
|
}
|
|
17651
|
+
if (!pattern.class && pattern.return_tainted && pattern.method && (language === "javascript" || language === "typescript")) {
|
|
17652
|
+
const expressRequestAccessors = /* @__PURE__ */ new Set(["query", "body", "params", "headers", "cookies"]);
|
|
17653
|
+
if (expressRequestAccessors.has(pattern.method)) {
|
|
17654
|
+
const recv = call.receiver?.toLowerCase() ?? "";
|
|
17655
|
+
if (recv !== "req" && recv !== "request" && recv !== "ctx") {
|
|
17656
|
+
return false;
|
|
17657
|
+
}
|
|
17658
|
+
}
|
|
17659
|
+
}
|
|
17317
17660
|
return pattern.return_tainted === true;
|
|
17318
17661
|
}
|
|
17319
17662
|
return false;
|
|
@@ -17407,6 +17750,7 @@ var SAFE_RECEIVERS_BY_METHOD = {
|
|
|
17407
17750
|
"database",
|
|
17408
17751
|
"sqlite",
|
|
17409
17752
|
"conn",
|
|
17753
|
+
"con",
|
|
17410
17754
|
"connection",
|
|
17411
17755
|
"client",
|
|
17412
17756
|
"pool",
|
|
@@ -17683,7 +18027,7 @@ function matchesSinkPattern(call, pattern, typeHierarchy, language) {
|
|
|
17683
18027
|
}
|
|
17684
18028
|
}
|
|
17685
18029
|
}
|
|
17686
|
-
if (!pattern.class && call.receiver) {
|
|
18030
|
+
if (!pattern.class && call.receiver && !pattern.requires_import) {
|
|
17687
18031
|
if (isKnownSafeReceiverForMethod(call.receiver, call.method_name, pattern.type)) {
|
|
17688
18032
|
return false;
|
|
17689
18033
|
}
|
|
@@ -18416,6 +18760,8 @@ function javaReplaceChainCredit(chainText) {
|
|
|
18416
18760
|
let lf = false;
|
|
18417
18761
|
let lt = false;
|
|
18418
18762
|
let gt = false;
|
|
18763
|
+
let openBrace = false;
|
|
18764
|
+
let closeBrace = false;
|
|
18419
18765
|
for (const m of chainText.matchAll(JAVA_REPLACE_ARGS_RE)) {
|
|
18420
18766
|
const kind = m[1];
|
|
18421
18767
|
const rawFrom = m[2] ?? m[3] ?? "";
|
|
@@ -18426,10 +18772,13 @@ function javaReplaceChainCredit(chainText) {
|
|
|
18426
18772
|
if (cov.lf) lf = true;
|
|
18427
18773
|
if (from === "<" && to.startsWith("&")) lt = true;
|
|
18428
18774
|
if (from === ">" && to.startsWith("&")) gt = true;
|
|
18775
|
+
if (from === "{") openBrace = true;
|
|
18776
|
+
if (from === "}") closeBrace = true;
|
|
18429
18777
|
}
|
|
18430
18778
|
const out2 = [];
|
|
18431
18779
|
if (cr && lf) out2.push("log_injection");
|
|
18432
18780
|
if (lt && gt) out2.push("xss");
|
|
18781
|
+
if (openBrace && closeBrace) out2.push("code_injection");
|
|
18433
18782
|
return out2;
|
|
18434
18783
|
}
|
|
18435
18784
|
function matchesSanitizerPattern(call, pattern) {
|
|
@@ -20543,6 +20892,9 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
20543
20892
|
case "assignment_statement":
|
|
20544
20893
|
this.handleGoAssignment(node);
|
|
20545
20894
|
return false;
|
|
20895
|
+
case "var_declaration":
|
|
20896
|
+
this.handleGoVarDeclaration(node);
|
|
20897
|
+
return false;
|
|
20546
20898
|
case "update_expression":
|
|
20547
20899
|
this.handleUpdateExpression(node);
|
|
20548
20900
|
return false;
|
|
@@ -20950,6 +21302,64 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
20950
21302
|
* helper that always returns a constant — clears the variable. Anything else stops being tracked here, which is what Go
|
|
20951
21303
|
* assignments got before this handler existed: the DFG decides.
|
|
20952
21304
|
*/
|
|
21305
|
+
/** `var x = expr` / `var x T = expr` — same clean/taint rules as `:=` (#783). */
|
|
21306
|
+
handleGoVarDeclaration(node) {
|
|
21307
|
+
for (let i2 = 0; i2 < node.childCount; i2++) {
|
|
21308
|
+
const spec = node.child(i2);
|
|
21309
|
+
if (!spec || spec.type !== "var_spec") continue;
|
|
21310
|
+
const nameNode = spec.childForFieldName("name");
|
|
21311
|
+
let valueNode = spec.childForFieldName("value");
|
|
21312
|
+
if (!nameNode || !valueNode) continue;
|
|
21313
|
+
const names = [];
|
|
21314
|
+
if (nameNode.type === "identifier") {
|
|
21315
|
+
names.push(getNodeText2(nameNode, this.source));
|
|
21316
|
+
} else if (nameNode.type === "identifier_list") {
|
|
21317
|
+
for (const c of nameNode.children) {
|
|
21318
|
+
if (c.type === "identifier") names.push(getNodeText2(c, this.source));
|
|
21319
|
+
}
|
|
21320
|
+
}
|
|
21321
|
+
if (valueNode.type === "expression_list") {
|
|
21322
|
+
valueNode = valueNode.children.find((c) => c.type !== "," && c.type !== "comment") ?? valueNode;
|
|
21323
|
+
}
|
|
21324
|
+
if (names.length === 0) continue;
|
|
21325
|
+
const line = getNodeLine(node);
|
|
21326
|
+
for (const varName of names) {
|
|
21327
|
+
if (varName === "_") continue;
|
|
21328
|
+
const scopedName = this.getScopedName(varName);
|
|
21329
|
+
this.goScopedNames.add(scopedName);
|
|
21330
|
+
if (this.isTaintedExpression(valueNode)) {
|
|
21331
|
+
this.goUnknownVars.add(scopedName);
|
|
21332
|
+
this.tainted.add(scopedName);
|
|
21333
|
+
this.sanitizedVars.delete(scopedName);
|
|
21334
|
+
this.sanitizedVarUncovered.delete(scopedName);
|
|
21335
|
+
this.definitionNodes.set(scopedName, valueNode);
|
|
21336
|
+
this.symbols.set(scopedName, createUnknown(line));
|
|
21337
|
+
continue;
|
|
21338
|
+
}
|
|
21339
|
+
if (!this.isGoKnownCleanRhs(valueNode) || this.goUnknownVars.has(scopedName)) {
|
|
21340
|
+
this.goUnknownVars.add(scopedName);
|
|
21341
|
+
this.symbols.delete(scopedName);
|
|
21342
|
+
this.sanitizedVars.delete(scopedName);
|
|
21343
|
+
this.sanitizedVarUncovered.delete(scopedName);
|
|
21344
|
+
if (!this.inConditionalBranch) {
|
|
21345
|
+
this.tainted.delete(scopedName);
|
|
21346
|
+
this.definitionNodes.delete(scopedName);
|
|
21347
|
+
}
|
|
21348
|
+
continue;
|
|
21349
|
+
}
|
|
21350
|
+
if (this.inConditionalBranch) {
|
|
21351
|
+
this.goUnknownVars.add(scopedName);
|
|
21352
|
+
this.symbols.delete(scopedName);
|
|
21353
|
+
continue;
|
|
21354
|
+
}
|
|
21355
|
+
this.tainted.delete(scopedName);
|
|
21356
|
+
this.definitionNodes.delete(scopedName);
|
|
21357
|
+
this.symbols.set(scopedName, this.evaluateExpression(valueNode));
|
|
21358
|
+
this.sanitizedVars.delete(scopedName);
|
|
21359
|
+
this.sanitizedVarUncovered.delete(scopedName);
|
|
21360
|
+
}
|
|
21361
|
+
}
|
|
21362
|
+
}
|
|
20953
21363
|
handleGoAssignment(node) {
|
|
20954
21364
|
const left = node.childForFieldName("left");
|
|
20955
21365
|
const right = node.childForFieldName("right");
|
|
@@ -21766,6 +22176,24 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
21766
22176
|
cache.set(rootId, false);
|
|
21767
22177
|
return false;
|
|
21768
22178
|
}
|
|
22179
|
+
/** Is an argument of `call` an inline sanitizer call over tainted input (itself possibly nested)? */
|
|
22180
|
+
argIsSanitizedTaint(call) {
|
|
22181
|
+
const argsNode = call.childForFieldName("arguments");
|
|
22182
|
+
if (!argsNode) return false;
|
|
22183
|
+
for (const arg of argsNode.children) {
|
|
22184
|
+
if (arg.type !== "method_invocation") continue;
|
|
22185
|
+
const name2 = arg.childForFieldName("name");
|
|
22186
|
+
if (!name2 || !SANITIZER_METHODS.has(getNodeText2(name2, this.source))) continue;
|
|
22187
|
+
const inner = arg.childForFieldName("arguments");
|
|
22188
|
+
if (!inner) continue;
|
|
22189
|
+
for (const a of inner.children) {
|
|
22190
|
+
if (a.type === "(" || a.type === ")" || a.type === ",") continue;
|
|
22191
|
+
if (this.isTaintedExpression(a)) return true;
|
|
22192
|
+
}
|
|
22193
|
+
if (this.argIsSanitizedTaint(arg)) return true;
|
|
22194
|
+
}
|
|
22195
|
+
return false;
|
|
22196
|
+
}
|
|
21769
22197
|
isTaintedExpressionStep(node) {
|
|
21770
22198
|
const text = getNodeText2(node, this.source);
|
|
21771
22199
|
if (node.type === "method_invocation") {
|
|
@@ -21773,6 +22201,9 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
21773
22201
|
const objectNode = node.childForFieldName("object");
|
|
21774
22202
|
if (nameNode) {
|
|
21775
22203
|
const methodName = getNodeText2(nameNode, this.source);
|
|
22204
|
+
if (ANTI_SANITIZER_METHODS.has(methodName) && this.argIsSanitizedTaint(node)) {
|
|
22205
|
+
return true;
|
|
22206
|
+
}
|
|
21776
22207
|
if (SANITIZER_METHODS.has(methodName) && !this.localNonSanitizing.has(methodName)) {
|
|
21777
22208
|
return false;
|
|
21778
22209
|
}
|