Repository navigation
fix: match request.uri on the forwarded path (absolute-form ACL bypass) - #614
Conversation
The RequestMatcher `request.uri` property returned the raw `request.uri()`, while the request is forwarded to the backend as the normalized path from `getUri()` (which strips a `scheme://authority` prefix). For an absolute-form request target -- which is valid HTTP/1.1 -- the two disagree: the matcher sees `http://host/path` while the backend receives just `/path`. Because `RegexpRequestMatcher` uses full-match semantics, a path-anchored route or ACL such as `request.uri ~ "/admin/.*"` does not match the absolute form and is skipped; the request then falls through to a proxy route and is forwarded to the backend as `/admin/...`, bypassing the ACL. Match on `getUri()` so the matcher and the backend evaluate the same path. This is a no-op for normal origin-form requests (`getUri()` == `request.uri()`) and only changes the absolute-form case. Signed-off-by: winklemad <[email protected]>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthrough
Poem
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
carapace-server/src/main/java/org/carapaceproxy/core/ProxyRequest.java (1)
189-198: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low valueReuse the
fullPathlocal variable instead of callingrequest.fullPath()again.Line 198 calls
request.fullPath()a second time (already computed at line 189 intofullPath). This is now on the request-matching hot path sincegetProperty(PROPERTY_URI)callsgetUri(). Reusing the local variable avoids a redundant call and keeps the logic DRY.♻️ Proposed refactor
} else { - int pos = uri.indexOf(request.fullPath()); + int pos = uri.indexOf(fullPath); if (pos > 0) {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@carapace-server/src/main/java/org/carapaceproxy/core/ProxyRequest.java` around lines 189 - 198, Update the URI construction logic in ProxyRequest.getUri to reuse the existing fullPath local variable instead of calling request.fullPath() again in the non-blank branch. Preserve the current path-matching behavior while eliminating the redundant request.fullPath() invocation.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@carapace-server/src/main/java/org/carapaceproxy/core/ProxyRequest.java`:
- Around line 189-198: Update the URI construction logic in ProxyRequest.getUri
to reuse the existing fullPath local variable instead of calling
request.fullPath() again in the non-blank branch. Preserve the current
path-matching behavior while eliminating the redundant request.fullPath()
invocation.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 97bdd7e8-92b3-422f-8790-fd7a149e7f21
📒 Files selected for processing (2)
carapace-server/src/main/java/org/carapaceproxy/core/ProxyRequest.javacarapace-server/src/test/java/org/carapaceproxy/server/mapper/requestmatcher/RequestMatcherTest.java
…) call Signed-off-by: winklemad <[email protected]>
NiccoMlt
left a comment
There was a problem hiding this comment.
Looks good to me, thank you very much for the contribution and for the thorough write-up!
No linked issue — found by reviewing the request-matching vs. forwarding paths.
Summary (security)
A route/ACL written against
request.urican be bypassed by sending the request in absolute-form, because the matcher and the backend evaluate two different views of the request path.request.urireturns the rawrequest.uri()— for an absolute-form request target this ishttp://host/path(ProxyRequest.getProperty,PROPERTY_URI).getUri(), which strips thescheme://authorityprefix — i.e. just/path(ProxyRequest.getUri(), used atProxyRequestsManager.forward()).RegexpRequestMatcheruses full-match semantics (Pattern.matcher(...).matches()).So a path-anchored deny rule does not match the absolute form and is skipped:
http://victim.example/admin/secret; the regex/admin/.*under.matches()must match the whole string (which starts withhttp), so route 0 does not match → the deny is skipped.all) → PROXY.getUri()strips the scheme prefix, so the backend receivesGET /admin/secret— the resource the ACL was meant to block.Impact: CWE-436 (interpretation conflict between proxy and backend) / CWE-863 (incorrect authorization). Any deployment that uses a path-anchored
request.urirule as an access-control boundary is affected.Fix
Make the
request.urimatcher property returngetUri()— the same normalized path that is forwarded — so the matcher and the backend can never disagree. This is a no-op for normal origin-form requests (theregetUri()equalsrequest.uri()); it only changes the absolute-form case.Testing
Added
RequestMatcherTest.testAbsoluteFormRequestTargetMatchesPathRule, which asserts that an absolute-form request target still matches a path-anchored rule (request.uri ~ "/admin/.*"). It fails before the change and passes after. The existingRequestMatcherTeststill passes (its request mock now also stubsfullPath(), which a real reactor-netty request always provides). Verified on a full Java 21 Maven build; no new checkstyle violations.