返回所有文章
發佈於 · 作者 Renaud Deraison

守衛與 shell 各說各話

6 月 30 日,Adversa AI 揭露了 GuardFall——一類繞過手法,讓數十年前的 bash 小把戲,直接從 11 款最熱門開源程式設計代理中 10 款內建的命令守衛前面走過去。原因很簡單,而且在那一層無法修補:守衛讀的是文字,而 bash 在執行前把文字改寫了。Bromure Agentic Coding 從未賭守衛能正確讀懂 bash。它賭的是——命令在執行的那一刻並不重要。

某個程式設計代理的安全過濾器看著字串 r''m -rf ~,看到一個它不認得的 命令,於是放行了。bash 看著同一個字串,丟掉空引號,執行了 rm -rf ~。 過濾器與 shell 讀的是兩種不同的語言。這道裂縫不是某個工具的錯誤——它是 「用模式比對代理所輸入的命令來守護代理」這整個念頭本身,而 Adversa AI 剛剛證明,它在受測的十一款代理中有十款失守。

命令守衛是每個代理式程式設計工具最先伸手去拿的功能。代理提出一個 shell 命令;過濾器檢查該字串;若看來具破壞性——rm -rf、以管線接的 curl … | sh、對裸裝置的 dd——守衛就攔下它或請你確認。它正是讓你敢打開 自動執行、覺得安全的那個東西。2026 年 6 月 30 日,Adversa AI 發表了研究,The Hacker NewsSecurityWeek 都跟進報導,指出這項功能只是演戲。

GuardFall 是什麼

他們稱它為 GuardFall,而它不是一個 CVE。沒有東西可以修補,因為它不是某個 元件裡的瑕疵——而是其中兩個元件之間的錯配。用 Adversa 的話說:*「守衛檢查 原始文字,而系統 shell(bash)在執行前會展開、去引號並改寫文字。」*過濾器 比對的是模型吐出的字串。bash 接著讓那個字串經過去引號、變數展開與命令替換, 才會有任何一個位元組被執行。兩者從不看同一個命令,因此任何能撐過改寫、卻撐 不過比對的把戲,都能一路暢行。

這些把戲比大多數使用這些代理的人還老。Adversa 把它們歸成五類:

  • 去引號。 r''m 對正規表示式是兩個記號,對 bash 卻是單字 rm,因為 它丟掉了空引號。
  • 變數展開。 rm$IFS-rf$IFS/ 對模式比對器看來像一個長字;bash 展開內部 欄位分隔符,得到 rm -rf /
  • 命令替換。 $(echo rm) -rf / 把危險的動詞藏在 $( ) 裡;檢查外層 字串的守衛從未看見 rm
  • 編碼管線。 echo <base64> | base64 -d | sh——每個可見的記號都無害; 酬載只在解碼後才存在。
  • 替代的破壞性旗標。 完全沒有 rmfind /x -deletetar -C / -xsed -i。一份圍繞 rmdd 建立的黑名單永遠不會觸發。

Adversa 拿這些把戲對付十一款最常用的開源程式設計與電腦操作代理——其中包括 opencode、Goose、Cline、Roo-Code、Aider、Plandex、Open Interpreter、 OpenHands、SWE-agent 與 Hermes 專案,到五月時合計約 548,000 顆 GitHub 星。 十款倒下。唯一撐住的 Continue,撐住有個明確的理由:它不比對原始字串,而是在 決定之前,照 bash 將要做的方式把命令記號化——吸收引號、解析 $IFS、從替換中 取出內層命令、對任何以 sh 結尾的管線做標記。它為 shell 建模。那是守衛這一層 唯一有效的做法,是真實而細心的工程,如今其餘十款都得正確地重現它,並在 bash 的每一個邊界情況與每一次發行中維持正確。

代理吐出一個字串r''m -rf ~守衛 · 比對原始文字看到這些記號r''m · -rf · ~沒有 rm → 放行 ✓BASH · 改寫後執行丟掉空引號rm -rf ~家目錄沒了
守衛為何會輸。代理吐出一個字串;模式比對的守衛照字面讀它,找不到任何破壞性的東西;bash 接著把同一個字串展開、去引號、改寫成 rm -rf,那時過濾器的裁決才有意義。守衛與 shell 看的是兩個不同的命令——正是 GuardFall 的整個前提。

問題出在那一層,不是正規表示式

