Repository navigation
check_resource_allowed treats /api/../admin as under /api #3464
Description
Activity
- addedv2Affects the v2 line (2.x on main)Affects the v2 line (2.x on main)v1Affects the v1.x maintenance lineAffects the v1.x maintenance line
on Sep 6, 2026 I'd like to work on this. I'll first verify the current
check_resource_allowedbehavior and existing path-boundary tests, then add regression coverage for dot-segments and percent-encoded traversal while preserving the existing prefix semantics. I'll also check how URL paths are parsed/decoded elsewhere in the SDK before settling on the normalization approach.I'd like to work on this security fix.
Root cause: check_resource_allowed does a naive prefix check on the URI path. A request to /api/../admin passes the /api prefix check because the path is not normalized before comparison.
Approach: Normalize the request path with posixpath.normpath (or equivalent) before the prefix check, resolving .. and . segments. This ensures /api/../admin correctly resolves to /admin and gets rejected.
I will include test cases for various traversal patterns (.., encoded variants).
Confirmed the reproduction locally on
main:check_resource_allowed("https://example.com/api/../admin", "https://example.com/api")evaluates toTruebecause trailing-slash padding produces/api/../admin/which naively.startswith("/api/").- The same applies to
/mcp/..and percent-encoded variants (%2e%2e/%2E%2E).
To address this cleanly without breaking existing prefix semantics:
- Decode percent-encoded characters via
urllib.parse.unquote()and normalize withposixpath.normpath()(ensuring a leading/). - Re-apply trailing slash normalization (
/fooand/foo/equivalence) so path-boundary protection against prefix collisions (like/api123/vs/api/) is preserved. - Apply this normalization to both
requested_pathandconfigured_pathbefore checkingrequested_path.startswith(configured_path).
I have this implemented and verified locally with comprehensive regression tests covering out-of-boundary traversal (
..,%2e%2e,../../admin,/mcp/..), intra-boundary traversals that legitimately stay under the configured root (/api/v1/../v2,/api/./v1), and all 16 existing auth tests passing cleanly.Happy to open the PR if maintainers would like to assign this or open it for external contributions!
Written with AI assistance; reproduced and tested locally.
Kludex commented
on Oct 10, 2026 MemberMore actionsBoth reports reproduce the same
check_resource_allowedpath-prefix bypass using raw and percent-encoded dot segments. This is tracked in #3303, so I’m closing this as a duplicate. AI-assisted triage; I reviewed both reports.
What happened
check_resource_allowedpads trailing slashes and then doesrequested_path.startswith(configured_path). It does not collapse./...On
main@08a3bc8these returnTrue:https://example.com/api/../adminvs configuredhttps://example.com/apihttps://example.com/mcp/..vs configuredhttps://example.com/mcphttps://example.com/api/%2e%2e/adminvs configuredhttps://example.com/apitests/shared/test_auth_utils.pyalready rejects/api123vs/api(path-boundary). Dot-segments are not covered.What I expected
A requested path that walks out of the configured prefix should not match.
/api/foo/../v1vs/apican still match after normalisation, because it stays under/api.How to reproduce
I can send a PR that percent-decodes once, runs
posixpath.normpath, then keeps the existing trailing-slash prefix rule. Happy to do that if you want it.Written with AI assistance. I read
auth_utils.pynext to the path-boundary tests and reproduced it locally.