跳到主要內容
OpenAI

2026年5月13日

工程系統防護

在 Windows 上建構安全而有效的沙盒以支援 Codex

作者:技術團隊成員 David Wiesen

正在載入...

我在 2025 年 9 月加入 Codex 工程團隊時,Windows 版 Codex 尚未有沙盒實作,這意味著 Windows 用戶在使用 OpenAI 的編碼智能代理時,被迫在兩個不盡理想的選項之間作出抉擇:

  1. 編碼智能代理幾乎每一項想執行的指令都需要批准(甚至連讀取操作也是),效率低而且煩擾。使用 Codex 的一大好處是,你不必親自處理所有繁瑣的工作。
  2. 啟用完整存取模式:讓 Codex 無需批准或限制即可執行所有指令,這雖然減少了阻礙,但也犧牲了監督能力。

Codex 是我們的編碼智能代理,可在開發人員手提電腦上執行,無論是透過 CLI、IDE 擴充功能還是桌面應用程式。它負責協調鍵盤前的用戶,以及在雲端執行推論的模型之間的互動。

Codex 預設會以真實用戶的權限執行,這表示它可以做用戶能做的一切。這很強大,但亦可能帶來風險。編碼模型可能會指示測試核心在本機執行指令,從執行測試、讀取或編輯檔案,到建立 Git 分支,因此 Codex 的預設模式會嘗試在效能與安全之間取得適當平衡。此預設模式容許 Codex 幾乎在任何位置讀取檔案,並在你的工作區內(即你執行 Codex 的目錄)寫入檔案;除非你明確指定,否則不會存取互聯網。要在安全範圍內自動限制檔案寫入及網絡存取,Codex 需要一個真正能強制執行這些限制的沙盒環境。

沙盒是受限制的執行環境。當開發人員使用 Codex 時,其電腦的作業系統會以較低權限啟動指令,而這些限制會沿程序樹向下傳播。每個 Codex 命令從一開始都會在沙盒環境中隔離執行,而每個後代程序亦會留在同一邊界內。

顯示 Codex 沙盒作業系統隔離邊界的圖表。

Codex 需要由電腦作業系統強制執行的隔離功能,才能實現有效的沙盒。一些作業系統提供可妥善完成這項工作的工具(例如 MacOs 上的 Seatbelt、Linux 上的 seccomp 或 bubblewrap);然而,Windows 目前並未開箱即用地提供這類能力。

為了讓 Codex 在 Windows 上和在其他地方一樣既安全又易用,我們需要自行實作沙盒。

現有 Windows 工具的不足之處

Windows 提供了一些用於隔離的工具和基礎機制。雖然它們都未完全符合我們的要求,但我們仍研究了多個潛在方案,即 AppContainer、Windows Sandbox,以及 Mandatory Integrity Control 標籤。

AppContainer

  • 簡介:AppContainer 是 Windows 原生沙盒,是一種以能力為基礎的隔離模型,為那些事先清楚知道自己需要存取甚麼的應用程式而設。
  • 採用原因:吸引之處在於它提供真正的作業系統邊界,而非盡力而為的限制。
  • 不採用原因:Codex 並不是功能範圍明確、用途單一的應用程式。它可驅動開放式開發人員工作流程:shell、Git、Python、套件管理器、建構工具,以及智能代理判斷需要的其他各種二進位檔。實際上,這令 AppContainer 的設計形態並不適合解決這個問題。它確實提供了強大的隔離性,但適用的工作負載類別相對有限,並不適合「讓智能代理像開發人員一樣運作」這種需求。

Windows Sandbox

  • 簡介: Windows Sandbox 是 Microsoft 的即棄式輕量級虛擬機器。你會得到一個全新的 Windows 桌面,具備強大的隔離邊界,而你在其中所做的一切都會在工作階段結束時消失。
  • 採用原因:原因顯而易見,因為它比 AppContainer 更加兼容任意軟件,從安全角度來看,它也是一個更強的隔離箱。
  • 不採用原因:Codex 需要直接操作使用者實際下載的程式碼、工具和環境,而不是在一個需要額外配置及主客機橋接的獨立臨時桌面內運作。它還有一個根本性的產品問題:Windows Sandbox 甚至無法在 Windows Home SKU 上使用。

