SSD Nodes Learn 🎉 VPS $5.50/月起
指南 Matt Connor作者: Matt Connor

Coding Agent 提示注入攻擊與防禦

Coding agent 會把攻擊者可寫入的文字當成指令。本文盤點伺服器上的注入入口,解析資料外洩的致命三要素,並排序真正能降低損害的防禦措施。

針對程式撰寫代理程式的提示注入是什麼

針對程式撰寫代理程式的提示注入可以簡單描述為:代理程式讀取的文字會被當成指令,並由代理程式執行。代理程式可能開啟檔案、提取要求留言、網頁或工具呼叫的結果。這些內容都會以與您要求相同的文字形式傳入。如果攻擊者能控制其中任何文字,就等於能在您的工作階段中寫入內容。

目前所有代理程式產品都有這項特性。模型會接收一連串 token。您的要求、系統提示、檔案內容與工具結果會串接在一起,模型則預測接下來的內容。token 沒有權限位元。格式中也沒有任何資訊指出哪些內容是您授權的,哪些內容來自陌生人的 README 檔案。

本頁說明威脅模型:攻擊者控制的文字會從哪些位置傳達給在伺服器上執行的代理程式、攻擊者在各個位置能取得什麼,以及哪些防禦措施值得投入。其他指南中的隔離建議,必須先了解代理程式需要隔離的威脅,才有實際意義。

模型無法將內容與指令分開的原因

訓練有幫助,但無法徹底解決問題。目前的模型經過訓練,會對擷取的文字提高警覺,也會拒絕許多粗糙的嘗試。拒絕是機率結果,不是規則。攻擊者可以改寫內容、重試,並將文字隱藏在未納入設計的格式中,而且沒有任何限制能控制他們可以嘗試多少種措辭。

OWASP GenAI project 將此問題列為 LLM01:2025 Prompt Injection,並分成兩類。直接注入是使用者透過自己的提示詞改變模型行為。間接注入則是外部內容,例如網站或檔案,在模型處理內容時改變模型行為。對伺服器而言,間接注入才是關鍵問題,因為 agent 讀取的文字遠多於你輸入的內容。

第一份系統性研究由 Greshake 及其同事發表,題為 你並未同意接受的內容(2023)。其中應記住的結論是:當應用程式將擷取的文字提供給能呼叫工具的模型時,處理該文字幾乎等同於執行任意程式碼。

將讀取轉變為資料外洩的條件

讀取惡意文字本身不會造成損害。損害必須有離開這台機器的途徑。

Simon Willison 在 2025 年 6 月將這種組合稱為致命三要素。如果 agent 持有私有資料、會接觸不受信任的內容,且能將資料傳送到外部,攻擊者就可能誘使它透過第三個條件,將第一個條件中的資料傳出去。

你的 VPS 上的 coding agent 從第一天起就具備這些條件。私有資料包括原始碼、.env 檔案、SSH keys 與 shell 歷程記錄。不受信任的內容包括它讀取的每個 repository、頁面與工具結果。資料離開的途徑則可能是 git pushcurlnpm publish、pull request 內容,或終端機中列出的連結,而你可能會點擊該連結。

你無法移除第二個條件,因為讀取不受信任的文字正是你雇用 agent 執行的工作。因此,所有實際可行的防禦措施都必須針對另外兩個條件。

不受信任的文字如何進入伺服器上的 coding agent

agent 正在處理的 repository

checkout 中的每個檔案都是輸入。原始碼註解、README.md、變更日誌、測試 fixture、vendor code,以及 agent instruction files 本身,包括 CLAUDE.mdAGENTS.md 和其對應檔案,都屬於輸入。要求 agent 了解 codebase 時,agent 會讀取這些內容,因為這正是你的要求。

攻擊者因此能接觸所有 clone repository 並將其交給 agent 的人。Instruction files 是最直接的途徑,因為這些檔案本來就是供 agent 讀取的指示。Pull request 只要在 CLAUDE.md 中加入 4 行有用內容,再加入 1 行重新導向 agent 的指示,就可能被人類 reviewer 快速略過。

issues、pull requests 與 code review comments

陌生人能在 tracker 中輸入的任何內容,都會在你要求 agent 進行 triage 時送達 agent。2025 年 5 月,Invariant Labs 發布了一項 GitHub MCP finding,情況正是如此。某位開發人員的 agent 可存取 1 個 public repository 和多個 private repository。攻擊者在 public repository 中建立 issue。開發人員要求 agent 查看 open issues 後,agent 讀取了 private repository 的內容,並將這些內容寫入 public repository 端的 pull request。

