Windows: 제한 토큰, 방화벽 필터, 전용 시스템 사용자 둘
seatbelt도 없고 bubblewrap도 없어요. Windows 샌드박스는 세 관문을 겹친 거예요. 토큰이 쓰기를 맡고, 전용 계정이 당신이 누구인지를 맡고, WFP가 그 신원으로 네트워크를 걸러요. AppContainer는 이 권한 모델에 맞지 않고, 세트 전체는 기본이 꺼져 있어요.
- Windows 스위치가 꺼져 있으면 플랫폼 샌드박스는 빈 값을 돌려주고, 뒤에 래퍼를 씌우지 않아요manager.rs L62
- 샌드박스가 필요하면 자기 exe에 숨은 인자를 붙여 래퍼로 써요wrapper.rs L20
- 깃발 셋으로 제한 토큰을 잘라요. 특권을 깎고, LUA, 쓰기는 교차 검사를 통과해야 해요token.rs L480
- RestrictedToken 단계에서는 읽기 검사가 capability SID를 보지 않아요windows.rs L109
- Elevated는 전용 계정으로 먼저 로그인한 뒤, 그 토큰에서 아래로 잘라요runner_client.rs L348
- 계정 이름 둘은 하드코딩이에요. Offline과 Onlinesetup.rs L50
- 네트워크 정책이 꺼져 있거나 프록시를 쓰면 Offline 신원을 고릅니다setup.rs L706
- 오프라인 계정에만 지속 WFP 필터를 설치해요wfp.rs L75
- 사용자 프로필 루트는 기본으로 .ssh 같은 디렉터리를 파내요setup.rs L56
- 백엔드가 없으면 읽기 전용 프로필에 Never를 더하고, 매칭되지 않은 명령은 Forbidden이어야 해요exec_policy_windows_tests.rs L113
동료가 Mac에서 Codex를 돌리면, 에이전트가 사용자 폴더의 SSH 개인키 경로를 건드리다 Seatbelt에 바로 막히고, 오류는 Operation not permitted예요. 같은 읽기를 Windows에 두면 Access is denied로 바뀌어요. Windows 샌드박스를 켜지 않은 기계로 바꾸면 이 명령은 ACL까지 가지도 못하고, execpolicy가 먼저 Forbidden 또는 Prompt로 판정해요.
기계 세 대, 같은 의도, 실패 세 가지. 모델이 보는 다음 문장이 다르니 다음 걸음도 따라 바뀌어요. Windows가 Unix와 같은 기본 샌드박스가 있는 척하면, 모델은 자신이 갇혀 있다고 계획하는데 실제 명령은 맨몸으로 달려요.
macOS는 이 프로세스가 어떤 경로에 어떤 동작을 할 수 있는지를 제한해요. Linux는 이 프로세스가 무엇을 보고 어떤 시스템 호출을 할 수 있는지를 제한해요. Windows에는 namespace도 seccomp도 없어요. Codex는 질문을 바꿉니다. 이 신원이 어떤 자원에 닿을 수 있는가.
토큰이 당신이 누구인지를 정해요. RestrictedToken 단계는 현재 사용자 토큰에서 자르고, Elevated 단계는 전용 계정으로 로그인한 뒤 그 토큰에서 자릅니다. 양쪽 끝은 같은 깃발 세트로 들어가요. DISABLE_MAX_PRIVILEGE, LUA_TOKEN, WRITE_RESTRICTED. 특권은 얇아지고, 쓰기는 일반 ACL과 restricting SID 교차 검사를 둘 다 통과해야 해요.
RestrictedToken 단계에서 읽기는 여전히 현재 사용자를 따라가요. 소스도 그렇게 적혀 있어요. WRITE_RESTRICTED 토큰의 capability SID deny-read ACE는 읽기 검사에 참여하지 않으니, 읽기 제한은 Elevated로 가야 하고 맨몸 실행은 바로 거절돼요.출처: codex-rs/windows-sandbox-rs/src/token.rs 480행;codex-rs/sandboxing/src/windows.rs 109–117행
주체가 할 수 있는 일과, 객체를 누가 만질 수 있는지는 격리 원시 두 세트예요. 다른 언어로 다시 써도 Windows에는 호출할 bwrap이 없어요. 물을 질문은 그대로예요. 신원이 있는지, 그 신원이 무엇을 만지도록 허용됐는지.
래핑 방식도 같은 종류의 자기 호출이에요. Windows는 자기 exe에 숨은 인자 --run-as-windows-sandbox를 붙이고, Linux가 arg0을 바꾸는 것과 같은 생각이에요.출처: codex-rs/windows-sandbox-rs/src/wrapper.rs 20행
WFP는 Windows Filtering Platform, 커널 안의 패킷 필터 프레임워크예요. 방화벽 규칙과 함께 SID로 매칭해요. SID 하나에는 네트워크 정책 한 세트만 대응해요. 같은 신원이 “네트워크가 전혀 없음”과 “프록시를 쓰거나 나갈 수 있음”을 동시에 뜻할 수는 없어요.
그래서 로컬 사용자 둘을 네트워크 신원 둘로 씁니다. CodexSandboxOffline과 CodexSandboxOnline. from_permissions는 프록시를 강제하거나 네트워크 정책이 꺼져 있으면 Offline을 고릅니다. 오프라인 신원은 아웃바운드 차단, 프록시 포트 허용 목록, 그리고 ICMP·DNS 53·DNS-over-TLS 853·SMB를 덮는 WFP 12개를 설치해요. 온라인 신원은 이 차단 세트를 설치하지 않아요.
Unix 네트워크 격리는 프로세스가 보는 NIC 뷰를 갈아끼워요. WFP는 프로세스가 호스트 네트워크 스택에 그대로 있고, 이 SID가 보내는 특정 트래픽만 막혀요. 프로세스는 여전히 NIC를 보고, 해석에 실패하고, 다른 포트에 연결할 수 있어요.
필터는 안정 GUID로 식별되고 지속 깃발을 달고 있어 재부팅 후에도 남아요. 제거와 충돌은 자동으로 뜯지 않아요. 저장소에는 사용자를 지우거나 WFP를 철거하는 함수가 없어요. WFP 실패는 치명적이지 않다고 적혀 있고, 방화벽 실패는 setup 전체를 실패로 만들어요.출처: codex-rs/windows-sandbox-rs/src/setup.rs 50–51행、706–714행;codex-rs/windows-sandbox-rs/src/wfp.rs 69–95행
신원으로 접근 제어를 하면, 신원 수가 정책 조합 수를 덮어야 해요. 네트워크 정책 단계가 둘이면 신원도 둘이에요. 출입 카드 두 장으로 건물 두 동에 들어가는 것과 같고, WFP라는 구체 API와는 상관없어요.
왜 AppContainer를 안 쓰느냐고 물을 거예요. 기본 읽기 범위가 좁고, 임의 경로 읽기는 호스트 DACL을 고쳐야 해요. Codex는 workspace가 읽히고 플랫폼 루트가 읽히며 deny 목록으로 파내야 해요. 제한 토큰 더하기 ACL이 이 권한 모델에 더 맞아요. codex-rs/에서 AppContainer를 검색하면 업무 코드는 0건이에요. 대조 저장소 DeepSeek Harness는 같은 판단을 평범한 문장으로 적었어요. AppContainer는 임의 경로 읽기를 못 해요.
Elevated가 읽기와 네트워크까지 잡으려면 계정을 만들고 UAC를 띄우며 방화벽과 WFP를 고쳐야 해요. 기업 정책이 로컬 사용자 생성을 막을 수 있어요. 기본을 Disabled로 두면, 이 제품 마찰을 첫 활성화에 남겨 두는 셈이에요.
열거형 자체가 기본값을 꺼짐으로 적어요. windows.sandbox를 쓰지 않고 legacy flag 둘도 없으면 Disabled로 떨어져요. legacy flag는 이미 Removed로 표시돼 있어요.
pub enum WindowsSandboxLevel {
#[default]
Disabled,
RestrictedToken,
Elevated,
}
openai/codex 기준, 확인 파일 codex-rs/protocol/src/config_types.rs, commit 4f39251a01, 확인일 2026-08-22. 코드 블록은 소스 원문을 유지하고, #[default]는 Disabled에 못 박혀 있어요.끄고 나면 플랫폼 샌드박스는 정말 사라져요. get_platform_sandbox(false)는 Windows에서 빈 값을 주고, select_initial은 그걸 SandboxType::None으로 바꿔요. 명령은 정책 층에서 여전히 막힐 수 있어요. Windows 테스트가 이걸 잠가요. 백엔드가 없으면 읽기 전용 프로필에 Never 승인을 더하고, 매칭되지 않은 cmd.exe /c dir은 Decision::Forbidden이어야 해요.출처: codex-rs/sandboxing/src/manager.rs 62–72행、301행;codex-rs/core/src/exec_policy_windows_tests.rs 113–130행
격리 깊이는 설치 예산에 묶여 있어요. 모델이 저장소를 실수로 고치는 것만 막으면 현재 사용자 제한 토큰으로 충분해요. 비밀을 읽어 이 기계 밖으로 보내는 것까지 막으려면 읽기 제한과 신원 기준 네트워크 필터가 더 필요해요. 세울 수 없는 벽을 약속하면 모델과 사용자 모두 그 벽을 기준으로 결정해요.
플랫폼 능력이 다를 때 대외 문구는 실제로 켜진 그 층으로 써야 해요. macOS는 기본으로 Seatbelt가 있고, Windows 기본은 Disabled일 수 있어요. 샌드박스가 있다고 들으면 기계 세 대가 같은 벽이라고 생각해요.
DeepSeek Harness: 같은 깃발 세트, 쓰기 제한에서 멈춰요
DSH의 @deepseek-ai/dsh-sandbox-windows-acl은 같은 CreateRestrictedToken 깃발을 씁니다. DISABLE_MAX_PRIVILEGE | LUA_TOKEN | WRITE_RESTRICTED. README는 읽기·네트워크·프로세스 가시성이 제한되지 않는다고 쓰고, enforcement는 partial이에요. 전용 계정을 만들지 않고 WFP도 설치하지 않아요.
설계 노트는 AppContainer를 거절한 이유를 못 박았어요. AppContainer 토큰에는 환경 읽기 권한이 없고, 임의 경로 읽기는 미리 허가해야 해서 harness 읽기 모델에 맞지 않아요. mxc가 거절된 이유는 OS 버전 하한이 너무 새롭고, 임의 경로 읽기는 여전히 호스트 DACL을 고쳐야 해서예요. Codex Elevated는 계정 둘과 WFP로 읽기와 네트워크 신원까지 넣었고, 대가는 설치 마찰과 제거 잔여물이에요. DSH가 고른 것은 설치되고 돌아가는 쪽이고, 읽기와 네트워크는 다른 메커니즘에 남겨 둬요.
DSH token.ts / win32-abi.ts / README / 설계 노트 확인 · 2026-08-22 · DSH · 샌드박스: seatbelt에서 실행 세계로Claude는 실행을 거절하고, Grok은 플랫폼을 unknown으로 표시해요
Claude Code는 네이티브 Windows에 샌드박스가 없다고 분명히 적어요. 기업 정책이 샌드박스를 요구하고 샌드박스 없는 명령을 금하면 PowerShell은 바로 실행을 거절하고, 문구는 sandboxing is not available on native Windows예요. 격리 층이 있는 척하지 않아요. Grok Build의 샌드박스 crate는 플랫폼을 linux/landlock과 macos/seatbelt로 쓰고 나머지는 unknown이에요. xai-grok-sandbox에서 AppContainer, RestrictedToken, CreateRestrictedToken을 검색하면 대응 구현이 없어요.
답은 세 단계로 나뉘어요. Claude와 Grok은 Windows에서 파일시스템 샌드박스를 하지 않아요. DSH는 쓰기 제한을 했고 설치되며, 읽기와 네트워크는 다른 메커니즘에 남겨 둬요. Codex는 신원·ACL·WFP·전용 계정을 전부 넣어서 마찰이 더 크고, 그래서 기본이 꺼져 있어요.
Claude PowerShellTool.tsx, Grok types.rs 확인 · 2026-08-22 · Grok · 샌드박스 Profile 다섯 가지같은 읽기, 단계를 바꾼 뒤 어디에 걸리는지
데모 동작은 워크스페이스 밖 읽기에 두고, 단계를 Elevated에서 RestrictedToken으로 내리세요. 먼저 예측을 적어요. 어느 관문이 허용하고, 어느 관문이 사라지며, 끝이 Access is denied인지 통과인지. 그다음 재생을 눌러 맞춰 보세요.
심화 질문: 동작을 네트워크 보내기로 바꾸세요. RestrictedToken 단계에서 세 번째 관문이 왜 건너뛰기이고, Elevated 단계에서야 WFP가 켜지는 이유는 뭘까요.