Commit Graph

58 Commits

  • windows-sandbox: remove SandboxPolicy runner plumbing (#23813)
    ## Why
    
    The Windows sandbox runner still carried the old `SandboxPolicy`
    compatibility path even though core now computes `PermissionProfile`.
    That meant Windows command-runner execution could only see the legacy
    projection, so profile-only filesystem rules such as deny globs were not
    part of the runner input.
    
    ## What Changed
    
    - Removed the Windows-local `SandboxPolicy` parser/export and deleted
    `windows-sandbox-rs/src/policy.rs`.
    - Changed restricted-token capture/session setup, elevated setup,
    world-writable audit, read-root grant, and command-runner session APIs
    to accept `PermissionProfile` plus the profile cwd.
    - Bumped the elevated command-runner IPC protocol to version 2 because
    `SpawnRequest` now carries `permission_profile` /
    `permission_profile_cwd` instead of the legacy `policy_json_or_preset` /
    `sandbox_policy_cwd` fields.
    - Updated core exec, unified exec, debug-sandbox, TUI setup/grant flows,
    and app-server setup to pass the actual effective `PermissionProfile`.
    - Left regression coverage asserting the old IPC policy fields are
    absent and the runner serializes tagged `PermissionProfile` JSON.
    
    ## Verification
    
    - `cargo test -p codex-windows-sandbox`
    - `cargo test -p codex-core windows_sandbox`
    - `cargo test -p codex-app-server
    request_processors::windows_sandbox_processor`
    - `just fix -p codex-windows-sandbox -p codex-core -p codex-app-server
    -p codex-cli -p codex-tui`
    - `just fix -p codex-cli -p codex-tui`
    - `just fix -p codex-windows-sandbox -p codex-tui`
    - `rg "\\bSandboxPolicy\\b" codex-rs/windows-sandbox-rs` returned no
    matches.
    
    Note: `cargo test -p codex-cli` was attempted but did not reach crate
    tests because local disk filled while compiling dependencies (`No space
    left on device`). The targeted clippy pass compiled the affected CLI/TUI
    surfaces afterward.
    
    
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/23813).
    * #24108
    * __->__ #23813
  • cli: support --profile for codex sandbox (#24110)
    ## Why
    
    `codex sandbox` now always runs the host sandbox backend, so it should
    accept the same profile selection mechanism as the rest of the runtime
    CLI surface. Without `--profile`, sandbox debugging can exercise only
    the default config stack unless users manually translate profile config
    into ad hoc `-c` overrides.
    
    Supporting `--profile` lets sandbox invocations load
    `$CODEX_HOME/<name>.config.toml`, including permission profile
    configuration, before resolving the sandbox policy for the command being
    run.
    
    ## What Changed
    
    - Added `--profile NAME` / `-p NAME` to the host-specific `codex
    sandbox` argument structs as `config_profile`.
    - Allowed root-level `codex --profile NAME sandbox ...` and made a
    sandbox-local `codex sandbox --profile NAME ...` override the root
    selection.
    - Threaded `LoaderOverrides` through sandbox config loading so selected
    config profile files participate in permission resolution before the
    legacy read-only fallback.
    - Documented the new sandbox flag in `codex-rs/README.md`.
    
    ## Verification
    
    - Added parser coverage for `codex sandbox --profile`.
    - Added sandbox config-loader coverage that verifies selected config
    profile loader overrides select the profile config rather than falling
    back to read-only.
    - Ran `cargo test -p codex-cli`.
  • cli: infer host sandbox backend (#24102)
    ## Why
    
    `codex sandbox` previously required an OS subcommand like `linux`,
    `macos`, or `windows`, even though the command can only run the sandbox
    backend available on the current host. That made the CLI imply a
    cross-OS choice that does not exist.
    
    ## What changed
    
    - Collapse `codex sandbox <os>` into `codex sandbox [COMMAND]...` by
    wiring the `sandbox` parser directly to the host-specific backend args
    with `cfg`.
    - Keep the existing backend runners for Seatbelt, Linux sandbox, and
    Windows restricted token.
    - Rename the public Windows debug sandbox runner to
    `run_command_under_windows_sandbox` for clarity.
    - Update the Rust sandbox docs and related README references to describe
    host OS selection and avoid pointing readers at legacy `sandbox_mode`
    config.
    
    ## Arg0 compatibility
    
    The `codex-linux-sandbox` helper path is still handled before normal CLI
    parsing. `arg0_dispatch()` checks whether the executable basename is
    `codex-linux-sandbox` and directly calls
    `codex_linux_sandbox::run_main()`, so removing the `sandbox linux`
    parser branch does not affect the arg0 helper flow.
    
    ## Verification
    
    - `cargo test -p codex-cli`
    - `cargo test -p codex-arg0`
    - `just fix -p codex-cli`
  • config: remove legacy profile v1 resolution (#24051)
    ## Why
    
    [#23883](https://github.com/openai/codex/pull/23883) moved user-facing
    `--profile` selection onto profile v2, and
    [#23886](https://github.com/openai/codex/pull/23886) removed the old CLI
    `config_profile` override path. Core still had a second legacy path:
    `profile = "..."` could select `[profiles.*]` values while runtime
    config was built. Keeping that resolver alive preserves the old
    precedence model and profile-carrying surfaces even though profile
    selection now points at `$CODEX_HOME/<name>.config.toml`.
    
    ## What
    
    - Reject legacy top-level `profile = "..."` config while loading runtime
    config, with an error that points callers at `--profile <name>` and
    `<name>.config.toml` in the [core load
    path](https://github.com/openai/codex/blob/3d923366eca10a29143623124c6c6e538f058269/codex-rs/core/src/config/mod.rs#L2524-L2531).
    - Remove the remaining profile-v1 merge points from runtime config
    resolution, including features, permissions, model/provider selection,
    web search, Windows sandbox settings, TUI settings, role reloads, and
    OSS provider lookup.
    - Drop the leftover profile override surface from
    [`ConfigOverrides`](https://github.com/openai/codex/blob/3d923366eca10a29143623124c6c6e538f058269/codex-rs/core/src/config/mod.rs#L2118-L2148)
    and from the MCP server `codex` tool schema.
    - Prune profile-precedence tests that only exercised the removed
    resolver and replace them with rejection coverage for the legacy
    selector.
    
    ## Testing
    
    - Not run in this metadata pass.
    - Added
    [`legacy_profile_selection_is_rejected`](https://github.com/openai/codex/blob/3d923366eca10a29143623124c6c6e538f058269/codex-rs/core/src/config/config_tests.rs#L7942-L7965)
    coverage for the new runtime guard.
  • permissions: resolve profile identity with constraints (#22683)
    ## Why
    
    This PR is the invariant-cleanup layer that follows the workspace-roots
    base merged in [#22610](https://github.com/openai/codex/pull/22610).
    
    #22610 adds `[permissions.<id>.workspace_roots]` and keeps runtime
    workspace roots separate from the raw permission profile, but its
    in-memory representation is intentionally transitional: `Permissions`
    still carries the selected profile identity next to a constrained
    `PermissionProfile`. That makes APIs such as
    `set_constrained_permission_profile_with_active_profile()` fragile
    because the id and value only mean the right thing when every caller
    keeps them in sync.
    
    This PR introduces a single resolved profile state so profile identity,
    `extends`, the profile value, and profile-declared workspace roots
    travel together. The next PR,
    [#22611](https://github.com/openai/codex/pull/22611), builds on this by
    changing the app-server turn API to select permission profiles by id
    plus runtime workspace roots.
    
    ## Stack Context
    
    - #22610, now merged: adds profile-declared `workspace_roots`, runtime
    workspace roots, and `:workspace_roots` materialization.
    - This PR: replaces the parallel active-profile/profile-value fields
    with `PermissionProfileState`.
    - #22611: switches app-server turn updates toward profile ids plus
    runtime workspace roots.
    - #22612: updates TUI/exec summaries to show the effective workspace
    roots.
    
    Keeping this separate from #22611 is deliberate: reviewers can validate
    the internal state invariant before reviewing the app-server protocol
    migration.
    
    ## What Changed
    
    - Added `ResolvedPermissionProfile::{Legacy, BuiltIn, Named}` and
    `PermissionProfileState`.
    - Typed built-in profile ids with `BuiltInPermissionProfileId`.
    - Moved selected profile identity and profile-declared workspace roots
    into the resolved state.
    - Replaced `Permissions` parallel profile fields with one
    `permission_profile_state`.
    - Removed `set_constrained_permission_profile_with_active_profile()`
    from session sync paths.
    - Kept trusted session replay/`SessionConfigured` compatibility through
    explicit session snapshot helpers.
    - Updated session configuration, MCP initialization, app-server, exec,
    TUI, and guardian call sites to consume `&PermissionProfile` directly.
    
    ## Review Guide
    
    Start with `codex-rs/core/src/config/resolved_permission_profile.rs`; it
    is the new invariant boundary. Then review
    `codex-rs/core/src/config/mod.rs` to see how config loading records
    active profile identity and profile workspace roots. The remaining
    call-site changes are mostly mechanical fallout from
    `Permissions::permission_profile()` returning `&PermissionProfile`
    instead of `&Constrained<PermissionProfile>`.
    
    ## Verification
    
    The existing config/session coverage now constructs and asserts through
    `PermissionProfileState`. The workspace-root config test also asserts
    that profile-declared roots are preserved in the resolved state, which
    is the behavior #22611 relies on when runtime roots become mutable
    through the app-server API.
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/22683).
    * #22612
    * #22611
    * __->__ #22683
  • permissions: support workspace roots in profiles (#22610)
    ## Why
    
    This is the configuration/model half of the alternative permissions
    migration we discussed as a comparison point for
    [#22401](https://github.com/openai/codex/pull/22401) and
    [#22402](https://github.com/openai/codex/pull/22402).
    
    The old `workspace-write` model mixes three concerns that we want to
    keep separate:
    - reusable profile rules that should stay immutable once selected
    - user/runtime workspace roots from `cwd`, `--add-dir`, and legacy
    workspace-write config
    - internal Codex writable roots such as memories, which should not be
    shown as user workspace roots
    
    This PR gives permission profiles first-class `workspace_roots` so users
    can opt multiple repositories into the same `:workspace_roots` rules
    without using broad absolute-path write grants. It also starts
    separating the raw selected profile from the effective runtime profile
    by making `Permissions` expose explicit accessors instead of public
    mutable fields.
    
    A representative `config.toml` looks like this:
    
    ```toml
    default_permissions = "dev"
    
    [permissions.dev.workspace_roots]
    "~/code/openai" = true
    "~/code/developers-website" = true
    
    [permissions.dev.filesystem.":workspace_roots"]
    "." = "write"
    ".codex" = "read"
    ".git" = "read"
    ".vscode" = "read"
    ```
    
    If Codex starts in `~/code/codex` with that profile selected, the
    effective workspace-root set becomes:
    - `~/code/codex` from the runtime `cwd`
    - `~/code/openai` from the profile
    - `~/code/developers-website` from the profile
    
    The `:workspace_roots` rules are materialized across each root, so
    `.git`, `.codex`, and `.vscode` stay scoped the same way everywhere.
    Runtime additions such as `--add-dir` can still layer on later stack
    entries without mutating the selected profile.
    
    ## Stack Shape
    
    This PR intentionally stops before the profile-identity cleanup in
    [#22683](https://github.com/openai/codex/pull/22683) so the base review
    stays focused on config loading, workspace-root materialization, and
    compatibility with legacy `workspace-write`.
    
    The representation in this PR is therefore transitional: `Permissions`
    carries enough state to distinguish the raw constrained profile from the
    effective runtime profile, and there are still call sites that must keep
    the active profile identity and constrained profile value in sync. The
    follow-up PR replaces that with a single resolved profile state
    (`ResolvedPermissionProfile` / `PermissionProfileState`) that keeps the
    profile id, immutable `PermissionProfile`, and profile-declared
    workspace roots together. That follow-up removes APIs such as
    `set_constrained_permission_profile_with_active_profile()` where
    separate arguments could drift out of sync.
    
    Downstream PRs then build on this base to switch app-server turn updates
    to profile ids plus runtime workspace roots and to finish the
    user-visible summary behavior. Reviewers should judge this PR as the
    workspace-roots foundation, not as the final in-memory shape of selected
    permission profiles.
    
    ## Review Guide
    
    Suggested review order:
    
    1. Start with `codex-rs/core/src/config/mod.rs`.
    This is the main shape change in the base slice. `Permissions` now
    stores a private raw `Constrained<PermissionProfile>` plus runtime
    `workspace_roots`. Callers use `permission_profile()` when they need the
    raw constrained value and `effective_permission_profile()` when they
    need a materialized runtime profile. As noted above,
    [#22683](https://github.com/openai/codex/pull/22683) replaces this
    transitional shape with a resolved profile state that keeps identity and
    profile data together.
    
    2. Review `codex-rs/config/src/permissions_toml.rs` and
    `codex-rs/core/src/config/permissions.rs`.
    These add `[permissions.<id>.workspace_roots]`, resolve enabled entries
    relative to the policy cwd, and keep `:workspace_roots` deny-read glob
    patterns symbolic until the actual roots are known.
    
    3. Review `codex-rs/protocol/src/permissions.rs` and
    `codex-rs/protocol/src/models.rs`.
    These add the policy/profile materialization helpers that expand exact
    `:workspace_roots` entries and scoped deny-read globs over every
    workspace root. This is also where `ActivePermissionProfileModification`
    is removed from the core model.
    
    4. Review the legacy bridge in
    `Config::load_from_base_config_with_overrides` and
    `Config::set_legacy_sandbox_policy`.
    This is where legacy `workspace-write` roots become runtime workspace
    roots, while Codex internal writable roots stay internal and do not
    appear as user-facing workspace roots.
    
    5. Then skim downstream call sites.
    The interesting pattern is raw-vs-effective access: state/proxy/bwrap
    paths keep the raw constrained profile, while execution, summaries, and
    user-visible status use the effective profile and workspace-root list.
    
    ## What Changed
    
    - added `[permissions.<id>.workspace_roots]` to the config model and
    schema
    - added runtime `workspace_roots` state to `Config`/`Permissions` and
    `ConfigOverrides`
    - made `Permissions` profile fields private and replaced direct mutation
    with accessors/setters
    - added `PermissionProfile` and `FileSystemSandboxPolicy` helpers for
    materializing `:workspace_roots` exact paths and deny-read globs across
    all roots
    - moved legacy additional writable roots into runtime workspace-root
    state instead of active profile modifications
    - removed `ActivePermissionProfileModification` and its app-server
    protocol/schema export
    - updated sandbox/status summary paths so internal writable roots are
    not reported as user workspace roots
    
    ## Verification Strategy
    
    The targeted tests cover the behavior at the layers where regressions
    are most likely:
    - `codex-rs/core/src/config/config_tests.rs` verifies config loading,
    legacy workspace-root seeding, effective profile materialization, and
    memory-root handling.
    - `codex-rs/core/src/config/permissions_tests.rs` verifies profile
    `workspace_roots` parsing and `:workspace_roots` scoped/glob
    compilation.
    - `codex-rs/protocol/src/permissions.rs` unit tests verify exact and
    glob materialization over multiple workspace roots.
    - `codex-rs/tui/src/status/tests.rs` and
    `codex-rs/utils/sandbox-summary/src/sandbox_summary.rs` verify the
    user-facing summaries show effective workspace roots and hide internal
    writes.
    
    I also ran `cargo check --tests` locally after the latest stack refresh
    to catch cross-crate API breakage from the private-field/accessor
    changes.
    
    
    
    
    
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/22610).
    * #22612
    * #22611
    * #22683
    * __->__ #22610
  • config: add strict config parsing (#20559)
    ## Why
    
    Codex intentionally ignores unknown `config.toml` fields by default so
    older and newer config files keep working across versions. That leniency
    also makes typo detection hard because misspelled or misplaced keys
    disappear silently.
    
    This change adds an opt-in strict config mode so users and tooling can
    fail fast on unrecognized config fields without changing the default
    permissive behavior.
    
    This feature is possible because `serde_ignored` exposes the exact
    signal Codex needs: it lets Codex run ordinary Serde deserialization
    while recording fields Serde would otherwise ignore. That avoids
    requiring `#[serde(deny_unknown_fields)]` across every config type and
    keeps strict validation opt-in around the existing config model.
    
    ## What Changed
    
    ### Added strict config validation
    
    - Added `serde_ignored`-based validation for `ConfigToml` in
    `codex-rs/config/src/strict_config.rs`.
    - Combined `serde_ignored` with `serde_path_to_error` so strict mode
    preserves typed config error paths while also collecting fields Serde
    would otherwise ignore.
    - Added strict-mode validation for unknown `[features]` keys, including
    keys that would otherwise be accepted by `FeaturesToml`'s flattened
    boolean map.
    - Kept typed config errors ahead of ignored-field reporting, so
    malformed known fields are reported before unknown-field diagnostics.
    - Added source-range diagnostics for top-level and nested unknown config
    fields, including non-file managed preference source names.
    
    ### Kept parsing single-pass per source
    
    - Reworked file and managed-config loading so strict validation reuses
    the already parsed `TomlValue` for that source.
    - For actual config files and managed config strings, the loader now
    reads once, parses once, and validates that same parsed value instead of
    deserializing multiple times.
    - Validated `-c` / `--config` override layers with the same
    base-directory context used for normal relative-path resolution, so
    unknown override keys are still reported when another override contains
    a relative path.
    
    ### Scoped `--strict-config` to config-heavy entry points
    
    - Added support for `--strict-config` on the main config-loading entry
    points where it is most useful:
      - `codex`
      - `codex resume`
      - `codex fork`
      - `codex exec`
      - `codex review`
      - `codex mcp-server`
      - `codex app-server` when running the server itself
      - the standalone `codex-app-server` binary
      - the standalone `codex-exec` binary
    - Commands outside that set now reject `--strict-config` early with
    targeted errors instead of accepting it everywhere through shared CLI
    plumbing.
    - `codex app-server` subcommands such as `proxy`, `daemon`, and
    `generate-*` are intentionally excluded from the first rollout.
    - When app-server strict mode sees invalid config, app-server exits with
    the config error instead of logging a warning and continuing with
    defaults.
    - Introduced a dedicated `ReviewCommand` wrapper in `codex-rs/cli`
    instead of extending shared `ReviewArgs`, so `--strict-config` stays on
    the outer config-loading command surface and does not become part of the
    reusable review payload used by `codex exec review`.
    
    ### Coverage
    
    - Added tests for top-level and nested unknown config fields, unknown
    `[features]` keys, typed-error precedence, source-location reporting,
    and non-file managed preference source names.
    - Added CLI coverage showing invalid `--enable`, invalid `--disable`,
    and unknown `-c` overrides still error when `--strict-config` is
    present, including compound-looking feature names such as
    `multi_agent_v2.subagent_usage_hint_text`.
    - Added integration coverage showing both `codex app-server
    --strict-config` and standalone `codex-app-server --strict-config` exit
    with an error for unknown config fields instead of starting with
    fallback defaults.
    - Added coverage showing unsupported command surfaces reject
    `--strict-config` with explicit errors.
    
    ## Example Usage
    
    Run Codex with strict config validation enabled:
    
    ```shell
    codex --strict-config
    ```
    
    Strict config mode is also available on the supported config-heavy
    subcommands:
    
    ```shell
    codex --strict-config exec "explain this repository"
    codex review --strict-config --uncommitted
    codex mcp-server --strict-config
    codex app-server --strict-config --listen off
    codex-app-server --strict-config --listen off
    ```
    
    For example, if `~/.codex/config.toml` contains a typo in a key name:
    
    ```toml
    model = "gpt-5"
    approval_polic = "on-request"
    ```
    
    then `codex --strict-config` reports the misspelled key instead of
    silently ignoring it. The path is shortened to `~` here for readability:
    
    ```text
    $ codex --strict-config
    Error loading config.toml:
    ~/.codex/config.toml:2:1: unknown configuration field `approval_polic`
      |
    2 | approval_polic = "on-request"
      | ^^^^^^^^^^^^^^
    ```
    
    Without `--strict-config`, Codex keeps the existing permissive behavior
    and ignores the unknown key.
    
    Strict config mode also validates ad-hoc `-c` / `--config` overrides:
    
    ```text
    $ codex --strict-config -c foo=bar
    Error: unknown configuration field `foo` in -c/--config override
    
    $ codex --strict-config -c features.foo=true
    Error: unknown configuration field `features.foo` in -c/--config override
    ```
    
    Invalid feature toggles are rejected too, including values that look
    like nested config paths:
    
    ```text
    $ codex --strict-config --enable does_not_exist
    Error: Unknown feature flag: does_not_exist
    
    $ codex --strict-config --disable does_not_exist
    Error: Unknown feature flag: does_not_exist
    
    $ codex --strict-config --enable multi_agent_v2.subagent_usage_hint_text
    Error: Unknown feature flag: multi_agent_v2.subagent_usage_hint_text
    ```
    
    Unsupported commands reject the flag explicitly:
    
    ```text
    $ codex --strict-config cloud list
    Error: `--strict-config` is not supported for `codex cloud`
    ```
    
    ## Verification
    
    The `codex-cli` `strict_config` tests cover invalid `--enable`, invalid
    `--disable`, the compound `multi_agent_v2.subagent_usage_hint_text`
    case, unknown `-c` overrides, app-server strict startup failure through
    `codex app-server`, and rejection for unsupported commands such as
    `codex cloud`, `codex mcp`, `codex remote-control`, and `codex
    app-server proxy`.
    
    The config and config-loader tests cover unknown top-level fields,
    unknown nested fields, unknown `[features]` keys, source-location
    reporting, non-file managed config sources, and `-c` validation for keys
    such as `features.foo`.
    
    The app-server test suite covers standalone `codex-app-server
    --strict-config` startup failure for an unknown config field.
    
    ## Documentation
    
    The Codex CLI docs on developers.openai.com/codex should mention
    `--strict-config` as an opt-in validation mode for supported
    config-heavy entry points once this ships.
  • feat(sandbox): add Windows deny-read parity (#18202)
    ## Why
    
    The split filesystem policy stack already supports exact and glob
    `access = none` read restrictions on macOS and Linux. Windows still
    needed subprocess handling for those deny-read policies without claiming
    enforcement from a backend that cannot provide it.
    
    ## Key finding
    
    The unelevated restricted-token backend cannot safely enforce deny-read
    overlays. Its `WRITE_RESTRICTED` token model is authoritative for write
    checks, not read denials, so this PR intentionally fails that backend
    closed when deny-read overrides are present instead of claiming
    unsupported enforcement.
    
    ## What changed
    
    This PR adds the Windows deny-read enforcement layer and makes the
    backend split explicit:
    
    - Resolves Windows deny-read filesystem policy entries into concrete ACL
    targets.
    - Preserves exact missing paths so they can be materialized and denied
    before an enforceable sandboxed process starts.
    - Snapshot-expands existing glob matches into ACL targets for Windows
    subprocess enforcement.
    - Honors `glob_scan_max_depth` when expanding Windows deny-read globs.
    - Plans both the configured lexical path and the canonical target for
    existing paths so reparse-point aliases are covered.
    - Threads deny-read overrides through the elevated/logon-user Windows
    sandbox backend and unified exec.
    - Applies elevated deny-read ACLs synchronously before command launch
    rather than delegating them to the background read-grant helper.
    - Reconciles persistent deny-read ACEs per sandbox principal so policy
    changes do not leave stale deny-read ACLs behind.
    - Fails closed on the unelevated restricted-token backend when deny-read
    overrides are present, because its `WRITE_RESTRICTED` token model is not
    authoritative for read denials.
    
    ## Landed prerequisites
    
    These prerequisite PRs are already on `main`:
    
    1. #15979 `feat(permissions): add glob deny-read policy support`
    2. #18096 `feat(sandbox): add glob deny-read platform enforcement`
    3. #17740 `feat(config): support managed deny-read requirements`
    
    This PR targets `main` directly and contains only the Windows deny-read
    enforcement layer.
    
    ## Implementation notes
    
    - Exact deny-read paths remain enforceable on the elevated path even
    when they do not exist yet: Windows materializes the missing path before
    applying the deny ACE, so the sandboxed command cannot create and read
    it during the same run.
    - Existing exact deny paths are preserved lexically until the ACL
    planner, which then adds the canonical target as a second ACL target
    when needed. That keeps both the configured alias and the resolved
    object covered.
    - Windows ACLs do not consume Codex glob syntax directly, so glob
    deny-read entries are expanded to the concrete matches that exist before
    process launch.
    - Glob traversal deduplicates directory visits within each pattern walk
    to avoid cycles, without collapsing distinct lexical roots that happen
    to resolve to the same target.
    - Persistent deny-read ACL state is keyed by sandbox principal SID, so
    cleanup only removes ACEs owned by the same backend principal.
    - Deny-read ACEs are fail-closed on the elevated path: setup aborts if
    mandatory deny-read ACL application fails.
    - Unelevated restricted-token sessions reject deny-read overrides early
    instead of running with a silently unenforceable read policy.
    
    ## Verification
    
    - `cargo test -p codex-core
    windows_restricted_token_rejects_unreadable_split_carveouts`
    - `just fmt`
    - `just fix -p codex-core`
    - `just fix -p codex-windows-sandbox`
    - GitHub Actions rerun is in progress on the pushed head.
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • feat(cli): add sandbox profile config controls (#20118)
    ## Why
    
    The explicit profile path from #20117 is meant for standalone testing,
    but it still inherited the
    shell cwd and all managed requirements implicitly. The pre-existing
    launcher path even called out
    that it did not support a separate cwd yet in
    
    [`debug_sandbox.rs`](https://github.com/openai/codex/blob/509453f688a30929432be866402d1ea46aa12169/codex-rs/cli/src/debug_sandbox.rs#L174-L179).
    
    For a standalone command, the useful default is to let the caller choose
    the project directory being
    tested and to avoid administrator-provided constraints unless the caller
    explicitly wants to test
    those too.
    
    ## What changed
    
    - Add explicit-profile-only `-C/--cd DIR`, and use that cwd for both
    profile resolution and command
      execution.
    - Add explicit-profile-only `--include-managed-config`.
    - Make explicit profile mode skip managed requirement sources by
    default, including cloud
    requirements, MDM requirements, `/etc/codex/requirements.toml`, and the
    legacy managed-config
      requirements projection.
    - Preserve all existing invocations outside the explicit-profile path.
    
    ## Stack
    
    1. #20117 `sandbox-ui-profile`
    2. #20118 `sandbox-ui-config` --> this PR
    
    Both PRs are additive. Replay JSON is intentionally deferred to a
    follow-up design pass.
    
    ## Tests ran
    
    - `cargo test -p codex-cli debug_sandbox`
    - `cargo test -p codex-cli sandbox_macos_`
    - `cargo test -p codex-core
    load_config_layers_can_ignore_managed_requirements`
    - `cargo test -p codex-core
    load_config_layers_includes_cloud_requirements`
    - macOS branch-binary smoke on the rebased top of stack: `-C` changed
    execution cwd, explicit
    profile mode omitted managed proxy env under `env -i`, and
    `--include-managed-config` restored it.
    - Linux devbox branch-binary smoke on the rebased top of stack: `-C`
    changed execution cwd for
      built-in and user-defined explicit profiles.
  • feat(cli): add explicit sandbox permission profiles (#20117)
    ## Why
    
    `codex sandbox` is useful for exercising sandbox behavior directly, but
    before this stack the CLI
    only picked up permission profiles indirectly from the active config.
    The existing debug-sandbox path
    already compiled `[permissions]` profiles through normal config loading,
    as covered by the existing
    profile tests in
    [`debug_sandbox.rs`](https://github.com/openai/codex/blob/de2ccf94735a3d8a2a7077e6a5292026413867cf/codex-rs/cli/src/debug_sandbox.rs#L715-L760).
    
    This adds the smallest stable entry point first: an explicit profile
    selector that reuses the same
    config machinery as normal Codex config, so standalone testing becomes
    possible without changing
    current no-selector behavior.
    
    ## What changed
    
    - Add additive `--permissions-profile NAME` support to `codex sandbox
    macos|linux|windows`.
    - Resolve built-in and user-defined profile names by feeding
    `default_permissions` through the
    existing config compilation path instead of inventing a sandbox-only
    parser.
    - Make an explicit selector win over an ambient active profile's legacy
    `sandbox_mode`.
    - Keep the existing no-selector behavior unchanged.
    
    ## Stack
    
    1. #20117 `sandbox-ui-profile` --> this PR
    2. #20118 `sandbox-ui-config`
    
    Both PRs are additive. Replay JSON is intentionally deferred to a
    follow-up design pass.
    
    ## Tests ran
    
    - `cargo test -p codex-cli debug_sandbox`
    - `cargo test -p codex-cli sandbox_macos_parses_permissions_profile`
    - `cargo test -p codex-core
    cli_override_takes_precedence_over_profile_sandbox_mode`
    - macOS branch-binary smoke on the rebased top of stack: built-in
    `:workspace` and user-defined
      profiles both executed successfully through `--permissions-profile`.
    - Linux devbox branch-binary smoke on the rebased top of stack: built-in
    `:workspace` and
    user-defined profiles both executed successfully through
    `--permissions-profile`.
  • chore(cli) deprecate --full-auto (#20133)
    ## Summary
    Starts the process of getting rid of `--full-auto`, with some
    concessions:
    1. Fully removes the command from the tui, since it just resolves to the
    default permissions there, and encourages users to use the one-time
    trust flow if they're not in a trusted repo.
    2. Marks the command as deprecated in `codex exec`, in case users are
    actively relying on this. We'll remove in an upcoming n+X release.
    3. Cleans up some of the `codex sandbox` cli logic, to keep supporting
    legacy sandbox policies for now.
    
    This isn't the cleanest setup, but I think it is worthwhile to warn
    users for one release before hard-removing it.
    
    ## Testing 
    - [x] Updated unit tests
  • linux-sandbox: switch helper plumbing to PermissionProfile (#20106)
    ## Why
    
    `PermissionProfile` is the canonical runtime permission model in the
    Rust workspace, but the Linux sandbox helper still accepted a legacy
    `SandboxPolicy` plus separate filesystem and network policy flags. That
    translation layer made the helper interface harder to reason about and
    left `linux-sandbox`-specific callers and tests coupled to the legacy
    policy representation.
    
    This change moves the helper onto `PermissionProfile` directly so the
    Linux sandbox plumbing matches the rest of the permission stack.
    
    ## What changed
    
    - changed `codex-linux-sandbox` to accept `--permission-profile` and
    derive the runtime filesystem and network policies internally
    - updated the in-process seccomp and legacy Landlock path in
    `codex-rs/linux-sandbox` to operate on `PermissionProfile`
    - updated Linux sandbox argv construction in `codex-rs/sandboxing`,
    `codex-rs/core`, and the CLI debug sandbox path to pass the canonical
    profile instead of serializing compatibility policy projections
    - simplified the Linux sandbox tests to build the exact permission
    profile under test, including the managed-proxy path and
    direct-runtime-enforcement carveout coverage
    - removed helper-local `SandboxPolicy` usage from `bwrap` tests where
    `FileSystemSandboxPolicy` is already the value being exercised
    
    ## Testing
    
    - `cargo test -p codex-sandboxing`
    - `cargo test -p codex-linux-sandbox` (on this macOS host, the crate
    compiled cleanly and its Linux-only tests were cfg-gated)
    - `cargo test -p codex-core --no-run`
    - `cargo test -p codex-cli --no-run`
  • permissions: centralize legacy sandbox projection (#19734)
    ## Why
    
    The remaining migration work still needs `SandboxPolicy` at a few
    compatibility boundaries, but those projections should come from one
    canonical path. Keeping ad hoc legacy projections scattered through
    app-server, CLI, and config code makes it easy for behavior to drift as
    `PermissionProfile` gains fidelity that the legacy enum cannot
    represent.
    
    ## What Changed
    
    - Adds `Permissions::legacy_sandbox_policy(cwd)` and
    `Config::legacy_sandbox_policy()` as the compatibility projection from
    the canonical `PermissionProfile`.
    - Adds `Permissions::can_set_legacy_sandbox_policy()` so legacy inputs
    are checked after they are converted into profile semantics.
    - Updates app-server command handling, Windows sandbox setup, session
    configuration, and sandbox summaries to use the centralized projection
    helper.
    - Leaves `SandboxPolicy` in place only for boundary inputs/outputs that
    still speak the legacy abstraction.
    
    ## Verification
    
    - `cargo check -p codex-config -p codex-core -p codex-sandboxing -p
    codex-app-server -p codex-cli -p codex-tui`
    - `cargo test -p codex-tui
    permissions_selection_history_snapshot_full_access_to_default --
    --nocapture`
    - `cargo test -p codex-tui
    permissions_selection_sends_approvals_reviewer_in_override_turn_context
    -- --nocapture`
    - `bazel test //codex-rs/tui:tui-unit-tests-bin
    --test_arg=permissions_selection_history_snapshot_full_access_to_default
    --test_output=errors`
    - `bazel test //codex-rs/tui:tui-unit-tests-bin
    --test_arg=permissions_selection_sends_approvals_reviewer_in_override_turn_context
    --test_output=errors`
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/19734).
    * #19737
    * #19736
    * #19735
    * __->__ #19734
  • permissions: migrate approval and sandbox consumers to profiles (#19393)
    ## Why
    
    Runtime decisions should not infer permissions from the lossy legacy
    sandbox projection once `PermissionProfile` is available. In particular,
    `Disabled` and `External` need to remain distinct, and managed profiles
    with split filesystem or deny-read rules should not be collapsed before
    approval, network, safety, or analytics code makes decisions.
    
    ## What Changed
    
    - Changes managed network proxy setup and network approval logic to use
    `PermissionProfile` when deciding whether a managed sandbox is active.
    - Migrates patch safety, Guardian/user-shell approval paths, Landlock
    helper setup, analytics sandbox classification, and selected
    turn/session code to profile-backed permissions.
    - Validates command-level profile overrides against the constrained
    `PermissionProfile` rather than a strict `SandboxPolicy` round trip.
    - Preserves configured deny-read restrictions when command profiles are
    narrowed.
    - Adds coverage for profile-backed trust, network proxy/approval
    behavior, patch safety, analytics classification, and command-profile
    narrowing.
    
    ## Verification
    
    - `cargo test -p codex-core direct_write_roots`
    - `cargo test -p codex-core runtime_roots_to_legacy_projection`
    - `cargo test -p codex-app-server
    requested_permissions_trust_project_uses_permission_profile_intent`
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/19393).
    * #19395
    * #19394
    * __->__ #19393
  • permissions: derive compatibility policies from profiles (#19392)
    ## Why
    
    After #19391, `PermissionProfile` and the split filesystem/network
    policies could still be stored in parallel. That creates drift risk: a
    profile can preserve deny globs, external enforcement, or split
    filesystem entries while a cached projection silently loses those
    details. This PR makes the profile the runtime source and derives
    compatibility views from it.
    
    ## What Changed
    
    - Removes stored filesystem/network sandbox projections from
    `Permissions` and `SessionConfiguration`; their accessors now derive
    from the canonical `PermissionProfile`.
    - Derives legacy `SandboxPolicy` snapshots from profiles only where an
    older API still needs that field.
    - Updates MCP connection and elicitation state to track
    `PermissionProfile` instead of `SandboxPolicy` for auto-approval
    decisions.
    - Adds semantic filesystem-policy comparison so cwd changes can preserve
    richer profiles while still recognizing equivalent legacy projections
    independent of entry ordering.
    - Updates config/session tests to assert profile-derived projections
    instead of parallel stored fields.
    
    ## Verification
    
    - `cargo test -p codex-core direct_write_roots`
    - `cargo test -p codex-core runtime_roots_to_legacy_projection`
    - `cargo test -p codex-app-server
    requested_permissions_trust_project_uses_permission_profile_intent`
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/19392).
    * #19395
    * #19394
    * #19393
    * __->__ #19392
  • use long-lived sessions for codex sandbox windows (#18953)
    `codex sandbox windows` previously did a one-shot spawn for all
    commands.
    This change uses the `unified_exec` session to spawn long-lived
    processes instead, and implements a simple bridge to forward stdin to
    the spawned session and stdout/stderr from the spawned session back to
    the caller.
    
    It also fixes a bug with the new shared spawn context code where the
    "no-network env" was being applied to both elevated and unelevated
    sandbox spawns. It should only be applied for the unelevated sandbox
    because the elevated one uses firewall rules instead of an env-based
    network suppression strategy.
  • Support Unix socket allowlists in macOS sandbox (#17654)
    ## Changes
    
    Allows sandboxes to restrict overall network access while granting
    access to specific unix sockets on mac.
    
    ## Details
    
    - `codex sandbox macos`: adds a repeatable `--allow-unix-socket` option.
    - `codex-sandboxing`: threads explicit Unix socket roots into the macOS
    Seatbelt profile generation.
    - Preserves restricted network behavior when only Unix socket IPC is
    requested, and preserves full network behavior when full network is
    already enabled.
    
    ## Verification
    
    - `cargo test -p codex-cli -p codex-sandboxing`
    - `cargo build -p codex-cli --bin codex`
    - verified that `codex sandbox macos --allow-unix-socket /tmp/test.sock
    -- test-client` grants access as expected
  • fix: support split carveouts in windows elevated sandbox (#14568)
    ## Summary
    - preserve legacy Windows elevated sandbox behavior for existing
    policies
    - add elevated-only support for split filesystem policies that can be
    represented as readable-root overrides, writable-root overrides, and
    extra deny-write carveouts
    - resolve those elevated filesystem overrides during sandbox transform
    and thread them through setup and policy refresh
    - keep failing closed for explicit unreadable (`none`) carveouts and
    reopened writable descendants under read-only carveouts
    - for explicit read-only-under-writable-root carveouts, materialize
    missing carveout directories during elevated setup before applying the
    deny-write ACL
    - document the elevated vs restricted-token support split in the core
    README
    
    ## Example
    Given a split filesystem policy like:
    
    ```toml
    ":root" = "read"
    ":cwd" = "write"
    "./docs" = "read"
    "C:/scratch" = "write"
    ```
    
    the elevated backend now provisions the readable-root overrides,
    writable-root overrides, and extra deny-write carveouts during setup and
    refresh instead of collapsing back to the legacy workspace-only shape.
    
    If a read-only carveout under a writable root is missing at setup time,
    elevated setup creates that carveout as an empty directory before
    applying its deny-write ACE; otherwise the sandboxed command could
    create it later and bypass the carveout. This is only for explicit
    policy carveouts. Best-effort workspace protections like `.codex/` and
    `.agents/` still skip missing directories.
    
    A policy like:
    
    ```toml
    "/workspace" = "write"
    "/workspace/docs" = "read"
    "/workspace/docs/tmp" = "write"
    ```
    
    still fails closed, because the elevated backend does not reopen
    writable descendants under read-only carveouts yet.
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • chore: clean up argument-comment lint and roll out all-target CI on macOS (#16054)
    ## Why
    
    `argument-comment-lint` was green in CI even though the repo still had
    many uncommented literal arguments. The main gap was target coverage:
    the repo wrapper did not force Cargo to inspect test-only call sites, so
    examples like the `latest_session_lookup_params(true, ...)` tests in
    `codex-rs/tui_app_server/src/lib.rs` never entered the blocking CI path.
    
    This change cleans up the existing backlog, makes the default repo lint
    path cover all Cargo targets, and starts rolling that stricter CI
    enforcement out on the platform where it is currently validated.
    
    ## What changed
    
    - mechanically fixed existing `argument-comment-lint` violations across
    the `codex-rs` workspace, including tests, examples, and benches
    - updated `tools/argument-comment-lint/run-prebuilt-linter.sh` and
    `tools/argument-comment-lint/run.sh` so non-`--fix` runs default to
    `--all-targets` unless the caller explicitly narrows the target set
    - fixed both wrappers so forwarded cargo arguments after `--` are
    preserved with a single separator
    - documented the new default behavior in
    `tools/argument-comment-lint/README.md`
    - updated `rust-ci` so the macOS lint lane keeps the plain wrapper
    invocation and therefore enforces `--all-targets`, while Linux and
    Windows temporarily pass `-- --lib --bins`
    
    That temporary CI split keeps the stricter all-targets check where it is
    already cleaned up, while leaving room to finish the remaining Linux-
    and Windows-specific target-gated cleanup before enabling
    `--all-targets` on those runners. The Linux and Windows failures on the
    intermediate revision were caused by the wrapper forwarding bug, not by
    additional lint findings in those lanes.
    
    ## Validation
    
    - `bash -n tools/argument-comment-lint/run.sh`
    - `bash -n tools/argument-comment-lint/run-prebuilt-linter.sh`
    - shell-level wrapper forwarding check for `-- --lib --bins`
    - shell-level wrapper forwarding check for `-- --tests`
    - `just argument-comment-lint`
    - `cargo test` in `tools/argument-comment-lint`
    - `cargo test -p codex-terminal-detection`
    
    ## Follow-up
    
    - Clean up remaining Linux-only target-gated callsites, then switch the
    Linux lint lane back to the plain wrapper invocation.
    - Clean up remaining Windows-only target-gated callsites, then switch
    the Windows lint lane back to the plain wrapper invocation.
  • feat(windows-sandbox): add network proxy support (#12220)
    ## Summary
    
    This PR makes Windows sandbox proxying enforceable by routing proxy-only
    runs through the existing `offline` sandbox user and reserving direct
    network access for the existing `online` sandbox user.
    
    In brief:
    
    - if a Windows sandbox run should be proxy-enforced, we run it as the
    `offline` user
    - the `offline` user gets firewall rules that block direct outbound
    traffic and only permit the configured localhost proxy path
    - if a Windows sandbox run should have true direct network access, we
    run it as the `online` user
    - no new sandbox identity is introduced
    
    This brings Windows in line with the intended model: proxy use is not
    just env-based, it is backed by OS-level egress controls. Windows
    already has two sandbox identities:
    
    - `offline`: intended to have no direct network egress
    - `online`: intended to have full network access
    
    This PR makes proxy-enforced runs use that model directly.
    
    ### Proxy-enforced runs
    
    When proxy enforcement is active:
    
    - the run is assigned to the `offline` identity
    - setup extracts the loopback proxy ports from the sandbox env
    - Windows setup programs firewall rules for the `offline` user that:
      - block all non-loopback outbound traffic
      - block loopback UDP
      - block loopback TCP except for the configured proxy ports
    - optionally allow broader localhost access when `allow_local_binding=1`
    
    So the sandboxed process can only talk to the local proxy. It cannot
    open direct outbound sockets or do local UDP-based DNS on its own.The
    proxy then performs the real outbound network access outside that
    restricted sandbox identity.
    
    ### Direct-network runs
    
    When proxy enforcement is not active and full network access is allowed:
    
    - the run is assigned to the `online` identity
    - no proxy-only firewall restrictions are applied
    - the process gets normal direct network access
    
    ### Unelevated vs elevated
    
    The restricted-token / unelevated path cannot enforce per-identity
    firewall policy by itself.
    
    So for Windows proxy-enforced runs, we transparently use the logon-user
    sandbox path under the hood, even if the caller started from the
    unelevated mode. That keeps enforcement real instead of best-effort.
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • permissions: remove macOS seatbelt extension profiles (#15918)
    ## Why
    
    `PermissionProfile` should only describe the per-command permissions we
    still want to grant dynamically. Keeping
    `MacOsSeatbeltProfileExtensions` in that surface forced extra macOS-only
    approval, protocol, schema, and TUI branches for a capability we no
    longer want to expose.
    
    ## What changed
    
    - Removed the macOS-specific permission-profile types from
    `codex-protocol`, the app-server v2 API, and the generated
    schema/TypeScript artifacts.
    - Deleted the core and sandboxing plumbing that threaded
    `MacOsSeatbeltProfileExtensions` through execution requests and seatbelt
    construction.
    - Simplified macOS seatbelt generation so it always includes the fixed
    read-only preferences allowlist instead of carrying a configurable
    profile extension.
    - Removed the macOS additional-permissions UI/docs/test coverage and
    deleted the obsolete macOS permission modules.
    - Tightened `request_permissions` intersection handling so explicitly
    empty requested read lists are preserved only when that field was
    actually granted, avoiding zero-grant responses being stored as active
    permissions.
  • Use AbsolutePathBuf for cwd state (#15710)
    Migrate `cwd` and related session/config state to `AbsolutePathBuf` so
    downstream consumers consistently see absolute working directories.
    
    Add test-only `.abs()` helpers for `Path`, `PathBuf`, and `TempDir`, and
    update branch-local tests to use them instead of
    `AbsolutePathBuf::try_from(...)`.
    
    For the remaining TUI/app-server snapshot coverage that renders absolute
    cwd values, keep the snapshots unchanged and skip the Windows-only cases
    where the platform-specific absolute path layout differs.
  • Move macOS sandbox builders into codex-sandboxing (#15593)
    ## Summary
    - move macOS permission merging/intersection logic and tests from
    `codex-core` into `codex-sandboxing`
    - move seatbelt policy builders, permissions logic, SBPL assets, and
    their tests into `codex-sandboxing`
    - keep `codex-core` owning only the seatbelt spawn wrapper and switch
    call sites to import the moved APIs directly
    
    ## Notes
    - no re-exports added
    - moved the seatbelt tests with the implementation so internal helpers
    could stay private
    - local verification is still finishing while this PR is open
  • Extract landlock helpers into codex-sandboxing (#15592)
    ## Summary
    - add a new `codex-sandboxing` crate for sandboxing extraction work
    - move the pure Linux sandbox argv builders and their unit tests out of
    `codex-core`
    - keep `core::landlock` as the spawn wrapper and update direct callers
    to use `codex_sandboxing::landlock`
    
    ## Testing
    - `cargo test -p codex-sandboxing`
    - `cargo test -p codex-core landlock`
    - `cargo test -p codex-cli debug_sandbox`
    - `just argument-comment-lint`
    
    ## Notes
    - this is step 1 of the move plan aimed at minimizing per-PR diffs
    - no re-exports or no-op proxy methods were added
  • Use released DotSlash package for argument-comment lint (#15199)
    ## Why
    The argument-comment lint now has a packaged DotSlash artifact from
    [#15198](https://github.com/openai/codex/pull/15198), so the normal repo
    lint path should use that released payload instead of rebuilding the
    lint from source every time.
    
    That keeps `just clippy` and CI aligned with the shipped artifact while
    preserving a separate source-build path for people actively hacking on
    the lint crate.
    
    The current alpha package also exposed two integration wrinkles that the
    repo-side prebuilt wrapper needs to smooth over:
    - the bundled Dylint library filename includes the host triple, for
    example `@nightly-2025-09-18-aarch64-apple-darwin`, and Dylint derives
    `RUSTUP_TOOLCHAIN` from that filename
    - on Windows, Dylint's driver path also expects `RUSTUP_HOME` to be
    present in the environment
    
    Without those adjustments, the prebuilt CI jobs fail during `cargo
    metadata` or driver setup. This change makes the checked-in prebuilt
    wrapper normalize the packaged library name to the plain
    `nightly-2025-09-18` channel before invoking `cargo-dylint`, and it
    teaches both the wrapper and the packaged runner source to infer
    `RUSTUP_HOME` from `rustup show home` when the environment does not
    already provide it.
    
    After the prebuilt Windows lint job started running successfully, it
    also surfaced a handful of existing anonymous literal callsites in
    `windows-sandbox-rs`. This PR now annotates those callsites so the new
    cross-platform lint job is green on the current tree.
    
    ## What Changed
    - checked in the current
    `tools/argument-comment-lint/argument-comment-lint` DotSlash manifest
    - kept `tools/argument-comment-lint/run.sh` as the source-build wrapper
    for lint development
    - added `tools/argument-comment-lint/run-prebuilt-linter.sh` as the
    normal enforcement path, using the checked-in DotSlash package and
    bundled `cargo-dylint`
    - updated `just clippy` and `just argument-comment-lint` to use the
    prebuilt wrapper
    - split `.github/workflows/rust-ci.yml` so source-package checks live in
    a dedicated `argument_comment_lint_package` job, while the released lint
    runs in an `argument_comment_lint_prebuilt` matrix on Linux, macOS, and
    Windows
    - kept the pinned `nightly-2025-09-18` toolchain install in the prebuilt
    CI matrix, since the prebuilt package still relies on rustup-provided
    toolchain components
    - updated `tools/argument-comment-lint/run-prebuilt-linter.sh` to
    normalize host-qualified nightly library filenames, keep the `rustup`
    shim directory ahead of direct toolchain `cargo` binaries, and export
    `RUSTUP_HOME` when needed for Windows Dylint driver setup
    - updated `tools/argument-comment-lint/src/bin/argument-comment-lint.rs`
    so future published DotSlash artifacts apply the same nightly-filename
    normalization and `RUSTUP_HOME` inference internally
    - fixed the remaining Windows lint violations in
    `codex-rs/windows-sandbox-rs` by adding the required `/*param*/`
    comments at the reported callsites
    - documented the checked-in DotSlash file, wrapper split, archive
    layout, nightly prerequisite, and Windows `RUSTUP_HOME` requirement in
    `tools/argument-comment-lint/README.md`
  • fix: honor active permission profiles in sandbox debug (#14293)
    ## Summary
    - stop `codex sandbox` from forcing legacy `sandbox_mode` when active
    `[permissions]` profiles are configured
    - keep the legacy `read-only` / `workspace-write` fallback for legacy
    configs and reject `--full-auto` for profile-based configs
    - use split filesystem and network policies in the macOS/Linux debug
    sandbox helpers and add regressions for the config-loading behavior
    
    
    assuming "codex/docs/private/secret.txt" = "none"
    ```
    codex -c 'default_permissions="limited-read-test"' sandbox macos -- <command> ...
    
    codex sandbox macos -- cat codex/docs/private/secret.txt >/dev/null; echo EXIT:$?
    cat: codex/docs/private/secret.txt: Operation not permitted
    EXIT:1
    ```
    
    ---------
    
    Co-authored-by: celia-oai <celia@openai.com>
  • Apply argument comment lint across codex-rs (#14652)
    ## Why
    
    Once the repo-local lint exists, `codex-rs` needs to follow the
    checked-in convention and CI needs to keep it from drifting. This commit
    applies the fallback `/*param*/` style consistently across existing
    positional literal call sites without changing those APIs.
    
    The longer-term preference is still to avoid APIs that require comments
    by choosing clearer parameter types and call shapes. This PR is
    intentionally the mechanical follow-through for the places where the
    existing signatures stay in place.
    
    After rebasing onto newer `main`, the rollout also had to cover newly
    introduced `tui_app_server` call sites. That made it clear the first cut
    of the CI job was too expensive for the common path: it was spending
    almost as much time installing `cargo-dylint` and re-testing the lint
    crate as a representative test job spends running product tests. The CI
    update keeps the full workspace enforcement but trims that extra
    overhead from ordinary `codex-rs` PRs.
    
    ## What changed
    
    - keep a dedicated `argument_comment_lint` job in `rust-ci`
    - mechanically annotate remaining opaque positional literals across
    `codex-rs` with exact `/*param*/` comments, including the rebased
    `tui_app_server` call sites that now fall under the lint
    - keep the checked-in style aligned with the lint policy by using
    `/*param*/` and leaving string and char literals uncommented
    - cache `cargo-dylint`, `dylint-link`, and the relevant Cargo
    registry/git metadata in the lint job
    - split changed-path detection so the lint crate's own `cargo test` step
    runs only when `tools/argument-comment-lint/*` or `rust-ci.yml` changes
    - continue to run the repo wrapper over the `codex-rs` workspace, so
    product-code enforcement is unchanged
    
    Most of the code changes in this commit are intentionally mechanical
    comment rewrites or insertions driven by the lint itself.
    
    ## Verification
    
    - `./tools/argument-comment-lint/run.sh --workspace`
    - `cargo test -p codex-tui-app-server -p codex-tui`
    - parsed `.github/workflows/rust-ci.yml` locally with PyYAML
    
    ---
    
    * -> #14652
    * #14651
  • Use a private desktop for Windows sandbox instead of Winsta0\Default (#14400)
    ## Summary
    - launch Windows sandboxed children on a private desktop instead of
    `Winsta0\Default`
    - make private desktop the default while keeping
    `windows.sandbox_private_desktop=false` as the escape hatch
    - centralize process launch through the shared
    `create_process_as_user(...)` path
    - scope the private desktop ACL to the launching logon SID
    
    ## Why
    Today sandboxed Windows commands run on the visible shared desktop. That
    leaves an avoidable same-desktop attack surface for window interaction,
    spoofing, and related UI/input issues. This change moves sandboxed
    commands onto a dedicated per-launch desktop by default so the sandbox
    no longer shares `Winsta0\Default` with the user session.
    
    The implementation stays conservative on security with no silent
    fallback back to `Winsta0\Default`
    
    If private-desktop setup fails on a machine, users can still opt out
    explicitly with `windows.sandbox_private_desktop=false`.
    
    ## Validation
    - `cargo build -p codex-cli`
    - elevated-path `codex exec` desktop-name probe returned
    `CodexSandboxDesktop-*`
    - elevated-path `codex exec` smoke sweep for shell commands, nested
    `pwsh`, jobs, and hidden `notepad` launch
    - unelevated-path full private-desktop compatibility sweep via `codex
    exec` with `-c windows.sandbox=unelevated`
  • refactor: make bubblewrap the default Linux sandbox (#13996)
    ## Summary
    - make bubblewrap the default Linux sandbox and keep
    `use_legacy_landlock` as the only override
    - remove `use_linux_sandbox_bwrap` from feature, config, schema, and
    docs surfaces
    - update Linux sandbox selection, CLI/config plumbing, and related
    tests/docs to match the new default
    - fold in the follow-up CI fixes for request-permissions responses and
    Linux read-only sandbox error text
  • feat(network-proxy): add embedded OTEL policy audit logging (#12046)
    **PR Summary**
    
    This PR adds embedded-only OTEL policy audit logging for
    `codex-network-proxy` and threads audit metadata from `codex-core` into
    managed proxy startup.
    
    ### What changed
    - Added structured audit event emission in `network_policy.rs` with
    target `codex_otel.network_proxy`.
    - Emitted:
    - `codex.network_proxy.domain_policy_decision` once per domain-policy
    evaluation.
      - `codex.network_proxy.block_decision` for non-domain denies.
    - Added required policy/network fields, RFC3339 UTC millisecond
    `event.timestamp`, and fallback defaults (`http.request.method="none"`,
    `client.address="unknown"`).
    - Added non-domain deny audit emission in HTTP/SOCKS handlers for
    mode-guard and proxy-state denies, including unix-socket deny paths.
    - Added `REASON_UNIX_SOCKET_UNSUPPORTED` and used it for unsupported
    unix-socket auditing.
    - Added `NetworkProxyAuditMetadata` to runtime/state, re-exported from
    `lib.rs` and `state.rs`.
    - Added `start_proxy_with_audit_metadata(...)` in core config, with
    `start_proxy()` delegating to default metadata.
    - Wired metadata construction in `codex.rs` from session/auth context,
    including originator sanitization for OTEL-safe tagging.
    - Updated `network-proxy/README.md` with embedded-mode audit schema and
    behavior notes.
    - Refactored HTTP block-audit emission to a small local helper to reduce
    duplication.
    - Preserved existing unix-socket proxy-disabled host/path behavior for
    responses and blocked history while using an audit-only endpoint
    override (`server.address="unix-socket"`, `server.port=0`).
    
    ### Explicit exclusions
    - No standalone proxy OTEL startup work.
    - No `main.rs` binary wiring.
    - No `standalone_otel.rs`.
    - No standalone docs/tests.
    
    ### Tests
    - Extended `network_policy.rs` tests for event mapping, metadata
    propagation, fallbacks, timestamp format, and target prefix.
    - Extended HTTP tests to assert unix-socket deny block audit events.
    - Extended SOCKS tests to cover deny emission from handler deny
    branches.
    - Added/updated core tests to verify audit metadata threading into
    managed proxy state.
    
    ### Validation run
    - `just fmt`
    - `cargo test -p codex-network-proxy` 
    - `cargo test -p codex-core` ran with one unrelated flaky timeout
    (`shell_snapshot::tests::snapshot_shell_does_not_inherit_stdin`), and
    the test passed when rerun directly 
    
    ---------
    
    Co-authored-by: viyatb-oai <viyatb@openai.com>
  • feat(core): add structured network approval plumbing and policy decision model (#11672)
    ### Description
    #### Summary
    Introduces the core plumbing required for structured network approvals
    
    #### What changed
    - Added structured network policy decision modeling in core.
    - Added approval payload/context types needed for network approval
    semantics.
    - Wired shell/unified-exec runtime plumbing to consume structured
    decisions.
    - Updated related core error/event surfaces for structured handling.
    - Updated protocol plumbing used by core approval flow.
    - Included small CLI debug sandbox compatibility updates needed by this
    layer.
    
    #### Why
    establishes the minimal backend foundation for network approvals without
    yet changing high-level orchestration or TUI behavior.
    
    #### Notes
    - Behavior remains constrained by existing requirements/config gating.
    - Follow-up PRs in the stack handle orchestration, UX, and app-server
    integration.
    
    ---------
    
    Co-authored-by: Codex <199175422+chatgpt-codex-connector[bot]@users.noreply.github.com>
  • feat: introduce Permissions (#11633)
    ## Why
    We currently carry multiple permission-related concepts directly on
    `Config` for shell/unified-exec behavior (`approval_policy`,
    `sandbox_policy`, `network`, `shell_environment_policy`,
    `windows_sandbox_mode`).
    
    Consolidating these into one in-memory struct makes permission handling
    easier to reason about and sets up the next step: supporting named
    permission profiles (`[permissions.PROFILE_NAME]`) without changing
    behavior now.
    
    This change is mostly mechanical: it updates existing callsites to go
    through `config.permissions`, but it does not yet refactor those
    callsites to take a single `Permissions` value in places where multiple
    permission fields are still threaded separately.
    
    This PR intentionally **does not** change the on-disk `config.toml`
    format yet and keeps compatibility with legacy config keys.
    
    ## What Changed
    - Introduced `Permissions` in `core/src/config/mod.rs`.
    - Added `Config::permissions` and moved effective runtime permission
    fields under it:
      - `approval_policy`
      - `sandbox_policy`
      - `network`
      - `shell_environment_policy`
      - `windows_sandbox_mode`
    - Updated config loading/building so these effective values are still
    derived from the same existing config inputs and constraints.
    - Updated Windows sandbox helpers/resolution to read/write via
    `permissions`.
    - Threaded the new field through all permission consumers across core
    runtime, app-server, CLI/exec, TUI, and sandbox summary code.
    - Updated affected tests to reference `config.permissions.*`.
    - Renamed the struct/field from
    `EffectivePermissions`/`effective_permissions` to
    `Permissions`/`permissions` and aligned variable naming accordingly.
    
    ## Verification
    - `just fix -p codex-core -p codex-tui -p codex-cli -p codex-app-server
    -p codex-exec -p codex-utils-sandbox-summary`
    - `cargo build -p codex-core -p codex-tui -p codex-cli -p
    codex-app-server -p codex-exec -p codex-utils-sandbox-summary`
  • feat: split codex-common into smaller utils crates (#11422)
    We are removing feature-gated shared crates from the `codex-rs`
    workspace. `codex-common` grouped several unrelated utilities behind
    `[features]`, which made dependency boundaries harder to reason about
    and worked against the ongoing effort to eliminate feature flags from
    workspace crates.
    
    Splitting these utilities into dedicated crates under `utils/` aligns
    this area with existing workspace structure and keeps each dependency
    explicit at the crate boundary.
    
    ## What changed
    
    - Removed `codex-rs/common` (`codex-common`) from workspace members and
    workspace dependencies.
    - Added six new utility crates under `codex-rs/utils/`:
      - `codex-utils-cli`
      - `codex-utils-elapsed`
      - `codex-utils-sandbox-summary`
      - `codex-utils-approval-presets`
      - `codex-utils-oss`
      - `codex-utils-fuzzy-match`
    - Migrated the corresponding modules out of `codex-common` into these
    crates (with tests), and added matching `BUILD.bazel` targets.
    - Updated direct consumers to use the new crates instead of
    `codex-common`:
      - `codex-rs/cli`
      - `codex-rs/tui`
      - `codex-rs/exec`
      - `codex-rs/app-server`
      - `codex-rs/mcp-server`
      - `codex-rs/chatgpt`
      - `codex-rs/cloud-tasks`
    - Updated workspace lockfile entries to reflect the new dependency graph
    and removal of `codex-common`.
  • feat: retain NetworkProxy, when appropriate (#11207)
    As of this PR, `SessionServices` retains a
    `Option<StartedNetworkProxy>`, if appropriate.
    
    Now the `network` field on `Config` is `Option<NetworkProxySpec>`
    instead of `Option<NetworkProxy>`.
    
    Over in `Session::new()`, we invoke `NetworkProxySpec::start_proxy()` to
    create the `StartedNetworkProxy`, which is a new struct that retains the
    `NetworkProxy` as well as the `NetworkProxyHandle`. (Note that `Drop` is
    implemented for `NetworkProxyHandle` to ensure the proxies are shutdown
    when it is dropped.)
    
    The `NetworkProxy` from the `StartedNetworkProxy` is threaded through to
    the appropriate places.
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/11207).
    * #11285
    * __->__ #11207
  • feat(sandbox): enforce proxy-aware network routing in sandbox (#11113)
    ## Summary
    - expand proxy env injection to cover common tool env vars
    (`HTTP_PROXY`/`HTTPS_PROXY`/`ALL_PROXY`/`NO_PROXY` families +
    tool-specific variants)
    - harden macOS Seatbelt network policy generation to route through
    inferred loopback proxy endpoints and fail closed when proxy env is
    malformed
    - thread proxy-aware Linux sandbox flags and add minimal bwrap netns
    isolation hook for restricted non-proxy runs
    - add/refresh tests for proxy env wiring, Seatbelt policy generation,
    and Linux sandbox argument wiring
  • feat(linux-sandbox): add bwrap support (#9938)
    ## Summary
    This PR introduces a gated Bubblewrap (bwrap) Linux sandbox path. The
    curent Linux sandbox path relies on in-process restrictions (including
    Landlock). Bubblewrap gives us a more uniform filesystem isolation
    model, especially explicit writable roots with the option to make some
    directories read-only and granular network controls.
    
    This is behind a feature flag so we can validate behavior safely before
    making it the default.
    
    - Added temporary rollout flag:
      - `features.use_linux_sandbox_bwrap`
    - Preserved existing default path when the flag is off.
    - In Bubblewrap mode:
    - Added internal retry without /proc when /proc mount is not permitted
    by the host/container.
  • Inject CODEX_THREAD_ID into the terminal environment (#10096)
    Inject CODEX_THREAD_ID (when applicable) into the terminal environment
    so that the agent (and skills) can refer to the current thread / session
    ID.
    
    Discussion:
    https://openai.slack.com/archives/C095U48JNL9/p1769542492067109
  • allow elevated sandbox to be enabled without base experimental flag (#10028)
    elevated flag = elevated sandbox
    experimental flag = non-elevated sandbox
    both = elevated
  • feat: support allowed_sandbox_modes in requirements.toml (#8298)
    This adds support for `allowed_sandbox_modes` in `requirements.toml` and
    provides legacy support for constraining sandbox modes in
    `managed_config.toml`. This is converted to `Constrained<SandboxPolicy>`
    in `ConfigRequirements` and applied to `Config` such that constraints
    are enforced throughout the harness.
    
    Note that, because `managed_config.toml` is deprecated, we do not add
    support for the new `external-sandbox` variant recently introduced in
    https://github.com/openai/codex/pull/8290. As noted, that variant is not
    supported in `config.toml` today, but can be configured programmatically
    via app server.
  • chore: cleanup Config instantiation codepaths (#8226)
    This PR does various types of cleanup before I can proceed with more
    ambitious changes to config loading.
    
    First, I noticed duplicated code across these two methods:
    
    
    https://github.com/openai/codex/blob/774bd9e432fa2e0f4e059e97648cf92216912e19/codex-rs/core/src/config/mod.rs#L314-L324
    
    
    https://github.com/openai/codex/blob/774bd9e432fa2e0f4e059e97648cf92216912e19/codex-rs/core/src/config/mod.rs#L334-L344
    
    This has now been consolidated in
    `load_config_as_toml_with_cli_overrides()`.
    
    Further, I noticed that `Config::load_with_cli_overrides()` took two
    similar arguments:
    
    
    https://github.com/openai/codex/blob/774bd9e432fa2e0f4e059e97648cf92216912e19/codex-rs/core/src/config/mod.rs#L308-L311
    
    The difference between `cli_overrides` and `overrides` was not
    immediately obvious to me. At first glance, it appears that one should
    be able to be expressed in terms of the other, but it turns out that
    some fields of `ConfigOverrides` (such as `cwd` and
    `codex_linux_sandbox_exe`) are, by design, not configurable via a
    `.toml` file or a command-line `--config` flag.
    
    That said, I discovered that many callers of
    `Config::load_with_cli_overrides()` were passing
    `ConfigOverrides::default()` for `overrides`, so I created two separate
    methods:
    
    - `Config::load_with_cli_overrides(cli_overrides: Vec<(String,
    TomlValue)>)`
    - `Config::load_with_cli_overrides_and_harness_overrides(cli_overrides:
    Vec<(String, TomlValue)>, harness_overrides: ConfigOverrides)`
    
    The latter has a long name, as it is _not_ what should be used in the
    common case, so the extra typing is designed to draw attention to this
    fact. I tried to update the existing callsites to use the shorter name,
    where possible.
    
    Further, in the cases where `ConfigOverrides` is used, usually only a
    limited subset of fields are actually set, so I updated the declarations
    to leverage `..Default::default()` where possible.
  • windows sandbox: support multiple workspace roots (#6854)
    The Windows sandbox did not previously support multiple workspace roots
    via config. Now it does
  • move cap_sid file into ~/.codex so the sandbox cannot overwrite it (#6798)
    The `cap_sid` file contains the IDs of the two custom SIDs that the
    Windows sandbox creates/manages to implement read-only and
    workspace-write sandbox policies.
    
    It previously lived in `<cwd>/.codex` which means that the sandbox could
    write to it, which could degrade the efficacy of the sandbox. This
    change moves it to `~/.codex/` (or wherever `CODEX_HOME` points to) so
    that it is outside the workspace.
  • add codex debug seatbelt --log-denials (#4098)
    This adds a debugging tool for analyzing why certain commands fail to
    execute under the sandbox.
    
    Example output:
    
    ```
    $ codex debug seatbelt --log-denials bash -lc "(echo foo > ~/foo.txt)"
    bash: /Users/nornagon/foo.txt: Operation not permitted
    
    === Sandbox denials ===
    (bash) file-write-data /dev/tty
    (bash) file-write-data /dev/ttys001
    (bash) sysctl-read kern.ngroups
    (bash) file-write-create /Users/nornagon/foo.txt
    ```
    
    It operates by:
    
    1. spawning `log stream` to watch system logs, and
    2. tracking all descendant PIDs using kqueue + proc_listchildpids.
    
    this is a "best-effort" technique, as `log stream` may drop logs(?), and
    kqueue + proc_listchildpids isn't atomic and can end up missing very
    short-lived processes. But it works well enough in my testing to be
    useful :)
  • Improve world-writable scan (#6381)
    1. scan many more directories since it's much faster than the original
    implementation
    2. limit overall scan time to 2s
    3. skip some directories that are noisy - ApplicationData, Installer,
    etc.
  • core: widen sandbox to allow certificate ops when network is enabled (#5980)
    This allows `gh api` to work in the workspace-write sandbox w/ network
    enabled. Without this we see e.g.
    
    ```
    $ codex debug seatbelt --full-auto gh api repos/openai/codex/pulls --paginate -X GET -F state=all
    Get "https://api.github.com/repos/openai/codex/pulls?per_page=100&state=all": tls: failed to verify certificate: x509: OSStatus -26276
    ```
  • log sandbox commands to $CODEX_HOME instead of cwd (#6171)
    Logging commands in the Windows Sandbox is temporary, but while we are
    doing it, let's always write to CODEX_HOME instead of dirtying the cwd.
  • Windows Sandbox - Alpha version (#4905)
    - Added the new codex-windows-sandbox crate that builds both a library
    entry point (run_windows_sandbox_capture) and a CLI executable to launch
    commands inside a Windows restricted-token sandbox, including ACL
    management, capability SID provisioning, network lockdown, and output
    capture
    (windows-sandbox-rs/src/lib.rs:167, windows-sandbox-rs/src/main.rs:54).
    - Introduced the experimental WindowsSandbox feature flag and wiring so
    Windows builds can opt into the sandbox:
    SandboxType::WindowsRestrictedToken, the in-process execution path, and
    platform sandbox selection now honor the flag (core/src/features.rs:47,
    core/src/config.rs:1224, core/src/safety.rs:19,
    core/src/sandboxing/mod.rs:69, core/src/exec.rs:79,
    core/src/exec.rs:172).
    - Updated workspace metadata to include the new crate and its
    Windows-specific dependencies so the core crate can link against it
    (codex-rs/
        Cargo.toml:91, core/Cargo.toml:86).
    - Added a PowerShell bootstrap script that installs the Windows
    toolchain, required CLI utilities, and builds the workspace to ease
    development
        on the platform (scripts/setup-windows.ps1:1).
    - Landed a Python smoke-test suite that exercises
    read-only/workspace-write policies, ACL behavior, and network denial for
    the Windows sandbox
        binary (windows-sandbox-rs/sandbox_smoketests.py:1).
  • add(core): managed config (#3868)
    ## Summary
    
    - Factor `load_config_as_toml` into `core::config_loader` so config
    loading is reusable across callers.
    - Layer `~/.codex/config.toml`, optional `~/.codex/managed_config.toml`,
    and macOS managed preferences (base64) with recursive table merging and
    scoped threads per source.
    
    ## Config Flow
    
    ```
    Managed prefs (macOS profile: com.openai.codex/config_toml_base64)
                                   ▲
                                   │
    ~/.codex/managed_config.toml   │  (optional file-based override)
                                   ▲
                                   │
                    ~/.codex/config.toml (user-defined settings)
    ```
    
    - The loader searches under the resolved `CODEX_HOME` directory
    (defaults to `~/.codex`).
    - Managed configs let administrators ship fleet-wide overrides via
    device profiles which is useful for enforcing certain settings like
    sandbox or approval defaults.
    - For nested hash tables: overlays merge recursively. Child tables are
    merged key-by-key, while scalar or array values replace the prior layer
    entirely. This lets admins add or tweak individual fields without
    clobbering unrelated user settings.
  • fix: ensure cwd for conversation and sandbox are separate concerns (#3874)
    Previous to this PR, both of these functions take a single `cwd`:
    
    
    https://github.com/openai/codex/blob/71038381aa0f51aa62e1a2bcc7cbf26a05b141f3/codex-rs/core/src/seatbelt.rs#L19-L25
    
    
    https://github.com/openai/codex/blob/71038381aa0f51aa62e1a2bcc7cbf26a05b141f3/codex-rs/core/src/landlock.rs#L16-L23
    
    whereas `cwd` and `sandbox_cwd` should be set independently (fixed in
    this PR).
    
    Added `sandbox_distinguishes_command_and_policy_cwds()` to
    `codex-rs/exec/tests/suite/sandbox.rs` to verify this.