該報告指出,這是架構問題,不是 MCP server 的程式碼缺陷。Agent 持有 1 個權限範圍過大的 access token,從 public inbox 讀取內容,並且具備寫入權限。一般意義上並沒有錯誤設定,因此解法是限制權限範圍,而不是修補程式碼。

agent 擷取的網頁

文件、論壇回答、vendor 頁面和搜尋結果,都可能包含寫給 agent 而非寫給你的文字。HTML 轉換成文字後,攻擊者可利用的空間會更大,因為瀏覽器不會顯示的內容仍會送達 model。

攻擊者能在 agent 最少受到監看時取得控制權。沒有人會完整閱讀 agent 為查詢資訊而擷取的網頁文字。

MCP tool output

MCP(model context protocol)是 agent 連線至外部工具的常見方式。工具結果會以文字傳回,並直接進入 context window。這裡有 2 個攻擊面,而不是 1 個。工具傳回的資料是明顯的攻擊面。工具本身的名稱與描述則是另一個攻擊面;model 會讀取這些內容,以決定何時呼叫工具,而你不控制的 server 可能在兩次呼叫之間修改其中任一項。

攻擊者只要將文字植入某個工具的輸出,就能接觸 agent 擁有的其他所有工具。低價值工具中的 injection 因此可能進一步驅動高價值工具。

CI logs、build output 與 dependency metadata

npm install 會輸出你未撰寫的 packages 所提供的文字。測試失敗時,library 會輸出 assertion message。Continuous integration(CI)job log 可能包含數千行第三方輸出。要求 agent 修正失敗的 build 時,agent 會讀取全部內容。

在這種情況下,攻擊者取得的是 build machine。該機器通常持有 deploy credentials 和 registry tokens,受到的注意卻比 laptop 少。

攻擊者實際上能取得什麼

有四種結果值得預先規劃。

憑證竊取。 代理程式處理程序能讀取的任何內容都可能遭到取得:環境變數、~/.aws/credentials~/.sshgh token,以及 Docker 設定檔。將這些資料傳出不需要 curl。對分支進行 commit、在 pull request 描述中加入內容、將套件發佈至 registry,或查詢攻擊者控制之名稱的 DNS,皆可將資料移出該主機。

你核准的程式碼變更。 代理程式的工作就是撰寫程式碼,因此讓它寫入一行不易察覺的錯誤內容,是最容易達成的結果。可能是新增相依套件,或加入 logging 呼叫,將 token 帶入之後會傳送到其他位置的 log。

持久化。 檔案只要寫入一次,即使沒有模型介入也能持續執行:在 .git/hooks 中加入 hook、在 package.json 中加入 postinstall script、將一行附加至 shell 啟動檔,或在 CLAUDE.md 中加入額外一行。下一個 command 執行時就會觸發它。

在網路內部移動。 代理程式會在你指定的位置執行。如果該主機能連到 loopback 上的資料庫、內部管理服務、雲端服務供應商的 metadata service,或私有網路上的其他主機,任何操控代理程式的內容也同樣能連到這些位置。

自動核准模式會移除最後一道檢查

在預設模式中,Claude Code 執行命令或編輯檔案前會先詢問。這個提示是每個上述攻擊面與實際操作之間的人工作業檢查。移除提示的模式也會移除這項檢查。

文件明確說明 bypassPermissions:只能在容器或 VM 等隔離環境中使用,確保 Claude Code 無法造成損害。Auto mode 的限制較寬鬆,會自動核准工具呼叫,並透過背景安全檢查確認操作符合要求。這些檢查能攔截許多問題,但本質上仍是模型對模型輸出的判斷。因此,應將其視為篩選機制,而不是安全邊界。

管理員可以同時移除這兩項限制。在設定檔中將 permissions.disableBypassPermissionsModepermissions.disableAutoMode 設為 "disable",再將該檔案放入 managed settings,讓 checkout 的專案無法覆寫設定。請參閱我們的 Claude Code 自動模式與權限規則 指南,瞭解每項規則的生效位置。

防禦措施,依其能提供的保障排序

這些措施都不是修正方案。每項措施只會縮小代理程式持有的內容,或縮小代理程式能利用這些內容執行的操作。

  1. 可銷毀並重建的機器,讓遭入侵時只需花費 1 小時重建,而不是處理一起資安事件。
  2. 與你自己的憑證分開、限制於單一儲存庫且有效期短的憑證。
  3. 不在代理程式命令繼承的環境中放置長期有效的 secret。
  4. 由作業系統強制執行的對外網路連線與檔案存取限制,適用於代理程式啟動的每個程序。
  5. 保持寫入與網路呼叫的核准提示啟用。
  6. 針對可明確列出的特定操作,使用 hook 作為確定性的最後防線。
  7. 合併前閱讀 diff。