誘惑在於把 GuardFall 讀成「十支團隊寫了弱過濾器」。那錯過了 Adversa 想說的。 他們最鋒利的一句,並非針對任何一款代理:

一個能在操作者主機上執行任意 shell 命令、僅靠一條比對 LLM 所吐字串的正規 表示式來把關的代理,並不是防禦。它在完全啟用且正確設定的狀態下失守,因為 字串比對無法為 bash 將執行什麼建模。

完全啟用且正確設定。 這不是你能關掉的錯誤設定。守衛坐在它永遠贏不了的那唯一 一層——在一個寫字串的模型與一個重新詮釋字串的 shell 之間——並要求一個文字比對器 去預測一個圖靈完備展開器的輸出。Continue 的「記號化並正規化」做法,藉由教守衛 像 bash 那樣思考來縮小裂縫,而如果你打算在那裡留一個守衛,這是正確的一步。 但那是一項逐代理、逐版本的承諾,要在解析上勝過一個花了三十年不斷累積改寫命令 手法的 shell。另外九款熱門代理,正說明了要弄錯有多容易。

Adversa 的補償性控制很說明問題。在任何結構性修補之前,他們要你$HOME 導向一個受限沙箱,保留專案存取但移除憑證,剝掉自動執行旗標,別讓代理在不可信的 拉取請求上執行,並把每個儲存庫設定檔都當成不可信的程式碼。再讀一遍那份清單。 那不是關於怎麼寫一個更好的過濾器的建議。那是關於怎麼在代理周圍造一個箱子的 建議——好讓過濾器輸的時候(他們正告訴你它會輸),漏過去的命令落在一個傷不了你的 地方。

那個箱子就是產品。

Bromure 從未要求守衛去讀 bash

Bromure Agentic Coding 從 GuardFall 所證明的假設出發:代理遲早會執行一個你 未曾核准的命令。它不試圖靠讀它來攔下那個命令。它把整個代理——Aider、Goose、 opencode,十款裡你喜歡哪個都行——放進一台可拋棄的 Linux VM 裡執行,並把每一個 網路請求都經由主機上的代理伺服器送出去。設計上的問題從不是「過濾器會認出這個 rm 嗎?」而是「當那個 rm 執行時,它能碰到什麼?」

把 GuardFall 自己的酬載放進那個箱子走一遍。

這個攻擊的重點,一旦一個藏起來的 r''m 或一個 find /x -delete 讓一份設了 陷阱的 README 溜過守衛,就是竊取這個帳號能碰到的東西——Adversa 點名 ~/.ssh~/.aws、雲端憑證、「任何躺在你家目錄裡的東西」——並把它抹除或外洩。在 Bromure 裡,那些是本就不在箱子裡的東西。真正的機密從不進入 VM:當你給一個設定檔一個 AWS_SECRET_ACCESS_KEY、一個 GitHub 權杖、一個 Anthropic 金鑰時,代理的環境拿到 的是一個 brm_… 佔位符,而主機代理伺服器只在送往應收取它的供應商的對外請求上, 於線路上把假的換成真值。Adversa 建議的緩解——把 $HOME 搬到某個「保留專案存取 但移除憑證」的地方——是 Bromure 設定檔預設就會做的事的手工、局部版本,對每一項 憑證,都不必你去寫腳本。

沒有 — 真家目錄、真金鑰GuardFall 酬載執行cat ~/.aws/credentials wJalrXUtn…cat ~/.ssh/id_ed25519 -----BEGIN…攻擊者拿到可用金鑰真帳號、真災情外洩有 BROMURE — 可拋棄箱、誘餌同一酬載執行(VM 內)cat ~/.aws/credentials brm_d4e5f6…cat ~/.ssh/id_ed25519 brm_a1b2c3…主機代理 — 真金鑰從不進入 VM;僅在真正供應商請求時換入攻擊者拿到 brm_… 誘餌什麼都認證不了家目錄是可拋棄複製外洩已記 · 重置即抹去
GuardFall 的酬載在兩個世界裡都能通過——Bromure 並不阻止命令執行。左:在一般主機上,被注入的命令讀取真正的 ~/.ssh 與 ~/.aws 內容並送出;它成功了。右:在 Bromure 裡同一個命令照樣執行,但家目錄是一份可拋棄的複製,它找到的憑證是 brm_… 誘餌,真正的金鑰留在主機上,而外洩的嘗試是安全記錄裡被記下的一行。命令執行了,卻什麼也碰不到。