Mandatory Integrity Control (MIC) 完整性標籤

  • 簡介:Windows 有一個稱為「完整性層級」的概念,例如低、中、高,用於判斷系統對物件和處理程序的信任程度。基本規則是,完整性層級較低的程序不能寫入完整性層級較高的物件,即使一般 ACL 原本會允許這樣做。例如,低完整性處理程序會被視為可信度較低,因此 Windows 會阻止它寫入一般中完整性物件,除非這些物件已明確重新標記為允許它寫入。
  • 採用原因:從設計概念上來看,MIC 是個相當優雅的方案,可以低完整性執行 Codex,把可寫入的根目錄重新標記為低完整性,然後讓 Windows 在其他地方強制禁止寫入。這樣本來可以為我們提供一條非管理員途徑,背後有真正的操作系統機制支撐。
  • 不採用原因:與 ACL 一樣,完整性標籤會修改真實主機檔案系統,而在此情況下,語義上的變更尤其廣泛。將工作區標記為低完整性,並不只是代表「Codex 可以在這裡寫入」。這表示低完整性處理程序一般來說都可以寫入該處。在真正的開發人員電腦上,這會把用戶實際簽出的工作副本變成主機的低完整性匯點,風險遠高於向某個沙盒設計授予經審慎限定範圍的存取控制清單 (ACL)。即使中完整性開發人員工具仍然繼續運作,工作區的底層信任模型已經以一種難以控制,而且更難以辯解的方式改變了。

在評估所有方案後,既然都不可行,我們便開始設計自己的解決方案,為 Windows 用戶帶來良好的 Codex 使用體驗。

第一個原型:「非提升權限沙盒」

我們首個可運作原型結合了 Windows 的概念和工具,來實作我們所需的隔離機制。從一開始,其中一個目標就是讓這項功能無需提升權限也能運作,亦即 Codex 不需要單純為了設定或執行沙盒而提示用戶授予管理員權限。這意味著要弄清楚如何為兩件事設下合理限制:檔案寫入和網絡存取。

限制檔案寫入

如果完全不限制檔案寫入,就會有安全問題;如果限制過多,沙盒又會損害用戶生產力,因為它需要不斷要求批准。為了解決這個問題,我們依賴了 Windows 的兩個重要基礎元件:SID 與寫入受限制 Token。

SID 讓我們可為沙盒賦予身分

SID(即安全性識別碼)是 Windows 連結至權限的身分識別。每個用戶都有一個 SID,群組也有 SID,甚至單一登入工作階段也會有自己的 SID。例如,目前已登入的工作階段可能會有類似 S-1-5-5-X-Y 的 SID。指派給本機系統管理員群組的 SID 可能是 S-1-5-32-544

Windows 亦容許你建立不對應真實用戶、但仍可出現在存取控制清單 (ACL) 中的合成 SID;ACL 用來定義誰可讀取/寫入/執行特定檔案或目錄。這令 SID 成為我們沙盒的一個有用基礎元件:我們可以專門為 Codex 沙盒建立 SID,而不會干擾電腦上的其他任何事物。

寫入受限制 Token 限制 Codex 可修改檔案的位置

處理程序 Token 是 Windows 中的安全性物件,用來定義執行中處理程序的身份和權限。它們決定處理程序可以執行哪些動作。寫入受限 Token 是一種特定類型的處理程序 Token,會令 Windows 對寫入操作執行額外的存取檢查。

要令寫入成功,必須通過兩項檢查:

  1. 一般用戶身分(Token 的「擁有者」)必須獲允許執行該操作
  2. Token 的受限制 SID 清單中,至少有一個 SID 亦必須獲授權存取
圖表標題為:Sandbox write 需要同時具備一般使用者存取權限及 sandbox-write SID 存取權限。

實際上,這些檢查讓我們可以利用 ACL 精確界定沙盒可修改檔案系統的位置,從而在寫入操作方面提供我們所需的細緻控制。

憑藉 SID 與寫入受限制 Token,我們的非提升權限沙盒運作如下:

  1. 沙盒設定程序建立一個名為 sandbox-write 的合成 SID。
  2. sandbox-write SID 已獲授予對以下項目的寫入、執行及刪除存取權
    1. 目前的工作目錄
    2. 任何在 config.toml 中設定的額外 writable_roots
  3. 沙盒設定程序明確拒絕同一個 SID 對「可寫範圍內唯讀」位置的寫入權限,例如:
    1. <cwd>/.git
    2. <cwd>/.codex
    3. <cwd>/.agents
  4. Codex 會以寫入受限制 Token 啟動指令,而其受限制 SID 清單包括 Everyone、目前登入工作階段 SID,以及 sandbox-write 合成 SID。

