Screen templated resource parameters for path traversal by default (#4482)

* Add ResourceSecurity screening for templated resources (defaults on)

* Add tests for resource path-security screening

* Document resource path-security; fix ty in tests

* Carry child template security policy through provider mount

Preserve a mounted template's explicit ResourceSecurity (per-param
exemptions or a deliberate opt-out) through FastMCPProviderResourceTemplate.wrap
so the parent read chokepoint honours it instead of the parent default.

* Defer mcp SDK import so fastmcp.resources loads without the [mcp] extra

* Make resource path-security docs examples self-contained and runnable

* Match exempt_params under both hyphen and underscore spellings

Template placeholders like {git-ref} extract as git_ref, so an exemption
written with the natural URI-template spelling never matched.

* Docs: describe net-depth traversal rule accurately; make example runnable

The screening only rejects .. segments that escape the starting depth
(foo/../bar passes) — saying any standalone .. is rejected overstated
the guarantee. Also define DOCS_ROOT so the example runs.
This commit is contained in:
Jeremiah Lowin 2026-07-17 17:42:48 -04:00 committed by GitHub
commit d779414f8a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 824 additions and 11 deletions

View file

@ -762,14 +762,15 @@ class TestResourceTemplateRequestBuilding:
mcp.add_provider(provider)
async with Client(mcp) as mcp_client:
await mcp_client.read_resource(
"resource://get_user/..%2F..%2Fadmin%2Fsecret"
)
# Reserved characters (encoded slash + space) must be
# re-encoded when building the outbound URL. A traversal
# payload (`..%2F...`) would be rejected by the default
# resource-security screening, so use a benign value that
# still exercises reserved-character encoding.
await mcp_client.read_resource("resource://get_user/a%2Fb%20c")
assert seen_urls == [
httpx.URL(
"https://api.example.com/api/v1/users/%2E%2E%2F%2E%2E%2Fadmin%2Fsecret"
)
httpx.URL("https://api.example.com/api/v1/users/a%2Fb%20c")
]
async def test_resource_template_ignores_unmatched_query_string(