破壞性的那一半也一樣落地。一個溜過守衛的 find /x -deletesed -i 確實 會執行——Bromure 不假裝它攔下了命令。它對著一個從共享基底複製而來的家目錄執行, 在一台你能從選單(而非事故表單)抹除並重置回基底的 VM 上。酬載可能植入的持久性, 死在那次重置。而且因為每個請求都經由主機代理離開,GuardFall 那類編碼管線本要藏起 的 curl … | base64 -d 信標並非隱形:它是安全記錄裡一行被記下、可歸屬的紀錄。

線路勝過 shell 的那唯一之處

Bromure 確實保有一個破壞性操作的守衛——它主機端的 Guardrails,我們曾在一個 Cursor 代理於九秒內刪掉一個正式資料庫 後寫過它。而在這裡,那一層對 Bromure 有利,因為 Guardrails 並不住在 GuardFall 住的地方。

GuardFall 是一種 shell 文字 攻擊。它的五類——去引號、$IFS$( )、base64 管線、稀奇的旗標——每一類都是 bash 如何解析一個字串的把戲。Bromure 的 Guardrails 從不讀那個字串。它們坐在主機代理伺服器上,對代理向它所理解的供應商發出的結構化 API 呼叫進行分類——對 AWS、Kubernetes、git 平台、受管資料庫的實際 HTTPS 請求 ——並對破壞性的那些在線路上回一個硬性的 403,就在 VM 裡被入侵的代理關不掉它的 地方。等到一個請求抵達那一層,它已經是一個被解析好的 DeleteDBInstance,而不是 一串引號與錢字號。沒有 shell 改寫可以夾帶任何東西,因為路徑上沒有 shell。 GuardFall 的整套技法都需要在檢查與行動之間有一個 bash;在 Guardrails 的路徑上, 沒有。

這劃出了哪條界線,把話說清楚

命令照樣執行

Bromure 是隔離,不是攔截。一個溜過你代理自身守衛的 GuardFall 酬載,會在 VM 裡執行。Bromure 改變的是災情範圍:一個可拋棄的家目錄、誘餌憑證、一條被記錄的 出口路徑。如果你需要命令本身被拒絕,那是你代理的守衛的工作——而 GuardFall 正是你不該倚賴它的理由。

本地破壞靠的是可拋棄性,不是阻擋

對 VM 內檔案的 find /x -delete 不是供應商 API 呼叫,所以 Guardrails 不去 把關它。對 VM 內破壞的答案,是 VM 可拋棄、而真正的工作住在一個掛載的儲存庫與 主機上,不是那個刪除被擋下。是重置回基底,不是回滾。

替換涵蓋你所設定的憑證

brm_… 的替換保護你放進設定檔的機密——模型金鑰、雲端與 git 權杖、受管資料庫 端點。一個你親手貼進箱內某檔案的密碼,或一個腳本在工作階段中途寫到磁碟的權杖, 就只是一個檔案。把機密放在代理中介裡,別放在工作區裡。

出口被記錄,並非預設禁止

外洩信標會出現在安全記錄裡;那是歸屬,不是防止。沒有任何真的東西離開,因為 憑證是誘餌,但如果你需要禁止 VM 與任意主機通訊,那是一條你仍要自行設定的網路 政策。

可以推而廣之的部分

GuardFall 是對一條整個類別一再重新學到的規則的清晰陳述:你無法靠要求一個過濾器 去預測它的命令將做什麼,來讓一個代理變安全。模型寫下一個字串,而在它底下某處, 一個 shell、一個 API 用戶端、一個套件管理器,或一個瀏覽器,用它自己的規則重新 詮釋那個字串。每一種檢查代理輸出、並指望它與最終行為相符的防禦,都是在對那道 裂縫下反向注,而裂縫總是贏——它在這裡贏了,在用 Adversa 的話說完全啟用且正確設定 的十一款代理中的十款上。

Bromure Agentic Coding 不下那個注。它假設命令會執行、守衛有時會漏、而你請進門的 代理可能被一份你從未寫過的 README 反過來對付你。所以它讓命令執行的那個地方變得 不值得攻擊:主機上的真金鑰、箱子裡的誘餌、一台可拋棄的 VM、一個留給沒有 shell 能改寫它的線路的硬性阻擋,以及一份所有試圖離開之物的紀錄。你可以執行 GuardFall 破掉的那十款代理中的任何一款——無論哪一款,結果都是同一個可拋棄的箱子。它是自由 且開源的