Skip to main content

Docker Patched a Model-Pull SSRF, Then Killed Its Sandbox: What 9 Months of Churn Means for Your AI-Sandbox Bet

12 min readDora NodaDora Noda
Share
On this page

Imagine pulling an AI model and having the pull itself quietly probe your internal network. That is what CVE-2026-33990 made possible: Docker Model Runner followed a realm URL from a malicious OCI registry's authentication challenge without checking its scheme, hostname, or IP range, issued the request from a host-networked process, and reflected the response back to the caller. (Wiz analysis; GitHub advisory GHSA-x2f5-332j-9xwq) The flaw scored CVSS v3.1 9.1 (Critical), was fixed in Model Runner 1.1.25 on March 30, 2026 — and then had to be fixed again in August, because the first patch covered only one of the code paths that touched the network.

That fix-twice arc is the real story. Over roughly nine months, Docker's AI-isolation surface has been patched, re-patched, and partly replaced: a model-pull SSRF in March, a container-to-host inference-backend escape eight days later, the experimental docker sandbox plugin killed in June in favor of a new docker sbx command, and a follow-up realm-validation fix in August for the pull path the March patch missed. (Docker Desktop release notes) If you run a self-hosted PaaS and you are deciding how much to trust vendor-shipped model-runner and sandbox tooling on the same nodes as tenant workloads, this timeline is the most honest input you will get all year. It does not say "never trust vendors." It says: know exactly which boundary each tool protects, and never adopt a sandbox primitive as your default before it has survived at least one migration.

Here is the churn up front, so the rest of the post has somewhere to land:

DateEventWhat it proves
Sept 2025Docker enables sandboxing of Model Runner's llama.cpp inference processes (Mac/Windows)Inference engines needed an isolation boundary from the start
Mar 30, 2026CVE-2026-33990 fixed in Model Runner 1.1.25 (Desktop 4.67.0)The model-pull path is remotely influenceable network input
Apr 7, 2026CVE-2026-5817 fixed in Desktop 4.68.0Inference backends execute remote code (trust_remote_code)
Jun 29, 2026Experimental docker sandbox plugin removed (Desktop 4.80.0), migrate to docker sbxThe sandbox primitive itself was replaced mid-flight
Aug 11, 2026PR #1038 validates token realm on pull and re-challenge pathsThe March SSRF fix missed the pull path for 4+ months
Sept 2026sbx reaches v0.45.0; Cloud Sandboxes go pay-as-you-goThe replacement is a hypervisor boundary, not a container one

The punchline: Docker spent nine months learning — in public, through CVEs and a deprecation — that AI-agent isolation wants a hypervisor boundary and a paranoid pull path. You get to inherit the lesson without shipping the CVEs, but only if you read the timeline as a design input instead of a patch Tuesday.

How the SSRF worked​

The mechanism is worth understanding precisely, because the same shape recurs anywhere a platform pulls OCI artifacts. When Model Runner pulls a model from a registry that requires authentication, the registry answers with a 401 and a WWW-Authenticate header naming a realm URL where the client should exchange credentials for a token. Model Runner's OCI client followed that realm URL without validating the scheme, hostname, or IP range — so a malicious registry could set realm="http://127.0.0.1:3000/" and Model Runner, running with host-level network access, would issue arbitrary GET requests to internal services and hand the full response body back to the caller. (Wiz; Tenable)

There is a second, nastier leg. The token-exchange flow forwards data through the Authorization: Bearer header, which means internal-service response data could be relayed to the attacker-controlled registry itself — turning reconnaissance into exfiltration in one pull. The precondition keeps this from being a worm: exploitation requires a user to pull a model from a malicious registry, a social-engineering or supply-chain step, not a drive-by. That is presumably why the CVSS v4.0 score lands at 6.8 (Medium) while v3.1 says 9.1 (Critical), and why the EPSS estimate sits around 0.013% with no public proof-of-concept and no CISA KEV listing. (Wiz)

Calibrate honestly, then: low probability, host-network blast radius. For a laptop developer pulling ai/qwen3.5 from Docker Hub, the realistic exposure was small. For a self-hosted PaaS that pulls tenant-specified models onto shared GPU nodes — where "the registry" is an input your tenants choose — the same bug shape is a tenant-escape primitive aimed at your internal metadata endpoints, not just localhost curiosities. The OWASP AISVS project now cites this CVE as its canonical example of model-pull-infrastructure SSRF, which tells you how the industry filed it: not a Docker oopsie, a category. (OWASP AISVS)

