Safeguard
Vulnerability Analysis•

MLflow CVE-2026-64849: unauthenticated SSRF via webhook redirects

A default MLflow tracking server let anyone make it fetch internal URLs and read the answer. CISA added CVE-2026-64849 to KEV on 19 August 2026.

Safeguard Security Team
6 min read

On 19 August 2026 CISA added CVE-2026-64849 to its Known Exploited Vulnerabilities catalog, with a federal deadline of 2 September. The bug sits in MLflow's webhook feature: an unauthenticated request to the webhook test endpoint makes the server call a URL of the attacker's choosing and return the response body. On a cloud VM, that can mean reading instance metadata, including temporary credentials.

The fix shipped in MLflow 3.15.0 on 31 July. The GitHub advisory went public on 2 August, and NVD published the CVE record on 17 August.

What's affected

The MLflow Python package, in the tracking server, before 3.15.0. The two main sources disagree slightly on where the range starts. The GitHub advisory GHSA-7gwp-5pfp-969j says >= 3.10.0, < 3.15.0. NVD says "starting in 3.3.0 and prior to 3.15.0". If you run anything in the 3.x line before 3.15.0, treat it as affected.

The scores differ too: the advisory rates it 8.6 (High) and NVD rates it 9.3 (Critical). The weakness is CWE-918, server-side request forgery.

There is a second step to remember. The advisory says IPv6 transition bypasses (IPv4-mapped, 6to4 and NAT64 addresses) were fixed later, in 3.16.0, and that deployments on IPv6 or NAT64 networks should upgrade to 3.16.0 or newer. So the practical target is 3.16.0 or later, not just 3.15.0.

How the bug works

MLflow lets you register webhooks that call out when something happens, for example when a model version changes. A function named _validate_webhook_url resolves the hostname and rejects private addresses. That sounds right, but the check had two gaps:

  • Delivery followed HTTP redirects. An attacker hosts a public HTTPS URL that passes validation, then redirects to http://169.254.169.254/ or another internal address. Nobody re-checked the redirect target.
  • The validated IP was never pinned. The check and the actual connection each resolved the hostname, so DNS rebinding could swap in an internal address between the two.

The test endpoint, POST /api/2.0/mlflow/webhooks/{id}/test, runs synchronously and returns the upstream status and body. That turns a blind SSRF into a full read. The advisory also notes that 307 and 308 redirects allow blind POSTs to internal management endpoints.

How to check if you're exposed

Start with the version and where the server listens.

pip show mlflow | grep -i '^version'
# or inside a container
docker exec <container> python -c "import mlflow; print(mlflow.__version__)"

Then ask whether the server is reachable by people who should not reach it. From a machine outside your network:

curl -s -o /dev/null -w '%{http_code}\n' https://mlflow.example.com/health

A 200 from the open internet means the tracking server is exposed. Next, look for requests to the webhook test route in your reverse proxy or access logs:

grep -E 'POST /api/2\.0/mlflow/webhooks/[^/]+/test' /var/log/nginx/access.log*

Any hit from an address you do not recognize deserves a closer look. On AWS, check whether the instance can hand out role credentials to anything that asks:

aws ec2 describe-instances --instance-ids i-0123456789abcdef0 \
  --query 'Reservations[].Instances[].MetadataOptions'

HttpTokens: optional means IMDSv1 is still answering plain GET requests, which is exactly what an SSRF needs.

A note on timing: the reporter first told the project privately on 12 June 2026. A second finder filed a public issue on 26 June, before the release on 31 July. So the bug was visible in a public tracker for five weeks before the fixed build existed.

What to do now

  1. Upgrade MLflow to 3.16.0 or later: pip install -U "mlflow>=3.16.0". Rebuild container images rather than patching in place.
  2. Take the tracking server off the public internet. Put it behind a VPN, an identity-aware proxy or at least an allowlist.
  3. Turn on authentication. MLflow ships a basic auth app (mlflow server --app-name basic-auth), and a reverse proxy with SSO is better for teams.
  4. Require IMDSv2 and set the hop limit to 1: aws ec2 modify-instance-metadata-options --instance-id i-0123456789abcdef0 --http-tokens required --http-put-response-hop-limit 1.
  5. If the server was reachable before the upgrade and the role attached to it could do anything useful, rotate those credentials and review CloudTrail for use from unexpected addresses.

How this could have been avoided

  • Check the peer address at connection time. This is what the fix does. Validating a URL string is not enough, because redirects and DNS answers change after the check. Any code that fetches user-supplied URLs should verify the IP it actually connected to.
  • Disable redirects on server-side fetches. The delivery code followed them by default. Set allow_redirects=False in your own HTTP clients unless you need them, and re-validate each hop if you do.
  • Do not expose experiment tracking to the internet. The attacker needed no account. A VPN or an authenticating proxy in front of the server removes the whole anonymous attack path.
  • Make metadata credentials hard to steal. IMDSv2 with a hop limit of 1 blocks the simple GET an SSRF can send. Scope the instance role so a stolen token can read only what the server needs.
  • Limit egress. A tracking server rarely needs to reach arbitrary internal hosts. A network policy that allows only the webhook destinations you registered turns many SSRF tricks into timeouts.

Why it matters

ML platforms tend to be set up fast and exposed by accident. A tracking server holds model artifacts, run parameters and often cloud credentials, and it usually runs with a generous role. An SSRF against it is a short path to the account behind it.

It is also a reminder that a "validated" URL is not a safe URL. The original check was fine for the first request and silent about everything after it. The same pattern shows up in image proxies, link previewers and webhook senders in many other projects.

Try it yourself

Pick one internal service that fetches URLs on behalf of users, such as a webhook sender or a previewer. Read its HTTP client setup and answer two questions: does it follow redirects, and does it check the connected IP? Then run the exposure checks above against your own MLflow, or against any similar tool you host.

Click through it at your own pace, or open it full screen.

For a quick look at what your Python projects pull in, the free scan at gold.safeguard.sh/scan runs in your browser. Your file never leaves the machine, no login is needed, and it accepts requirements.txt for up to 100 dependencies.

Quick check

Why is validating the webhook URL once not enough?

Because the delivery code followed redirects and resolved the hostname again. A URL that passes the check can redirect to an internal address, or resolve to one on the second lookup (DNS rebinding).

Which MLflow version should you target?

3.16.0 or later. 3.15.0 fixes the main bug, and 3.16.0 adds the IPv6 and NAT64 hardening the advisory calls out.

Sources

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.