這個流程有效解決了限制檔案寫入的問題,看起來相當有潛力。接下來我們需要一個方法來限制沙盒的網絡存取。

限制網絡存取

限制網絡存取是沙盒的重要部分;沒有它,惡意程式碼便可能把資料從電腦外傳到互聯網。由於我們希望避免需要提升權限,因此可用來強力封鎖網絡流量的選項十分有限。我們想使用的工具,例如 Windows Firewall,一般都不能在沒有管理員權限的情況下安裝。

若沒有 Windows 防火牆這個選項,我們可控制的範圍便受到限制。我們嘗試讓子環境針對開發人員實際會使用的各類網絡工具採用失敗即封閉 (Fail-closed) 的方式,讓 Git 指令、套件安裝器等在沙盒內失敗,而用戶必須批准任何涉及互聯網的操作。這個想法是向顯而易見的逃生口下毒:將懂得使用代理的流量送到一個失效端點,令 Git 的 HTTP(S) 傳輸亦同樣如此,並令透過 SSH 使用 Git 立即失敗。除此之外,我們將一個小型 denybin 目錄加到 PATH 的最前面,並重新排列 PATHEXT,讓充當存根的 SSH 和 SCP 腳本會先於真正的二進制執行檔被解析到。

例如,以下是我們用來限制網絡存取的一些具體環境覆寫:

  • HTTPS_PROXY=http://127.0.0.1:9
  • ALL_PROXY=http://127.0.0.1:9
  • GIT_HTTPS_PROXY=http://127.0.0.1:9
  • NO_PROXY=localhost,127.0.0.1,::1
  • GIT_SSH_COMMAND=cmd /c exit 1
顯示包含防火牆規則及專用 Windows 使用者的提升權限沙盒架構圖表。

這確實攔截了不少由一般工具發出的流量,但始終只是建議性措施。程序可以忽略環境、繞過 PATH,或直接開啟 Socket,但這樣做風險太高。

非提升權限方案伴隨取捨

就像任何有趣的軟件實作一樣,第一個原型都有一些優點和缺點。雖然它只利用少量標準 Windows 功能便完成工作,允許非常明確而細緻的檔案系統寫入,而且可在非提升權限情況下執行,減少用戶接受過多提升權限提示詞或必須在本機擁有管理員權限的需要,但它存在一些實際缺點,其中部分令它無法成為我們的最終設計:

  • 設定速度:視乎工作區目錄的拓撲而定,套用工作區 ACL 的成本可能很高。
  • 足跡:我們在開發人員系統上套用了真實 ACL,雖然因所有套用的 ACL 都只與一個自訂建立、只供沙盒使用的合成 SID 相關,所以這個足跡並不算特別具侵入性。
  • 難以變更的語義:依賴存取控制清單 (ACL) 來實作以檔案為基礎的限制,意味著變更沙盒語義的成本高昂且相當複雜。而在 macOS 上,我們可以動態變更產生 .sbpl 的方式用於設定 Seatbelt 的檔案,Windows 沙盒可能需要執行耗時且繁重的操作來調整 ACL。
  • 網絡防護薄弱。正如前文所述,這只是「建議性」措施,一些自行實作網絡堆疊的程式肯定能夠繞過,而且原本亦不是為了抵禦惡意程式碼而設計。

前三個問題是夠靈活、可支援智能代理流程的自訂沙盒實作所固有的。不過,網絡抑制的情況則有所不同。

網絡抑制太重要

除了惡意智能代理能輕易繞過基於環境的網絡抑制外,很多善意的程式碼/二進位檔,只要不遵從環境 Proxy 變數,或自行實作以 Socket 為基礎的網絡程式碼,也同樣會繞過它。我們認為,單是這一點已足以值得投資於更好的沙盒模式。

為了獲得更好的網絡抑制能力,我們希望使用 Windows Firewall,它容許我們封鎖某些用戶或程式的對外網絡流量。不幸的是,由於幾個原因,我們無法有效建立只適用於由 Codex 測試核心產生之指令的防火牆規則:

  • Windows 不允許將防火牆規則與受限制 Token 的非主體身分識別進行比對。這表示我們無法把防火牆規則套用到「任何在其受限制 SID 清單中包含我們合成 SID 的 Token」。
  • 雖然我們可以建立與特定二進位檔相符的防火牆規則,但這只容許我們限制 codex.exe 本身的網絡活動。它不會套用到智能代理代用戶產生的程序,例如 Git 或 Python 程序。
  • 其他防火牆比對維度的結構也不正確。以用戶為範圍的規則,在未提升權限的設計中仍然會比對到真正的 Windows 用戶,而不只是受限制的子行程。程式路徑規則過於粗略:它們可以一般性地封鎖 codex.exepython.exe,但無法封鎖這一次沙盒化的 python.exe 執行個體。基於連接埠或位址的規則,也完全是錯誤的政策。例如,我們並不是想封鎖連接埠 443;而是想封鎖這個特定受限制的處理程序樹的任意對外存取。

