跳至主要內容
OpenAI

2026年5月13日

工程安全防護

打造安全且有效的沙箱,讓 Codex 能在 Windows 上運作

作者:技術團隊成員 David Wiesen

載入中…

我在 2025 年 9 月加入 Codex 工程團隊時,Windows 版 Codex 還沒有沙箱實作,因此 Windows 使用者在運用 OpenAI 的程式碼編寫智慧體時,被迫在兩個不盡理想的選項之間做選擇:

  1. 程式碼編寫智慧體想執行的每一個命令幾乎都需要核准(甚至連讀取操作也是),效率低又惱人。使用 Codex 的一大好處是,你不必獨自包辦所有繁瑣工作。
  2. 啟用完整存取模式:讓 Codex 在未經核准或限制的情況下執行所有命令,雖然減少了阻礙,但也犧牲了監督機制。

Codex 是我們的程式碼編寫智慧體,會透過 CLI、IDE 擴充功能或桌面應用程式,在開發者的筆電上執行。它負責協調鍵盤前的使用者,以及在雲端執行推論的模型之間的互動。

Codex 預設會以真實使用者的權限執行,也就是說,使用者能做的事,它幾乎都做得到。這樣的能力很強大,但也伴隨風險。程式碼編寫模型可能會要求 harness 在本機執行各種命令,從執行測試、讀取或編輯檔案,到建立 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 (MIC) 完整性標記。

AppContainer

  • 簡介:AppContainer 是 Windows 原生的沙箱,採用以能力為基礎的隔離模型,適合那些能預先明確知道自己需要存取哪些資源的應用程式。
  • 採用原因:AppContainer 之所以吸引人,是因為它提供了真正的作業系統層級隔離,而不只是盡力限制風險的機制。
  • 不採用原因:Codex 並不是功能範圍明確、用途單一的應用程式。它支援的是開放式的開發者工作流程,包括 shell、Git、Python、套件管理工具、建置工具,以及智慧體判斷需要使用的其他各種二進位檔。實際上,AppContainer 並不適合解決這類問題。它雖然具備強大的隔離能力,但適用的工作負載類型相對有限,並不適合「讓智慧體像開發者一樣操作」這種需求。

Windows Sandbox

  • 簡介:Windows Sandbox 是 Microsoft 的可拋棄式輕量型虛擬機器。你會擁有一個全新的 Windows 桌面環境,具備強大的隔離機制,而且你在其中執行的所有操作,都會在工作階段結束後消失。
  • 採用原因:原因其實很明顯,此環境對各類軟體的相容性遠高於 AppContainer,而且從安全性的角度來看,也提供了更強的隔離保護。
  • 不採用原因:Codex 需要直接操作使用者實際的 checkout、工具與開發環境,而不是在另一個需要額外設定,以及主機與虛擬環境之間橋接的暫時性桌面中執行。它還有一個根本性的產品問題:Windows Sandbox 甚至不支援 Windows 家用版。

Mandatory Integrity Control (MIC) 完整性標記

  • 簡介:Windows 有一套稱為「完整性等級」的機制,例如低、中、高等級,用來決定系統對物件與處理程序的信任程度。基本規則是,低完整性等級的處理程序無法寫入高完整性等級的物件,即使一般 ACL 原本允許這麼做也一樣。例如,低完整性處理程序會被視為較不受信任,因此 Windows 會阻止它寫入一般的中完整性物件,除非那些物件已明確重新標記,允許它寫入。
  • 採用原因:從設計概念來看,MIC 是個相當優雅的方案。我們可以讓 Codex 以低完整性執行,將可寫入的根目錄重新標記為低完整性,再交由 Windows 在其他位置強制禁止寫入。這樣一來,我們就能建立一條不需要管理員權限、且背後有真正作業系統機制支撐的執行路徑。
  • 不採用原因:和 ACL 一樣,完整性標記會直接修改主機上的實際檔案系統,而這次改變的影響範圍尤其大。將工作區標記為低完整性,不只是代表「Codex 可以在這裡寫入」。它真正代表的是,一般的低完整性處理程序也都能寫入這個位置。在真實的開發者電腦上,這等於把使用者實際 checkout 出來的工作副本,變成整個主機系統的低完整性寫入區。和只針對單一沙箱設計授予精準 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 也允許建立不對應任何真實使用者的合成 SID,但它們仍然可以出現在 ACL(存取控制清單)中,用來定義誰能讀取、寫入或執行特定檔案與目錄。這讓 SID 成為非常適合沙箱使用的基礎機制:我們可以建立專屬於 Codex 沙箱的 SID,而不會干擾系統上的其他部分。

寫入受限 Token 會限制 Codex 可在哪些位置修改檔案

處理程序 Token 是 Windows 中的安全性物件,用來定義執行中處理程序的身分與權限。它們決定處理程序能執行哪些操作。寫入受限 Token 是其中一種特殊的處理程序 Token,會讓 Windows 在執行寫入操作時,額外進行一次存取檢查。

