If your team upgraded MLflow past version 3.10.0 sometime in the last year, you probably believe the server-side request forgery hole in its webhook feature got closed. The pull request that shipped that release added a hostname check specifically to stop webhooks from reaching internal services. That belief is wrong, and attackers have known it since mid-August. CVE-2026-64849, an unauthenticated SSRF in MLflow's webhook delivery, is CVSS 9.3, it is on CISA's Known Exploited Vulnerabilities catalog as of August 19, and it walks straight past the guard everyone assumed was doing its job.
Who this hits: anyone running a self-hosted MLflow Tracking Server below version 3.15.0, especially one reachable from the internet or sitting inside a cloud VPC where the instance metadata service answers on 169.254.169.254. If your MLflow usage is entirely through a managed offering where you never stand up the open-source tracking server yourself, this specific CVE is not about your deployment, though the underlying lesson about redirect-based SSRF still is. If you self-host and you have not confirmed your version this week, treat the rest of this article as your Tuesday.
The myth: a hostname allowlist stops SSRF
MLflow's model-registry webhooks let a tracking server call out to a URL whenever a registered model event fires. That is a legitimate integration point, and it is also a textbook SSRF surface: a server making outbound requests on a user's say-so. MLflow's maintainers knew this. Pull request #20747 added _validate_webhook_url, a guard that resolves the target hostname and rejects anything that resolves to a private, loopback, or link-local address. It shipped in MLflow 3.10.0, and it does exactly what it says: register a webhook pointed at 127.0.0.1 or 169.254.169.254 directly, and the server refuses it.
Teams that patched past 3.10.0 reasonably assumed the SSRF class was handled. It is the kind of fix that reads clean in a changelog: hostname resolved, IP checked, request blocked if it lands somewhere it should not. Nobody audits a webhook allowlist twice once it ships.
The reality: the guard checks the front door, not where the request actually goes
The bypass, tracked as GHSA-7gwp-5pfp-969j, is almost insultingly simple once you see it. _validate_webhook_url checks the URL you register. It does not check where that URL redirects to, and it does not re-validate if the destination's DNS answer changes between the check and the delivery. An attacker registers a webhook pointed at a public HTTPS endpoint they control, which passes the guard cleanly, then has that endpoint respond with 302 Location: http://169.254.169.254/latest/meta-data/iam/security-credentials/<role>. MLflow's webhook delivery code follows the redirect. The DNS-rebinding variant does the same thing without even needing a redirect: the hostname resolves to a public IP at validation time and to a loopback or metadata address by the time the actual request goes out.
The part that turns this from an information leak into a credential-theft primitive is the POST /api/2.0/mlflow/webhooks/{id}/test endpoint, which ships unauthenticated by default on the open-source tracking server. It exists so you can fire a test event and see what the remote endpoint returned. It returns the upstream response status and body straight back to the caller. That makes this a full-read SSRF, not a blind one. An attacker does not have to guess whether the request landed. They read the IAM credentials in the response.
# Fingerprint whether a tracking server you own is exposed and unpatched.
# Run this against hosts you control or are authorized to test.
TARGET="https://mlflow.internal.example.com"
# Version check - anything below 3.15.0 is vulnerable to CVE-2026-64849
curl -s "$TARGET/version"
# Confirm the tracking API answers without auth (expected on a default install -
# if this also returns data, the server has no auth layer in front of it at all)
curl -s -o /dev/null -w "unauthenticated API reachable: %{http_code}\n" \
"$TARGET/api/2.0/mlflow/experiments/search"
# List any registered webhooks and eyeball the target URLs for anything
# you did not configure yourself
curl -s "$TARGET/api/2.0/mlflow/webhooks/list" | jq '.webhooks[] | {id, url, events}'
Run that against your own tracking server, not someone else's. If the version check comes back below 3.15.0 and the API answers without credentials, you have the exact conditions CISA flagged as under active exploitation.
Why the CVSS 9.3 is not an overreaction
watchTowr reported scanning activity against exposed MLflow instances within hours of the CVE assignment on August 17, and both watchTowr and VulnCheck confirmed active exploitation before CISA added the flaw to the KEV catalog on August 19 with a federal remediation deadline of September 2. That is a five-day window between a CVE getting a number and a federal agency being told to have it fixed. The intent behind the fix in 3.10.0 was correct. The execution left the exact gap attackers went looking for, and they found it fast.
What happens after the credential theft is not hypothetical. Once an attacker reads temporary IAM credentials off the metadata endpoint, the documented pattern is straightforward: enumerate what the role can reach, deploy a cryptominer if the role has compute permissions, and create a new IAM user or access key somewhere the victim is not watching, so the access survives a credential rotation on the original role. SecurityAffairs notes MLflow's open-source package moves through more than 60 million downloads a month. Most of those installs are not internet-exposed tracking servers, but the ones that are represent a lot of cloud IAM roles sitting one redirect away from a stranger.
What to check right now
Confirm the version, then assume compromise if you cannot
Hit /version on every MLflow tracking server you run, internal or otherwise. Anything below 3.15.0 gets the same treatment as a host with a confirmed intrusion until you can prove otherwise, especially if it has ever been reachable from outside its own subnet.
Pull the credential trail before you touch the patch
- Review CloudTrail (or your cloud provider's equivalent audit log) for any API calls originating from the instance profile or service account attached to the MLflow host, filtered to the period since the server went live.
- Look specifically for new IAM users, new access keys, or new role trust relationships created by that identity. That is the persistence step attackers take once they have the temporary credentials.
- Check the webhook list on the server itself, per the script above, for any URL you did not register.
Close the access path, not just the code path
- Enforce IMDSv2 on the underlying instance if you are on AWS, which requires a session token and defeats the simplest version of this attack even against an unpatched server.
- Put the tracking server behind a VPN or an authenticating reverse proxy. It has no business answering requests from the open internet, patched or not.
- Patch to 3.15.0, which closes both the redirect bypass and the DNS-rebinding variant by validating the destination at delivery time instead of trusting the check done at registration.
Find every exposed instance, not just the ones you remember
The MLflow Tracking Server binds to port 5000 by default and its landing page carries a distinctive title string, which makes it a five-minute search on Shodan or Censys for anyone running your own external attack surface scan: query for the default port plus the MLflow branding in the response body, cross-reference the hits against your known IP ranges, and flag anything you cannot immediately attribute to a project owner. Do this even if you are confident you know every AI/ML system in your environment. The whole reason this bug is dangerous is that MLflow servers get stood up outside the normal provisioning process, by a data science team moving faster than the asset inventory can track, which is exactly the blind spot an external scan is built to close.
Do the credential review before the patch, not after. Patching first fixes the code path and destroys your only chance to see what already walked through it.
Patch to 3.15.0, then go find the servers you forgot you had
Redirect-based SSRF bypasses are a known class, and MLflow's maintainers will not be the last team to ship a hostname check that forgets about the response it is validating against. What actually stings is how many MLflow tracking servers exist that nobody on the security team remembers standing up. A data scientist spins one up for a project, points it at a cloud role with broad permissions because it was Friday afternoon, and two years later it is still running, unpatched, on a subnet someone assumed was internal. Patch the servers you know about today. Then go find the ones you do not, because CVE-2026-64849 will not be the last bug that turns an internet-reachable MLflow instance into a cloud credential vending machine.
Want to try our open-source security tools?
We built scopecheck to map an organization's external attack surface, including the internet-exposed dev and AI/ML tooling nobody remembers deploying. Check it out on GitHub, run it against your own domains before an attacker's scanner does, or book a session to talk through hardening your AI infrastructure.