要把防火牆規則具體套用到我們在沙盒中的指令,我們需要讓它們以不同的主體執行,而不是「真實」用戶。這個做法令我們走上新方向,也就是放寬「不提升權限」的限制。

重新設計:「提升權限沙盒」

沙盒的下一個迭代版本(也就是我們目前的實作)在設定時需要提升的管理員權限。因此,我將其稱為「提升權限的沙盒」。在 Codex 於系統上啟動命令的邊界處,已提升權限的沙箱看起來與未提升權限的沙箱相同。它仍會使用受限制 Token 執行子處理程序,同樣是 write_restricted Token,且其受限 SID 清單同樣為 [Everyone, Logon, Synthetic]。但這個 Token 的主體不再是實際的 Windows 用戶,而是由 Codex 本身建立的兩個本機用戶之一:

  • CodexSandboxOffline(受防火牆規則限制的用戶)
  • CodexSandboxOnline(不受防火牆規則限制的用戶)

這個看似細微的差別,實際上對沙盒、可使用它的人,以及其設定與執行期的複雜性都有重大影響。

顯示非提升權限沙盒網絡環境覆寫設定的圖表。

從視覺上看,它與非提升權限原型相似,只是加入了防火牆規則及專用 Windows 用戶,而真正執行指令的正是該用戶。(不過,這些新概念的引入也意味著,在沙盒可以開始執行和保護指令之前,需要先完成更多設定工作。)

我們現在需要正式的設定步驟

非提升權限沙盒設計有一個簡單的設定步驟,但規模相對較小:

  • 如有需要,建立合成 SID
  • 為 sandbox-write 合成 SID 套用 ACL

然而,提升權限沙盒需要做更多事情。

  • 如尚未建立,建立合成 SID
  • 如尚未建立,建立線上與離線沙盒用戶
  • 在本機儲存新建立用戶的憑證,並使用 Windows Data Protection API (DPAPI) 加密,存放於沙盒用戶實際上無法讀取的位置
  • 建立防火牆規則,以封鎖 CodexSandboxOffline 用戶的所有對外網絡存取;若規則已存在,則驗證其正確性

在設定階段還有一個額外的複雜因素。Codex 的沙盒預期會具備與實際 Windows 用戶相同的讀取存取權。在未提升權限的沙盒中,受限制 Token 的主體 SID 為 Windows 用戶,因此已達成這一點。然而,當該主體成為新的 CodexSandbox 用戶時,這並非無需付出代價。Windows 上許多相關目錄會向「已驗證的用戶」授予讀取/執行權限。其中一個值得注意的例子是用戶的設定檔目錄。預設情況下,Windows 用戶無法讀取其他 Windows 用戶的個人設定檔目錄,因此在許多情況下,即使是簡單的檔案讀取操作也會失敗。

為了解決這個問題,我們在沙盒設定流程中加入了另一層,向沙盒用戶授予讀取 ACL,以涵蓋那些可能尚未有此 ACL 的位置。例如,一些常用的 Windows 目錄:

  • C:\Users\<real-user>
  • C:\Windows\
  • C:\Program Files\
  • C:\Program Files (x86)\
  • C:\ProgramData\

由於這份目錄清單只是盡力而為,而且在每個目錄上安裝 ACL 的成本可能相當高,我們會以非同步方式執行這項邏輯,讓對用戶而言具阻塞性的沙盒設定步驟毋須等待它們完成。

我們將安裝邏輯封裝在獨立可執行檔中,部分原因是為了只在需要時才跨越用戶帳戶控制 (UAC) 界限。但更深層的原因在於架構上:沙盒設定與 codex.exe 的職責從根本上不同。把沙盒設定邏輯保留在專用二進位檔中,可讓 codex.exe 保持為一般、非提升權限的測試核心狀態;避免只適用於 Windows 的設定機制令其他平台上的 codex.exe 膨脹;把運行時間較長的設定工作與主程序生命週期解耦;並讓我們有一個地方處理沙盒所需的不同設定路徑。

