最近在替 mikan 設計 secret 注入機制時遇到一個問題。
mikan 是一個 coding agent,會在隔離的 sandbox 環境裡執行使用者交辦的任務。這類 agent 幾乎都需要存取外部服務:push 程式碼到 GitHub、呼叫部署 API、讀取私有套件庫……這些操作都需要帶 credential(認證金鑰)。
問題是:credential 要怎麼安全地交到 sandbox 手上?
最直覺的做法是直接把 token 塞進環境變數(export GITHUB_TOKEN=xxx),但環境變數會被所有子 process 繼承,agent spawn 出來的每個工具、腳本、甚至 env 指令都能看到它。另一個選項是把 token 寫進 workspace,但 agent 一不小心就可能讀到、commit 進去,或印到 log 裡。
所以我開始看其他 sandbox runtime 怎麼解這個問題。
boxd.sh 是什麼
exe.dev 和 boxd.sh 都是提供持久性 Linux VM 的服務,面向開發者和 AI agent。兩者都能透過 SSH 連進去,VM 的狀態會保留,重新連線時環境還在。功能定位相近,是直接競品。
這類服務通常會替使用者預先設定好 GitHub 授權,讓 VM 裡的 gh 和 git 指令不需要手動登入就能直接存取 GitHub。
我想弄清楚的是:它怎麼把 GitHub token 交給 VM?
連進免費試用的 VM 之後,我原本以為 token 只從一個地方來。實測後要修正:
gh CLI 走 /run/boxd/github-token 這個本地 cache;git 的 credential helper 則直接向 metadata API 取 token。
這是兩條不同的整合路徑,不能混為一談。
第一條路:gh CLI
boxd-github-token:帶 cache 的取 token 工具
VM 裡有一個二進位工具 /usr/local/bin/boxd-github-token,工作是拿到當下有效的 GitHub token:
CACHE=/run/boxd/github-tokenmkdir -p /run/boxd
if [ -f "$CACHE" ]; then AGE=$(( $(date +%s) - $(stat -c %Y "$CACHE" 2>/dev/null || echo 0) )) if [ "$AGE" -lt 300 ] && [ -s "$CACHE" ]; then cat "$CACHE" # cache hit:直接輸出檔案 exit 0 fifi
# cache miss:向 metadata API 拿新 token,寫回 cache 再輸出TOKEN=$(wget -qO- http://10.20.0.1:9002/v1/github/token 2>/dev/null) || exit 1if [ -n "$TOKEN" ]; then printf '%s' "$TOKEN" > "$CACHE" printf '%s' "$TOKEN"else exit 1fi邏輯:
- cache hit:
/run/boxd/github-token存在且距上次寫入不超過 300 秒(5 分鐘),直接輸出檔案內容 - cache miss:向
http://10.20.0.1:9002/v1/github/token取新 token,寫回 cache 再輸出
10.20.0.1:9002 是 boxd 的 metadata API——類似 AWS EC2 的 169.254.169.254,是 VM host 開放給 VM 查詢自己 metadata(包含臨時 credential)的內部端點,只有 VM 內部能打到。
NOTE
/run/boxd/github-token本身只是一個 cache 檔案,不是 token 的來源。真正的上游是 metadata API。
gh 怎麼用這個 helper
gh CLI 被 /etc/profile.d/boxd-github.sh 包了一層 shell function:
gh() { _token=$(boxd-github-token 2>/dev/null) if [ -z "$_token" ]; then command gh "$@" return $? fi GH_TOKEN="$_token" command gh "$@" # token 只注入這一個指令的環境 unset _token}也就是說,在 login shell 裡執行 gh repo view,實際的路徑是:
gh function → boxd-github-token → GH_TOKEN=<token> command gh ...注意:GH_TOKEN 不是預先 export 在整個 session 的環境變數,而是只在執行那一個 gh command 時才臨時注入。在 shell 裡跑 env,不會看到 GH_TOKEN。
第二條路:git
Git 有一套叫做 credential helper 的機制:當 git 需要對 HTTPS URL 做認證時,它會呼叫設定好的 helper 程式,把 protocol、host 傳進去,helper 再回傳 username 和 password。
boxd 透過 /etc/gitconfig 設定讓所有 GitHub 操作都走自己的 helper:
[credential "https://github.com"] helper = boxd
[url "https://github.com/"]helper 的核心邏輯:
if [ "$GITHUB" = "1" ] && [ "$HTTPS" = "1" ]; then TOKEN=$(wget -qO- http://10.20.0.1:9002/v1/github/token 2>/dev/null) printf 'protocol=https\nhost=github.com\nusername=x-access-token\npassword=%s\n\n' "$TOKEN"fiNOTE這裡沒有讀
/run/boxd/github-token,也沒有寫 cache。每次git需要 GitHub credential,helper 都直接打 metadata API 拿新 token。
兩條路徑的比較
gh CLI | git over HTTPS | |
|---|---|---|
| 進入點 | /etc/profile.d/boxd-github.sh(shell function) | /etc/gitconfig credential.helper |
| 取 token | boxd-github-token(有 cache) | git-credential-boxd(無 cache) |
| cache | /run/boxd/github-token,TTL 5 分鐘 | 無 |
| 上游來源 | metadata API(cache miss 時) | metadata API(每次直接打) |
這是這次實測最重要的結論:/run/boxd/github-token 不是整個 VM 唯一的 GitHub token 輸出點,它只是 gh 這條路徑的本地 cache。
值得注意的安全點
1. /run/boxd/github-token 是 644
drwxr-xr-x boxd boxd /run/boxd-rw-r--r-- boxd boxd /run/boxd/github-tokenOwner 是 boxd,但 mode 644 代表同機其他 user 也能讀這個檔案。如果 VM 裡只有一個使用者,影響可能有限;如果存在多個 user 或不受信任的 process,這個設計值得留意。
2. token 不在預設 env
一般 shell session 裡,GH_TOKEN 不是常駐的環境變數。這比直接 export GITHUB_TOKEN=xxx 好:token 不會被所有子 process 自動繼承,只有在 gh function 執行的那一刻才存在於環境中。
3. gh 和 git 行為不同,容易誤判
只看 /run/boxd/github-token 這個檔案,只能理解 gh 這條路徑。git clone、git push 走的是另一條完全獨立的機制,不會經過這個 cache。
4. log 洩漏仍然是最大的攻擊面
不管 token 從 cache 來還是 metadata API 來,最後都會進到某個指令的環境或 credential response 裡。
WARNING最直接的風險是把 token 印出來。常見的觸發方式:
Terminal window set -x # 會把每個指令和它的參數都輸出到 stderr任何會把 token 帶進 stdout / stderr / CI log / agent transcript 的操作,才是實際上最需要防守的地方。
小結
boxd.sh 把 GitHub token 注入 VM 的方式有兩條路:
ghCLI:透過 shell function 呼叫boxd-github-token,後者用 5 分鐘 cache 減少 metadata API 請求git:透過 credential helper 直接打 metadata API,每次都拿新 token
兩條路共用同一個 metadata endpoint 作為上游來源,但 cache 策略完全不同。設計 sandbox 的 credential 注入機制時,要把這兩條路分開考慮,不能只看其中一條。





