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

符號連結走出了工作區,卻一無所獲

GhostApproval 展示了一個惡意儲存庫如何誘騙六款 AI 編碼助理,在它們本該待著的資料夾之外寫入與讀取檔案:在開發者自己的機器上植入 SSH 金鑰、劫持 shell 啟動流程、讀走雲端憑證。這個 bug 是一個跟隨符號連結的錯誤。它之所以造成傷害,是因為代理跑在一台裝滿你祕密的機器上。Bromure Agentic Coding 改變的正是後面這個事實。

編碼代理是一支你邀請來讀寫你筆電上檔案的程式。GhostApproval 講的是:當一個惡意儲存庫說服這支程式往界線外多寫那麼一點—— 寫進你的 SSH 金鑰、你的 shell 設定、你的雲端憑證——會發生 什麼事。這個 bug 又老又乏味。傷害來自代理所站的位置。

2026 年 7 月 8 日,Wiz 的研究團隊發表了 GhostApproval, 記述同一個缺陷以六種不同形式出現在六款熱門 AI 編碼助理中: Amazon Q Developer、Anthropic 的 Claude Code、Augment、Cursor、 Google 的 Antigravity,以及 Windsurf。細節因產品而異,形狀卻 如出一轍。你打開一個儲存庫——也許是某人傳來的教學,或同事 請你看看的專案——把你的編碼代理指向它。而在那個儲存庫的某個 角落,有一個不是檔案的檔案。

一個不是檔案的檔案。

這個把戲是符號連結(symbolic link,symlink):一個看起來像 檔案的檔案系統項目,實際上是指向另一條路徑的路標。符號連結是 再正常不過、有四十年歷史的 Unix 功能。而這個安全問題——編錄 為 CWE-61—— 幾乎一樣老:一支程式若打開符號連結而不檢查它指向哪裡,就等於 讓攻擊者把它引向作者從未打算碰的檔案。

在 GhostApproval 裡,惡意儲存庫附上一個名字看似無害的符號 連結,比如 project_settings.json。它裝的不是設定,而是一個 指向 ~/.ssh/authorized_keys~/.zshrc~/.aws/credentials 的指標——這些檔案坐在開發者的家目錄裡, 遠在儲存庫資料夾之外。接著,一份 README——或依儲存庫指示 行事的使用者——要求代理「更新 project_settings.json」。代理 跟著路標走,沒有解析它通往何處,於是寫進了真正的目標。

工作區 — clone 的儲存庫project_settings.json看似設定檔 — 實為符號連結「請更新 project_settings.json」— 儲存庫的 README,代理照辦工作區邊界假定安全 — 這個假定就是 bug開發者的家目錄(~)— 儲存庫之外~/.ssh/authorized_keys~/.zshrc~/.aws/credentials真實主機存取 · 真實雲端金鑰 · 每次登入都會執行的 shell代理跟著連結走CWE-451 — 提示誤述了即將發生的事代理的真正目標:~/.ssh/authorized_keys你看到的:「編輯 project_settings.json?」[是]
GhostApproval 的運作方式。clone 下來的儲存庫裡藏著一個偽裝成設定檔的符號連結;它指向工作區之外,直達開發者的家目錄。當代理編輯它時,寫入落在 ~/.ssh/authorized_keys、~/.zshrc 或 ~/.aws/credentials 上。更糟的是,在好幾款產品裡,確認提示顯示的是代理被要求編輯的那個無害名字,而不是它即將碰觸的敏感路徑(CWE-451)。

有兩件事讓這不只是個趣聞。第一是這些目標各自的作用。往 ~/.ssh/authorized_keys 追加一行,就把攻擊者的金鑰裝進你的 機器,換得免密碼的 SSH 登入。寫入 ~/.zshrc,攻擊者的命令就 會在你每次打開終端機時執行。Augment 的代理甚至不必寫入:被 問到「專案裡的 AWS 金鑰」時,它循著符號連結一路走到憑證檔案, 把祕密直接印進了對話——連一個提示都沒有。