順序很重要。第 1 至第 4 項即使模型已完全受攻擊者控制,仍然有效。第 5 至第 7 項則仰賴人員持續注意,而長時間執行代理程式時,最容易停止的正是這件事。

將代理程式放在可丟棄的機器上

只存有 checkout 和一組受限 token 的 VPS,遠不如存有你各種金鑰的筆記型電腦有價值。讓代理程式以獨立的非特權使用者執行,不要使用你的登入帳號,也不要使用 root。我們的 供 coding agents 使用的可拋棄 VMVPS 上的最小權限使用者 指南涵蓋設定方式,在 VPS 上安全執行 Claude Code 則說明日常使用方式。

將 secret 移出環境

每個子程序都能讀取環境變數,也就是代理程式執行的每個命令都能讀取。Claude Code 的 sandbox 可在每次 sandbox 命令執行前取消指定變數。在 Linux 上,sandbox 必須先安裝兩個套件:

sudo apt-get install bubblewrap socat

接著在 ~/.claude/settings.json 中:

{
  "sandbox": {
    "enabled": true,
    "network": {
      "allowedDomains": ["github.com", "*.npmjs.org"]
    },
    "credentials": {
      "envVars": [
        { "name": "GITHUB_TOKEN", "mode": "deny" },
        { "name": "NPM_TOKEN", "mode": "deny" }
      ]
    }
  }
}

deny 項目會在每次 sandbox 命令執行前取消該變數,而 allowedDomains 會將 sandbox 命令限制在你列出的主機。credentials 區塊需要 Claude Code v2.1.187 或更新版本;此資訊確認於 August 2026。於工作階段中執行 /sandbox,即可查看目前啟用的層,以及缺少哪些相依項目。決定哪些 secret 根本需要存在於該機器上,是這項工作的另一大部分;讓 secret 不落入 AI agent 可及範圍會進一步說明。

在作業系統層級阻擋對外網路連線

防火牆規則不在意模型做了什麼決定。讓代理程式以專用的 agent 使用者執行,然後丟棄該使用者送出的封包:

table inet agentcage {
  chain output {
    type filter hook output priority filter; policy accept;
    meta skuid "agent" ct state established,related accept
    meta skuid "agent" oif lo accept
    meta skuid "agent" counter drop
  }
}

這會讓 agent 使用者只能使用 loopback,因此其網路流量必須經過你在同一台機器上執行的 proxy,而 proxy 會持有主機名稱允許清單。將 https_proxy 指向該 proxy 後,client 會送出 CONNECT 請求,由 proxy 執行名稱解析,因此代理程式不需要自行進行對外 DNS(domain name system)查詢。使用 sudo nft list ruleset 驗證設定,並在代理程式嘗試連線至新目標時,觀察丟棄規則的計數器是否增加。

套用防火牆變更時,保持第二個 SSH 工作階段開啟。也要確認容器 runtime 如何處理這些規則:Docker 會建立自己的 chain,而 published Docker ports 會繞過 ufw 說明了這個意外行為的原因。

Hooks:模型無法辯解繞過的檢查

權限規則與 hook 由 Claude Code 強制執行,而不是由模型執行。文件明確指出:提示中的指示或 CLAUDE.md 會影響 Claude 嘗試執行的內容,但不會改變 Claude Code 允許的操作。這項區別就是其全部價值所在。CLAUDE.md 中的「never run curl」只是建議,注入的段落可以對此提出反駁。hook 則是會傳回結束碼的程序。

.claude/settings.json 中註冊 PreToolUse hook:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/no-egress.sh"
          }
        ]
      }
    ]
  }
}

hook 會透過標準輸入接收 JSON 格式的 tool call。結束碼 2 會阻擋該呼叫,並將標準錯誤輸出的原因顯示給 Claude。結束碼 0 會讓該呼叫繼續通過一般權限流程。

#!/usr/bin/env bash
# PreToolUse: stdin holds the tool call, exit 2 blocks it.
cmd=$(jq -r '.tool_input.command // ""')
if printf '%s' "$cmd" | grep -qE '(^|[;&|]|\s)(curl|wget|nc|ncat)(\s|$)'; then
  echo "Blocked: this repository does not allow outbound network commands." >&2
  exit 2
fi
exit 0

現在說明限制。這是針對 shell 字串的 denylist,而針對 shell 字串的 denylist 可能被繞過。python3 -c 可以不使用 curl 這個字詞來開啟 socket。make deploy 目標則可將同一呼叫再隱藏一層。請針對你能列出的錯誤撰寫 hook,並將真正依賴的邊界放在 kernel 或網路層。

