Unauthenticated full-read SSRF via the parse-from-URL endpoint (POST /v1/url)
Summary
MegaParse's API exposes POST /v1/url, which fetches a caller-supplied URL server-side (via httpx for PDFs, or a Playwright loader otherwise) and returns the parsed content. There is no authentication and no validation of the URL host or scheme. An unauthenticated client can therefore make the server fetch internal-only services and cloud instance-metadata endpoints, and the response body is returned in the JSON response (full-read SSRF). Confirmed against the real app: the endpoint fetched an internal listener and returned its secret body to the caller.
Details
libs/megaparse/src/megaparse/api/app.py, POST /v1/url (~lines 122 to 157):
async def upload_url(url: str, playwright_loader = Depends(get_playwright_loader)):
if url ends with .pdf:
response = await client.get(url) # ~line 131, raw httpx GET
else:
... playwright_loader.aload() # ~line 144, loads attacker URL
return {"result": <page text>} # ~lines 154-157, body returnedThe url query parameter is taken raw, with no scheme allowlist and no private/loopback/link-local/metadata filtering anywhere in the codebase. The route has no authentication dependency or middleware, and the app binds 0.0.0.0:8000 (~line 161).
Impact
An unauthenticated network client can coerce the MegaParse server into fetching internal-only services and cloud instance-metadata endpoints and read their responses (returned in the result field), enabling internal reconnaissance and theft of cloud-metadata credentials. This is unauthenticated full-read SSRF on the default configuration.
Remediation
Before fetching, parse the URL and reject loopback, private (RFC1918), link-local (169.254.0.0/16, fe80::/10), unique-local, and other reserved ranges plus cloud-metadata hostnames; enforce an http/https allowlist; disable or re-validate redirects and pin the validated IP (close DNS rebinding). Require authentication on the API.
Source: QuivrHQ/MegaParse