pull request #126, “feat: harden SSRF on external APIs”
get_dataservice_openapi_spec makes a GET request on the URL provided by machine_documentation_url from the catalog on the MCP host. That field is set by any registered data.gouv.fr producer, so a malicious dataservice could point at loopback, RFC1918, or cloud metadata ( 169.254.169.254 ).
Checking the URL string before requests.get is not enough: DNS can rebind between check and connect, and a 302 can still land on an internal target.
Example (what this blocked)
An attacker creates a dataservice with machine_documentation_url= (or , or ).
Anyone using the MCP later calls get_dataservice_openapi_spec on that id.
Before this change, mcp.data.gouv.fr issued that GET (not the attacker's laptop). A live internal port vs a closed one produced different error text, which is enough to scan the server's network. A 302 from a "normal" public URL could also bounce to metadata. After this change, the MCP refuses to connect to those addresses (and re-checks the IP after each redirect).
This follows the same approach as opendatateam/udata#3877 ( udata.ssrf / ssrf_session ):
destination IP validated at connect time (same resolution as the socket), including IPv6-mapped / NAT64 forms
each redirect hop re-checked
environment/explicit proxies refused
same URLS_ALLOW_LOCAL / URLS_ALLOW_PRIVATE grouping (hosted MCP: both off)
uv run pytest tests/test_ssrf.py (loopback, metadata, RFC1918, localhost, IPv6-mapped, redirect to metadata, HTTP_PROXY)
There was an error while loading. Please reload this page .
There was an error while loading. Please reload this page .
The full story
This article is one source in a clustered incident — the cluster page carries the summary, timeline and every other outlet covering it.