The fix that wasn't​

Here is the part that should change your runbook. The March 30 fix for CVE-2026-33990 covered only the hand-rolled Exchange() function used by the push flow. The pull path — remote.Image through createResolver — and the push re-challenge authorizer were both built on containerd's default authorizer, which follows the realm URL from a 401 challenge without validating it. A malicious registry could therefore keep driving token fetches at internal addresses through the pull path for more than four months after the CVE was declared fixed.

The follow-up landed on August 11, 2026, as PR #1038: "validate token realm on pull and re-challenge paths." Its commit message says the quiet part out loud — the CVE "was fixed only in the hand-rolled Exchange() used by the push flow," and the paths the fix missed were the ones built on library-default code. The patch you verified was not the patch that mattered; the code you did not write was the code that stayed vulnerable.

Generalize that into a rule your own platform can use: audit every path that touches the network, including the ones your dependencies wrote. When your model-puller, your buildpack fetcher, or your deploy pipeline follows a redirect, a realm, or a challenge URL, each call site needs the same validation — scheme allowlist, hostname/IP-range denylist for link-local and RFC 1918 space, response-size caps — regardless of whether the HTTP client is yours or containerd's. Docker's March patch was correct code in the wrong scope. Scope is the whole game with SSRF.

Eight days later, a second hole​

The SSRF fix had barely cooled when Desktop 4.68.0, released April 7, 2026, addressed CVE-2026-5817: container-to-host code execution in Model Runner's vLLM-metal inference backend via unsandboxed trust_remote_code tokenizer loading. (Docker Desktop release notes)

Read that slowly, because it names the second category. The SSRF was about where the pull goes. This one is about what the model runs: a tokenizer that loads remote code with the backend's privileges, unsandboxed, with a container-to-host path. And it lands in a lineage — Docker had already found it necessary to sandbox the llama.cpp inference processes back in September 2025. Three data points, one conclusion: inference backends are executable attack surface, not inert math. Tokenizers, custom ops, and model-card code fences all execute something, and "it came with the weights" is not a trust decision.

For a self-hosted PaaS, this is the sharper of the two CVEs. You can firewall a pull path. You cannot firewall a tokenizer that your inference backend executes as a matter of course — you can only sandbox the backend, pin and hash the model artifacts, and treat trust_remote_code-equivalents as code deployment, with the same review and signature bar you would apply to a tenant's container image. If your roadmap has GPU-backed agent sandboxes or tenant inference, that bar belongs in the design doc now, not in the postmortem.


The plugin Docker killed​

With the CVEs as context, the June event reads differently than a routine deprecation. Desktop 4.80.0, released June 29, 2026, removed the experimental docker sandbox plugin with a single line: "The experimental docker sandbox plugin has been removed. Migrate to docker sbx." (Docker Desktop release notes)

Note what this is and is not. It is not the SSRF fix and the sandbox removal landing "in the same release" — those shipped three months apart (March 30 vs. June 29), and collapsing them undersells the actual story. It is a vendor replacing an isolation primitive mid-flight: one sandbox implementation dropped, another offered, while the CVEs above were still being found in adjacent code. If you had standardized your agent-sandbox workflow on docker sandbox during its experimental window, June 29 handed you a migration, not an upgrade.

The replacement is worth taking seriously on its own terms. Docker Sandboxes centers on the standalone sbx CLI — no Docker Desktop or Engine required — with each sandbox in its own microVM carrying its own kernel, Docker Engine, and network, plus network, filesystem, and MCP policy controls and a Cloud Sandboxes tier on pay-as-you-go compute. The project is moving fast: v0.38.0 in early August 2026, v0.43.0 mid-September, v0.45.0 late September. That velocity is both the good news (active hardening) and the caution (APIs and environment-file schemas are still churning; pin versions and read the migration notes before you build automation on top).

The architectural signal matters more than any single version. Docker's answer to "how do we isolate AI agents" moved from a Desktop plugin around containers to a hypervisor boundary — dedicated microVMs per agent. That is the same direction the rest of the industry converged on in 2026: Fly.io Sprites, E2B, Kata Containers, and AWS Lambda all landing on container-in-a-VM nested isolation for untrusted agent code. When the vendor that taught the world to love containers puts its own agents in microVMs, take the hint about which boundary your platform should default to for untrusted workloads.