第二是一個更隱微的失誤,Wiz 將它歸類為 CWE-451, 使用者介面誤述。在 Claude Code 上,代理自己的內部推理已經認出 那是個 shell 設定檔,確認對話框卻仍然只問「要對 project_settings.json 進行這個編輯嗎?」人類唯一能攔下危險 的那一刻,看到的卻是錯的資訊。Windsurf 與 Amazon Q Developer 更糟:它們在顯示核准按鈕之前就把檔案寫進了磁碟,於是對話框 成了復原提示,而不是閘門。攻擊者的 SSH 金鑰早已就位。

大多數廠商後來都修補了自家的變體:Cursor 在 v3.0 (CVE-2026-50549)、 Amazon Q 在 language-server 1.69.0 (CVE-2026-12958)、 Google 在 Antigravity v1.19.6,Claude Code 則加上了符號連結 解析警告。這是正確的回應,你也應該裝上這些更新。但逐一修補 每款產品的符號連結處理,修好的是機制賭注卻留在原地。

賭注多大,取決於代理站在哪裡。

這裡的每一個酬載都建立在同一個假設上:抵達開發者的家目錄是 值得的。~/.ssh 裡有一把能打開真實機器的金鑰, ~/.aws/credentials 裡有一枚有效的雲端權杖,而 ~/.zshrc 會在你明天打開的 shell 上執行。符號連結是路,終點則是一台裝著 你全部工作生活的筆電。

大多數針對編碼代理的攻擊都仰賴同一個假設。代理是一支能力強大、 不設邊界的程式,讀著不受信任的輸入(儲存庫、網頁、套件 tarball、工具輸出),並在一台憑證以明文檔案躺在幾層目錄之外的 機器上採取行動。你儘管加固代理;它和你跑的其他一切共用同一個 檔案系統、同一串鑰匙。攻擊者會不斷鋪出新路。問題在終點。

Bromure Agentic Coding 拿走了終點。代理、它衍生的 shell、它 clone 的儲存庫,以及其中的任何程式碼,全都跑在一台依設定檔 區隔的 Linux VM 裡:一台由 hypervisor 與你的 Mac 隔開的獨立 機器。你真正的憑證從不進入其中。我們在 為什麼 Bromure Agentic Coding 不是沙箱 裡寫過為什麼一道邊界勝過一座牢籠;GhostApproval 是這個想法的 一次乾淨檢驗,因為它逃出的正是工作區資料夾——整個產業賴以 維繫的那道小小邊界。

代理跑在你的 Mac 上一台機器,一個家目錄代理 + clone 的儲存庫符號連結 → ~/.ssh/authorized_keys~/.ssh/authorized_keys真實~/.aws/credentials真實~/.zshrc真實結果攻擊者的 SSH 金鑰裝上了,雲端金鑰被讀走、shell 被劫持——就在你日常工作的那台機器上。Bromure Agentic Coding依設定檔區隔的 VM — 可拋棄代理 + clone 的儲存庫符號連結 → ~/.ssh/authorized_keys寫入照樣發生~/.ssh/authorized_keys~/.aws/credentials~/.zshrc客體結果金鑰加進了一台用完即丟的客體機,外洩的是占位用的雲端金鑰——你的 Mac 從未進過這個房間。
同一場符號連結逃逸,兩台機器。在一般的配置下,代理跑在你真正的 ~/.ssh 與 ~/.aws 旁邊,跟著連結走就碰得到有效的金鑰和一台值得接管的機器。在 Bromure 裡,代理跑在一台依設定檔區隔的 VM 中;符號連結照樣解析,寫入照樣發生,但它落在一台可拋棄的客體機上,那裡的家目錄只放著樁:在公開網際網路上毫無意義的占位檔案。你 Mac 的檔案系統位在 hypervisor 這條線之下,從未被掛載進 VM。

在 VM 裡把同一場逃逸再走一遍。符號連結照樣解析(Bromure 根本 不需要偵測或阻擋這個把戲),寫入也照樣落在 ~/.ssh/authorized_keys 上。但那個檔案住在客體機的家目錄 裡,在一台沒有任何路徑通往你 Mac 的可拋棄機器上。攻擊者的 金鑰如今換來的,是登入一台你即將丟棄的機器——一台打從一開始 就無法從外部連上的機器。

