## Summary
1. Add a Security Boundaries section to `SECURITY.md`.
2. Point readers to the Codex Agent approvals and security documentation
for sandboxing, approvals, and network controls.
## Validation
1. Reviewed the `SECURITY.md` diff in a clean worktree.
2. No tests run. Docs only change.
Azure Responses providers were still falling back to local compaction
because the compaction gate only checked
`ModelProviderInfo::is_openai()`.
Move the capability check onto `ModelProviderInfo` with
`supports_remote_compaction()`, backed by the existing Azure Responses
endpoint detection used in `codex-api`, and have `core::compact`
delegate to that helper.
Add regression coverage for:
- OpenAI providers using remote compaction
- Azure providers using remote compaction
- non-OpenAI/non-Azure providers staying on the local path
resolves#17773
---------
Co-authored-by: Michael Bolin <mbolin@openai.com>
## Why
#17763 moved sandbox-state delivery for MCP tool calls to request
`_meta` via the `codex/sandbox-state-meta` experimental capability.
Keeping the older `codex/sandbox-state` capability meant Codex still
maintained a second transport that pushed updates with the custom
`codex/sandbox-state/update` request at server startup and when the
session sandbox policy changed.
That duplicate MCP path is redundant with the per-tool-call metadata
path and makes the sandbox-state contract larger than needed. The
existing managed network proxy refresh on sandbox-policy changes is
still needed, so this keeps that behavior separate from the removed MCP
notification.
## What Changed
- Removed the exported `MCP_SANDBOX_STATE_CAPABILITY` and
`MCP_SANDBOX_STATE_METHOD` constants.
- Removed detection of `codex/sandbox-state` during MCP initialization
and stopped sending `codex/sandbox-state/update` at server startup.
- Removed the `McpConnectionManager::notify_sandbox_state_change`
plumbing while preserving the managed network proxy refresh when a user
turn changes sandbox policy.
- Slimmed `McpConnectionManager::new` so startup paths pass only the
initial `SandboxPolicy` needed for MCP elicitation state.
- Kept `codex/sandbox-state-meta` support intact; servers that opt in
still receive the current `SandboxState` on tool-call request `_meta`
([remaining call
path](ff2d3c1e72/codex-rs/core/src/mcp_tool_call.rs (L487-L526))).
- Added regression coverage for refreshing the live managed network
proxy on a per-turn sandbox-policy change.
## Verification
- `cargo test -p codex-core
new_turn_refreshes_managed_network_proxy_for_sandbox_change`
- `cargo test -p codex-mcp`
## Summary
- Track outbound remote-control sequence IDs independently for each
client stream.
- Retain unacked outbound messages per stream using FIFO buffers.
- Require stream-scoped acks and update tests for contiguous per-stream
sequencing.
## Why
The remote-control peer uses outbound sequence gaps to detect lost
messages and re-initialize. A single global outbound sequence counter
can create apparent gaps on an individual stream when another stream
receives an interleaved message.
## Validation
- `just fmt`
- `cargo test -p codex-app-server remote_control`
- `just fix -p codex-app-server`
- `git diff --check`
## Summary
- Move auth header construction into the
`AuthProvider::add_auth_headers` contract.
- Inline `CoreAuthProvider` header mutation in its provider impl and
remove the shared header-map helper.
- Update HTTP, websocket, file upload, sideband websocket, and test auth
callsites to use the provider method.
- Add direct coverage for `CoreAuthProvider` auth header mutation.
## Testing
- `just fmt`
- `cargo test -p codex-api`
- `cargo test -p codex-core
client::tests::auth_request_telemetry_context_tracks_attached_auth_and_retry_phase`
- `cargo test -p codex-core` failed on unrelated/reproducible
`tools::handlers::multi_agents::tests::multi_agent_v2_followup_task_interrupts_busy_child_without_losing_message`
---------
Co-authored-by: Celia Chen <celia@openai.com>
Builds on top of #17659
Move the filesystem + sqlite thread listing-related operations inside of
a local ThreadStore implementation and call ThreadStore from the places
that used to perform these filesystem/sqlite operations.
This is the first of a series of PRs that will implement the rest of the
local ThreadStore.
Testing:
- added unit tests for the thread store implementation
- adjusted some unit tests in the realtime + personality packages whose
callsites changed. Specifically I'm trying to hide ThreadMetadata inside
of the local implementation and make ThreadMetadata a sqlite
implementation detail concern rather than a public interface, preferring
the more generate StoredThread interface instead
- added a corner case test for the personality migration package that
wasn't covered by the existing test suite
- adjust the behavior of searched thread listing to run the existing
local rollout repair/backfill pass _before_ querying SQLite results, so
callers using ThreadStore::list_threads do not miss matches after a
partial metadata warm-up
## Summary
- Ensure direct namespaced MCP tool groups are emitted with a non-empty
namespace description even when namespace metadata is missing or blank.
- Add regression coverage for missing MCP namespace descriptions.
## Cause
Latest `main` can serialize a direct namespaced MCP tool group with an
empty top-level `description`. The namespace description path used
`unwrap_or_default()` when `tool_namespaces` did not include metadata
for that namespace, so the outbound Responses API payload could contain
a tool like `{"type":"namespace","description":""}`. The Responses API
rejects that because namespace tool descriptions must be a non-empty
string.
## Fix
- Add a fallback namespace description: `Tools in the <namespace>
namespace.`
- Preserve provided namespace descriptions after trimming, but treat
blank descriptions as missing.
### Issue I am seeing
This is what I am seeing on the local build.
<img width="1593" height="488" alt="Screenshot 2026-04-15 at 10 55 55
AM"
src="https://github.com/user-attachments/assets/bab668ba-bf17-4c71-be4e-b102202fce57"
/>
---------
Co-authored-by: Sayan Sisodiya <sayan@openai.com>
## Why
While reviewing https://github.com/openai/codex/pull/17958, the helper
name `is_azure_responses_wire_base_url` looked misleading because the
helper returns true for either the `azure` provider name or an Azure
Responses `base_url`. The new name makes both inputs part of the
contract.
## What
- Rename `is_azure_responses_wire_base_url` to
`is_azure_responses_provider`.
- Move the `openai.azure.` marker into
`matches_azure_responses_base_url` so all base URL marker matching is
centralized.
- Keep `Provider::is_azure_responses_endpoint()` behavior unchanged.
## Verification
- Compared the parent and current implementations.
`name.eq_ignore_ascii_case("azure")` still returns true before
consulting `base_url`, `None` still returns false, base URLs are still
lowercased before marker matching, and the same Azure marker set is
checked.
- Ran `cargo test -p codex-api`.
## Summary
- Skip directory entries whose metadata lookup fails during
`fs/readDirectory`
- Add an exec-server regression test covering a broken symlink beside
valid entries
## Testing
- `just fmt`
- `cargo test -p codex-exec-server` (started, but dependency/network
updates stalled before completion in this environment)
## Summary
Stack PR 2 of 4 for feature-gated agent identity support.
This PR adds agent identity registration behind
`features.use_agent_identity`. It keeps the app-server protocol
unchanged and starts registration after ChatGPT auth exists rather than
requiring a client restart.
## Stack
- PR1: https://github.com/openai/codex/pull/17385 - add
`features.use_agent_identity`
- PR2: https://github.com/openai/codex/pull/17386 - this PR
- PR3: https://github.com/openai/codex/pull/17387 - register agent tasks
when enabled
- PR4: https://github.com/openai/codex/pull/17388 - use `AgentAssertion`
downstream when enabled
## Validation
Covered as part of the local stack validation pass:
- `just fmt`
- `cargo test -p codex-core --lib agent_identity`
- `cargo test -p codex-core --lib agent_assertion`
- `cargo test -p codex-core --lib websocket_agent_task`
- `cargo test -p codex-api api_bridge`
- `cargo build -p codex-cli --bin codex`
## Notes
The full local app-server E2E path is still being debugged after PR
creation. The current branch stack is directionally ready for review
while that follow-up continues.
## Summary
- Remove the exec-server-side manual filesystem request path preflight
before invoking the sandbox helper.
- Keep sandbox helper policy construction and platform sandbox
enforcement as the access boundary.
- Add a portable local+remote regression for writing through an
explicitly configured alias root.
- Remove the metadata symlink-escape assertion that depended on the
deleted manual preflight; no replacement metadata-specific access probe
is added.
## Tests
- `cargo test -p codex-exec-server --lib`
- `cargo test -p codex-exec-server --test file_system`
- `git diff --check`
stacked on #17402.
MCP tools returned by `tool_search` (deferred tools) get registered in
our `ToolRegistry` with a different format than directly available
tools. this leads to two different ways of accessing MCP tools from our
tool catalog, only one of which works for each. fix this by registering
all MCP tools with the namespace format, since this info is already
available.
also, direct MCP tools are registered to responsesapi without a
namespace, while deferred MCP tools have a namespace. this means we can
receive MCP `FunctionCall`s in both formats from namespaces. fix this by
always registering MCP tools with namespace, regardless of deferral
status.
make code mode track `ToolName` provenance of tools so it can map the
literal JS function name string to the correct `ToolName` for
invocation, rather than supporting both in core.
this lets us unify to a single canonical `ToolName` representation for
each MCP tool and force everywhere to use that one, without supporting
fallbacks.
## Summary
- Fix marketplace-add local path detection on Windows by using
`Path::is_absolute()`.
- Make marketplace-add local-source tests parse/write TOML through the
same helpers instead of raw string matching.
- Update `rand` 0.9.x to 0.9.3 and document the remaining audited `rand`
0.8.5 advisory exception.
- Refresh `MODULE.bazel.lock` after the Cargo.lock update.
## Why
Latest `main` had two independent CI blockers: marketplace-add tests
were not portable to Windows path/TOML escaping, and cargo-deny still
reported `RUSTSEC-2026-0097` after the recent rustls-webpki fix.
## Validation
- `cargo test -p codex-core marketplace_add -- --nocapture`
- `cargo deny --all-features check`
- `just bazel-lock-check`
- `just fix -p codex-core`
- `just fmt`
- `git diff --check`
## 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
## Changes
Allows MCPs to opt in to receiving sandbox config info through `_meta`
on model-initiated tool calls. This lets MCPs adhere to the thread's
sandbox if they choose to.
## Details
- Adds the `codex/sandbox-state-meta` experimental MCP capability.
- Tracks whether each MCP server advertises that capability.
- When a server opts in, `codex-core` injects the current `SandboxState`
into model-initiated MCP tool-call request `_meta`.
## Verification
- added an integration test for the capability
`exec()` had a number of arguments that were unused, making the function
signature misleading. This PR aims to clean things up to clarify the
role of this function and to clarify which fields of `ExecParams` are
unused and why.
## Why
`spawn_command_under_seatbelt()` in `codex-rs/core/src/seatbelt.rs` had
fallen out of production use and was only referenced by test-only
wrappers. That left us with sandbox tests that could stay green even if
the actual seatbelt exec path regressed, because production shell
execution now flows through `SandboxManager::transform()` and
`ExecRequest::from_sandbox_exec_request()` instead of that helper.
Removing the dead helper also exposed one downstream `codex-exec`
integration test that still imported it, which broke `just clippy`.
## What Changed
- Removed `codex-rs/core/src/seatbelt.rs` and stopped exporting
`codex_core::seatbelt`.
- Removed the redundant `codex-rs/core/tests/suite/seatbelt.rs` coverage
that only exercised the dead helper.
- Kept the `openpty` regression check, but moved it into
`codex-rs/core/tests/suite/exec.rs` so it now runs through
`process_exec_tool_call()`.
- Fixed the seatbelt denial test in `codex-rs/core/tests/suite/exec.rs`
to use `/usr/bin/touch`, so it actually exercises the sandbox instead of
a nonexistent path.
- Updated `codex-rs/exec/tests/suite/sandbox.rs` on macOS to build the
sandboxed command through `build_exec_request()` and spawn the
transformed command, instead of importing the removed helper.
- Left the lower-level seatbelt policy coverage in
`codex-rs/sandboxing/src/seatbelt_tests.rs`, where the policy generator
is still covered directly.
## Verification
- `cargo test -p codex-core suite::exec::`
- `cargo test -p codex-exec`
- `cargo clippy -p codex-exec --tests -- -D warnings`
## Summary
- reuse a shared remote exec-server for remote-aware codex-core
integration tests within a test binary process
- keep per-test remote cwd creation and cleanup so tests retain
workspace isolation
- leave codex_self_exe, codex_linux_sandbox_exe, cwd_path(), and
workspace_path() behavior unchanged
## Validation
- rustfmt codex-rs/core/tests/common/test_codex.rs
- git diff --check
- CI is running on the updated branch
Fix clippy warnings in external agent config migration
```
error: this expression creates a reference which is immediately dereferenced by the compiler
--> core/src/external_agent_config.rs:188:55
|
188 | let migrated = build_config_from_external(&settings)?;
| ^^^^^^^^^ help: change this to: `settings`
|
= help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.93.0/index.html#needless_borrow
= note: requested on the command line with `-D clippy::needless-borrow`
error: useless conversion to the same type: `codex_utils_absolute_path::AbsolutePathBuf`
--> core/src/external_agent_config.rs:355:27
|
355 | match AbsolutePathBuf::try_from(
| ___________________________^
356 | | add_marketplace_outcome
357 | | .installed_root
358 | | .join(INSTALLED_MARKETPLACE_MANIFEST_RELATIVE_PATH),
359 | | ) {
| |_____________________^
|
= help: consider removing `AbsolutePathBuf::try_from()`
= help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.93.0/index.html#useless_conversion
= note: `-D clippy::useless-conversion` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::useless_conversion)]`
error: aborting due to 2 previous errors
```
## Summary
- wrap routed delegation text in a small XML envelope before submitting
it as a user turn
- escape XML text content so the envelope stays well formed
- update focused coverage for the wrapper and the affected routed-turn
expectations
## What
Disable `Feature::CodexHooks` when building guardian review session
config
## Why
Guardian review sessions were respecting the Stop hook and could ingest
synthetic `<hook_prompt>` user turns Guardian should ignore hooks, while
the main session and regular subagents continue to respect them
In other words Guardian was getting ralph-looped
Co-authored-by: Codex <noreply@openai.com>
### **Issue**
guardian_parallel_reviews_fork_from_last_committed_trunk_history was
failing on Windows/Bazel with a stack overflow:
`thread
'guardian::tests::guardian_parallel_reviews_fork_from_last_committed_trunk_history'
has overflowed its stack`
- This problem was a stack-headroom problem
### **Solution**
Reduced stack pressure in the guardian async path by boxing thin wrapper
futures, and run the affected test on a dedicated 2 MiB thread stack.
Concretely:
- added Box::pin(...) around thin async wrapper hops in the guardian
review/delegate path
- changed
guardian_parallel_reviews_fork_from_last_committed_trunk_history to run
inside an explicitly sized thread stack so it has enough headroom in
low-stack environments
## Summary
- Port marketplace source support into the shared core marketplace-add
flow
- Support local marketplace directory sources
- Support direct `marketplace.json` URL sources
- Persist the new source types in config/schema and cover them in CLI
and app-server tests
## Validation
- `cargo test -p codex-core marketplace_add`
- `cargo test -p codex-cli marketplace_add`
- `cargo test -p codex-app-server marketplace_add`
- `just write-config-schema`
- `just fmt`
- `just fix -p codex-core`
- `just fix -p codex-cli`
## Context
Current `main` moved marketplace-add behavior into shared core code and
still assumed only git-backed sources. This change keeps that structure
but restores support for local directories and direct manifest URLs in
the shared path.
## Summary
- Allows selected MCP results to return a larger default result set.
- Keeps the existing default cap for other MCP results.
- Applies the cap consistently when higher explicit limits are
requested.
## Testing
- `cargo test -p codex-core tool_search`
- Ran a local CLI smoke test with two stdio MCP servers exposing 100
tools each; the selected-server query returned 20 tools and the
regular-server query returned 8.
- Add trace-only wire logging for realtime websocket request/event text
payloads and the WebRTC call SDP request.
- Gate raw realtime logs behind
`RUST_LOG=codex_api::realtime_websocket::wire=trace` so normal logs stay
quiet.
---------
Co-authored-by: Codex <noreply@openai.com>
Introduce a ThreadStore interface for mediating access to the filesystem
(rollout jsonl files + sqlite db) based thread storage.
In later PRs we'll move the existing fs code behind a "local"
implementation of this ThreadStore interface.
This PR should be a no-op behaviorally, it only introduces the
interface.