← Blog
AI SecurityFedRAMPConMonCISA

Partial Impact, Whole Credential: MLflow CVE-2026-64849 and the KEV Item You Installed with pip

On August 19 CISA added exactly one vulnerability to the Known Exploited Vulnerabilities catalog. Not a Fortinet batch, not an edge appliance, not a hypervisor. MLflow — the experiment-tracking and model-registry server that is, for a very large number of teams, the closest thing they have to an ML platform.

CVE-2026-64849, MLflow Server-Side Request Forgery Vulnerability, CWE-918, federal remediation due September 2. Known ransomware campaign use: unknown.

We've written about the AI orchestrator on the KEV catalog and the model gateway on the KEV catalog enough times that the shape is familiar. This one is a little different, and the difference is worth the post: the affected thing isn't an appliance somebody procured. It's a line in a requirements file.

What the bug actually is

MLflow's model registry can fire webhooks, and it exposes an endpoint to test one: POST /api/2.0/mlflow/webhooks/{webhook_id}/test. The endpoint is not authenticated — the advisory's own interim mitigation is "restrict network access to the tracking server, or turn on the optional authentication plugin," which tells you where the default sits.

MLflow does try to stop SSRF here. _validate_webhook_url resolves the webhook hostname with socket.getaddrinfo and checks that every resolved address is public (ip.is_global). The gap, in the maintainers' own words on the fix, is that the validation discards the resolved IP. The subsequent request re-resolves the hostname independently, and the HTTP client follows redirects without re-validating where they land. Two ways through the same door:

  • DNS rebinding — the original report. Answer the validation lookup with a public IP, answer the request lookup with 169.254.169.254 or 127.0.0.1. A textbook time-of-check/time-of-use gap.
  • An ordinary redirect — the variant the advisory is titled for. Point the webhook at an attacker-controlled HTTPS host that passes validation cleanly, then have it return 302 Location: http://169.254.169.254/.... Nothing re-checks the destination.

And then the part that turns a blind SSRF into a full-read one: the test endpoint hands the answer back. The WebhookTestResult message carries response_status and response_body — which is why CISA's KEV description, unusually, names those two fields directly. The attacker isn't inferring anything from timing. They're reading the response.

The fix in 3.15.0 is the right one structurally: a SSRFProtectedHTTPAdapter that validates the peer IP of the actual connected socket, before the TLS handshake, so there's no second DNS resolution left for rebinding to exploit.

Affected: MLflow below 3.15.0. Fixed: 3.15.0. That range is consistent between NVD and the GitHub advisory, which is a nicer position than the last time we tried to pin down a version.

The 9.3 and the "partial" are both correct

GitHub, as CNA, scores this 9.3 CriticalAV:N/AC:L/PR:N/UI:N/S:C/C:H/I:L/A:N. NVD has analyzed it and carries that score as the secondary metric; there's no independent NIST base score and no CVSS v4.0 assessment yet, so 9.3 is the operative number.

But look at what CISA's own SSVC decision (dated August 20) says:

  • Exploitation: active
  • Automatable: yes
  • Technical impact: partial

Three of four lights red, one amber — and partial is why the clock is fourteen days instead of three. Under BOD 26-04's risk-sorted tiers, the top band wants total control of the affected component. This bug reads. It doesn't own the box. The CVSS vector agrees with CISA, incidentally: C:H but I:L and A:N. High confidentiality, barely any integrity, no availability impact. The 9.3 comes mostly from S:C — scope change — which is the score's way of saying the damage happens somewhere other than here.

That is the whole vulnerability in one metric, and it's the thing to argue with in your own triage. "Partial technical impact" is a statement about the MLflow process. It is not a statement about what the MLflow process can read. On a cloud instance with IMDSv1 reachable, what it can read is the instance role's credentials — and those are not partial anything. Nor is a response body from an internal admin endpoint, a metadata-served config blob, or a survey of which private hosts answer.

So: fourteen days is the federal floor, and it's a defensible tier. Whether it's your tier depends on a question the catalog can't answer for you — what is one hop away from your tracking server? If the answer is "an instance profile with real permissions," you're not doing fourteen-day work.

The seven weeks nobody was on the clock

The timeline here is worth writing down, because it does something we don't usually get to say.

  • June 26 — the SSRF bypass is filed as a public GitHub issue, with the full technical writeup.
  • July 2 — the fix merges.
  • July 31 — it ships in MLflow 3.15.0.
  • August 17 — the GitHub Security Advisory and NVD entry publish, naming CVE-2026-64849.
  • August 19 — KEV listing. Clock starts.
  • September 2 — federal due date.