若要成功寫入,必須同時通過兩項檢查:

  1. 一般使用者身分(也就是 Token 的「擁有者」)本身必須擁有執行該操作的權限
  2. Token 的受限 SID 清單中,至少也要有一個 SID 被授予存取權
標題為「Sandbox write requires both regular user access and sandbox-write SID access」的圖表。

實際上,這些檢查讓我們能透過 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 下啟動命令,而該 Token 的受限 SID 清單包含 Everyone、目前登入工作階段的 SID,以及 sandbox-write 這個合成 SID。

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

限制網路存取

限制網路存取是沙箱中非常重要的一環;否則惡意程式碼可能會把機器上的資料外洩到網際網路。由於我們想避免要求提升權限,因此能用來強力封鎖網路流量的選項相當有限。我們原本想使用的工具,例如 Windows 防火牆,通常都需要管理員權限才能安裝。

既然無法使用 Windows 防火牆,我們能控制的範圍也受到限制。我們試圖讓子環境對開發者實際會使用的連網工具採取「預設失敗」的設計。這樣一來,Git 命令、套件安裝工具等操作在沙箱中就會直接失敗,而使用者必須明確核准任何需要連上網際網路的操作。核心概念是封住那些最明顯的逃脫路徑:將會使用 Proxy 的流量導向無效端點,讓 Git 的 HTTP(S) 傳輸也套用相同設定,並讓透過 SSH 執行的 Git 立即失敗。除此之外,我們也將一個小型 denybin 目錄加到 PATH 的最前面,並重新排序 PATHEXT,讓 stub 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 實作檔案層級的限制,因此每次調整沙箱語意,都可能伴隨高昂且複雜的 ACL 修改成本。相較之下,在 macOS 上,我們可以動態調整用來設定 Seatbelt 的 .sbpl 檔案產生方式;但在 Windows 沙箱中,光是調整 ACL,就可能需要執行耗時又繁重的操作。
  • 網路防護偏弱。如前所述,這套機制本質上只是「建議性」措施。某些自行實作網路堆疊的程式一定能繞過,而且它原本就不是為了抵禦惡意程式碼而設計。

前三個問題,其實是所有夠靈活、能支援智慧體工作流程的自訂沙箱實作,都難以避免的取捨。不過,網路抑制則是另一回事。

網路抑制極其重要

除了惡意智慧體能輕易繞過基於環境變數的網路抑制之外,許多本意良善的程式碼或二進位檔,只要不理會 Proxy 環境變數,或自行實作以 socket 為基礎的網路程式碼,也同樣能直接繞過限制。我們認為,光是這點,就足以讓我們投入資源打造更完善的沙箱模式。

為了進一步強化網路抑制能力,我們原本希望使用 Windows 防火牆。它可以針對特定使用者或程式封鎖對外網路流量。不過,基於幾個原因,我們始終無法有效建立只套用於 Codex harness 所啟動命令的防火牆規則:

  • Windows 不允許將防火牆規則比對到受限制 Token 的非主體身分。因此,我們無法把防火牆規則套用到「任何受限 SID 清單中包含我們合成 SID 的 Token」。
  • 雖然我們可以建立針對特定二進位檔的防火牆規則,但這只能限制 codex.exe 本身的網路行為。它無法套用到智慧體代表使用者啟動的其他處理程序,例如 Git 或 Python 處理程序。
  • 其他防火牆比對方式也同樣不適合。在未提升權限的設計中,以使用者為範圍的規則仍會比對到真正的 Windows 使用者,而不只是受限制的子程序。以程式路徑為基礎的規則粒度又太粗。它們可以全面封鎖 codex.exepython.exe,卻無法只封鎖這次在沙箱中執行的 python.exe 呼叫。以連接埠或位址為基礎的規則,也完全不符合我們的需求。例如,我們並不是想封鎖 443 連接埠,而是想封鎖這個特定受限處理程序樹的任意對外存取。

若要把防火牆規則精準套用到沙箱命令上,我們需要讓這些命令以獨立主體執行,而不是真正的使用者身分。這也讓我們另闢蹊徑:開始放寬「不提升權限」這項限制。

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

沙箱的下一個迭代版本,也就是目前的實作,在設定階段需要提升系統管理員權限。因此,我們稱它為「提升權限沙箱」。在 Codex 於系統上啟動命令的那一層邊界中,提升權限沙箱看起來和非提升權限版本很相似。它仍然會在受限制的 Token 下執行子處理程序,也同樣使用具有 [Everyone, Logon, Synthetic] 受限 SID 清單的 write_restricted Token。不過,這個 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 就能維持為一般、非提升權限的 harness;也能避免只適用於 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 部分

  • 在 runner 內部,OpenProcessToken(GetCurrentProcess(), ...) 會開啟 runner 自身的 Token,而這個 Token 已經屬於沙箱使用者。
  • Runner 會呼叫 GetTokenInformation(...) 擷取沙箱登入 SID,然後呼叫 CreateRestrictedToken(...) 建立最終的受限 Token。
  • Runner 仍會在自身程序內,使用該受限 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 沙箱實際運作的樣子嗎?試用看看