Baseten's Admin GitHub Token Sat in Docker History for 3 Years

An autonomous security agent needed 25 minutes to get admin access to Baseten's internal GitHub repos โ a company valued at $13 billion. No credentials, no source code access, just a public domain.
What happened
Strix, a company that builds autonomous pentesting agents, was evaluating Baseten as an inference provider for its own product. Before handing data and models to a third party, the team routinely scans a vendor's infrastructure first โ this time pointing their own agent at *.baseten.co with no credentials or code access.
The agent found a Harbor registry on an easily overlooked subdomain, with one project set to public. From there it pulled baseten/baseten-app without logging in. Inside the image was a pair of AWS keys โ dead on arrival, a read-only sts:GetCallerIdentity call returned InvalidClientTokenId. The agent kept looking, ran TruffleHog against the layers, then inspected the image's config directly rather than just its filesystem, and found a GitHub personal access token sitting in the history[].created_by field โ the record of the build command, not a file inside the container.
The token belonged to account basetenbot, carried the full repo scope, and was tied to the basetenlabs organization. It had admin and push access to the main product repository, to the GitOps repo that drives production clusters, to the Homebrew tap that distributes their CLI, and read/write access to several other private repos โ including directories named after individual customers. The image containing this token was built in March 2023. When Strix found it in July 2026, the token still worked โ more than three years later.
Why this hides in plain sight
The root cause is a familiar Dockerfile pattern: a build needs to fetch a private dependency from GitHub, so someone passes a token as a build argument and uses it to configure git config --global for that build step.
# conceptual example โ not a real tool
ARG GITHUB_TOKEN
RUN GITHUB_TOKEN=${GITHUB_TOKEN} bash -c '\
if [[ "${GITHUB_TOKEN}" != "" ]]; then \
git config --global --add \
url."https://${GITHUB_TOKEN}@github.com/".insteadOf "git@github.com:"; \
fi'
The problem isn't the final filesystem of the image โ cleaning up a credential file after the build doesn't help. Docker records every RUN command in the image config's history, and the expanded build argument goes with it. That config downloads alongside the image itself, entirely separate from the regular filesystem layers โ so a scan that only inspects files inside the container misses it completely. Docker explicitly warns about this behavior, yet the pattern persists because it's simple and it works.
What matters here
Baseten's response was fast: they confirmed the finding as critical in under a day, locked down the registry, and rotated the token by the next afternoon. But the real lesson isn't the response speed โ it's that this credential sat exposed for three and a half years before anyone found it.
- Build history is its own leak surface, separate from the image filesystem. Run
docker history --no-truncor readhistory[].created_bydirectly in the config blob โ a standard layer-file scan will miss it entirely. - The fix is a BuildKit secret mount, not a build argument. A secret mount never writes the value into a layer or the history, and doesn't persist past the build step.
- Tokens need minimal scope and expiration. A token that only needed read access to fetch a dependency ended up with admin/push on the product and GitOps repos โ that's an authorization failure, not just a leak.
- Deleting the Dockerfile doesn't delete a published image. Anyone who already pulled the old image still has the token โ the mandatory step after discovery is revoking the credential, not just fixing the next build.
- Private registries need periodic public-project audits. The exposed Harbor project wasn't a one-time misconfiguration โ it was a forgotten corner of infrastructure, exactly the kind of stale subdomain that every recon tool checks first.
Baseten uses AI security tooling and has a responsive security team, and the token still sat there since 2023 waiting to be found. The question worth asking for any team building container images: what's still sitting in the build history of your oldest image?
Content assisted by AI (Amy ๐ธ). Reviewed by the author.
Related Posts
3,800 GitHub Repos Breached via VSCode Extension: What Every Developer Should Know
GitHub confirmed 3,800 internal repositories were exposed after an employee installed a malicious VSCode extension. Here's how to protect yourself.
AI Bot Spam Is Killing Open Source: A Story From Archestra
A Y Combinator startup received 253 spam comments from AI bots on a single issue, and 27 untested PRs for one feature. This is a real problem every maintainer faces today.
OpenAI Agents Hacked RubyGems Before Humans Found the Flaw
In May 2026, an OpenAI agent swarm tried exploiting a RubyGems bug nine years old โ two months before RubyGems itself even knew it existed.