Files
app/backend/tests/mcp/test_mcp_tools.py
T
stroblmeandClaude Opus 5.5 17c2f4ff70 Quote every path parameter the MCP tools put into a URL
httpx resolves `..` before sending, so save_flow("../ai/config") reached
PUT /ai/config. Each segment is now percent-encoded, dots included.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013GAeg4Jo6xjZzjwy9QDWMQ
2026-10-06 12:16:48 +02:00

42 lines
1.3 KiB
Python

"""What the MCP tools send, whatever an agent hands them."""
import asyncio
import httpx
from fluksio.mcp import server
def test_a_name_cannot_climb_out_of_its_route() -> None:
"""httpx resolves ``..``, so ``save_flow("../ai/config")`` was ``PUT /ai/config``.
A prompt-injected assistant acting for a superuser could repoint the
provider that way while the stored key stayed.
"""
seen: list[str] = []
def record(request: httpx.Request) -> httpx.Response:
seen.append(f"{request.method} {request.url.raw_path.decode()}")
return httpx.Response(200, json={})
async def calls() -> None:
previous = server._client
mock = httpx.AsyncClient(
transport=httpx.MockTransport(record), base_url="http://test"
)
server.set_client(mock)
try:
await server.save_flow("../ai/config", {})
await server.delete_flow("..")
await server.get_node_source("..", "../source")
finally:
server.set_client(previous)
await mock.aclose()
asyncio.run(calls())
assert seen == [
"PUT /api/v1/flows/%2E%2E%2Fai%2Fconfig",
"DELETE /api/v1/flows/%2E%2E",
"GET /api/v1/flows/%2E%2E/nodes/%2E%2E%2Fsource/source",
]