The patch was publicly available for seven weeks before the CVE existed. Which means a meaningful number of teams reading this are already remediated and don't know it — they took a routine dependency bump in early August and closed this finding without ever seeing it.

That's a good problem, but it is still a problem, because "we think we're fine" is not what closes a KEV item. It also cuts the other way: the technical detail sat in a public issue tracker from June 26 onward. Anyone watching the repo had a working writeup six weeks before the catalog said the word "exploited." The KEV listing is when the obligation started. It is not when the risk started, and the gap between those two dates is the thing BOD 26-04's risk questions were written to shrink.

The part that's specific to MLflow

Every KEV post has a "did you know you were running it" paragraph. This one is sharper than usual, because MLflow is not a product in the sense your asset inventory understands.

It's an open-source Python package. There's no vendor PSIRT feed to subscribe to, no support contract, no account rep who emails you when something lands. It arrives because a data scientist ran pip install mlflow to stop losing track of experiments, it worked, and then it slowly accumulated the things that make it load-bearing: the registry of what's in production, artifact storage credentials, the S3 or blob-store role, database access, and — because it's genuinely useful — a webhook or two wired into deployment.

Nobody reclassified it from "tool" to "system." So when a KEV entry lands on it, the questions are the awkward ones:

  • Is it in the inventory at all, as a named component with a version and an owner — or is it a container image someone built in 2025 whose tag nobody can map to an upstream release?
  • Do you know the version, and can you produce that from a query rather than by SSH-ing somewhere? A pinned mlflow== in a lockfile is a real answer. "We're on latest-ish" is not.
  • How many of them are there? This is the failure mode specific to research tooling. One sanctioned tracking server, plus three that individual teams stood up, plus one in a notebook environment that's been running since a project ended.
  • What can it reach? SSRF is entirely a question about network position. The same bug on a box with no metadata service, egress-filtered, and no internal admin endpoints in reach is a genuinely minor finding. On a default-configured cloud VM it's a credential disclosure.

If it's inside a FedRAMP boundary

MLflow is almost never the authorized service. It's a component inside someone's authorized boundary — which is exactly why it slips. The service you sell is on the marketplace; the tracking server that trains the model behind it is an implementation detail until a federal due date attaches to it.

What we'd do between now and September 2:

  • Enumerate first, patch second. Find every MLflow instance, not the one you already knew about. The one that costs you the deadline is the one that isn't on the diagram.
  • Get to 3.15.0 or later. Clean version boundary, no ambiguity, and the fix is structural rather than a blocklist.
  • Where you can't patch this week, take the advisory's mitigation seriously as a control. Restricting who can reach the tracking server, or gating it behind the auth plugin, is documentable, defensible, and doesn't depend on a release window. Write it down as a compensating control with a date, not as a shrug.
  • Enforce IMDSv2 and egress-filter the tracking server. This is the durable fix. It doesn't just close CVE-2026-64849 — it makes the next SSRF in any Python service on that host a non-event. Session-token-required metadata plus outbound allowlisting turns "reads your instance role" back into "gets a connection refused."
  • Then decide about rotation. The KEV entry asserts active exploitation. If your instance was internet-reachable on a vulnerable version with IMDSv1 available, the instance role's credentials, artifact-store keys, and registry database password are rotation candidates — and reviewing access logs for POST to /api/2.0/mlflow/webhooks/*/test is a narrow, cheap, high-signal thing to go look for. The required action for this entry points at forensics triage the same way the FortiOS one did; "we patched it" isn't the whole ask.
  • Record the version and the date you reached it. Given the seven-week gap above, a lot of teams will find they were patched before the CVE published. That's a great outcome and it needs an artifact — the deployment record showing 3.15.0 landed on a date that precedes August 19 closes this finding more convincingly than any assertion you can write in a POA&M.

What we keep coming back to

The KEV item changes every few weeks and the lesson refuses to move: know what you run before you're asked to account for it under a clock. This one adds a wrinkle we haven't hit quite this cleanly before — the thing you run may not have arrived as a thing you bought. No procurement record, no vendor relationship, no bulletin. Just a package that solved a real problem, and then quietly became the system that knows which model is in production.

That's the shape of work the Novaprospect audit engine is built toward: infrastructure-native discovery that finds the tracking server nobody put on the diagram, version and network position attached to the asset rather than reconstructed under deadline, and the evidence trail — the bump, the date, the egress rule, the log review — collected as a byproduct of operating. So a fourteen-day clock on a pip install is a query, not an expedition.

The question this one leaves us with: for every open-source component inside your boundary that holds a credential, do you know its version and what it can reach — without asking a person? If yes, September 2 is an afternoon. If no, the next catalog entry is going to ask you again.

Reference