顯示正式設定沙盒步驟的圖表。

指令執行器是一個真正執行用戶指令的新二進位檔

由於 Windows 用戶及 Token 登入邊界的運作方式,我們無法像在非提升權限沙盒中那樣,繼續建立受限制 Token 並在其下產生程序。若要真正以另一個 Windows 用戶身分產生指令,我們最初的想法是以下流程:

  • codex.exe 會以實際的 Windows 用戶身份執行。接著,Codex 會依序:
    • 為沙盒用戶呼叫 LogonUserW(...)
    • 在該沙盒用戶 Token 上呼叫 CreateRestrictedToken(...)
    • 使用該受限制的沙盒用戶 Token,呼叫 CreateProcessAsUserW(...) 以啟動最終子程序。

實際上,該預期流程無法運作,因為在 CreateProcessAsUserW(...) 遇到了權限障礙。這表示 codex.exe 可以為沙盒用戶建立受限制的 Token,但無法可靠地從邊界的真實用戶一側,以該 Token 啟動子進程。我們需要一個已經以沙盒用戶身份運行的程序,這樣限制步驟和最終生成就能在沙盒用戶這一側發生,而不是在真實用戶那一側。

該需求促成了 codex-command-runner.exe 這個新的二進制檔;它的唯一職責就是產生受限制的 Token,並啟動所要求的指令。我們不再要求 codex.exe 自行完成整個流程(真實用戶 → 沙盒用戶 → 受限制 Token → 子程序),而是把流程分成兩部分:

第 1 部分

  • codex.exe 呼叫 CreateProcessWithLogonW(...),以沙盒用戶身分啟動 codex-command-runner.exe,此時尚未使用受限制 Token。

第 2 部分

  • 在執行器內部,OpenProcessToken(GetCurrentProcess(), ...) 會開啟執行器自己的 Token,而該 Token 已屬於沙盒用戶。
  • 執行器會呼叫 GetTokenInformation(...) 以擷取沙盒登入 SID,然後再呼叫 CreateRestrictedToken(...) 建立最終受限制 Token。
  • 仍在執行器內部,它會以該受限制 Token 呼叫 CreateProcessAsUserW(...),以啟動真正的子程序。
顯示用於產生受限制指令的 command runner 流程圖表。

完整圖像

Albert Einstein 曾說:「凡事都應該盡量化繁為簡,但不能簡化過頭。」本著這種精神,我們的設計足以妥善解決每個問題。最終架構有我們之前介紹過的四個層次:

  • codex.exe 本身
  • codex-windows-sandbox-setup.exe,負責所有與提升權限設定相關的工作
  • codex-command-runner.exe,用於執行受限制 Token 指令
  • 子程序

當我最初著手這個項目時,並不太清楚它最終會走向何方。我的方法是先在 Codex 與作業系統之間的邊界,為沙盒化功能加入檢測機制。這種方法非常接近 Codex 的沙盒在 MacOs 和 Linux 上的實作方式。

隨著我更深入了解 Windows 所提供的特定工具,以及經歷數十個在安全性與易用性之間權衡的決策後,系統逐漸演變成現在的形式:多個二進位檔、自訂用戶、防火牆規則、提升權限的設定步驟、非同步程序等等。

這並不是一個特別簡單的系統,但每一部分複雜性都是出於必要而加入,目的是建立一個既安全、又盡可能不妨礙用戶的沙盒。

顯示最終 Windows 沙盒架構的圖表。

在安全與真正可用性之間取得平衡

為了向 Windows 上的 Codex 用戶提供良好體驗,我們的目標是做出一個安全而不犧牲實用性的方案,Codex 的重點就是讓智能代理能在不需要你持續關注的情況下完成工作。

這個項目帶來的其中一個最大教訓是,Windows 並沒有直接提供一個可乾淨對應「安全的自主編碼智能代理」的單一基礎元件。我們需要組合多種工具與概念,才能建構出一個一致的解決方案。有些早期想法最後成了死胡同。最終設計則是多個早期原型的混合體,每個原型都解決了問題的一部分。

另一個教訓是,編碼智能代理的安全性,與較傳統的應用程式安全性是截然不同的事物。Codex 必須能配合真實的開發人員工作流程。工程工作的重點,是在與智能代理工作負載的兼容性與真正的強制執行之間取得平衡。這種張力塑造了最終設計中的各項取捨。

想看看 Codex 沙盒實際運作嗎?立即試用