News7 days ago

A Live GitHub Admin Token Sat in Baseten's Public Docker Image for Three Years

An autonomous pentesting agent went from a bare domain to admin on three private repos in about 25 minutes. We checked the part that generalises: reading a Docker image's entire build history costs 5,664 bytes and no Docker at all.

The WJS Desk

Sep 17, 2026 · 7 min read

Photo by Wolfgang Weiser on Pexels

On 13 July at 11:10 PM, an autonomous pentesting agent called Strix reported to Baseten that it had admin access to three of the company's private GitHub repositories. It had started from nothing: no credentials, no account, just the domain *.baseten.co. Strix says the whole run took about 25 minutes.

Baseten is an AI inference company that closed a $1.5 billion Series F this June at a valuation reported as high as $13 billion. The token that let a stranger in was created on 3 March 2023 and was still live when Strix found it, which is three years and four months of a repo-scoped personal access token sitting in a file anyone could download. The writeup went up this week and hit 297 points and 171 comments on Hacker News. We went and checked the part that actually generalises, which is not the open registry but how little work it takes to look for this on any image, anywhere.

What actually leaked

The entry point was a Harbor container registry at gcp-us-east4-zlw.registry.baseten.co that served the baseten/baseten-app image without authentication. Strix pulled the manifest and layers anonymously, ran TruffleHog over them, and then went looking at the image config.

The token belonged to an account Strix identifies as basetenbot and carried the repo scope, which on a classic GitHub personal access token is about as narrow as a barn door. Strix reports it returned admin: true, push: true on three repositories and read/write on four more, including customer-specific ones. The repository names are redacted in the post as basetenlabs/b*** and similar.

Baseten's response was quick. The Harbor project went private the next morning, the security team confirmed critical severity at 4:34 PM on 14 July and rotated the credential, and the remaining findings were closed by 17 July. On Hacker News, Baseten's philipkiely wrote that the company "took immediate steps to invalidate the leaked key" and that logs confirm the vulnerability was never exploited. We have no way to check that independently, so treat it as the company's statement rather than as a finding.

We did verify the fix. As of this writing, GET /v2/ on that registry host returns 401 UNAUTHORIZED, and so does a tag listing for the image. The door is shut.

The field nobody reads

Open registries get closed. The durable part of this story is where the token was hiding.

Every Docker image carries a config blob, a small JSON document describing how the image was built. Inside it is a history array, and each entry has a created_by string holding the literal build command that produced that layer. Strix found the token there, in a RUN step where a GITHUB_TOKEN build argument had been expanded straight into a git config call so that private dependencies would clone during the build.

This is the shape of the mistake:

ARG GITHUB_TOKEN
RUN git config --global \
  url."https://${GITHUB_TOKEN}@github.com/".insteadOf "git@github.com:"

The secret never lands in a file. It is never left in an ENV. Nothing in the filesystem of the finished image contains it. It is in the metadata, in a string describing a command that ran three years ago, and that string ships with the image forever.

We checked how cheap this is to look for

What we wanted to know was whether finding this genuinely requires pulling images and running secret scanners over layers, or whether it is cheaper than that. So we wrote a short script against the plain registry HTTP API and pointed it at a public image we had no relationship with, library/python:3.12-slim on Docker Hub. Our Docker daemon was not running. It never needed to be.

The script fetches an anonymous pull token, resolves the multi-arch index down to the amd64 manifest, downloads exactly one blob (the config), and prints every created_by string it finds.

history entries: 10
  * # debian.sh --arch 'amd64' out/ 'trixie' '@1787529600'
  * ENV PATH=/usr/local/bin:/usr/local/sbin:...
  * ENV LANG=C.UTF-8
  * RUN /bin/sh -c set -eux; apt-get update; apt-get install -y ...
  * ENV GPG_KEY=7169605F62C751356D054A26A821E680E5FA6305
  * ENV PYTHON_VERSION=3.12.14
  * RUN /bin/sh -c set -eux; savedAptMark="$(apt-mark showmanual)"; ...
  * CMD ["python3"]

Ten entries, every build command legible, start to finish. The Python image is maintained by people who know better and there is nothing sensitive in it, which is the point: this is the normal, healthy output. Any secret passed as a build arg would be sitting in that list in exactly the same plain text.

Here is what the two approaches cost. We measured the config-only path end to end. The layer figure is the size the registry reports for the same image, which we deliberately did not download.

 Config blob onlyPulling the whole image
