Docker has fixed a critical Docker Hub flaw, tracked as CVE-2026-105445 and nicknamed "GhostPat", that let someone create an access token on their own account and then make it act as another user at the registry. The bug was open for about two years, from August 2024 until a fix on September 30, 2026. The Hacker News has the details.
What happened
Docker Hub lets you convert a personal account into an organization. When you do, you name a user as the organization's owner. According to the report, the conversion "transferred the account's personal access tokens (PATs) to the user designated as the organization's owner." The "conversion changed the owner recorded for each token and left its secret unchanged," and "the named owner was not asked to confirm the change and was not notified."
That turned into an attack with two steps:
- Create a PAT on your own account. You know its secret.
- Convert the account into an organization and name the victim as owner.
The token now worked at the registry as the victim. Within its permissions, it could pull, push or delete images in the victim's repositories and in organizations the victim could access, which, as the report notes, "includes publishing images under a legitimate name." The impact was limited to the registry: no access to the web UI, no password, email or 2FA change, no account takeover.
Key facts from Docker and the report:
- Rated critical, CVSS 4.0 score 9.4.
- Open from August 26, 2024 until the fix on September 30, 2026, deployed less than four hours after the report.
- Docker reviewed about 304,000 pushes made with transferred tokens. All went to the converted organization or its named owner. "We found no evidence that the vulnerability was exploited," Docker said. The report notes Docker did not say what it found for pulls or deletions.
- Docker notified users on October 2 and deactivated transferred tokens on October 8, except those whose owners asked to keep them.
- It was found by Peter Molnar and Krisztian Hozak (mptrdev.com).
- Docker now requires consent before naming an organization owner, notifies the user and logs every token ownership change.
- A documentation note dated April 22, 2025 described the token transfer. It was removed on September 30.
Why it worked
The registry decided "who is this request from?" by looking up the token's recorded owner at request time. That is a normal design. The problem was that another operation could rewrite the owner field, and nothing tied the token back to the person who actually created it and knew its secret.
A token is a statement: "whoever holds this secret may act as user X, with these permissions." If X can be changed later by someone other than X, then the person who holds the secret, here the account converter, gets to act as whoever they name. The token's secret never changed, so the converter kept using it.
Here is an illustrative example in Python, not Docker's code. The vulnerable version accepts the new owner from the request and moves every token across:
# illustrative example, not the product's code (vulnerable)
@app.post("/accounts/{account_id}/convert")
def convert_to_org(account_id: str, body: ConvertRequest, user=Depends(current_user)):
account = get_account_owned_by(user, account_id)
org = create_org_from(account, owner_id=body.owner_id) # anyone can be named
db.tokens.update_many(
{"owner_id": account.id},
{"$set": {"owner_id": body.owner_id}}, # token now acts as the named owner
)
return orgThe fixed version keeps tokens bound to their creator, requires the named owner to accept, and records the change:
# illustrative example, not the product's code (fixed)
@app.post("/accounts/{account_id}/convert")
def convert_to_org(account_id: str, body: ConvertRequest, user=Depends(current_user)):
account = get_account_owned_by(user, account_id)
invite = create_owner_invite(account, invitee_id=body.owner_id) # owner must accept
notify(body.owner_id, "owner_invite", invite.id)
revoke_tokens(owner_id=account.id, reason="account_converted") # never re-point them
audit("org_conversion_requested", actor=user.id, target=body.owner_id)
return {"status": "pending_owner_acceptance"}Three general rules come out of it:
- Bind credentials to who created them. If the subject has to change, revoke the old credential and let the new subject issue their own.
- Make ownership changes consensual and visible. Ask the new owner, notify them, and log every change, so a transfer can't happen silently.
- Authorize at use time against the current, legitimate grant, and treat any field that grants authority, such as
owner_id,roleororg_id, as something the client must never set directly.
What to do now
If you use Docker Hub:
- Check Docker's notice if you received one on October 2, and review the tokens on your account and organizations. Revoke any you don't recognize.
- Review recent pushes to your repositories for images you didn't publish.
- Pin images by digest (
image@sha256:...) in Dockerfiles, Compose files and Kubernetes manifests, rather than a tag that a push can move. - Sign images and verify signatures at deploy time, so "published under a legitimate name" isn't enough to be trusted.
- Scope tokens tightly. Prefer read-only tokens where you only pull, and short-lived tokens for CI.
If you build a product with accounts, tokens or organizations:
- Find every code path that changes an owner, role or organization field on a credential, resource or membership, and check it requires the affected user's consent.
- Revoke, don't transfer. On account conversion, merge or deletion, revoke the old credentials.
- Never bind ownership fields from the request body. Allowlist the fields a client may set.
- Log and notify on every ownership or permission change, so it can be audited and noticed.
How OpenRouting helps
This was a logic flaw in a hosted service, so no scan of your repositories would have found it in Docker Hub. What OpenRouting can do is look for the same class of bug in your own code and API design:
- The [access control agent](/agents/access-control) reviews findings about authorization: checks that trust ids from the request, and operations that change who owns what without checking the caller's right to do it.
- The [authentication agent](/agents/authentication-jwt) reviews token and session handling, such as tokens that aren't bound to a subject or aren't revoked when they should be.
- API collection rules check imported Postman, OpenAPI and other collections for missing auth, guessable IDs and mass assignment, for example an
owner_idfield accepted in a request body. - Semgrep's Dockerfile rules and the container agent flag risky settings in your own Dockerfiles and manifests, and the CI/CD pipeline agent calls out pipeline images and actions not pinned by SHA. Our post on Dockerfile security mistakes covers the common ones.
- AI triage (Claude, via Amazon Bedrock) gives each finding a verdict, a plain-English explanation and a suggested fix, and PR review runs the same checks on the files a pull request changes.
What it won't catch
- Business-logic flaws are hard for any scanner. A token transfer that "works as designed" doesn't match a pattern. Our findings and triage are prompts for a design review, not proof that ownership logic is sound. The broken access control post has a two-user test worth running by hand.
- Your registry. OpenRouting doesn't monitor Docker Hub or any registry, your tokens, or who pushed what.
- Vendor services. It scans your repositories and, passively, your websites, not a provider's infrastructure.
- Findings need a scan. Results appear when you run a scan or review a pull request.
Sources
- Docker fixes critical two-year-old Hub flaw, The Hacker News