如何避免 AI agent 洩漏 API key 與機密
AI agent 只需一次工具呼叫就可能洩漏 API key。改用 credential gateway 提供具範圍與時效限制的 token,切勿將真實 key 放入 agent 環境。
將機密排除在 AI 代理程式之外的意義
AI 代理程式是執行命令的一般 Linux 程序。該程序持有的每個環境變數,都能由它所執行的程式碼讀取。因此,代理程式環境中的 API key 可能被代理程式傳送至它能連線的任何主機。將機密排除在代理程式之外,表示提供給它的是存取控制項,而非 key:例如有時效限制且具範圍限制的 token,或由其他元件在網路邊界將其替換為實際值的 placeholder。
這不是模型變得具有敵意的故事。實際機制更為單純。代理程式讀取包含指示的網頁、README 或 issue 留言,然後遵循這些指示,因為對語言模型而言,你撰寫的文字與它擷取的文字沒有差別。這就是 prompt injection。發生這種情況後,損害範圍完全取決於一件事:該程序能讀取的內容。如果你尚未設定邊界,在伺服器上安全地執行 coding agent 說明了本指南所依據的隔離層級。
以簡明方式說明威脅模型
請以 agent 執行時所使用的使用者身分執行此操作。
tr '\0' '\n' < /proc/self/environ | grep -iE 'key|token|secret|password'它輸出的每一行,都可能對陌生人的伺服器發出一個 HTTP 要求。現在查看 agent 附近磁碟上的內容。
grep -rIl --exclude-dir=.git -e 'API_KEY' -e 'SECRET' ~/projects
find ~ -maxdepth 3 -name '.env' -o -name 'credentials' -o -name '*.pem'具備 shell 存取權的 agent 不需要巧妙的漏洞利用方式,就能將這些資料傳出。4 種一般路徑即可完成,而且在記錄中看起來都像正常工作:
- 對任何主機發出 outbound
curl或fetch,並將值放在查詢字串中。 - 對 agent 具有寫入權限的 repository 執行
git commit和git push。 - 執行 package install script,以 agent 使用者身分執行任意程式碼。
- 查詢包含該值的主機名稱,即使 HTTP egress 受到阻擋,資料仍會透過 DNS 查詢傳出。
無法僅靠檢查來解決這個問題。正確作法是確保 agent 可存取的範圍內沒有任何重要資料。
工作樹中的機密會進入內容視窗
代理程式會讀取檔案。工作所在的儲存庫中若有 .env 檔案,代理程式就會讀取它。檔案一旦被讀取,就會進入內容視窗,也就是會出現在逐字記錄、保留的任何日誌,以及代理程式接下來寫入的內容中。
之前,金鑰位於代理程式工作的樹狀目錄中:
cd ~/projects/billing
cat .env
# STRIPE_SECRET_KEY=sk_live_...
# DATABASE_URL=postgres://app:hunter2@db.internal:5432/billing之後,檔案移至代理程式無法存取的位置:
sudo install -d -m 750 -o root -g agent-review /etc/agent-review
sudo install -m 640 -o root -g agent-review ~/projects/billing/.env /etc/agent-review/billing.env
rm ~/projects/billing/.env代理程式的使用者將無法再開啟該檔案,因為工作樹中已不再包含它。代理程式自身設定中的拒絕規則是第二層防護,不是第一層。Claude Code 會從專案中的 .claude/settings.json 讀取權限規則:
{
"permissions": {
"deny": ["Read(./.env)", "Read(./secrets/**)", "Read(./**/*.pem)"]
}
}這可防止代理程式在探索時誤開檔案。但它無法阻止注入的指令執行 base64 .env,因為那是 shell 命令,而不是檔案讀取。請將設定視為防護措施,將檔案系統權限視為阻隔。相同的區分也適用於容器內部:Docker Compose 中的環境檔案與機密涵蓋這個問題在下一層的版本。
為每個代理程式建立專用的非特權使用者
如果代理程式以您的身分執行,就會繼承您的 SSH 金鑰、雲端認證資訊和 shell 歷程。建立獨立使用者只需一個命令,就能移除這些存取權。
sudo adduser --disabled-password --gecos "" agent-review
sudo chmod 700 /home/agent-review
sudo -u agent-review cat ~/.ssh/id_ed25519最後一行必須以 cat: /home/you/.ssh/id_ed25519: Permission denied 失敗。如果它輸出金鑰,表示您的家目錄允許群組或其他使用者讀取;chmod 700 ~ 可修正此問題。請勿將代理程式使用者加入 sudo,也不要授予它範圍超過實際所需單一命令的 NOPASSWD 規則。VPS 上的最小權限使用者詳細說明群組和 sudoers 的設定。
在雲端 VPS 上,還應增加一道界線。執行個體中繼資料服務會在固定的鏈路本機位址上回應,而且通常會將角色認證資訊提供給提出請求的任何對象。
sudo iptables -A OUTPUT -m owner --uid-owner agent-review -d 169.254.169.254 -j REJECT從代理程式端進行檢查。sudo -u agent-review curl -s --max-time 3 http://169.254.169.254/ 應不輸出任何內容並以非零狀態結束,因為封包在離開主機前就會遭到拒絕。
在邊界注入憑證
真正能解決這個問題的模式是憑證注入。代理程式不會持有真正的金鑰。它會將要求傳送至本機閘道,閘道在送出要求時,將預留位置替換為真正的密碼。密碼儲存在閘道的儲存區中,位於不同的程序內,並由不同的使用者擁有。
OneCLI 是此模式的其中一個開放原始碼實作,採用 Apache-2.0 授權,並以容器形式在代理程式旁執行。截至 2026 年 7 月,專案文件說明的設定如下:
git clone https://github.com/onecli/onecli.git
cd onecli
docker compose -f docker/docker-compose.yml up -d --wait儀表板在連接埠 10254 上接聽,閘道則在 10255 上接聽。您只需儲存一次真正的憑證,然後為每個代理程式提供預留位置值,而不是金鑰,並提供該代理程式專用的受限存取權杖。代理程式會將權杖放在 Proxy-Authorization 標頭中傳送。閘道會依主機和路徑比對輸出要求,解密相符的憑證,並將其替換進去。代理程式的環境中不會保留任何值得竊取的內容。
這裡的價值不在於加密,而在於「此代理程式使用了什麼,以及何時使用」會成為一項日誌查詢。您只需讀取一份稽核記錄,不必猜測六個環境中的哪一個保留了金鑰副本。
將密鑰交給處理程序,而不是環境
如果您在 systemd 下執行代理程式,完全不需要環境變數。LoadCredential= 會將密鑰放在只有該服務能讀取的私有目錄中;在 unit 檔案中,該目錄會以 %d 呈現,在處理程序內則以 $CREDENTIALS_DIRECTORY 呈現。該值永遠不會出現在 /proc/<pid>/environ 中,因此 ps eww 無法顯示它;服務停止時,該目錄也會消失。
先將認證資料加密至該機器。這些命令來自 systemd 文件,適用於 systemd 250 或更新版本,涵蓋 Ubuntu 24.04 和 Debian 13:
echo -n 'sk-example-value' > /tmp/plain.txt
sudo systemd-creds encrypt --name=api_key /tmp/plain.txt /etc/credstore/api_key.cred
shred -u /tmp/plain.txt
sudo systemd-run -P --wait -p LoadCredentialEncrypted=api_key:/etc/credstore/api_key.cred \
systemd-creds cat api_key最後一個命令會輸出 sk-example-value。這證明加密檔案可以在此主機上解密。接著在 unit 中參照它:
[Service]
User=agent-review
LoadCredentialEncrypted=api_key:/etc/credstore/api_key.cred
Environment=AGENT_KEY_FILE=%d/api_key
ExecStart=/usr/local/bin/agent-worker代理程式程式碼需要該值時,會開啟 $AGENT_KEY_FILE 中的檔案。讀取檔案只發生在當下。環境變數則會在處理程序的整個生命週期內存在,並存在於該處理程序建立的每個子處理程序中。
優先使用短期有效的權杖,而非長期有效的金鑰
永不過期的金鑰在數個月後出現在日誌或記錄內容中時,仍然有效。服務提供工作階段權杖時,請使用工作階段權杖,並將有效期限設為工作所允許的最短時間。
aws sts assume-role \
--role-arn arn:aws:iam::123456789012:role/agent-readonly \
--role-session-name agent-review \
--duration-seconds 900AWS STS(security token service)接受的最短期限是 15 分鐘,通常足以完成一項代理程式工作。對於 GitHub,請為代理程式使用者建立專用的 gh 登入,並使用限制範圍至單一存放庫的細粒度權杖,讓 gh auth token 在該工作階段內傳回的內容無法存取其他資源。先依資源限制範圍,再依時間限制。
驗證後持續驗證
對 agent 的設定進行任何變更後,都應執行以下 3 項檢查。請以 agent 的使用者身分執行,而不是使用您自己的身分。
sudo -u agent-review env | grep -iE 'key|token|secret'
sudo -u agent-review ls -la /home/you/ 2>&1 | head -3
sudo -u agent-review curl -s -o /dev/null -w '%{http_code}\n' --max-time 5 https://api.github.com/user第一項檢查應完全不輸出任何內容。第二項應輸出 ls: cannot open directory '/home/you/': Permission denied。第三項會顯示 agent 的網路路徑所呈現的身分。這正是 gateway 模式要回答的問題:401 表示 agent 未攜帶自己的 GitHub 憑證,而 200 表示 agent 攜帶了憑證,因此您應確認該憑證使用的是哪個 token。若您讓 agent 在無人值守的情況下執行,控制 VPS 上的 AI agent 成本說明了可與這些存取限制配套使用的預算上限。
FAQ
我可以直接相信模型不會洩漏我的金鑰嗎?
不行,因為在此威脅模型中,模型不是攻擊者。代理程式會讀取網頁、儲存庫和問題追蹤器中的文字,而這些文字可能包含指示。模型沒有可靠的方法區分您的指示與它擷取的文字。任何依賴模型做出正確選擇的控制措施,都會在注入的指示具有足夠說服力時首次失效。因此,控制措施必須放在作業系統或網路中。
環境變數真的不適合存放代理程式密語嗎?
它們有一個特定缺點:會被繼承。代理程式啟動的每個子程序都會取得副本,包括組建指令碼、測試執行器和任何套件安裝勾點。相同使用者也可以透過 /proc/<pid>/environ 讀取這些變數,因此代理程式執行的任何程式都能讀取它們,不需要代理程式轉交。使用 LoadCredential= 或閘道在使用當下讀取檔案,可將暴露範圍限制在該時刻。
將密語放入保存庫,是否就能單獨解決這個問題?
只能部分解決。保存庫能解決儲存問題,但無法解決最後一步:某個元件從保存庫取出密語,並以環境變數交給代理程式,使您回到原本的狀態。關鍵在於由誰執行替換。如果代理程式取得密語,代理程式就持有密語。如果由閘道或 init 系統在代理程式程序之外執行替換,代理程式就不會持有密語。
我要如何知道代理程式是否已經洩漏某些內容?
通常事後無法確定,這正是應使用閘道的理由。沒有閘道時,證據會分散在 shell 歷程記錄、代理程式的文字記錄,以及您可能根本沒有保留的輸出連線記錄中。使用認證閘道時,每次使用認證都會產生一行記錄,其中包含代理程式身分和時間戳記。如果您懷疑發生洩漏,先輪替金鑰,再進行調查。輪替的成本低,而確定性沒有。
我今天至少應該做什麼?
將每個 .env 檔案移出代理程式工作所在的目錄,並為每個代理程式建立一個未授權使用者。這兩項變更約需十分鐘,可關閉最常見的攻擊路徑:代理程式讀取原本不應與程式碼放在一起的認證檔案。閘道和短效權杖是下一個步驟,不是第一步。相同的起點適用於任何代理程式執行環境,包括 在 VPS 上安全執行自主代理程式。