CVE-2026-54549: meta-ads-mcp SSRF in upload_ad_image Fetch

abstract network free stock image for MCPwatch editorial use
Topic image: abstract network free stock image for MCPwatch editorial use.
API network free stock image for MCPwatch editorial use
Free stock image for MCPwatch editorial use. Source: Openverse/flickr. Original: API. Creator: Erick V. License: by 2.0. License URL: https://creativecommons.org/licenses/by/2.0/.

CVE-2026-54549 affects the meta-ads-mcp Python MCP server. The upload_ad_image tool forwards an attacker-supplied image_url directly to an HTTP fetch helper without scheme, host, or IP validation, so a server exposed over the documented streamable-http transport can be coerced into issuing outbound requests to loopback, RFC 1918, and cloud-metadata endpoints. The CVE record is rated CVSS 3.1 base score 8.3 (High), and the fixed boundary is 1.0.115.

This is a vulnerability report, not an incident report. The primary record is a GitHub Security Advisory published through the GitHub CNA on 2026-07-17; the MITRE CVE record is still reserved as of this writing and the NVD entry is not yet published. MCPwatch did not reproduce the issue or assess any live meta-ads-mcp deployment.

What the primary records establish

The GitHub Security Advisory GHSA-45gf-fjxp-cjpq describes the vulnerable code path. In meta_ads_mcp/core/ads.py, the MCP tool upload_ad_image is registered with @mcp_server.tool() and accepts image_url: Optional[str] as a direct tool argument. That value is forwarded to try_multiple_download_methods(image_url) without sanitization, and three independent fetch helpers in meta_ads_mcp/core/utils.py open the URL with httpx.AsyncClient(follow_redirects=True).get(url). None of the fetch paths call urlparse, urlsplit, ipaddress, or any host/IP allow-list; a search for terms such as localhost, 169.254, private, allowlist, blocklist, or is_global across those files returns no matches.

The advisory assigns CVSS 3.1 base score 8.3, vector CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:L. The vector records a network-reachable, low-complexity, no-privileges, no-user-interaction path that crosses a scope boundary and produces limited confidentiality, integrity, and availability impact. The advisory text confirms a confirmed impact path: when meta-ads-mcp is deployed with the documented --transport streamable-http option, an unauthenticated remote attacker can supply an arbitrary URL — including http://127.0.0.1/, RFC 1918 addresses, or http://169.254.169.254/ — and the server will issue an outbound HTTP request to that target.

The pre-fetch authentication check is also part of the exposure. The advisory states that the Authorization middleware only verifies that a non-empty Bearer token is present; the actual Meta API credential check happens after the image download completes. A dummy Bearer token therefore satisfies the pre-fetch middleware, while the SSRF sink still fires. The same authorization pattern is also reported in the GitLab advisory mirror.

Why streamable-http and image-fetch change the consequence

Two design choices make the missing validation materially more dangerous than a private helper that fetches an internal URL. First, the MCP transport itself is the attack surface. streamable-http is a documented, officially supported deployment option for MCP servers, not a development-only stub, so the same code that talks to a local agent can be reached over the network in production. Second, image fetching is the kind of MCP capability that operators expect to fetch arbitrary URLs by design. The combination — a network-reachable tool whose normal behavior is to follow an HTTP redirect to an arbitrary address — is exactly the threat model that a server-side request forgery exploits. The CVE describes one project, but the class applies to every MCP tool that accepts a caller-supplied URL and forwards it without validation.

Exposure is distinct from confirmed misuse. A successful SSRF request may reach a benign internal listener, return an error, or simply time out. The disclosed impact path does not prove that a given deployment has been abused; it establishes that the affected deployments are reachable by unauthenticated attackers and can be coerced into issuing outbound requests they did not initiate. Operators should preserve relevant logs and trace evidence and make rotation or incident-response decisions based on their own evidence, not on the existence of the CVE.

Patch and version boundary

The GitHub Security Advisory lists < 1.0.115 as the affected range and 1.0.115 as the first patched release. MCPwatch verified the public meta-ads-mcp repository and the v1.0.115 release page; the matching source change is commit 7d99263 titled fix(security): block SSRF in upload_ad_image image_url fetch (GHSA-45gf-fjxp-cjpq) (1.0.115).

The fix introduces a validate_public_url() helper that allows only the http and https schemes and rejects hosts resolving to private, loopback, link-local, reserved, multicast, or unspecified addresses, including IPv4-mapped IPv6 unwrapping. Later PyPI releases on the 1.0.x line (1.0.116, 1.0.117, 1.0.118) are not flagged as additional security fixes in this advisory, but operators should use the maintained release line appropriate to their deployment rather than pinning indefinitely to the minimum fixed version.

After upgrading, verify the effective runtime rather than trusting a source checkout or image tag alone. Check every replica, worker, container, and cached deployment path; a mixed-version environment can leave the vulnerable fetch helper reachable even after one application instance is patched.

Defensive actions for operators

  1. Upgrade meta-ads-mcp to 1.0.115 or a later supported release and verify the deployed runtime rather than only the manifest version.
  2. Audit transport exposure. Confirm whether any deployment is reachable over streamable-http to a network other than localhost, including sidecar, shared host, container host, and cloud-metadata subnets.
  3. Treat image_url as untrusted everywhere. The CVE is one example of the class; the same hardening applies to any MCP tool that fetches caller-supplied URLs. Validate scheme, resolve the host, and reject private, loopback, link-local, reserved, multicast, and unspecified addresses before issuing the request.
  4. Bind metadata endpoints off the reachable network. Even with validation in place, deploy IMDSv2 (or the cloud-equivalent) and ensure that 169.254.169.254 is not on a path the MCP server can reach.
  5. Inspect outbound traffic. Review egress logs around the period in which vulnerable versions were deployed for unexpected loopback, private-network, or metadata-endpoint destinations originating from MCP server processes.
  6. Keep authorization semantics aligned with the fetch path. A middleware that only checks for the presence of a token still allows unauthenticated SSRF. Place real credential validation ahead of any outbound request, or restrict the tool to authenticated callers.

Bottom line

CVE-2026-54549 is a server-side request forgery in a Python MCP server whose image-fetching tool, by design, follows caller-supplied URLs. The lack of scheme, host, or IP validation turns a routine capability into an unauthenticated, network-reachable SSRF, and a presence-only Bearer token check leaves the pre-fetch path exposed. The fixed boundary is 1.0.115, but the durable lesson applies to every MCP tool that accepts URLs from a caller: validate the scheme, resolve the host, reject the unsafe address ranges, and ensure that any authentication step actually runs before the outbound request.

For teams deploying MCP servers in production, the review should include every tool that accepts a URL, every transport option that exposes the tool to a non-localhost network, and every authorization middleware that runs ahead of an outbound fetch. A successful patch review confirms both that the vulnerable release is gone and that the new helper rejects the address ranges the original code did not. This is a defensive validation approach, not an exploit procedure.

Sources

Leave a Comment