Why Enhanced Container Isolation didn't save you​

A reasonable objection: doesn't Docker Desktop's Enhanced Container Isolation (ECI) blunt all of this? It is worth being precise about what ECI does, because the mismatch is the lesson. ECI runs each container in a dedicated Linux user namespace via the Sysbox runtime, blocks bind-mounting the Docker socket, contains even --privileged containers to their own boundary, and prevents containers from breaching the Desktop Linux VM or tampering with engine configuration. (Docker ECI docs) It is a container-boundary tool, and a good one.

But CVE-2026-33990 never crossed the container boundary. The SSRF lived in Model Runner's host-side OCI registry client — the tooling, not a tenant container — issuing requests with host-level network access. ECI constrains what containers can reach; it has nothing to say about what your model-puller fetches when a registry tells it to. Add that the Model Runner API itself is unauthenticated — Docker's own docs note that any client able to reach it, including other containers on the same network, can use it (Model Runner docs) — and the threat model snaps into focus: the vulnerable surface was the trusted vendor tooling sitting outside the isolation boundary, holding host network access and an open localhost API.

That inverts the usual hardening order. Most teams sandbox tenant code first and bless vendor tooling by default. This timeline argues the reverse priority for AI workloads: isolate the tooling, not just the tenants. Your model-puller, your inference backends, and your agent runtimes are network-reachable programs that process adversary-influenced input (registries, weights, tokenizer code). They deserve their own network policy, their own authentication, and their own sandbox — before you spend the same effort on the second tenant container.

The self-hosted sandbox-bet checklist​

Distill the nine months into six rules for a Cluster-API fleet (or any owned hardware) that needs to run agent sandboxes or tenant inference without reliving Docker's timeline:

  1. Default untrusted agent code to a hypervisor boundary. MicroVMs per sandbox (Kata, Firecracker-class, or your cloud's nested virt) for anything executing tenant or model-supplied code; plain containers only for workloads you fully control. Docker's own migration from plugin to microVM-based sbx is the reference datum.
  2. Treat the model-pull path as untrusted input. Validate every realm/redirect/challenge URL (scheme allowlist, no link-local/RFC 1918/metadata IPs), pin registries per tenant or proxy all pulls through an allowlisted mirror, cap response sizes, and audit library-default HTTP clients — the August follow-up fix exists because containerd's default authorizer was the hole.
  3. Authenticate localhost APIs. If your model-runner-equivalent serves an unauthenticated API on a shared node, any workload on that node is a client. Bind to loopback at minimum, add tokens, and never assume "only our tooling talks to it" on multi-tenant hardware.
  4. Sandbox inference backends including tokenizer code. Assume weights ship executable code until proven otherwise: run backends sandboxed, hash-pin artifacts, and gate trust_remote_code-style features behind the same review bar as a container deploy. Budget for the CVE-2026-5817 class, not just the CVE-2026-33990 class.
  5. Adopt no vendor sandbox primitive as your default until it survives one migration. docker sandbox went from experimental to removed; sbx is on v0.45 with a still-churning schema. Wrap vendor CLIs behind your own interface, pin versions, and keep a second backend (even a dumb one) so a vendor migration is a config change, not an architecture rewrite.
  6. Split the network blast radius between tooling and tenants. Model-pullers and inference schedulers get their own segment with egress policy; tenant workloads get another; internal metadata and control-plane endpoints are reachable from neither by default. SSRF dies in a subnet it cannot route from.

None of this requires believing Docker did anything unusual. Every item above is ordinary platform engineering — the timeline just prices what skipping it costs: two CVEs, one killed plugin, and a four-month window where the declared fix did not cover the pull path.


Docker's nine months read as embarrassment only if you grade vendors on never erring. Grade them on what their errors teach, and this stretch is generous: a named CVE for the pull-path SSRF category, a second for unsandboxed inference backends, a public migration from container to hypervisor isolation, and a follow-up patch that documents exactly which code path the first patch missed. Rent the lesson, own the boundary — and build your fleet's sandbox bet on the timeline above, not on any single tool's promises.

Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own. Star the repo on GitHub or deploy your first app today.

Related articles

Give your agents a chain backend

Autonomous agents hit RPC endpoints very differently than people do. See what bex router handles on their behalf.

Read the agents guide