權限 deny 規則有一項值得了解的比對限制。ReadEdit deny 規則涵蓋 Claude 自己的檔案工具,以及它在 Bash 中辨識的檔案命令,例如 catheadtailsed。這些規則不涵蓋自行開啟檔案的 Python 或 Node script。規則會先評估 deny,再評估 ask,最後評估 allow,因此 deny 規則無法加入 allowlist 例外。

{
  "permissions": {
    "deny": [
      "Read(.env)",
      "Read(./secrets/**)",
      "Bash(git push *)"
    ]
  }
}

監看對外傳送的內容,並閱讀 diff

代理程式執行會產生 diff 和一組網路呼叫。在合併或部署任何內容前,兩者都應檢查。針對 diff 進行 自架的安全審查流程,能發現與人工快速瀏覽不同類型的變更;了解 coding agent 會將哪些內容傳出機器,則能讓你知道正常流量的樣貌,異常請求也會更容易被發現。

目前仍未解決的問題

目前沒有可靠的方法能區分內容與指令。現有的每種防禦措施,不是具有失效率的篩選器,就是限制遭利用後果的機制。整個技術堆疊中,沒有任何機制能標記一段文字,表示該文字只能視為資料,絕對不可遵循。

篩選器確實有幫助,但也確實會失效。即使分類器能攔截大多數注入嘗試,也必須每次都判斷正確;攻擊者只需成功一次。這種不對稱性表示,防禦措施已公布的成功率,只是下一次嘗試的起點,不是保證。

最有希望的研究方向在設計層,而不是模型層。CaMeL 來自 Defeating Prompt Injections by Design(Debenedetti 等人,2025),會先從受信任的請求擷取控制流程與資料流程,確保不受信任的資料無法改變程式行為,接著在呼叫工具時強制執行 capability 檢查。該論文在 AgentDojo benchmark 上公布的數據,顯示了這種做法的代價。

ChartAgentDojo tasks solved, published figures from the CaMeL paper (2025)
The data behind this chart
[
  {
    "label": "Undefended agent",
    "tasks_solved_pct": 84
  },
  {
    "label": "CaMeL",
    "tasks_solved_pct": 77
  }
]

未受防護的 agent 解決了 84% 的工作。CaMeL 在附帶安全性保證的情況下,解決了 77% 的工作。這些數字是該論文在單一 benchmark 上公布的結果,不代表你的工作負載。兩者之間的差距,大致就是現今實際安全保證的代價。

在這類設計正式納入你日常使用的工具之前,請預期 agent 某個時刻會遭到入侵,並讓這起事件容易處理。這正是使用可拋棄式機器、限制憑證權限、控管對外連線,以及養成閱讀 diff 習慣的完整理由。

FAQ

我可以告訴 agent 忽略檔案中的指示,來阻止 prompt injection 嗎?

不行。這句話與攻擊內容位於同一個 context window 中,會與攻擊者的文字以相同優先級競爭。Claude Code 的文件清楚劃分了兩者:prompt 中的指示或 CLAUDE.md 會影響 agent 嘗試執行的工作,但不會改變工具允許執行的操作。應將指示檔視為意圖聲明,並把任何需要依賴的限制寫入 permission rules、PreToolUse hook 或 firewall rule。

如果 agent 只操作我自己的 repository,prompt injection 仍是真實風險嗎?

是,因為 repository 中充滿你未撰寫的文字。相依套件的 README 檔案、lockfile URL、test fixture、vendored code,以及 npm install 的輸出,都會在一般工作期間載入。從 issue tracker 或 documentation site 取得的內容也一樣。agent 讀取的內容越多,風險越高;而實用的 agent 通常會讀取大量內容。

在 container 中執行 agent 能解決這個問題嗎?

這只能限制損害,而且前提是你也要移除憑證。如果 container 轉送了你的 SSH agent、環境中含有 cloud credentials,且具備不受限制的 network access,攻擊者幾乎可以取得 host 上的所有權限。container 真正提供的是可直接刪除的 filesystem,以及可在其中強制套用 egress rules 的乾淨環境。請搭配只限於單一 repository 的 token 使用。

哪一項單一變更最能降低風險?

先從 agent 命令繼承的環境中移除長期有效的 credentials,再為該機器設定 default-deny egress policy。兩者合併後,會破壞致命三要件中的第三項:文字仍可能劫持 agent,但 agent 能接觸的資料無法流向任何有用的目的地。Approval prompts 與 diff review 也有幫助,但它們仰賴人員在長時間執行期間持續保持警覺,因此優先級低於前述兩項變更。