Permission and Security Architecture#
Codex enforces security boundaries on agent shell commands through a layered permission system. There are two coexisting models:
PermissionProfile— the canonical, actively developed modelSandboxPolicy— the legacy model, now derived fromPermissionProfilefor backward compatibility
The architecture has three layers:
- Wire/protocol types —
codex-rs/protocol/src/andcodex-rs/app-server-protocol/ - Core resolution logic —
codex-rs/core/src/config/resolved_permission_profile.rsresolves named/built-in/legacy profiles into aPermissionProfileSnapshot - Platform backends — Landlock and Bubblewrap on Linux (
codex-rs/linux-sandbox/), Windows Sandbox (codex-rs/windows-sandbox-rs/)
At session startup, SessionConfiguration calls permission_profile() to obtain the active PermissionProfile, then derives a SandboxPolicy from it via to_legacy_sandbox_policy() — with a fallback to codex_sandboxing::compatibility_sandbox_policy_for_permission_profile() when direct conversion is not possible.
PermissionProfile#
PermissionProfile is defined in codex-rs/protocol/src/models.rs with three variants that encode who owns sandbox enforcement:
| Variant | Meaning |
|---|---|
Managed { file_system, network } | Codex owns sandbox construction end-to-end |
Disabled | No outer sandbox is applied |
External { network } | Filesystem isolation is the caller's responsibility; Codex controls only network |
The file_system field of Managed is a ManagedFileSystemPermissions enum with two sub-variants:
Restricted { entries, glob_scan_max_depth }— sandbox enforced via an explicitVec<FileSystemSandboxEntry>Unrestricted— all filesystem access allowed, but Codex still owns the sandbox process
Three built-in named profiles are provided as string constants :
:read-only:workspace:danger-full-access
The network axis is controlled separately by NetworkSandboxPolicy, which defaults to Restricted and can be set to Enabled.
Legacy SandboxPolicy (Migration In Progress)#
SandboxPolicy is defined in codex-rs/protocol/src/protocol.rs and represents the original security model. Its four variants map roughly onto the new model:
| Variant | Equivalent |
|---|---|
DangerFullAccess | PermissionProfile::Managed { Unrestricted } |
ReadOnly | PermissionProfile::Managed { Restricted, read-only entries } |
ExternalSandbox | PermissionProfile::External |
WorkspaceWrite | PermissionProfile::Managed { Restricted, workspace entries } |
Migration path. PermissionProfile provides bidirectional conversion :
from_legacy_sandbox_policy()— upgrades aSandboxPolicytoPermissionProfilefrom_legacy_sandbox_policy_for_cwd()— same, with cwd-relative path resolutionto_legacy_sandbox_policy()— downgrades for consumers that still requireSandboxPolicy
New code should target PermissionProfile directly. SandboxPolicy is preserved as a compatibility shim.
Core Types: FileSystemSandboxPolicy and Profile Resolution#
FileSystemSandboxPolicy#
FileSystemSandboxPolicy is the operational model consumed by platform sandbox backends. It is obtained by calling PermissionProfile::to_runtime_permissions(), which returns a (FileSystemSandboxPolicy, NetworkSandboxPolicy) tuple.
Key sub-types:
FileSystemAccessMode—Read | Write | Denywith deny-wins precedenceFileSystemPath— absolute path, glob pattern, or special tokenFileSystemSpecialPath— portable tokens::root,:minimal,:workspace_roots,:tmpdir,:slash-tmp
Protected metadata directories (.git, .agents, .codex) are automatically carved out as read-only from any writable root .
Key query methods on FileSystemSandboxPolicy:
resolve_access_with_cwd()— compute effective access mode for a given pathget_unreadable_roots_with_cwd()/get_unreadable_globs_with_cwd()— enumerate paths to deny/maskto_legacy_sandbox_policy()— convert to legacy format
Profile Resolution#
ResolvedPermissionProfile in codex-rs/core/src/config/resolved_permission_profile.rs captures the resolution state:
ResolvedPermissionProfile
├── Legacy(LegacyPermissionProfile)
├── BuiltIn(BuiltInPermissionProfile) — :read-only, :workspace, :danger-full-access
└── Named(NamedPermissionProfile) — user-defined in config.toml
A PermissionProfileSnapshot is the trusted, durable form used at runtime, carrying the resolved profile plus workspace root metadata.
Platform Sandbox Backends#
Both Linux backends are given a FileSystemSandboxPolicy extracted from PermissionProfile via to_runtime_permissions().
Linux: Landlock#
apply_permission_profile_to_current_thread() in codex-rs/linux-sandbox/src/landlock.rs:
- Calls
permission_profile.to_runtime_permissions()to getFileSystemSandboxPolicy - Checks
has_full_disk_write_access()andhas_full_disk_read_access()to short-circuit when no restriction is needed - Calls
get_writable_roots_with_cwd(cwd)to enumerate paths for Landlock rules
Linux: Bubblewrap (bwrap)#
create_bwrap_command_args() in codex-rs/linux-sandbox/src/bwrap.rs:
- Accepts a
&FileSystemSandboxPolicydirectly - Calls
get_unreadable_globs_with_cwd()to identify paths to mask via--tmpfs/--dirbinds - Checks
has_full_disk_write_access()to decide whether bwrap wrapping is needed at all - Passes the policy to
create_filesystem_args()for detailed mount construction
Windows#
Windows-specific sandbox logic lives in codex-rs/windows-sandbox-rs/src/ with path resolution in resolved_permissions.rs.
TOML Configuration and Inheritance#
Permission profiles are configured in config.toml under [permission_profiles.<name>]. The schema is defined in codex-rs/config/src/permissions_toml.rs:
[permission_profiles.my-profile]
description = "..."
extends = ":workspace" # inherit from a built-in or named profile
[permission_profiles.my-profile.filesystem.entries]
"/sensitive/**" = "deny"
"~/projects" = "write"
[permission_profiles.my-profile.network]
enabled = true
Key schema fields :
extends— profile inheritance; parent is merged before child overrides are applied; cycle detection is enforcedworkspace_roots— override the set of workspace root directoriesfilesystem.entries—BTreeMap<path, access>where access isRead | Write | Deny, or a scoped sub-mapfilesystem.glob_scan_max_depth— cap on glob expansion depth
Known regression (v0.132.0 / App 26.519.22136): A canonicalization change renamed the filesystem access value none → deny. Existing config.toml files using none caused a hard parse failure that cascaded into broken chat, hooks, and MCP server loading . If upgrading from an older config, audit all = "none" entries and replace with = "deny".
V2 API Protocol Layer#
The app-server V2 protocol (codex-rs/app-server-protocol/src/protocol/v2/permissions.rs) exposes permission types to API clients with full bidirectional conversion to/from core types .
Key types:
RequestPermissionProfile— per-command permission overlay (optionalnetwork+file_systemamendments)AdditionalFileSystemPermissions— supports both the newentries-based model and deprecatedread/writeroot lists for backward compatibilityGrantedPermissionProfile— response carrying the effective permissions after approvalPermissionsRequestApprovalParams/Response— approval flow withTurnorSessionscope, optionalstrict_auto_reviewflagPermissionProfileSummary/PermissionProfileListResponse— paginated listing of available named profiles
The legacy SandboxPolicy enum is still accepted by the V2 layer with deprecation warnings and is converted to core types via to_core() .