竊取憑證的變體收場更是平淡。當一個被入侵的代理循著符號連結 走到 ~/.aws/credentials、把內容印進對話時,它讀到的是一個 :一份格式無誤、金鑰卻無法向任何地方驗證的憑證檔案。 真正的權杖留在主機上,由一個仲介看管——它只在你允許的對外 呼叫發出的那一刻,才把占位符換成真祕密,再從回應裡把它換 回來。一個掃遍檔案系統找祕密的依賴套件或被誘騙的代理,找到的 只是擺設。

那個提示,以及第二張網。

Bromure 在不同的層面上迎擊 GhostApproval 的兩道利刃。

第一道是確認提示的問題,CWE-451:對話框說的是 project_settings.json,代理心裡明白指的卻是 ~/.zshrc。 Bromure 直接拿掉了檔案寫入對話框;發生在可拋棄 VM 裡的寫入, 碰的是一顆可消耗的磁碟,不需要核准。Bromure 把人放回真正的 邊界上,攔的是那些會離開 VM、改變狀態的呼叫:一次 git push、一句資料庫 DROP、一個雲端 Terminate。它們會在 主機上暫停,讓你看到字面上的操作本身,而不是代理寫的摘要。 這正是 GhostApproval 違背的那條原則,只是用在了真正要緊的 地方:一個決定將在沙箱之外產生後果的那一刻,就是人類該看到 真實呼叫的那一刻。

第二道利刃是啟動整場攻擊的那條指令:一份 README 要代理 「更新 project_settings.json」,模型把它當成脈絡讀進去,又 當成命令執行。Bromure 會先在裝置上讀過那些不受信任的內容: 一個本機分類器替傳入的檔案、網頁抓取與工具輸出打分,找出那些 根本不該出現在那裡的指令;而代理奉為長期命令的規則檔案,會 受到更嚴格的檢查。分類器會漏掉某些措辭,而且光靠隔離就足以 擊敗這個攻擊;把它當成第一張網底下的第二張網。

這條線救不了你的地方。

隔離是一種特定的形狀,而形狀有它的邊。

設定檔是長壽的,所以持久化會持續存在。

那個 ~/.zshrc 酬載會在該設定檔的 VM 裡下一次打開 shell 時執行。它跑在一台沒有主機金鑰、只有樁憑證的客體機 上——一個空房間裡的 shell——但它確實會跑。如果某項任務 讓你放心不下,就在一個全新的設定檔裡跑它。

你掛載什麼,就暴露什麼。

VM 預設不放你的任何東西。如果你刻意把一個真實的主機目錄 掛載進某個設定檔,或把一枚有效權杖貼進客體機,符號連結 逃逸就搆得到它。這條線把你的祕密擋在外面;把它們放回去, 是只有你能做的選擇。

刻意限縮仲介的範圍。

樁意味著磁碟上的檔案一文不值,但仲介仍為你允許的呼叫握著 真權杖。一個只需要讀取儲存庫的設定檔,不該帶著一枚能發佈 它的權杖。隔離控制住爆炸的範圍;限縮則決定它最大能有多大。

工作區是錯的那道牆。

GhostApproval 骨子裡講的,是整個產業同意把什麼當成邊界的 故事:工作區資料夾——把「代理待在專案目錄裡」當成一項安全 性質,而不是一個任何路標都能繞開的預設值。符號連結本身是廠商 能修、也確實修了的 bug。把邊界畫在資料夾上,你就得永遠證明 沒有任何符號連結、任何 ..、任何巧妙的路徑會離開它。把邊界 畫在 hypervisor 上,這個問題就不再重要——因為代理逃的 地方,是一台從未放過你任何東西的可拋棄機器。

裝上廠商的修補吧;它們關上的是真實存在的洞。但這個洞之所以 傷人,是因為代理當時就站在你的筆電上,離你的金鑰只有一層 目錄。把代理移出你的筆電,下一次逃逸(一定會有)抵達的,是 一個空房間。Bromure Agentic Coding 免費、 開源,今天就已上線。