fix: encode path parameter values before calling API tools by Dcastroro · Pull Request #356 · tadata-org/fastapi_mcp · GitHub
Skip to content

fix: encode path parameter values before calling API tools - #356

Open
Dcastroro wants to merge 2 commits into
tadata-org:mainfrom
Dcastroro:fix/encode-tool-path-parameters
Open

Dcastroro wants to merge 2 commits into
tadata-org:mainfrom
Dcastroro:fix/encode-tool-path-parameters

Conversation

@Dcastroro

@Dcastroro Dcastroro commented Oct 16, 2026 •

Copy link
Copy Markdown

Describe your changes

Path parameters were substituted into operation URLs as raw strings. Values such as x?q=injected, a#b and ../admin could be truncated or change the request's route; {version} inside a value could also be overwritten while substituting another parameter.

Percent-encode each value before inserting it into the path. Escape the exact . and .. values too, since quote(..., safe="") leaves dots unescaped and HTTPX otherwise normalizes those segments. There are no API, dependency, query/header/body handling or permission changes.

The follow-up commit adds three compatibility cases for /nested/{name:path} after the automated review raised a concern about escaped slashes. They verify both %2F in the raw request path and the original nested name received by FastAPI. The default ASGI transport decodes the path before routing, so these cases work without changing the runtime fix. Reverse proxies and custom HTTP clients may impose their own encoded-slash policies.

Issue ticket number and link

Fixes #354.

Testing

  • 17 path-parameter cases pass, covering spaces, Unicode, query/fragment delimiters, percent escapes, dot segments, nested paths, another placeholder's name, explicit query parameters and argument immutability.
  • The original 14 regression/control cases demonstrated 8 failures and 6 passes on unchanged main before the runtime correction.
  • Ordinary single-segment traversal cases still return 404 without invoking the excluded /admin endpoint. The three added catch-all cases preserve nested names, including literal %2F text.
  • Full Linux / Python 3.12 suite with the existing lockfile: 134 passed, 83.13% coverage (required: 80%), including existing real HTTP and SSE tests with local servers.
  • MyPy: no issues in 45 source files. Ruff, formatting and applicable pre-commit hooks pass.

The existing Windows real-transport fixtures request unsupported fork, causing 40 baseline errors; those were reproduced on unchanged main. Windows was not rerun for this test-only follow-up. Existing Linux test-harness warnings remain.

Tests use synthetic data and local/in-memory transports, without credentials or paid API calls. URL escaping and the existing traversal controls remain intact.

Checklist before requesting a review

  • Added relevant tests
  • Run Ruff and mypy
  • Complete test suite passes on the project's Linux platform

Prepared with AI assistance.

Upstream CI awaits maintainer workflow approval; no upstream jobs have executed. The complete Linux suite and quality checks above were run locally.

Copilot AI balanced review requested due to automatic review settings October 16, 2026 10:11

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Encoding / with safe="" silently breaks FastAPI {param:path} converter endpoints, a behavioral tradeoff that should be consciously confirmed before approval.

1 open finding
What changed in this PR

This PR fixes a security/correctness bug in FastApiMCP._execute_api_tool, where path parameter values were substituted into the operation URL as raw strings. Because tool arguments originate from an LLM, reserved characters like /, ?, #, and .. could truncate the path, inject query/fragment data, or normalize to a different route — including routes deliberately excluded via include_operations/exclude_operations (issue #354). The fix percent-encodes each path value before substitution, additionally escaping the exact ./.. dot-segments that quote() leaves untouched.

Changes:

  • Percent-encode each path parameter value with quote(..., safe="") before inserting it into the path template, and escape literal ./.. values to %2E/%2E%2E to prevent HTTP-client dot-segment normalization.
  • Add a dedicated test module covering literal-data round-trips, excluded-route traversal attempts, and placeholder-collision protection.
File Description
fastapi_mcp/​server.py Encodes path parameter values (and escapes ./..) before substituting them into the request path.
tests/​test_mcp_path_parameters.py New offline ASGI tests verifying encoding behavior, traversal protection, and argument immutability.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread fastapi_mcp/server.py
Comment on lines +516 to +520

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I checked this through the default ASGI transport and added test_path_converter_preserves_nested_names with three cases. The escaped slash does not cause a 404 there: ASGI provides a percent-decoded scope['path'] for FastAPI to match, while scope['raw_path'] still contains %2F.

The test asserts both the encoded raw URL and the exact name received by /nested/{name:path}, including docs/readme.txt, a nested name with spaces/Unicode/query-fragment characters, and a literal %2F inside a nested name. The explicit query parameter and original arguments remain unchanged.

All 17 path-parameter cases pass, including the existing ordinary-segment traversal/excluded-route controls, and the complete Linux suite passes with 134 tests. The runtime code and escaping remain unchanged. A custom HTTP client or reverse proxy can impose its own encoded-slash policy; these tests establish the library's default in-process ASGI behavior.

Reference: ASGI HTTP connection scope.

Test commit: 573eeb5.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] Path parameter values are not URL-encoded, so "/", "?" and ".." change which endpoint is called

2 participants