circle-ir 4.9.12 → 4.9.13
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/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 +94 -0
- package/dist/analysis/passes/sink-filter-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 +208 -17
- package/dist/core/circle-ir-core.cjs +27 -1
- package/dist/core/circle-ir-core.js +27 -1
- package/dist/core/extractors/dfg.js +78 -1
- package/dist/core/extractors/dfg.js.map +1 -1
- package/package.json +1 -1
|
@@ -36,6 +36,25 @@ export declare class BlockingMainThreadPass implements AnalysisPass<BlockingMain
|
|
|
36
36
|
readonly name = "blocking-main-thread";
|
|
37
37
|
readonly category: "performance";
|
|
38
38
|
run(ctx: PassContext): BlockingMainThreadResult;
|
|
39
|
+
/**
|
|
40
|
+
* Synthetic `<verb>_handler` names that really do belong to an HTTP route.
|
|
41
|
+
*
|
|
42
|
+
* The extractor tags *any* function argument of *any* member-expression call
|
|
43
|
+
* this way, so `items.map(x => …)` becomes `map_handler` and
|
|
44
|
+
* `cache.get(k, () => …)` becomes `get_handler`. Accepting every
|
|
45
|
+
* `*_handler` would pull ordinary callbacks into a pass that is explicitly
|
|
46
|
+
* about the HTTP request path, so two conditions are required: the method is
|
|
47
|
+
* a router verb, and the callback's own parameters look like a request
|
|
48
|
+
* handler's — the same `HANDLER_PARAM_NAMES` test used for methods.
|
|
49
|
+
*
|
|
50
|
+
* Known limitation: `in_method` carries no receiver, so two callbacks that
|
|
51
|
+
* share a verb in one file are indistinguishable. A file containing both
|
|
52
|
+
* `app.get('/x', (req, res) => …)` and `cache.get(k, () => …)` will treat a
|
|
53
|
+
* blocking call in the second as in-handler. Both conditions still have to
|
|
54
|
+
* hold for the verb to register at all, which keeps that to files already
|
|
55
|
+
* mounting a real route of the same verb.
|
|
56
|
+
*/
|
|
57
|
+
private collectInlineHandlerNames;
|
|
39
58
|
private isRequestHandler;
|
|
40
59
|
}
|
|
41
60
|
//# sourceMappingURL=blocking-main-thread-pass.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"blocking-main-thread-pass.d.ts","sourceRoot":"","sources":["../../../src/analysis/passes/blocking-main-thread-pass.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;
|
|
1
|
+
{"version":3,"file":"blocking-main-thread-pass.d.ts","sourceRoot":"","sources":["../../../src/analysis/passes/blocking-main-thread-pass.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AA6C9E,MAAM,WAAW,wBAAwB;IACvC,kBAAkB,EAAE,KAAK,CAAC;QACxB,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,MAAM,CAAC;QACf,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,EAAE,aAAa,GAAG,QAAQ,CAAC;KAClC,CAAC,CAAC;CACJ;AAED,qBAAa,sBAAuB,YAAW,YAAY,CAAC,wBAAwB,CAAC;IACnF,QAAQ,CAAC,IAAI,0BAA0B;IACvC,QAAQ,CAAC,QAAQ,EAAG,aAAa,CAAU;IAE3C,GAAG,CAAC,GAAG,EAAE,WAAW,GAAG,wBAAwB;IAiF/C;;;;;;;;;;;;;;;;;OAiBG;IACH,OAAO,CAAC,yBAAyB;IA4BjC,OAAO,CAAC,gBAAgB;CAazB"}
|
|
@@ -42,6 +42,22 @@ const CRYPTO_BLOCKING_METHODS = new Set([
|
|
|
42
42
|
'generateKeyPairSync', 'generateKeySync', 'deriveKeySync',
|
|
43
43
|
]);
|
|
44
44
|
const SYNC_SUFFIX_RE = /Sync$/;
|
|
45
|
+
/**
|
|
46
|
+
* Router methods that mount an HTTP request handler (Express / Koa / Hono /
|
|
47
|
+
* Fastify). Lowercase counterparts of HTTP_DECORATORS, plus `use` for
|
|
48
|
+
* middleware — middleware runs in the request path, so a blocking call there
|
|
49
|
+
* stalls the loop exactly as one in a route handler does.
|
|
50
|
+
*/
|
|
51
|
+
const ROUTER_METHODS = new Set([
|
|
52
|
+
'get', 'post', 'put', 'patch', 'delete', 'all', 'options', 'head', 'use',
|
|
53
|
+
]);
|
|
54
|
+
/**
|
|
55
|
+
* Parameter list of a function passed as an argument, for the two shapes that
|
|
56
|
+
* appear as route callbacks: `(req, res) => …` / `async (req, res) => …` /
|
|
57
|
+
* `function (req, res) { … }` (group 1), and the single-parameter arrow
|
|
58
|
+
* `ctx => …` (group 2).
|
|
59
|
+
*/
|
|
60
|
+
const FN_ARG_PARAMS_RE = /^(?:async\s+)?(?:function\s*[\w$]*\s*)?\(([^)]*)\)\s*(?:=>|\{)|^(?:async\s+)?([A-Za-z_$][\w$]*)\s*=>/;
|
|
45
61
|
export class BlockingMainThreadPass {
|
|
46
62
|
name = 'blocking-main-thread';
|
|
47
63
|
category = 'performance';
|
|
@@ -64,8 +80,22 @@ export class BlockingMainThreadPass {
|
|
|
64
80
|
}
|
|
65
81
|
}
|
|
66
82
|
}
|
|
67
|
-
|
|
83
|
+
// Inline route callbacks are invisible to the loop above: `app.get('/x',
|
|
84
|
+
// (req, res) => …)` produces no entry in `graph.ir.types`, so it has no
|
|
85
|
+
// method and no line range — which silently exempted the most common
|
|
86
|
+
// Express/Koa shape in the ecosystem (cognium-dev#315). A named
|
|
87
|
+
// `function handler(req, res)` was caught, because top-level functions do
|
|
88
|
+
// land in the synthetic `<module>` type; an inline arrow did not.
|
|
89
|
+
//
|
|
90
|
+
// No line range is needed. The calls extractor already names the enclosing
|
|
91
|
+
// function for this shape: an arrow or function-expression passed as an
|
|
92
|
+
// argument to a member-expression call is tagged `<property>_handler` by
|
|
93
|
+
// `findJSEnclosingFunction` (core/extractors/calls.ts), so a blocking call
|
|
94
|
+
// inside `app.get(…)` carries `in_method === 'get_handler'`.
|
|
95
|
+
const inlineHandlers = this.collectInlineHandlerNames(graph.ir.calls);
|
|
96
|
+
if (handlerRanges.length === 0 && inlineHandlers.size === 0) {
|
|
68
97
|
return { blockingInHandlers: [] };
|
|
98
|
+
}
|
|
69
99
|
const blockingInHandlers = [];
|
|
70
100
|
for (const call of graph.ir.calls) {
|
|
71
101
|
const name = call.method_name;
|
|
@@ -74,7 +104,11 @@ export class BlockingMainThreadPass {
|
|
|
74
104
|
if (!isCrypto && !isSyncSuffix)
|
|
75
105
|
continue;
|
|
76
106
|
const line = call.location.line;
|
|
77
|
-
const
|
|
107
|
+
const enclosing = call.in_method ?? null;
|
|
108
|
+
const range = handlerRanges.find(r => line >= r.start && line <= r.end)
|
|
109
|
+
?? (enclosing && inlineHandlers.has(enclosing)
|
|
110
|
+
? { start: line, end: line, name: enclosing }
|
|
111
|
+
: undefined);
|
|
78
112
|
if (!range)
|
|
79
113
|
continue;
|
|
80
114
|
const reason = isCrypto ? 'crypto' : 'sync-suffix';
|
|
@@ -97,6 +131,45 @@ export class BlockingMainThreadPass {
|
|
|
97
131
|
}
|
|
98
132
|
return { blockingInHandlers };
|
|
99
133
|
}
|
|
134
|
+
/**
|
|
135
|
+
* Synthetic `<verb>_handler` names that really do belong to an HTTP route.
|
|
136
|
+
*
|
|
137
|
+
* The extractor tags *any* function argument of *any* member-expression call
|
|
138
|
+
* this way, so `items.map(x => …)` becomes `map_handler` and
|
|
139
|
+
* `cache.get(k, () => …)` becomes `get_handler`. Accepting every
|
|
140
|
+
* `*_handler` would pull ordinary callbacks into a pass that is explicitly
|
|
141
|
+
* about the HTTP request path, so two conditions are required: the method is
|
|
142
|
+
* a router verb, and the callback's own parameters look like a request
|
|
143
|
+
* handler's — the same `HANDLER_PARAM_NAMES` test used for methods.
|
|
144
|
+
*
|
|
145
|
+
* Known limitation: `in_method` carries no receiver, so two callbacks that
|
|
146
|
+
* share a verb in one file are indistinguishable. A file containing both
|
|
147
|
+
* `app.get('/x', (req, res) => …)` and `cache.get(k, () => …)` will treat a
|
|
148
|
+
* blocking call in the second as in-handler. Both conditions still have to
|
|
149
|
+
* hold for the verb to register at all, which keeps that to files already
|
|
150
|
+
* mounting a real route of the same verb.
|
|
151
|
+
*/
|
|
152
|
+
collectInlineHandlerNames(calls) {
|
|
153
|
+
const names = new Set();
|
|
154
|
+
for (const call of calls) {
|
|
155
|
+
if (!ROUTER_METHODS.has(call.method_name))
|
|
156
|
+
continue;
|
|
157
|
+
for (const arg of call.arguments) {
|
|
158
|
+
const match = FN_ARG_PARAMS_RE.exec((arg.expression ?? '').trim());
|
|
159
|
+
if (!match)
|
|
160
|
+
continue;
|
|
161
|
+
const params = (match[1] ?? match[2] ?? '')
|
|
162
|
+
.split(',')
|
|
163
|
+
.map(p => p.trim().toLowerCase())
|
|
164
|
+
.filter(p => p.length > 0);
|
|
165
|
+
if (params.some(p => HANDLER_PARAM_NAMES.has(p))) {
|
|
166
|
+
names.add(`${call.method_name}_handler`);
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return names;
|
|
172
|
+
}
|
|
100
173
|
isRequestHandler(method) {
|
|
101
174
|
// NestJS / Fastify HTTP decorators
|
|
102
175
|
if (method.annotations.some(a => HTTP_DECORATORS.has(a)))
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"blocking-main-thread-pass.js","sourceRoot":"","sources":["../../../src/analysis/passes/blocking-main-thread-pass.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAIH,4FAA4F;AAC5F,MAAM,eAAe,GAAG,IAAI,GAAG,CAAC;IAC9B,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM;IACjE,OAAO,EAAE,SAAS;CACnB,CAAC,CAAC;AAEH,6DAA6D;AAC7D,MAAM,mBAAmB,GAAG,IAAI,GAAG,CAAC;IAClC,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,KAAK,EAAE,GAAG,EAAE,OAAO;CACzD,CAAC,CAAC;AAEH,gEAAgE;AAChE,MAAM,oBAAoB,GAAG,IAAI,GAAG,CAAC;IACnC,QAAQ,EAAE,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO;CACnD,CAAC,CAAC;AAEH,4EAA4E;AAC5E,MAAM,uBAAuB,GAAG,IAAI,GAAG,CAAC;IACtC,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE,YAAY;IACpD,qBAAqB,EAAE,iBAAiB,EAAE,eAAe;CAC1D,CAAC,CAAC;AAEH,MAAM,cAAc,GAAG,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"blocking-main-thread-pass.js","sourceRoot":"","sources":["../../../src/analysis/passes/blocking-main-thread-pass.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"language-sources-pass.d.ts","sourceRoot":"","sources":["../../../src/analysis/passes/language-sources-pass.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,cAAc,EAAwB,WAAW,EAAO,MAAM,sBAAsB,CAAC;AAC3H,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAqB9E,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;IAoF/B,CAAC;AAwDF,MAAM,WAAW,qBAAqB;IACpC,iBAAiB,EAAE,WAAW,EAAE,CAAC;IACjC,eAAe,EAAE,SAAS,EAAE,CAAC;IAC7B;;;;OAIG;IACH,oBAAoB,EAAE,cAAc,EAAE,CAAC;IACvC;;;OAGG;IACH,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC;;;OAGG;IACH,eAAe,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC7B;;;OAGG;IACH,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACpC;AAMD,qBAAa,mBAAoB,YAAW,YAAY,CAAC,qBAAqB,CAAC;IAC7E,QAAQ,CAAC,IAAI,sBAAsB;IACnC,QAAQ,CAAC,QAAQ,EAAG,UAAU,CAAU;IAExC,GAAG,CAAC,GAAG,EAAE,WAAW,GAAG,qBAAqB;
|
|
1
|
+
{"version":3,"file":"language-sources-pass.d.ts","sourceRoot":"","sources":["../../../src/analysis/passes/language-sources-pass.ts"],"names":[],"mappings":"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"}
|
|
@@ -333,6 +333,8 @@ export class LanguageSourcesPass {
|
|
|
333
333
|
// Sprint 77a (#216 Pattern X): Jinja2 Environment(autoescape=...) +
|
|
334
334
|
// .render(...) sanitizer.
|
|
335
335
|
additionalSanitizers.push(...findPythonJinjaAutoescapeSanitizers(code));
|
|
336
|
+
// cognium-dev#293: user-defined CRLF-stripping helper credited at call sites.
|
|
337
|
+
additionalSanitizers.push(...findCrlfWrapperFunctionSanitizers(code, 'python'));
|
|
336
338
|
// Sprint 71 (#190): pattern-based misconfig findings for subscript/context
|
|
337
339
|
// assignment shapes (cors-wildcard-origin, xfo-csp-mismatch, tls-verify-
|
|
338
340
|
// disabled) that the language-agnostic detectors miss in Python.
|
|
@@ -494,6 +496,12 @@ export class LanguageSourcesPass {
|
|
|
494
496
|
ctx.addFinding(finding);
|
|
495
497
|
}
|
|
496
498
|
}
|
|
499
|
+
// -- C#: cognium-dev#286 C — Path.GetFullPath + !StartsWith(root) reject
|
|
500
|
+
// guard (the canonicalize-then-contain path-traversal defence). Java and
|
|
501
|
+
// JS/TS already credit their equivalents above; C# was the gap.
|
|
502
|
+
if (language === 'csharp') {
|
|
503
|
+
additionalSanitizers.push(...findCSharpFullPathStartsWithGuardSanitizers(code));
|
|
504
|
+
}
|
|
497
505
|
// -- Java: Sprint 73 (#216 Pattern A) — Jackson readValue / Gson
|
|
498
506
|
// fromJson recognized as ETE terminator (does not affect
|
|
499
507
|
// configured `deserialization` sinks).
|
|
@@ -509,6 +517,8 @@ export class LanguageSourcesPass {
|
|
|
509
517
|
// guard (the java.io.File OWASP-recommended containment defence).
|
|
510
518
|
additionalSanitizers.push(...findJavaCanonicalPathStartsWithGuardSanitizers(code));
|
|
511
519
|
additionalSanitizers.push(...findJavaInlineCrlfStripLogSanitizers(code));
|
|
520
|
+
// cognium-dev#293: the same strip, but living in a user-defined helper.
|
|
521
|
+
additionalSanitizers.push(...findCrlfWrapperFunctionSanitizers(code, 'java'));
|
|
512
522
|
// Sprint 77a (#216 Pattern X): argv-form exec sanitizer.
|
|
513
523
|
additionalSanitizers.push(...findJavaArgvFormExecSanitizers(code));
|
|
514
524
|
// Sprint 78 (#190): Java misconfig pattern findings —
|
|
@@ -4614,8 +4624,10 @@ function findJsPathResolveStartsWithGuardSanitizers(code) {
|
|
|
4614
4624
|
for (let l = c.line; l < Math.min(lines.length, c.line + 6); l++) {
|
|
4615
4625
|
if (!guardRe.test(lines[l]))
|
|
4616
4626
|
continue;
|
|
4617
|
-
|
|
4618
|
-
|
|
4627
|
+
// Terminator on the guard line (single-line if) or the next one (block
|
|
4628
|
+
// form) — but never the enclosing function's own terminator reached past
|
|
4629
|
+
// a guard block that already closed. See `guardRejects` (#333).
|
|
4630
|
+
if (guardRejects(lines, l, terminatorRe, 2)) {
|
|
4619
4631
|
guardLine = l + 1;
|
|
4620
4632
|
break;
|
|
4621
4633
|
}
|
|
@@ -4800,10 +4812,11 @@ function findJavaPathNormalizeStartsWithGuardSanitizers(code) {
|
|
|
4800
4812
|
for (let l = c.line; l < Math.min(lines.length, c.line + 6); l++) {
|
|
4801
4813
|
if (!guardRe.test(lines[l]))
|
|
4802
4814
|
continue;
|
|
4803
|
-
// The terminator may be on the same line (single-line if) or on
|
|
4804
|
-
//
|
|
4805
|
-
|
|
4806
|
-
|
|
4815
|
+
// The terminator may be on the same line (single-line if) or on the next
|
|
4816
|
+
// line (block-form if) — but never the enclosing function's own
|
|
4817
|
+
// terminator reached past a guard block that already closed. See
|
|
4818
|
+
// `guardRejects` (#333).
|
|
4819
|
+
if (guardRejects(lines, l, terminatorRe, 2)) {
|
|
4807
4820
|
guardLine = l + 1;
|
|
4808
4821
|
break;
|
|
4809
4822
|
}
|
|
@@ -4881,18 +4894,7 @@ function findJavaCanonicalPathStartsWithGuardSanitizers(code) {
|
|
|
4881
4894
|
const m = guardRe.exec(lines[i]);
|
|
4882
4895
|
if (!m)
|
|
4883
4896
|
continue;
|
|
4884
|
-
|
|
4885
|
-
if (!hasTerminator) {
|
|
4886
|
-
for (let j = i + 1; j < Math.min(lines.length, i + 6); j++) {
|
|
4887
|
-
if (terminatorRe.test(lines[j])) {
|
|
4888
|
-
hasTerminator = true;
|
|
4889
|
-
break;
|
|
4890
|
-
}
|
|
4891
|
-
if (lines[j].includes('}'))
|
|
4892
|
-
break;
|
|
4893
|
-
}
|
|
4894
|
-
}
|
|
4895
|
-
if (!hasTerminator)
|
|
4897
|
+
if (!guardRejects(lines, i, terminatorRe, 6))
|
|
4896
4898
|
continue;
|
|
4897
4899
|
const guarded = new Set([m[1]]);
|
|
4898
4900
|
const mentions = (t) => {
|
|
@@ -4927,6 +4929,135 @@ function findJavaCanonicalPathStartsWithGuardSanitizers(code) {
|
|
|
4927
4929
|
}
|
|
4928
4930
|
return sanitizers;
|
|
4929
4931
|
}
|
|
4932
|
+
/**
|
|
4933
|
+
* Does the `if (...)` guard on `lines[i]` actually **reject**?
|
|
4934
|
+
*
|
|
4935
|
+
* Path-containment guards (`!full.startsWith(root)`) only sanitise when the
|
|
4936
|
+
* failing branch leaves — returns or throws. A guard that merely logs lets
|
|
4937
|
+
* control fall through to the sink, so the path was never rejected and the
|
|
4938
|
+
* value is still attacker-controlled.
|
|
4939
|
+
*
|
|
4940
|
+
* cognium-dev#333: three guards got this wrong the same way. They scanned
|
|
4941
|
+
* forward from the guard line for a `return`/`throw` without noticing the
|
|
4942
|
+
* guard's own block had already closed, so
|
|
4943
|
+
*
|
|
4944
|
+
* if (!full.startsWith(root)) { log("odd"); }
|
|
4945
|
+
* return read(full); // <- this return was credited
|
|
4946
|
+
*
|
|
4947
|
+
* found the *enclosing method's* terminator and suppressed a genuine
|
|
4948
|
+
* path_traversal. Rust's equivalent (`findRustCanonicalizeGuardSanitizers`) was
|
|
4949
|
+
* the only one written correctly — it requires the guard line to open a block,
|
|
4950
|
+
* brace-matches to the close, and demands the terminator inside. This gives the
|
|
4951
|
+
* other three the same discipline cheaply.
|
|
4952
|
+
*
|
|
4953
|
+
* `window` bounds the forward scan and preserves each caller's original reach:
|
|
4954
|
+
* the canonical-path guard scanned six lines, the normalize and path.resolve
|
|
4955
|
+
* guards only looked at the next one.
|
|
4956
|
+
*/
|
|
4957
|
+
function guardRejects(lines, i, terminatorRe, window) {
|
|
4958
|
+
// Single-line rejecting form: `if (!full.startsWith(root)) return null;`
|
|
4959
|
+
if (terminatorRe.test(lines[i]))
|
|
4960
|
+
return true;
|
|
4961
|
+
// Block opens and closes on the guard line with no terminator inside it, so
|
|
4962
|
+
// control falls through: `if (!full.startsWith(root)) { log("odd"); }`.
|
|
4963
|
+
const opens = (lines[i].match(/\{/g) ?? []).length;
|
|
4964
|
+
const closes = (lines[i].match(/\}/g) ?? []).length;
|
|
4965
|
+
if (opens > 0 && closes >= opens)
|
|
4966
|
+
return false;
|
|
4967
|
+
for (let j = i + 1; j < Math.min(lines.length, i + window); j++) {
|
|
4968
|
+
if (terminatorRe.test(lines[j]))
|
|
4969
|
+
return true;
|
|
4970
|
+
// Reached the end of the guard body without terminating.
|
|
4971
|
+
if (lines[j].includes('}'))
|
|
4972
|
+
return false;
|
|
4973
|
+
}
|
|
4974
|
+
return false;
|
|
4975
|
+
}
|
|
4976
|
+
/**
|
|
4977
|
+
* C# `Path.GetFullPath(...)` + `!x.StartsWith(root)` reject guard — the
|
|
4978
|
+
* canonicalize-then-contain path-traversal defence (cognium-dev#286 C).
|
|
4979
|
+
*
|
|
4980
|
+
* Counterpart of `findJavaCanonicalPathStartsWithGuardSanitizers` and
|
|
4981
|
+
* `findJsPathResolveStartsWithGuardSanitizers`; C# was the only one of the
|
|
4982
|
+
* three missing it, so the OWASP-canonical shape reported a false positive:
|
|
4983
|
+
*
|
|
4984
|
+
* var root = Path.GetFullPath("/srv/data") + Path.DirectorySeparatorChar;
|
|
4985
|
+
* var full = Path.GetFullPath(Path.Combine(root, input));
|
|
4986
|
+
* if (!full.StartsWith(root)) return;
|
|
4987
|
+
* File.ReadAllText(full); // was path_traversal
|
|
4988
|
+
*
|
|
4989
|
+
* `Path.GetFileName` is already credited, but it cannot express this case —
|
|
4990
|
+
* it flattens the path, so a legitimate subdirectory cannot be preserved.
|
|
4991
|
+
*
|
|
4992
|
+
* One requirement the Java version gets for free. There, canonicalisation and
|
|
4993
|
+
* the containment test are the same expression
|
|
4994
|
+
* (`!f.getCanonicalPath().startsWith(base)`), so matching the guard proves the
|
|
4995
|
+
* value is canonical. In C# they are separate statements, and the guard alone
|
|
4996
|
+
* is just `full.StartsWith(root)` — indistinguishable from an ordinary string
|
|
4997
|
+
* prefix test. Crediting on the guard alone would silence path_traversal for
|
|
4998
|
+
* any `if (!name.StartsWith("x")) return;`. So the guarded variable must also
|
|
4999
|
+
* be traceable to an assignment whose right-hand side calls
|
|
5000
|
+
* `Path.GetFullPath(`.
|
|
5001
|
+
*
|
|
5002
|
+
* Scope note: only the **reject** polarity (`if (!x.StartsWith(root))` plus a
|
|
5003
|
+
* `return`/`throw`) is credited, matching the Java precedent. The enclosing
|
|
5004
|
+
* form `if (x.StartsWith(root)) { … }` still reports, and a test pins that so
|
|
5005
|
+
* the gap stays visible rather than silently assumed.
|
|
5006
|
+
*/
|
|
5007
|
+
function findCSharpFullPathStartsWithGuardSanitizers(code) {
|
|
5008
|
+
const sanitizers = [];
|
|
5009
|
+
const lines = code.split('\n');
|
|
5010
|
+
// Variables assigned from an expression that calls Path.GetFullPath(...).
|
|
5011
|
+
const assignRe = /^\s*(?:(?:var|string)\s+)?([A-Za-z_]\w*)\s*=(?!=)\s*(.*)$/;
|
|
5012
|
+
const fullPathRe = /\bPath\s*\.\s*GetFullPath\s*\(/;
|
|
5013
|
+
const canonicalVars = new Set();
|
|
5014
|
+
for (const line of lines) {
|
|
5015
|
+
const a = assignRe.exec(line);
|
|
5016
|
+
if (a && fullPathRe.test(a[2]))
|
|
5017
|
+
canonicalVars.add(a[1]);
|
|
5018
|
+
}
|
|
5019
|
+
if (canonicalVars.size === 0)
|
|
5020
|
+
return sanitizers;
|
|
5021
|
+
const guardRe = /\bif\s*\(\s*!\s*([A-Za-z_]\w*)\s*\.\s*StartsWith\s*\(/;
|
|
5022
|
+
const terminatorRe = /\b(throw|return)\b/;
|
|
5023
|
+
for (let i = 0; i < lines.length; i++) {
|
|
5024
|
+
const m = guardRe.exec(lines[i]);
|
|
5025
|
+
if (!m || !canonicalVars.has(m[1]))
|
|
5026
|
+
continue;
|
|
5027
|
+
if (!guardRejects(lines, i, terminatorRe, 6))
|
|
5028
|
+
continue;
|
|
5029
|
+
// Credit the guarded variable across the whole scan, as the Java version
|
|
5030
|
+
// does: a containment check proves the path is inside the root regardless
|
|
5031
|
+
// of where it was built, so a sink preceding the guard is safe too.
|
|
5032
|
+
// Derivations are still tracked in source order.
|
|
5033
|
+
const guarded = new Set([m[1]]);
|
|
5034
|
+
const mentions = (t) => {
|
|
5035
|
+
for (const n of guarded)
|
|
5036
|
+
if (new RegExp(`\\b${n}\\b`).test(t))
|
|
5037
|
+
return true;
|
|
5038
|
+
return false;
|
|
5039
|
+
};
|
|
5040
|
+
for (let l = 0; l < lines.length; l++) {
|
|
5041
|
+
const lt = lines[l];
|
|
5042
|
+
const a = assignRe.exec(lt);
|
|
5043
|
+
if (a) {
|
|
5044
|
+
if (mentions(a[2]))
|
|
5045
|
+
guarded.add(a[1]);
|
|
5046
|
+
else if (a[1] !== m[1])
|
|
5047
|
+
guarded.delete(a[1]);
|
|
5048
|
+
}
|
|
5049
|
+
if (mentions(lt)) {
|
|
5050
|
+
sanitizers.push({
|
|
5051
|
+
type: 'csharp_fullpath_startswith_guard',
|
|
5052
|
+
method: 'GetFullPath',
|
|
5053
|
+
line: l + 1,
|
|
5054
|
+
sanitizes: ['path_traversal', 'external_taint_escape'],
|
|
5055
|
+
});
|
|
5056
|
+
}
|
|
5057
|
+
}
|
|
5058
|
+
}
|
|
5059
|
+
return sanitizers;
|
|
5060
|
+
}
|
|
4930
5061
|
function findJavaPathGetFileNameSanitizers(code) {
|
|
4931
5062
|
const sanitizers = [];
|
|
4932
5063
|
const lines = code.split('\n');
|
|
@@ -5002,6 +5133,76 @@ function findJavaPathGetFileNameSanitizers(code) {
|
|
|
5002
5133
|
* Emits a `log_injection` + `external_taint_escape` sanitizer at
|
|
5003
5134
|
* that line.
|
|
5004
5135
|
*/
|
|
5136
|
+
/**
|
|
5137
|
+
* Java / Python user-defined CRLF-stripping wrapper — credited at its call
|
|
5138
|
+
* sites (cognium-dev#293).
|
|
5139
|
+
*
|
|
5140
|
+
* `findJavaInlineCrlfStripLogSanitizers` only credits a strip applied *on the
|
|
5141
|
+
* log line itself*. When the strip lives in a small helper, the sanitizer was
|
|
5142
|
+
* detected inside the helper body and never reached the call site:
|
|
5143
|
+
*
|
|
5144
|
+
* private static String redact(String s) {
|
|
5145
|
+
* return s.replace("\r", "_").replace("\n", "_"); // sanitizer seen HERE
|
|
5146
|
+
* }
|
|
5147
|
+
* logger.info("user=" + redact(req.getParameter("user"))); // still log_injection
|
|
5148
|
+
*
|
|
5149
|
+
* JS/TS has had this for a while (`findJsWrapperFunctionSanitizers`); Java and
|
|
5150
|
+
* Python did not, which is two of the seven fixtures on #293.
|
|
5151
|
+
*
|
|
5152
|
+
* Scoped to CRLF / `log_injection` deliberately. The JS version also infers an
|
|
5153
|
+
* `xss` wrapper from an HTML char class, but #293 supplies no Java or Python
|
|
5154
|
+
* xss-wrapper fixture, and a sanitizer credit with no fixture behind it is how
|
|
5155
|
+
* a false negative gets shipped.
|
|
5156
|
+
*
|
|
5157
|
+
* Accepts three strip forms, matching what Java and Python actually write:
|
|
5158
|
+
* `.replaceAll("[\r\n]", …)`, `.replace('\n', …)`, `.replace("\r", …)`
|
|
5159
|
+
* and Python's `re.sub(r"[\r\n\t]", …)`.
|
|
5160
|
+
* The double-quoted `replace("\r", …)` form is notably absent from the inline
|
|
5161
|
+
* detector above, which is part of why the fixture on #293 reported.
|
|
5162
|
+
*
|
|
5163
|
+
* A helper qualifies only if it is a *value-returning* declaration whose body
|
|
5164
|
+
* strips CRLF. In Java that means an explicit `String`/`CharSequence` return
|
|
5165
|
+
* type, which keeps `void` log-and-forget methods out.
|
|
5166
|
+
*/
|
|
5167
|
+
function findCrlfWrapperFunctionSanitizers(code, language) {
|
|
5168
|
+
const sanitizers = [];
|
|
5169
|
+
const lines = code.split('\n');
|
|
5170
|
+
const declRe = language === 'java'
|
|
5171
|
+
? /^\s*(?:public|private|protected)?\s*(?:static\s+)?(?:final\s+)?(?:String|CharSequence)\s+([A-Za-z_]\w*)\s*\(/
|
|
5172
|
+
: /^\s*def\s+([A-Za-z_]\w*)\s*\(/;
|
|
5173
|
+
// `\\[rnt]` matches the two-character escape as it appears in source text.
|
|
5174
|
+
const stripRe = language === 'java'
|
|
5175
|
+
? /\.\s*replace(?:All)?\s*\(\s*(?:"[^"]*\\[rnt]|'\\[rnt]')/
|
|
5176
|
+
: /(?:re\s*\.\s*sub\s*\(\s*r?["'][^"']*\\[rnt]|\.\s*replace\s*\(\s*["']\\[rnt])/;
|
|
5177
|
+
const bodyWindow = 10;
|
|
5178
|
+
const wrappers = new Set();
|
|
5179
|
+
for (let i = 0; i < lines.length; i++) {
|
|
5180
|
+
const m = declRe.exec(lines[i]);
|
|
5181
|
+
if (!m)
|
|
5182
|
+
continue;
|
|
5183
|
+
const body = lines.slice(i, Math.min(lines.length, i + bodyWindow)).join('\n');
|
|
5184
|
+
if (stripRe.test(body))
|
|
5185
|
+
wrappers.add(m[1]);
|
|
5186
|
+
}
|
|
5187
|
+
if (wrappers.size === 0)
|
|
5188
|
+
return sanitizers;
|
|
5189
|
+
for (let i = 0; i < lines.length; i++) {
|
|
5190
|
+
for (const name of wrappers) {
|
|
5191
|
+
// Never credit the declaration line itself.
|
|
5192
|
+
if (declRe.test(lines[i]) && new RegExp(`\\b${name}\\s*\\(`).test(lines[i]))
|
|
5193
|
+
continue;
|
|
5194
|
+
if (!new RegExp(`\\b${name}\\s*\\(`).test(lines[i]))
|
|
5195
|
+
continue;
|
|
5196
|
+
sanitizers.push({
|
|
5197
|
+
type: language === 'java' ? 'java_wrapper_crlf_strip' : 'python_wrapper_crlf_strip',
|
|
5198
|
+
method: name,
|
|
5199
|
+
line: i + 1,
|
|
5200
|
+
sanitizes: ['log_injection', 'external_taint_escape'],
|
|
5201
|
+
});
|
|
5202
|
+
}
|
|
5203
|
+
}
|
|
5204
|
+
return sanitizers;
|
|
5205
|
+
}
|
|
5005
5206
|
function findJavaInlineCrlfStripLogSanitizers(code) {
|
|
5006
5207
|
const sanitizers = [];
|
|
5007
5208
|
const lines = code.split('\n');
|