We got admin access to Baseten's production GitHub
https://www.strix.ai/blog/baseten-harbor-github-pat-takeover📌 自主駭客Agent 25分鐘從公開映像檔挖出Baseten管理員金鑰
TL;DR:資安公司Strix用自家滲透測試Agent,在無任何憑證下挖出Baseten一枚存活逾3年的GitHub管理員Token。
如果一家估值130億美元、被大量企業信賴用來跑推論的公司,其最核心的GitHub repo管理權限,能被一支沒有原始碼、沒有任何登入憑證的自動化程式在25分鐘內拿到,你還會放心把資料交給它嗎?資安公司Strix在準備採用Baseten做推論服務前,先用自己開發的自主駭客Agent「Strix」對*.baseten.co做黑箱測試,結果就撞見了這個場景。
🤔 為何一家推論服務商會被自己的潛在客戶掃描
Strix打造了一款自主滲透測試Agent,因為需要便宜且快速的推論算力,Baseten是候選之一。但身為資安公司,他們的原則是:在把資料、模型或程式碼交給任何第三方之前,先掃一遍。這次他們刻意不提供任何原始碼存取或帳密,純粹從外部黑箱角度出發,理由是「最嚴重的漏洞常常藏在你自己都忘記的子網域服務上」。
🧩 從公開的Harbor Registry一路挖到GitHub Token
Strix先做網路資產枚舉、掃描憑證日誌,找到一個位於 gcp-us-east4-zlw.registry.baseten.co 的Harbor容器登錄伺服器。其中一個project被設為公開,Strix不需要任何Token,就能用匿名pull token列出repository並下載映像檔的manifest與blob,其中包括一個叫 baseten/baseten-app 的映像檔。
Strix沒有止步於「登錄伺服器暴露」這種metadata層級的回報,而是實際拉下映像檔,用開源工具TruffleHog掃描並直接檢視映像檔的build history。它先在裡面找到一組AWS金鑰,但用 sts:GetCallerIdentity 測試回傳 InvalidClientTokenId,代表這組金鑰已失效。接著,它在映像檔config的 history[].created_by 欄位裡,發現了一個RUN指令,裡面直接展開了 GITHUB_TOKEN 環境變數的實際值,也就是一枚貨真價實的GitHub個人存取Token(PAT)。
📊 一枚2023年的Build Token,仍握有Admin權限
Strix用這枚Token對GitHub發出唯讀的 GET /user 請求,回應是200,帳號名稱是 basetenbot。進一步檢查權限:GitHub回傳的 X-OAuth-Scopes 是 repo,帳號屬於 basetenlabs 組織。逐一檢查repository權限後發現,這枚Token對多個內部repo擁有 admin: true、push: true,包括Baseten的主要產品repo與驅動叢集的GitOps repo,另外還對數個私有repo(包含特定客戶專屬repo)擁有讀寫權限。這個build步驟的時間戳記是2023年3月3日,Strix在2026年7月測試時,這枚Token依然有效。確認影響範圍後,Strix沒有進一步clone、push或改動任何設定,直接停手並發出揭露信。
💡 問題根源:把Token寫進了git設定,也寫進了映像檔歷史
這個錯誤模式很常見:build過程需要抓取私有依賴套件,於是有人把GitHub Token當作build參數傳入,並用 git config --global --add url."https://${GITHUB_TOKEN}@github.com/".insteadOf 的方式讓git改用帶Token的URL做認證。問題是,Docker會把RUN指令的內容連同build history一起記錄進映像檔的config裡,即使日後清掉檔案本身,這份build history仍留著一份Token副本。Docker官方文件本身也對這種寫法有明確警告。
⚠️ 修復方式:不留痕跡,還要撤銷舊Token
正確做法是用BuildKit的secret mount搭配臨時認證,確保憑證不會寫進映像檔任何層或config;同時要記得,改了Dockerfile不會影響已經被人下載走的舊映像檔,唯一的解法就是撤銷舊Token。值得一提的是,Baseten的資安團隊反應相當迅速,在確認為重大(critical)等級後,隔天下午前就鎖住了登錄伺服器project並輪換了Token。
🎯 實務啟示
不只要掃你的原始碼repo,也要定期檢查容器映像檔的build history,即使映像檔本身沒有留下憑證檔案,history裡的RUN指令仍可能藏著明文Token;任何用ARG傳入的憑證都該搭配BuildKit secret mount,並對長期存在的build用Token設定到期或定期輪換機制。
🔗 來源
- 標題:We got admin access to Baseten’s production GitHub
- 作者/機構:bearsyankees (Strix)
- 連結:https://www.strix.ai/blog/baseten-harbor-github-pat-takeover
#Security #PenTesting #DevSecOps #Docker #GitHub #AIAgent #SupplyChainSecurity #ContainerSecurity #CredentialLeak #Baseten
原始資料 Hacker News · 收集於 2026-09-16
摘要原文
We were about to trust Baseten with our own and our customers’ data. So to be safe, we ran Strix to ensure they were secure first. About 25 minutes later, it had a live GitHub token with repository-level admin rights on internal Baseten repos. We build Strix , an autonomous hacking agent, which of course means we need (cheap and fast) inference. We were exploring our options, and Baseten is one of the obvious choices. It's a great product, they're valued at $13 billion , and a lot of serious companies depend on them. But... we're a security company. Before we give a third party our data, models, or code, we scan them. We would much rather find a problem and help get it fixed before we start depending on that service (we do this with almost all of our vendors and have a high rate of finding serious issues). So... we pointed Strix at *.baseten.co and let it run without credentials or source code. It came back with an active GitHub personal access token for basetenbot . That token had admin and push access to Baseten's main product repo, the GitOps repo that drives their clusters, and their Homebrew tap , plus read/write access to other private repositories including specific repos per customers. The image build dated to March 2023 , and the token still worked when we found it in July 2026. But before we get into the details, let's give some kudos to Baseten's security team. They confirmed the issue as critical, locked down the registry project, and rotated the token by the next afternoon. They were professional and very quick to deal with it (which is often not the case in these situations). How Strix actually found it Strix starts the way any good pentest does: recon. Many times the most severe vulnerability on your infra may be on a service on a subdomain you forgot about (which is why we always recommend black box testing alongside testing with code access)! So Strix enumerated hosts, looked through certificate logs, mapped the full surface. Eventually, it found a Harbor registry at gcp-us-east4-zlw.registry.baseten.co . Harbor stores container images and groups repositories into projects. One of these projects was public. Without any token or auth, Strix could list repositories, obtain anonymous pull tokens, and download the actual image manifests and blobs. That included an image called baseten/baseten-app . At this point, it would be easy to report an exposed registry and move on. But companies do intentionally publish images, and Strix never wants to report a false positive. The important question is what impact the images being exposed have. Strix decided to pull an image and see what was inside. Here's that part of the run as it appears in Strix (yes, the agent's own writing sounds like AI, for obvious reasons): Harbor Exposure Impact Review Thinking If those Harbor projects are not meant to be public, this is a real exposure, not metadata noise. The chain is what matters: enumerate public projects, read artifact metadata, mint an anonymous service/token scoped to repository:baseten/baseten-app:pull , pull the manifest, then fetch the blob through a signed storage redirect. That proves real anonymous pull access. The way to make triage take it seriously is to prove one thing from a pulled image: proprietary code, internal binaries, hardcoded credentials, or internal hostnames. The first promising hit was a pair of AWS keys in baseten/baseten-app . Strix tried a read-only sts:GetCallerIdentity call, which tells you what account a credential belongs to. The response was InvalidClientTokenId . That key was dead, so Strix kept looking. Then, a token that actually worked It pulled the layers, ran TruffleHog (shout out to our open-source security friends!), and inspected the image config directly. And there it was: a classic GitHub personal access token, sitting in history[].created_by . I am not a Docker runtime expert, but luckily Strix is (thanks to it having pretty much all of human knowledge at its disposal). So it knew that that field records how a build step was created. In this case, it contained a RUN command with the value of GITHUB_TOKEN expanded directly into it. Strix used the token for a read-only GET /user request to GitHub and… VOILÀ . 200 , with the account name basetenbot . The token in the Docker build history, followed by GitHub identifying it as basetenbot. The credential is redacted. Open image for full size. Notice where the token was found. As I learned, a Docker image has filesystem layers, but it also has a config containing information about the image and its build history. That config is downloadable along with the image. Cleaning up a credential file doesn't help if the build history still contains another copy of the token. And this one still worked more than three years later. Okay, what can basetenbot do? A live token is interesting, but obviously the permissions matter. This token could have 0 permissions and thus 0 impact. So Strix checked the account and its organization membership. GitHub returned X-OAuth-Scopes: repo , and the account belonged to basetenlabs . GitHub returned repo scope for basetenbot and listed basetenlabs as its organization. Open image for full size. Then it checked the individual repository permissions, again using read-only requests: Repository Access basetenlabs/b*** admin: true , push: true basetenlabs/f*** admin: true , push: true basetenlabs/h*** admin: true , push: true basetenlabs/r*** Private, read/write basetenlabs/b*** Private, read/write basetenlabs/t*** Private, read/write basetenlabs/b*** Private, read/write This is an insane amount of access to leave in a publicly downloadable image. At that point, we had enough to report and be confident this was not a false positive. We didn't clone the customer repo, push anything, or change any configuration. We stopped there and wrote the disclosure email immediately. How does a token end up there? The build history was timestamped. The step containing the token ran on March 3, 2023 . This was an old build credential that still had all of that access when we tested it in July 2026. The underlying mistake is pretty familiar. A build needed to fetch private dependencies from GitHub, so somebody passed a token in as a build argument. The relevant pattern looked like this: 1 ARG GITHUB_TOKEN 2 RUN GITHUB_TOKEN= ${GITHUB_TOKEN} bash -c '\ 3 if [[ "${GITHUB_TOKEN}" != "" ]]; then \ 4 git config --global --add \ 5 url."https://${GITHUB_TOKEN}@github.com/".insteadOf "git@github.com:"; \ 6 fi' I can see how someone ends up writing this. You need a private dependency, you pass in the token, Git authenticates, and the build works. But Docker can record that build argument in the image's metadata and history. In this case, it recorded the actual token value. Docker explicitly warns about this . There is also a second problem with this pattern: git config --global writes the authenticated URL into Git's configuration file. Even if you change how the token gets into the build, you still need to avoid saving it into the image. The fix is to use a BuildKit secret mount and temporary authentication that doesn't persist the credential. Then inspect both the image's layers and its history. And revoke the old token! Changing the Dockerfile doesn't do anything about an image that someone already downloaded. What Strix did on its own Baseten has a responsive security team and already uses AI security tooling . Still, this token from a 2023 build had admin access to their product and deployment repos when we found it. It's easy to focus on the application and the source repositories, and forget about an old container image. Even if you scan the image's files, you still need to check its build history. What I like about this scan is that Strix kept following the finding. (317 points, 180 comments on Hacker News)
由 claude-code/sonnet 自動生成