Bytes over the wire5,664 (5.5 KB)44.0 MB
Wall clock, measured10.3 secondsnot measured
Docker installednot requiredrequired
Every build commandyesyes
Secrets written into files in layersno, invisibleyes, scannable

The config blob is 8,152 times smaller than the layers it describes. That row where it loses is real and worth sitting with: reading history alone will miss a key that got COPYed into the image as a file. Metadata reading is not a replacement for scanning layers. It is just enormously cheaper, and it happens to be where this particular class of mistake lives.

The bit that does not expire: rotating the token fixes the account. It does not unpublish the image. Any copy of that config blob pulled before the registry closed still has the original string in it, and there is no mechanism to reach into someone else's disk and redact it. Rotation is the only real remedy, and it has to be fast.

Docker warned about this before the token existed

None of this is a novel attack. Docker's own build documentation says it plainly: "Build arguments and environment variables are inappropriate for passing secrets to your build, because they persist in the final image."

The documented alternative is a secret mount, which makes the value available during a single RUN and leaves nothing behind in the metadata:

RUN --mount=type=secret,id=gh \
    GITHUB_TOKEN=$(cat /run/secrets/gh) \
    git config --global \
      url."https://${GITHUB_TOKEN}@github.com/".insteadOf "git@github.com:"

Which leaves the awkward question of why a well-funded infrastructure company was using the build-arg pattern in 2023. The honest answer is probably the same reason your build does: it was the pattern in the Stack Overflow answer, it worked, and nobody looked at the image config afterwards because there is no reason to unless you already suspect something.

Hacker News argued about the T-shirts

Strix's reward for a critical finding was, in their words, "some T-shirts and sweatshirts as a thank-you." The thread spent a lot of its energy on that.

Commenter mtlynch argued that no financial reward from a company at this valuation sends the wrong message. Aurornis pushed back that this was not independent research but a B2B relationship, since Strix was evaluating Baseten as a potential inference provider when it ran the scan, and that swag is a reasonable token in that context. stickfigure was blunter: "The main payment is all the viral advertising that this AI hacking tool is getting right now."

The sharper dissent was about method rather than money. Commenter nrmitchi argued Strix crossed the white-hat line by exfiltrating data and touching customer-specific directories, and otterley raised CFAA exposure for scanning without written authorisation. calvinmorrison compared it to finding a key on the pavement and then walking into the house. Those are not rhetorical objections. An agent that autonomously escalates from recon to enumerating private repositories has made a series of decisions that a human pentester would normally have to get signed off first.

The most useful comment was neither. catidegla pointed out the mundane thing underneath all of it: a token minted in March 2023 still worked in July 2026. Classic personal access token with no expiry. Everything else in this story is downstream of that one checkbox.

The uncomfortable part

The vulnerability here is boring and roughly as old as Docker. What changed is the cost of looking.

A human does not scan a thousand registries for fun. A process that costs 5.5 KB and ten seconds per image scans everything, forever, because there is no budget line that makes it not worth doing. Strix is a product with a blog and a responsible disclosure process. The same loop costs the same amount for someone without either.

We should be clear about the limit of our own work here. We demonstrated that reading build history is trivially cheap on one public image we chose deliberately. We did not go looking through other companies' registries to find out how widespread the leak pattern is, because that is the exact line the Hacker News thread was arguing about. So we cannot tell you what fraction of images out there are carrying a credential in created_by. We can only tell you that finding out would cost almost nothing, which is the part that should bother you.

Go read your own build history

Run docker history --no-trunc against your oldest production image, the one built before anyone on your team had heard of secret mounts, and actually read the RUN lines. If you find something, we want to know how old the token was. Ours is a story about three years and four months. Beat it.

And if you enjoy security holes that live in the plumbing rather than the application, we wrote up the Forgejo bug where a template repo could plant its own .git folder and git would adopt it, which is the same species of problem: a field nobody inspects, doing exactly what it was designed to do.

Share

Reading a Docker image's entire build history costs 5,664 bytes and needs no Docker at all. A live GitHub admin token sat in that field at Baseten for three years. #DevSecOps #Docker #GitHub #SupplyChain

Never miss a ship

The best stuff that shipped this week, delivered every Thursday. Free, no spam. We read all the boring stuff so you get the fun parts.

Keep reading