circle-ir 4.9.12 → 4.9.14
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/config-loader.d.ts.map +1 -1
- package/dist/analysis/config-loader.js +25 -5
- package/dist/analysis/config-loader.js.map +1 -1
- package/dist/analysis/passes/blocking-main-thread-pass.d.ts +19 -0
- package/dist/analysis/passes/blocking-main-thread-pass.d.ts.map +1 -1
- package/dist/analysis/passes/blocking-main-thread-pass.js +75 -2
- package/dist/analysis/passes/blocking-main-thread-pass.js.map +1 -1
- package/dist/analysis/passes/language-sources-pass.d.ts.map +1 -1
- package/dist/analysis/passes/language-sources-pass.js +219 -18
- package/dist/analysis/passes/language-sources-pass.js.map +1 -1
- package/dist/analysis/passes/sink-filter-pass.d.ts.map +1 -1
- package/dist/analysis/passes/sink-filter-pass.js +225 -4
- package/dist/analysis/passes/sink-filter-pass.js.map +1 -1
- package/dist/analysis/passes/taint-propagation-pass.d.ts +48 -0
- package/dist/analysis/passes/taint-propagation-pass.d.ts.map +1 -1
- package/dist/analysis/passes/taint-propagation-pass.js +166 -1
- package/dist/analysis/passes/taint-propagation-pass.js.map +1 -1
- package/dist/analysis/taint-propagation.d.ts.map +1 -1
- package/dist/analysis/taint-propagation.js +17 -2
- package/dist/analysis/taint-propagation.js.map +1 -1
- package/dist/browser/circle-ir.js +405 -34
- package/dist/core/circle-ir-core.cjs +60 -6
- package/dist/core/circle-ir-core.js +60 -6
- package/dist/core/extractors/calls.js +19 -0
- package/dist/core/extractors/calls.js.map +1 -1
- package/dist/core/extractors/dfg.js +78 -1
- package/dist/core/extractors/dfg.js.map +1 -1
- package/package.json +1 -1
|
@@ -6700,6 +6700,14 @@ function buildResolutionContext(tree, cache) {
|
|
|
6700
6700
|
}
|
|
6701
6701
|
}
|
|
6702
6702
|
}
|
|
6703
|
+
const resourceDecls = getNodesFromCache(tree.rootNode, "resource", cache);
|
|
6704
|
+
for (const res of resourceDecls) {
|
|
6705
|
+
const typeNode = res.childForFieldName("type");
|
|
6706
|
+
const nameNode = res.childForFieldName("name");
|
|
6707
|
+
if (typeNode && nameNode) {
|
|
6708
|
+
context.localVarTypes.set(getNodeText(nameNode), getNodeText(typeNode));
|
|
6709
|
+
}
|
|
6710
|
+
}
|
|
6703
6711
|
const imports = getNodesFromCache(tree.rootNode, "import_declaration", cache);
|
|
6704
6712
|
for (const imp of imports) {
|
|
6705
6713
|
const text = getNodeText(imp);
|
|
@@ -9363,6 +9371,7 @@ function buildCSharpDFG(tree, cache) {
|
|
|
9363
9371
|
];
|
|
9364
9372
|
for (const method of methods) {
|
|
9365
9373
|
scopeStack.push(/* @__PURE__ */ new Map());
|
|
9374
|
+
const methodDefStart = defs.length;
|
|
9366
9375
|
const params = method.childForFieldName("parameters");
|
|
9367
9376
|
if (params) {
|
|
9368
9377
|
for (const def of extractParameterDefs(params, defIdCounter)) {
|
|
@@ -9392,6 +9401,7 @@ function buildCSharpDFG(tree, cache) {
|
|
|
9392
9401
|
}
|
|
9393
9402
|
}
|
|
9394
9403
|
const bodyUses = extractUses(body2, useIdCounter, scopeStack, false);
|
|
9404
|
+
resolveReassignedUses(defs.slice(methodDefStart), bodyUses.uses);
|
|
9395
9405
|
uses.push(...bodyUses.uses);
|
|
9396
9406
|
useIdCounter = bodyUses.nextId;
|
|
9397
9407
|
}
|
|
@@ -10101,6 +10111,30 @@ function findReachingDef(name2, scopeStack) {
|
|
|
10101
10111
|
}
|
|
10102
10112
|
return null;
|
|
10103
10113
|
}
|
|
10114
|
+
function resolveReassignedUses(methodDefs, methodUses) {
|
|
10115
|
+
const defsByVar = /* @__PURE__ */ new Map();
|
|
10116
|
+
for (const def of methodDefs) {
|
|
10117
|
+
if (def.kind !== "local" && def.kind !== "param") continue;
|
|
10118
|
+
const existing = defsByVar.get(def.variable);
|
|
10119
|
+
if (existing) existing.push(def);
|
|
10120
|
+
else defsByVar.set(def.variable, [def]);
|
|
10121
|
+
}
|
|
10122
|
+
for (const list of defsByVar.values()) {
|
|
10123
|
+
if (list.length > 1) list.sort((a, b) => a.line - b.line || a.id - b.id);
|
|
10124
|
+
}
|
|
10125
|
+
for (const use of methodUses) {
|
|
10126
|
+
const list = defsByVar.get(use.variable);
|
|
10127
|
+
if (!list || list.length < 2) continue;
|
|
10128
|
+
let strictlyBefore;
|
|
10129
|
+
let sameLine;
|
|
10130
|
+
for (const def of list) {
|
|
10131
|
+
if (def.line < use.line) strictlyBefore = def;
|
|
10132
|
+
else if (def.line === use.line) sameLine = def;
|
|
10133
|
+
}
|
|
10134
|
+
const reaching = strictlyBefore ?? sameLine;
|
|
10135
|
+
if (reaching) use.def_id = reaching.id;
|
|
10136
|
+
}
|
|
10137
|
+
}
|
|
10104
10138
|
function computeChains(defs, uses) {
|
|
10105
10139
|
const chains = [];
|
|
10106
10140
|
const defById = /* @__PURE__ */ new Map();
|
|
@@ -12346,9 +12380,27 @@ var DEFAULT_SINKS = [
|
|
|
12346
12380
|
// Command Injection (CWE-78)
|
|
12347
12381
|
{ method: "exec", class: "Runtime", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0, 1] },
|
|
12348
12382
|
{ method: "start", class: "ProcessBuilder", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [] },
|
|
12349
|
-
// ProcessBuilder constructor
|
|
12350
|
-
|
|
12351
|
-
|
|
12383
|
+
// ProcessBuilder constructor + command(): cognium-dev#351 — BOTH take
|
|
12384
|
+
// `String...` varargs (or a single List<String>), and EVERY element is a
|
|
12385
|
+
// command token, so no position is safe. Pinning these to `[0]` missed the
|
|
12386
|
+
// canonical shell-wrapper shape, where the payload is the last argument:
|
|
12387
|
+
//
|
|
12388
|
+
// new ProcessBuilder("bash", "-c", tainted) // payload at position 2
|
|
12389
|
+
// pb.command("bash", "-c", tainted)
|
|
12390
|
+
//
|
|
12391
|
+
// `Runtime.exec(String[])` with the identical array already fired, so the
|
|
12392
|
+
// taint was available and only the sink model was wrong.
|
|
12393
|
+
//
|
|
12394
|
+
// The positions are enumerated rather than left `[]` because the two layers
|
|
12395
|
+
// that read this field disagree about what an empty array means:
|
|
12396
|
+
// `isInDangerousPosition` (taint-matcher) does `arg_positions.includes(pos)`,
|
|
12397
|
+
// so `[]` matches NOTHING, while the sink-reachability loop in
|
|
12398
|
+
// taint-propagation treats `length === 0` as "any argument".
|
|
12399
|
+
// `ProcessBuilder.start` gets away with `[]` only because it takes no
|
|
12400
|
+
// arguments at all. Enumerating is unambiguous in both, and matches how the
|
|
12401
|
+
// logging sinks in this file already spell it.
|
|
12402
|
+
{ method: "ProcessBuilder", class: "constructor", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0, 1, 2, 3, 4, 5, 6, 7] },
|
|
12403
|
+
{ method: "command", class: "ProcessBuilder", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0, 1, 2, 3, 4, 5, 6, 7] },
|
|
12352
12404
|
// Commons Exec
|
|
12353
12405
|
// Note: bare class 'Executor' removed — it collided with java.util.concurrent.Executor
|
|
12354
12406
|
// (Executor.execute(Runnable) is not command injection). Apache Commons Exec users
|
|
@@ -13626,8 +13678,10 @@ var DEFAULT_SINKS = [
|
|
|
13626
13678
|
// =========================================================================
|
|
13627
13679
|
// Java CWE-Bench Enhancement Patterns (Collection/Builder)
|
|
13628
13680
|
// =========================================================================
|
|
13629
|
-
// Collection-based command injection (ProcessBuilder with List)
|
|
13630
|
-
|
|
13681
|
+
// Collection-based command injection (ProcessBuilder with List) — the
|
|
13682
|
+
// `ProcessBuilder.command` entry lives with the other ProcessBuilder sinks
|
|
13683
|
+
// above; a second copy here only risked the two drifting apart, which is
|
|
13684
|
+
// what happened (cognium-dev#351).
|
|
13631
13685
|
// ProcessBuilder.inheritIO removed in 3.83.0 (#124): no args, no command
|
|
13632
13686
|
// string flows into it. See note above next to the Process-related cluster.
|
|
13633
13687
|
// Jenkins DSL patterns
|
|
@@ -15557,7 +15611,7 @@ function isSafeGoExecCommandCall(call, pattern, language) {
|
|
|
15557
15611
|
const stripped = expr.slice(1, -1);
|
|
15558
15612
|
program = stripped.split("/").pop() ?? stripped;
|
|
15559
15613
|
}
|
|
15560
|
-
const
|
|
15614
|
+
const SHELL_PROGRAMS2 = /* @__PURE__ */ new Set([
|
|
15561
15615
|
"sh",
|
|
15562
15616
|
"bash",
|
|
15563
15617
|
"zsh",
|
|
@@ -15571,7 +15625,7 @@ function isSafeGoExecCommandCall(call, pattern, language) {
|
|
|
15571
15625
|
"powershell.exe",
|
|
15572
15626
|
"pwsh.exe"
|
|
15573
15627
|
]);
|
|
15574
|
-
if (
|
|
15628
|
+
if (SHELL_PROGRAMS2.has(program)) return false;
|
|
15575
15629
|
const PACKAGE_MANAGER_EXEC_SUBCOMMANDS = {
|
|
15576
15630
|
go: /* @__PURE__ */ new Set(["install", "run", "get"]),
|
|
15577
15631
|
npm: /* @__PURE__ */ new Set(["install", "i", "exec"]),
|
|
@@ -15622,7 +15676,7 @@ function isSafeJSChildProcessCall(call, pattern, language) {
|
|
|
15622
15676
|
const stripped = expr.slice(1, -1);
|
|
15623
15677
|
program = stripped.split("/").pop() ?? stripped;
|
|
15624
15678
|
}
|
|
15625
|
-
const
|
|
15679
|
+
const SHELL_PROGRAMS2 = /* @__PURE__ */ new Set([
|
|
15626
15680
|
"sh",
|
|
15627
15681
|
"bash",
|
|
15628
15682
|
"zsh",
|
|
@@ -15636,7 +15690,7 @@ function isSafeJSChildProcessCall(call, pattern, language) {
|
|
|
15636
15690
|
"powershell.exe",
|
|
15637
15691
|
"pwsh.exe"
|
|
15638
15692
|
]);
|
|
15639
|
-
if (
|
|
15693
|
+
if (SHELL_PROGRAMS2.has(program)) return false;
|
|
15640
15694
|
return true;
|
|
15641
15695
|
}
|
|
15642
15696
|
var CSHARP_SHELL_PROGRAMS = /* @__PURE__ */ new Set([
|
|
@@ -15869,7 +15923,7 @@ function isSafeRustCommandCall(call, pattern, language) {
|
|
|
15869
15923
|
if (language !== "rust") return false;
|
|
15870
15924
|
if (pattern.type !== "command_injection") return false;
|
|
15871
15925
|
if (pattern.class !== void 0 && pattern.class !== "Command") return false;
|
|
15872
|
-
const
|
|
15926
|
+
const SHELL_PROGRAMS2 = /* @__PURE__ */ new Set([
|
|
15873
15927
|
"sh",
|
|
15874
15928
|
"bash",
|
|
15875
15929
|
"zsh",
|
|
@@ -15904,13 +15958,13 @@ function isSafeRustCommandCall(call, pattern, language) {
|
|
|
15904
15958
|
const stripped = expr.slice(1, -1);
|
|
15905
15959
|
program = stripped.split("/").pop() ?? stripped;
|
|
15906
15960
|
}
|
|
15907
|
-
return !
|
|
15961
|
+
return !SHELL_PROGRAMS2.has(program);
|
|
15908
15962
|
}
|
|
15909
15963
|
if (pattern.method === "arg" || pattern.method === "args" || pattern.method === "spawn" || pattern.method === "output") {
|
|
15910
15964
|
const receiverText = call.receiver ?? "";
|
|
15911
15965
|
const program = extractProgram(receiverText);
|
|
15912
15966
|
if (program === null) return false;
|
|
15913
|
-
return !
|
|
15967
|
+
return !SHELL_PROGRAMS2.has(program);
|
|
15914
15968
|
}
|
|
15915
15969
|
return false;
|
|
15916
15970
|
}
|
|
@@ -18965,7 +19019,7 @@ function checkSanitized(fromLine, toLine, sinkType, sanitizersByLine, ctx) {
|
|
|
18965
19019
|
function buildTaintFlow(source, sink, taintInfo) {
|
|
18966
19020
|
const path = [];
|
|
18967
19021
|
path.push({
|
|
18968
|
-
variable: taintInfo.variable,
|
|
19022
|
+
variable: source.variable ?? taintInfo.variable,
|
|
18969
19023
|
line: source.line,
|
|
18970
19024
|
type: "source",
|
|
18971
19025
|
description: `Tainted data enters via ${source.type}`
|
|
@@ -27163,6 +27217,7 @@ var LanguageSourcesPass = class {
|
|
|
27163
27217
|
additionalSanitizers.push(...findPythonUrlAllowlistRedirectSanitizers(code));
|
|
27164
27218
|
additionalSanitizers.push(...findPythonDefusedXmlSanitizers(code));
|
|
27165
27219
|
additionalSanitizers.push(...findPythonJinjaAutoescapeSanitizers(code));
|
|
27220
|
+
additionalSanitizers.push(...findCrlfWrapperFunctionSanitizers(code, "python"));
|
|
27166
27221
|
const pyMisconfigFindings = findPythonPatternFindings(code, graph.ir.meta.file);
|
|
27167
27222
|
for (const finding of pyMisconfigFindings) {
|
|
27168
27223
|
ctx.addFinding(finding);
|
|
@@ -27321,6 +27376,11 @@ var LanguageSourcesPass = class {
|
|
|
27321
27376
|
ctx.addFinding(finding);
|
|
27322
27377
|
}
|
|
27323
27378
|
}
|
|
27379
|
+
if (language === "csharp") {
|
|
27380
|
+
additionalSanitizers.push(
|
|
27381
|
+
...findCSharpFullPathStartsWithGuardSanitizers(code)
|
|
27382
|
+
);
|
|
27383
|
+
}
|
|
27324
27384
|
if (language === "java") {
|
|
27325
27385
|
additionalSanitizers.push(...findJavaSafeJsonParseSanitizers(code));
|
|
27326
27386
|
additionalSanitizers.push(
|
|
@@ -27329,6 +27389,7 @@ var LanguageSourcesPass = class {
|
|
|
27329
27389
|
additionalSanitizers.push(...findJavaPathGetFileNameSanitizers(code));
|
|
27330
27390
|
additionalSanitizers.push(...findJavaCanonicalPathStartsWithGuardSanitizers(code));
|
|
27331
27391
|
additionalSanitizers.push(...findJavaInlineCrlfStripLogSanitizers(code));
|
|
27392
|
+
additionalSanitizers.push(...findCrlfWrapperFunctionSanitizers(code, "java"));
|
|
27332
27393
|
additionalSanitizers.push(...findJavaArgvFormExecSanitizers(code));
|
|
27333
27394
|
for (const finding of findJavaPatternFindings(code, graph.ir.meta.file)) {
|
|
27334
27395
|
ctx.addFinding(finding);
|
|
@@ -29896,7 +29957,7 @@ function findJsPathResolveStartsWithGuardSanitizers(code) {
|
|
|
29896
29957
|
let guardLine = -1;
|
|
29897
29958
|
for (let l = c.line; l < Math.min(lines.length, c.line + 6); l++) {
|
|
29898
29959
|
if (!guardRe.test(lines[l])) continue;
|
|
29899
|
-
if (
|
|
29960
|
+
if (guardRejects(lines, l, terminatorRe, 2)) {
|
|
29900
29961
|
guardLine = l + 1;
|
|
29901
29962
|
break;
|
|
29902
29963
|
}
|
|
@@ -30005,7 +30066,7 @@ function findJavaPathNormalizeStartsWithGuardSanitizers(code) {
|
|
|
30005
30066
|
let guardLine = -1;
|
|
30006
30067
|
for (let l = c.line; l < Math.min(lines.length, c.line + 6); l++) {
|
|
30007
30068
|
if (!guardRe.test(lines[l])) continue;
|
|
30008
|
-
if (
|
|
30069
|
+
if (guardRejects(lines, l, terminatorRe, 2)) {
|
|
30009
30070
|
guardLine = l + 1;
|
|
30010
30071
|
break;
|
|
30011
30072
|
}
|
|
@@ -30039,17 +30100,7 @@ function findJavaCanonicalPathStartsWithGuardSanitizers(code) {
|
|
|
30039
30100
|
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
30040
30101
|
const m = guardRe.exec(lines[i2]);
|
|
30041
30102
|
if (!m) continue;
|
|
30042
|
-
|
|
30043
|
-
if (!hasTerminator) {
|
|
30044
|
-
for (let j = i2 + 1; j < Math.min(lines.length, i2 + 6); j++) {
|
|
30045
|
-
if (terminatorRe.test(lines[j])) {
|
|
30046
|
-
hasTerminator = true;
|
|
30047
|
-
break;
|
|
30048
|
-
}
|
|
30049
|
-
if (lines[j].includes("}")) break;
|
|
30050
|
-
}
|
|
30051
|
-
}
|
|
30052
|
-
if (!hasTerminator) continue;
|
|
30103
|
+
if (!guardRejects(lines, i2, terminatorRe, 6)) continue;
|
|
30053
30104
|
const guarded = /* @__PURE__ */ new Set([m[1]]);
|
|
30054
30105
|
const mentions = (t) => {
|
|
30055
30106
|
for (const n of guarded) if (new RegExp(`\\b${n}\\b`).test(t)) return true;
|
|
@@ -30074,6 +30125,58 @@ function findJavaCanonicalPathStartsWithGuardSanitizers(code) {
|
|
|
30074
30125
|
}
|
|
30075
30126
|
return sanitizers;
|
|
30076
30127
|
}
|
|
30128
|
+
function guardRejects(lines, i2, terminatorRe, window2) {
|
|
30129
|
+
if (terminatorRe.test(lines[i2])) return true;
|
|
30130
|
+
const opens = (lines[i2].match(/\{/g) ?? []).length;
|
|
30131
|
+
const closes = (lines[i2].match(/\}/g) ?? []).length;
|
|
30132
|
+
if (opens > 0 && closes >= opens) return false;
|
|
30133
|
+
for (let j = i2 + 1; j < Math.min(lines.length, i2 + window2); j++) {
|
|
30134
|
+
if (terminatorRe.test(lines[j])) return true;
|
|
30135
|
+
if (lines[j].includes("}")) return false;
|
|
30136
|
+
}
|
|
30137
|
+
return false;
|
|
30138
|
+
}
|
|
30139
|
+
function findCSharpFullPathStartsWithGuardSanitizers(code) {
|
|
30140
|
+
const sanitizers = [];
|
|
30141
|
+
const lines = code.split("\n");
|
|
30142
|
+
const assignRe = /^\s*(?:(?:var|string)\s+)?([A-Za-z_]\w*)\s*=(?!=)\s*(.*)$/;
|
|
30143
|
+
const fullPathRe = /\bPath\s*\.\s*GetFullPath\s*\(/;
|
|
30144
|
+
const canonicalVars = /* @__PURE__ */ new Set();
|
|
30145
|
+
for (const line of lines) {
|
|
30146
|
+
const a = assignRe.exec(line);
|
|
30147
|
+
if (a && fullPathRe.test(a[2])) canonicalVars.add(a[1]);
|
|
30148
|
+
}
|
|
30149
|
+
if (canonicalVars.size === 0) return sanitizers;
|
|
30150
|
+
const guardRe = /\bif\s*\(\s*!\s*([A-Za-z_]\w*)\s*\.\s*StartsWith\s*\(/;
|
|
30151
|
+
const terminatorRe = /\b(throw|return)\b/;
|
|
30152
|
+
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
30153
|
+
const m = guardRe.exec(lines[i2]);
|
|
30154
|
+
if (!m || !canonicalVars.has(m[1])) continue;
|
|
30155
|
+
if (!guardRejects(lines, i2, terminatorRe, 6)) continue;
|
|
30156
|
+
const guarded = /* @__PURE__ */ new Set([m[1]]);
|
|
30157
|
+
const mentions = (t) => {
|
|
30158
|
+
for (const n of guarded) if (new RegExp(`\\b${n}\\b`).test(t)) return true;
|
|
30159
|
+
return false;
|
|
30160
|
+
};
|
|
30161
|
+
for (let l = 0; l < lines.length; l++) {
|
|
30162
|
+
const lt = lines[l];
|
|
30163
|
+
const a = assignRe.exec(lt);
|
|
30164
|
+
if (a) {
|
|
30165
|
+
if (mentions(a[2])) guarded.add(a[1]);
|
|
30166
|
+
else if (a[1] !== m[1]) guarded.delete(a[1]);
|
|
30167
|
+
}
|
|
30168
|
+
if (mentions(lt)) {
|
|
30169
|
+
sanitizers.push({
|
|
30170
|
+
type: "csharp_fullpath_startswith_guard",
|
|
30171
|
+
method: "GetFullPath",
|
|
30172
|
+
line: l + 1,
|
|
30173
|
+
sanitizes: ["path_traversal", "external_taint_escape"]
|
|
30174
|
+
});
|
|
30175
|
+
}
|
|
30176
|
+
}
|
|
30177
|
+
}
|
|
30178
|
+
return sanitizers;
|
|
30179
|
+
}
|
|
30077
30180
|
function findJavaPathGetFileNameSanitizers(code) {
|
|
30078
30181
|
const sanitizers = [];
|
|
30079
30182
|
const lines = code.split("\n");
|
|
@@ -30112,6 +30215,34 @@ function findJavaPathGetFileNameSanitizers(code) {
|
|
|
30112
30215
|
}
|
|
30113
30216
|
return sanitizers;
|
|
30114
30217
|
}
|
|
30218
|
+
function findCrlfWrapperFunctionSanitizers(code, language) {
|
|
30219
|
+
const sanitizers = [];
|
|
30220
|
+
const lines = code.split("\n");
|
|
30221
|
+
const declRe = language === "java" ? /^\s*(?:public|private|protected)?\s*(?:static\s+)?(?:final\s+)?(?:String|CharSequence)\s+([A-Za-z_]\w*)\s*\(/ : /^\s*def\s+([A-Za-z_]\w*)\s*\(/;
|
|
30222
|
+
const stripRe = language === "java" ? /\.\s*replace(?:All)?\s*\(\s*(?:"[^"]*\\[rnt]|'\\[rnt]')/ : /(?:re\s*\.\s*sub\s*\(\s*r?["'][^"']*\\[rnt]|\.\s*replace\s*\(\s*["']\\[rnt])/;
|
|
30223
|
+
const bodyWindow = 10;
|
|
30224
|
+
const wrappers = /* @__PURE__ */ new Set();
|
|
30225
|
+
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
30226
|
+
const m = declRe.exec(lines[i2]);
|
|
30227
|
+
if (!m) continue;
|
|
30228
|
+
const body2 = lines.slice(i2, Math.min(lines.length, i2 + bodyWindow)).join("\n");
|
|
30229
|
+
if (stripRe.test(body2)) wrappers.add(m[1]);
|
|
30230
|
+
}
|
|
30231
|
+
if (wrappers.size === 0) return sanitizers;
|
|
30232
|
+
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
30233
|
+
for (const name2 of wrappers) {
|
|
30234
|
+
if (declRe.test(lines[i2]) && new RegExp(`\\b${name2}\\s*\\(`).test(lines[i2])) continue;
|
|
30235
|
+
if (!new RegExp(`\\b${name2}\\s*\\(`).test(lines[i2])) continue;
|
|
30236
|
+
sanitizers.push({
|
|
30237
|
+
type: language === "java" ? "java_wrapper_crlf_strip" : "python_wrapper_crlf_strip",
|
|
30238
|
+
method: name2,
|
|
30239
|
+
line: i2 + 1,
|
|
30240
|
+
sanitizes: ["log_injection", "external_taint_escape"]
|
|
30241
|
+
});
|
|
30242
|
+
}
|
|
30243
|
+
}
|
|
30244
|
+
return sanitizers;
|
|
30245
|
+
}
|
|
30115
30246
|
function findJavaInlineCrlfStripLogSanitizers(code) {
|
|
30116
30247
|
const sanitizers = [];
|
|
30117
30248
|
const lines = code.split("\n");
|
|
@@ -33778,6 +33909,29 @@ var PROTOCOL_CLIENT_PACKAGES = [
|
|
|
33778
33909
|
];
|
|
33779
33910
|
var OS_EXEC_RECEIVER_RE = /\b(?:Runtime|ProcessBuilder|DefaultExecutor|Executor|Exec|Launcher|ProcStarter|ProcessExecutor|RuntimeUtil)\s*[.(]/;
|
|
33780
33911
|
var PROCESS_BUILDER_ARGV_FORM_RE = /\bnew\s+ProcessBuilder\s*\(\s*(?:Arrays\.asList\b|List\.of\b|Collections\.singletonList\b|new\s+ArrayList\b|new\s+String\s*\[\s*\]\s*\{|"[^"]*"\s*,)/;
|
|
33912
|
+
var PROCESS_BUILDER_COMMAND_ARGV_FORM_RE = /\.\s*command\s*\(\s*(?:Arrays\.asList\b|List\.of\b|Collections\.singletonList\b|new\s+ArrayList\b|new\s+String\s*\[\s*\]\s*\{|"[^"]*"\s*,)/;
|
|
33913
|
+
var SHELL_PROGRAMS = /* @__PURE__ */ new Set([
|
|
33914
|
+
"sh",
|
|
33915
|
+
"bash",
|
|
33916
|
+
"zsh",
|
|
33917
|
+
"ksh",
|
|
33918
|
+
"dash",
|
|
33919
|
+
"ash",
|
|
33920
|
+
"csh",
|
|
33921
|
+
"tcsh",
|
|
33922
|
+
"fish",
|
|
33923
|
+
"cmd",
|
|
33924
|
+
"command",
|
|
33925
|
+
"powershell",
|
|
33926
|
+
"pwsh"
|
|
33927
|
+
]);
|
|
33928
|
+
var ARGV_FIRST_LITERAL_RE = /(?:\bnew\s+ProcessBuilder\s*\(|\.\s*command\s*\()\s*(?:Arrays\.asList\s*\(|List\.of\s*\(|Collections\.singletonList\s*\(|new\s+ArrayList\s*(?:<[^>]*>)?\s*\(\s*(?:Arrays\.asList\s*\()?|new\s+String\s*\[\s*\]\s*\{)?\s*"([^"]*)"/;
|
|
33929
|
+
function argvFirstLiteralIsShell(lineText) {
|
|
33930
|
+
const m = ARGV_FIRST_LITERAL_RE.exec(lineText);
|
|
33931
|
+
if (!m) return false;
|
|
33932
|
+
const base = m[1].split(/[\\/]/).pop().replace(/\.exe$/i, "").toLowerCase();
|
|
33933
|
+
return SHELL_PROGRAMS.has(base);
|
|
33934
|
+
}
|
|
33781
33935
|
var JAVA_THROW_STATEMENT_RE = /^\s*throw\s+new\s+\w+(?:Exception|Error)\b[^;]*;\s*(?:\/\/.*|\/\*.*)?$/;
|
|
33782
33936
|
var JEXL_ENGINE_TYPES = /* @__PURE__ */ new Set(["JexlEngine", "Jexl", "JxltEngine"]);
|
|
33783
33937
|
var JEXL_EXPRESSION_TYPE_RE = /(?:Jexl)?(?:Expression|Script|Template)(?:Script)?$/;
|
|
@@ -33924,10 +34078,39 @@ function jsSsrfHostGuardedLines(code) {
|
|
|
33924
34078
|
}
|
|
33925
34079
|
return covered;
|
|
33926
34080
|
}
|
|
34081
|
+
var CSHARP_ALLOWLIST_STRIP_RE = /\bRegex\s*\.\s*Replace\s*\([^,]*,\s*@?"\[\^(?:[A-Za-z0-9_\- ]|\\w|\\d|\\s)+\]"\s*,\s*@?"[A-Za-z0-9_]?"\s*\)/;
|
|
34082
|
+
function isUnwrittenLocalMapKeyRead(code, source) {
|
|
34083
|
+
if (source.type !== "plugin_param") return false;
|
|
34084
|
+
const lines = code.split("\n");
|
|
34085
|
+
const lineText = lines[source.line - 1] ?? "";
|
|
34086
|
+
const read = /\b([A-Za-z_]\w*)\s*\.\s*get\s*\(\s*"([^"]*)"\s*\)/.exec(lineText);
|
|
34087
|
+
if (!read) return false;
|
|
34088
|
+
const [, recv, key] = read;
|
|
34089
|
+
const ctorRe = new RegExp(
|
|
34090
|
+
`\\b${recv}\\s*=\\s*new\\s+(?:Hash|Tree|LinkedHash|ConcurrentHash|Concurrent|Identity|Weak)?Map\\s*(?:<[^>]*>)?\\s*\\(`
|
|
34091
|
+
);
|
|
34092
|
+
if (!lines.some((l) => ctorRe.test(l))) return false;
|
|
34093
|
+
const escapeRe2 = new RegExp(`\\w\\s*\\([^)]*(?<![\\w.])${recv}(?![\\w.])[^)]*\\)`);
|
|
34094
|
+
for (const l of lines) {
|
|
34095
|
+
if (!escapeRe2.test(l)) continue;
|
|
34096
|
+
const stripped = l.replace(new RegExp(`\\b${recv}\\s*\\.\\s*\\w+\\s*\\(`, "g"), "X(");
|
|
34097
|
+
if (escapeRe2.test(stripped)) return false;
|
|
34098
|
+
}
|
|
34099
|
+
const putRe = new RegExp(`\\b${recv}\\s*\\.\\s*put\\s*\\(\\s*"${key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}"`);
|
|
34100
|
+
if (lines.some((l) => putRe.test(l))) return false;
|
|
34101
|
+
return true;
|
|
34102
|
+
}
|
|
33927
34103
|
var CSHARP_SANITIZER_RES = [
|
|
33928
34104
|
{ re: /\b(?:HtmlEncode|JavaScriptStringEncode)\s*\(/, type: "xss" },
|
|
33929
34105
|
{ re: /\bHtmlEncoder\s*\.\s*Encode\s*\(/, type: "xss" },
|
|
33930
|
-
{ re: /\bPath\s*\.\s*GetFileName\s*\(/, type: "path_traversal" }
|
|
34106
|
+
{ re: /\bPath\s*\.\s*GetFileName\s*\(/, type: "path_traversal" },
|
|
34107
|
+
// An allowlist strip leaves only inert characters, so it covers every family
|
|
34108
|
+
// whose exploitation needs a metacharacter. Each has a fixture; see
|
|
34109
|
+
// `issue-272-csharp-allowlist-strip.test.ts`.
|
|
34110
|
+
{ re: CSHARP_ALLOWLIST_STRIP_RE, type: "ldap_injection" },
|
|
34111
|
+
{ re: CSHARP_ALLOWLIST_STRIP_RE, type: "xpath_injection" },
|
|
34112
|
+
{ re: CSHARP_ALLOWLIST_STRIP_RE, type: "command_injection" },
|
|
34113
|
+
{ re: CSHARP_ALLOWLIST_STRIP_RE, type: "sql_injection" }
|
|
33931
34114
|
];
|
|
33932
34115
|
function csharpSanitizedVarsByType(code) {
|
|
33933
34116
|
const lines = code.split("\n");
|
|
@@ -34169,9 +34352,10 @@ var SinkFilterPass = class {
|
|
|
34169
34352
|
const constProp = ctx.getResult("constant-propagation");
|
|
34170
34353
|
const langSources = ctx.getResult("language-sources");
|
|
34171
34354
|
const mergedSources = [...taintMatcher.sources, ...langSources.additionalSources];
|
|
34172
|
-
const
|
|
34355
|
+
const executableSources = ctx.code && ctx.language ? mergedSources.filter(
|
|
34173
34356
|
(s) => !isNonExecutableSourceLine(ctx.code, s.line, ctx.language)
|
|
34174
34357
|
) : mergedSources;
|
|
34358
|
+
const sources = ctx.code ? executableSources.filter((s) => !isUnwrittenLocalMapKeyRead(ctx.code, s)) : executableSources;
|
|
34175
34359
|
const sinks = [...taintMatcher.sinks];
|
|
34176
34360
|
for (const s of langSources.additionalSinks) {
|
|
34177
34361
|
if (!sinks.some((x) => x.line === s.line && x.cwe === s.cwe && x.type === s.type)) {
|
|
@@ -34439,10 +34623,19 @@ var SinkFilterPass = class {
|
|
|
34439
34623
|
const irTypes = ctx.graph.ir.types;
|
|
34440
34624
|
filtered = filtered.filter((sink) => {
|
|
34441
34625
|
if (sink.type !== "command_injection") return true;
|
|
34442
|
-
if (sink.method !== "ProcessBuilder" && sink.method !== "start")
|
|
34626
|
+
if (sink.method !== "ProcessBuilder" && sink.method !== "start" && sink.method !== "command") {
|
|
34627
|
+
return true;
|
|
34628
|
+
}
|
|
34443
34629
|
const sinkLineText = sourceLines[sink.line - 1] ?? "";
|
|
34444
|
-
|
|
34445
|
-
|
|
34630
|
+
const isCtorLine = /\bnew\s+ProcessBuilder\s*\(/.test(sinkLineText);
|
|
34631
|
+
const isCommandLine = /\.\s*command\s*\(/.test(sinkLineText);
|
|
34632
|
+
if (!isCtorLine && !isCommandLine) return true;
|
|
34633
|
+
if (argvFirstLiteralIsShell(sinkLineText)) return true;
|
|
34634
|
+
if (isCtorLine && PROCESS_BUILDER_ARGV_FORM_RE.test(sinkLineText)) return false;
|
|
34635
|
+
if (isCommandLine && PROCESS_BUILDER_COMMAND_ARGV_FORM_RE.test(sinkLineText)) {
|
|
34636
|
+
return false;
|
|
34637
|
+
}
|
|
34638
|
+
if (!isCtorLine) return true;
|
|
34446
34639
|
const ctorCall = irCalls.find(
|
|
34447
34640
|
(c) => c.method_name === "ProcessBuilder" && c.location.line === sink.line && (c.receiver === null || c.receiver === void 0)
|
|
34448
34641
|
);
|
|
@@ -34746,6 +34939,36 @@ var SinkFilterPass = class {
|
|
|
34746
34939
|
return true;
|
|
34747
34940
|
});
|
|
34748
34941
|
}
|
|
34942
|
+
if (language === "csharp") {
|
|
34943
|
+
const sourceLines = ctx.code.split("\n");
|
|
34944
|
+
const constBaseClients = /* @__PURE__ */ new Set();
|
|
34945
|
+
const declRe = /\b(?:var|HttpClient)\s+([A-Za-z_]\w*)\s*=\s*new\s+HttpClient\s*\{[^}]*\bBaseAddress\s*=\s*new\s+Uri\s*\(\s*(?:@?"[^"]*"|\$?"[^"{}]*")\s*\)/;
|
|
34946
|
+
for (const line of sourceLines) {
|
|
34947
|
+
const m = declRe.exec(line);
|
|
34948
|
+
if (m) constBaseClients.add(m[1]);
|
|
34949
|
+
}
|
|
34950
|
+
if (constBaseClients.size > 0) {
|
|
34951
|
+
const relativeLiteralArgRe = /\.\s*(?:Get|Post|Put|Patch|Delete|Send|GetString|GetByteArray|GetStream)\w*Async\s*\(\s*(?:@?\$?)"([^"]*)"/;
|
|
34952
|
+
filtered = filtered.filter((sink) => {
|
|
34953
|
+
if (sink.type !== "ssrf") return true;
|
|
34954
|
+
const text = sourceLines[sink.line - 1] ?? "";
|
|
34955
|
+
let onConstClient = false;
|
|
34956
|
+
for (const v of constBaseClients) {
|
|
34957
|
+
if (new RegExp(`(?<![\\w])${escapeRegex(v)}\\s*\\.`).test(text)) {
|
|
34958
|
+
onConstClient = true;
|
|
34959
|
+
break;
|
|
34960
|
+
}
|
|
34961
|
+
}
|
|
34962
|
+
if (!onConstClient) return true;
|
|
34963
|
+
const m = relativeLiteralArgRe.exec(text);
|
|
34964
|
+
if (!m) return true;
|
|
34965
|
+
const lit = m[1];
|
|
34966
|
+
if (/^[A-Za-z][A-Za-z0-9+.-]*:/.test(lit)) return true;
|
|
34967
|
+
if (lit.startsWith("//")) return true;
|
|
34968
|
+
return false;
|
|
34969
|
+
});
|
|
34970
|
+
}
|
|
34971
|
+
}
|
|
34749
34972
|
if (["javascript", "typescript"].includes(language)) {
|
|
34750
34973
|
const sourceLines = ctx.code.split("\n");
|
|
34751
34974
|
filtered = filtered.filter((sink) => {
|
|
@@ -36983,6 +37206,103 @@ function isReassignedToLiteralBetween(code, variable, srcLine, sinkLine) {
|
|
|
36983
37206
|
}
|
|
36984
37207
|
return false;
|
|
36985
37208
|
}
|
|
37209
|
+
var MASKABLE_STRING_LANGUAGES = /* @__PURE__ */ new Set(["python", "javascript", "typescript"]);
|
|
37210
|
+
function maskStringLiteralsForTokenScan(expr) {
|
|
37211
|
+
let out2 = "";
|
|
37212
|
+
let i2 = 0;
|
|
37213
|
+
const braceExprs = (body2) => {
|
|
37214
|
+
let acc = "";
|
|
37215
|
+
for (let j = 0; j < body2.length; ) {
|
|
37216
|
+
if (body2[j] === "{" && body2[j + 1] === "{") {
|
|
37217
|
+
j += 2;
|
|
37218
|
+
continue;
|
|
37219
|
+
}
|
|
37220
|
+
if (body2[j] === "}" && body2[j + 1] === "}") {
|
|
37221
|
+
j += 2;
|
|
37222
|
+
continue;
|
|
37223
|
+
}
|
|
37224
|
+
if (body2[j] === "{") {
|
|
37225
|
+
j++;
|
|
37226
|
+
let depth = 1;
|
|
37227
|
+
let inner = "";
|
|
37228
|
+
while (j < body2.length && depth > 0) {
|
|
37229
|
+
if (body2[j] === "{") depth++;
|
|
37230
|
+
else if (body2[j] === "}") {
|
|
37231
|
+
depth--;
|
|
37232
|
+
if (depth === 0) break;
|
|
37233
|
+
}
|
|
37234
|
+
inner += body2[j];
|
|
37235
|
+
j++;
|
|
37236
|
+
}
|
|
37237
|
+
j++;
|
|
37238
|
+
acc += " " + maskStringLiteralsForTokenScan(inner) + " ";
|
|
37239
|
+
continue;
|
|
37240
|
+
}
|
|
37241
|
+
j++;
|
|
37242
|
+
}
|
|
37243
|
+
return acc;
|
|
37244
|
+
};
|
|
37245
|
+
while (i2 < expr.length) {
|
|
37246
|
+
const ch = expr[i2];
|
|
37247
|
+
if (ch === "`") {
|
|
37248
|
+
i2++;
|
|
37249
|
+
let acc = "";
|
|
37250
|
+
while (i2 < expr.length && expr[i2] !== "`") {
|
|
37251
|
+
if (expr[i2] === "\\") {
|
|
37252
|
+
i2 += 2;
|
|
37253
|
+
continue;
|
|
37254
|
+
}
|
|
37255
|
+
if (expr[i2] === "$" && expr[i2 + 1] === "{") {
|
|
37256
|
+
i2 += 2;
|
|
37257
|
+
let depth = 1;
|
|
37258
|
+
let inner = "";
|
|
37259
|
+
while (i2 < expr.length && depth > 0) {
|
|
37260
|
+
if (expr[i2] === "{") depth++;
|
|
37261
|
+
else if (expr[i2] === "}") {
|
|
37262
|
+
depth--;
|
|
37263
|
+
if (depth === 0) break;
|
|
37264
|
+
}
|
|
37265
|
+
inner += expr[i2];
|
|
37266
|
+
i2++;
|
|
37267
|
+
}
|
|
37268
|
+
i2++;
|
|
37269
|
+
acc += " " + maskStringLiteralsForTokenScan(inner) + " ";
|
|
37270
|
+
continue;
|
|
37271
|
+
}
|
|
37272
|
+
i2++;
|
|
37273
|
+
}
|
|
37274
|
+
i2++;
|
|
37275
|
+
out2 += " " + acc + " ";
|
|
37276
|
+
continue;
|
|
37277
|
+
}
|
|
37278
|
+
if (ch === '"' || ch === "'") {
|
|
37279
|
+
const pm = /[A-Za-z_][A-Za-z0-9_]*$/.exec(out2);
|
|
37280
|
+
const prefix = pm ? pm[0] : "";
|
|
37281
|
+
const isPrefix = prefix.length > 0 && /^[rRbBuUfF]+$/.test(prefix);
|
|
37282
|
+
const isF = isPrefix && /[fF]/.test(prefix);
|
|
37283
|
+
if (isPrefix) out2 = out2.slice(0, out2.length - prefix.length) + " ";
|
|
37284
|
+
const triple = expr.startsWith(ch.repeat(3), i2);
|
|
37285
|
+
const delim = triple ? ch.repeat(3) : ch;
|
|
37286
|
+
i2 += delim.length;
|
|
37287
|
+
let body2 = "";
|
|
37288
|
+
while (i2 < expr.length && !expr.startsWith(delim, i2)) {
|
|
37289
|
+
if (expr[i2] === "\\") {
|
|
37290
|
+
body2 += " ";
|
|
37291
|
+
i2 += 2;
|
|
37292
|
+
continue;
|
|
37293
|
+
}
|
|
37294
|
+
body2 += expr[i2];
|
|
37295
|
+
i2++;
|
|
37296
|
+
}
|
|
37297
|
+
i2 += delim.length;
|
|
37298
|
+
out2 += isF ? " " + braceExprs(body2) + " " : " ";
|
|
37299
|
+
continue;
|
|
37300
|
+
}
|
|
37301
|
+
out2 += ch;
|
|
37302
|
+
i2++;
|
|
37303
|
+
}
|
|
37304
|
+
return out2;
|
|
37305
|
+
}
|
|
36986
37306
|
function detectExpressionScanFlows(calls, sources, sinks, sanitizers, unreachableLines, tainted, code, language, types) {
|
|
36987
37307
|
const flows = [];
|
|
36988
37308
|
const sourcesWithVar = sources.filter(
|
|
@@ -37336,7 +37656,8 @@ function detectExpressionScanFlows(calls, sources, sinks, sanitizers, unreachabl
|
|
|
37336
37656
|
}
|
|
37337
37657
|
const expr = arg.expression;
|
|
37338
37658
|
if (!expr) continue;
|
|
37339
|
-
const
|
|
37659
|
+
const scannable = language !== void 0 && MASKABLE_STRING_LANGUAGES.has(language) ? maskStringLiteralsForTokenScan(expr) : expr;
|
|
37660
|
+
const exprIdentMatches = scannable.match(/[\p{L}\p{N}_]+/gu);
|
|
37340
37661
|
let candidates = complexSources.length > 0 ? complexSources.slice() : null;
|
|
37341
37662
|
if (exprIdentMatches) {
|
|
37342
37663
|
for (const id of new Set(exprIdentMatches)) {
|
|
@@ -40575,6 +40896,18 @@ var CRYPTO_BLOCKING_METHODS = /* @__PURE__ */ new Set([
|
|
|
40575
40896
|
"deriveKeySync"
|
|
40576
40897
|
]);
|
|
40577
40898
|
var SYNC_SUFFIX_RE2 = /Sync$/;
|
|
40899
|
+
var ROUTER_METHODS = /* @__PURE__ */ new Set([
|
|
40900
|
+
"get",
|
|
40901
|
+
"post",
|
|
40902
|
+
"put",
|
|
40903
|
+
"patch",
|
|
40904
|
+
"delete",
|
|
40905
|
+
"all",
|
|
40906
|
+
"options",
|
|
40907
|
+
"head",
|
|
40908
|
+
"use"
|
|
40909
|
+
]);
|
|
40910
|
+
var FN_ARG_PARAMS_RE = /^(?:async\s+)?(?:function\s*[\w$]*\s*)?\(([^)]*)\)\s*(?:=>|\{)|^(?:async\s+)?([A-Za-z_$][\w$]*)\s*=>/;
|
|
40578
40911
|
var BlockingMainThreadPass = class {
|
|
40579
40912
|
name = "blocking-main-thread";
|
|
40580
40913
|
category = "performance";
|
|
@@ -40596,7 +40929,10 @@ var BlockingMainThreadPass = class {
|
|
|
40596
40929
|
}
|
|
40597
40930
|
}
|
|
40598
40931
|
}
|
|
40599
|
-
|
|
40932
|
+
const inlineHandlers = this.collectInlineHandlerNames(graph.ir.calls);
|
|
40933
|
+
if (handlerRanges.length === 0 && inlineHandlers.size === 0) {
|
|
40934
|
+
return { blockingInHandlers: [] };
|
|
40935
|
+
}
|
|
40600
40936
|
const blockingInHandlers = [];
|
|
40601
40937
|
for (const call of graph.ir.calls) {
|
|
40602
40938
|
const name2 = call.method_name;
|
|
@@ -40604,7 +40940,8 @@ var BlockingMainThreadPass = class {
|
|
|
40604
40940
|
const isSyncSuffix = SYNC_SUFFIX_RE2.test(name2);
|
|
40605
40941
|
if (!isCrypto && !isSyncSuffix) continue;
|
|
40606
40942
|
const line = call.location.line;
|
|
40607
|
-
const
|
|
40943
|
+
const enclosing = call.in_method ?? null;
|
|
40944
|
+
const range = handlerRanges.find((r) => line >= r.start && line <= r.end) ?? (enclosing && inlineHandlers.has(enclosing) ? { start: line, end: line, name: enclosing } : void 0);
|
|
40608
40945
|
if (!range) continue;
|
|
40609
40946
|
const reason = isCrypto ? "crypto" : "sync-suffix";
|
|
40610
40947
|
blockingInHandlers.push({ line, method: name2, handler: range.name, reason });
|
|
@@ -40625,6 +40962,40 @@ var BlockingMainThreadPass = class {
|
|
|
40625
40962
|
}
|
|
40626
40963
|
return { blockingInHandlers };
|
|
40627
40964
|
}
|
|
40965
|
+
/**
|
|
40966
|
+
* Synthetic `<verb>_handler` names that really do belong to an HTTP route.
|
|
40967
|
+
*
|
|
40968
|
+
* The extractor tags *any* function argument of *any* member-expression call
|
|
40969
|
+
* this way, so `items.map(x => …)` becomes `map_handler` and
|
|
40970
|
+
* `cache.get(k, () => …)` becomes `get_handler`. Accepting every
|
|
40971
|
+
* `*_handler` would pull ordinary callbacks into a pass that is explicitly
|
|
40972
|
+
* about the HTTP request path, so two conditions are required: the method is
|
|
40973
|
+
* a router verb, and the callback's own parameters look like a request
|
|
40974
|
+
* handler's — the same `HANDLER_PARAM_NAMES` test used for methods.
|
|
40975
|
+
*
|
|
40976
|
+
* Known limitation: `in_method` carries no receiver, so two callbacks that
|
|
40977
|
+
* share a verb in one file are indistinguishable. A file containing both
|
|
40978
|
+
* `app.get('/x', (req, res) => …)` and `cache.get(k, () => …)` will treat a
|
|
40979
|
+
* blocking call in the second as in-handler. Both conditions still have to
|
|
40980
|
+
* hold for the verb to register at all, which keeps that to files already
|
|
40981
|
+
* mounting a real route of the same verb.
|
|
40982
|
+
*/
|
|
40983
|
+
collectInlineHandlerNames(calls) {
|
|
40984
|
+
const names = /* @__PURE__ */ new Set();
|
|
40985
|
+
for (const call of calls) {
|
|
40986
|
+
if (!ROUTER_METHODS.has(call.method_name)) continue;
|
|
40987
|
+
for (const arg of call.arguments) {
|
|
40988
|
+
const match = FN_ARG_PARAMS_RE.exec((arg.expression ?? "").trim());
|
|
40989
|
+
if (!match) continue;
|
|
40990
|
+
const params = (match[1] ?? match[2] ?? "").split(",").map((p) => p.trim().toLowerCase()).filter((p) => p.length > 0);
|
|
40991
|
+
if (params.some((p) => HANDLER_PARAM_NAMES.has(p))) {
|
|
40992
|
+
names.add(`${call.method_name}_handler`);
|
|
40993
|
+
break;
|
|
40994
|
+
}
|
|
40995
|
+
}
|
|
40996
|
+
}
|
|
40997
|
+
return names;
|
|
40998
|
+
}
|
|
40628
40999
|
isRequestHandler(method) {
|
|
40629
41000
|
if (method.annotations.some((a) => HTTP_DECORATORS.has(a))) return true;
|
|
40630
41001
|
if (HANDLER_METHOD_NAMES.has(method.name)) return true;
|