mirror of
https://github.com/openai/codex.git
synced 2026-10-20 17:38:45 +00:00
dev/kyleb/plugin-script-command-resolver
21
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
c8e5db16c9 |
feat: show enterprise monthly credit limits in status (#24812)
## Summary
Enterprise users can have an effective monthly credit limit, but Codex
`/status` currently drops that metadata from the account-usage response.
This change adds the optional `spend_control.individual_limit`
projection to the existing rate-limit snapshot flow. The backend client
reads the monthly limit, app-server exposes it as `individualLimit`, and
the TUI renders a `Monthly credit limit` row through the existing
progress-bar renderer.
When the backend does not return an effective monthly limit, existing
rate-limit behavior is unchanged.
## Existing backend state
The account-usage backend already returns the effective monthly limit
and current usage together:
```json
{
"spend_control": {
"reached": false,
"individual_limit": {
"limit": "25000",
"used": "8000",
"remaining": "17000",
"used_percent": 32,
"remaining_percent": 68,
"reset_after_seconds": 86400,
"reset_at": 1778137680
}
}
}
```
Before this change, Codex projected rolling `primary` and `secondary`
windows plus `credits`. It ignored `spend_control.individual_limit`, so
app-server clients and `/status` could not render the monthly cap.
The updated flow is:
```text
account usage backend
-> backend-client reads spend_control.individual_limit
-> existing rate-limit snapshot carries optional individual_limit
-> app-server exposes optional individualLimit
-> TUI renders Monthly credit limit
```
## App-server contract
`account/rateLimits/read` and sparse `account/rateLimits/updated`
notifications now include an additive nullable
`rateLimits.individualLimit` field:
```json
{
"individualLimit": {
"limit": "25000",
"used": "8000",
"remainingPercent": 68,
"resetsAt": 1778137680
}
}
```
In an `account/rateLimits/read` response, `null` means no monthly limit
is available. `account/rateLimits/updated` remains a sparse rolling
notification: clients merge available values into their most recent
`account/rateLimits/read` snapshot or refetch. Nullable account metadata
in a rolling notification does not clear a previously observed value.
## Design decisions
- Extend the existing rate-limit snapshot instead of introducing a
separate request or wire-level update protocol.
- Keep the Codex projection narrow: `/status` needs the effective limit,
current usage, remaining percentage, and reset timestamp.
- Render the monthly row through the existing progress-bar renderer,
with one optional detail line for `8,000 of 25,000 credits used`.
- Keep the backend response optional so existing accounts and older
usage states preserve their current behavior.
- Preserve cached monthly metadata when sparse rolling notifications
omit it. Live account-usage reads remain authoritative and can clear a
removed limit.
## Visual evidence
```text
Monthly credit limit: [██████████████░░░░░░] 68% left (resets 07:08 on 7 May)
8,000 of 25,000 credits used
```
Snapshot:
`codex-rs/tui/src/status/snapshots/codex_tui__status__tests__status_snapshot_includes_enterprise_monthly_credit_limit.snap`
## Testing
Tests: generated app-server schema verification, protocol tests,
backend-client tests, app-server integration coverage, TUI snapshot
coverage, formatting, and workspace lint cleanup.
|
||
|
|
5314f55097 |
fix: run standalone updates noninteractively (#24637)
# Summary The standalone update action currently downloads and runs the Codex installer as an interactive command. When an existing managed Codex install is present, accepting an update can therefore enter an installer prompt instead of completing the update. This change runs the standalone installer with `CODEX_NON_INTERACTIVE=1` on macOS/Linux and Windows. The installer environment-variable support is introduced by the parent PR; this PR wires that behavior into the Codex CLI update action. The rendered Windows command remains shell-safe, and long update commands wrap within the update-notice card. The standard test target snapshots the standalone notice for both platforms. # Stack 1. [#21567](https://github.com/openai/codex/pull/21567) - Adds environment-controlled release selection and noninteractive installer behavior. 2. [#24637](https://github.com/openai/codex/pull/24637) - Runs standalone updates with `CODEX_NON_INTERACTIVE=1`. (current) 3. [#24639](https://github.com/openai/codex/pull/24639) - Removes explicit release argument inputs in favor of `CODEX_RELEASE`. # Evidence Standalone updater-shaped macOS install with an existing npm-managed Codex on `PATH`: https://github.com/user-attachments/assets/a27fe9e9-db3a-4c39-a514-24bd3d1f01e8 # Testing Tests: targeted `codex-tui` update-action and update-notice snapshot tests, Rust formatting, benchmark smoke validation, macOS live-terminal standalone-update smoke testing, Windows ARM64 PowerShell standalone-update smoke testing through Parallels, and CI. |
||
|
|
8ed38fe38e |
fix: add noninteractive install script mode (#21567)
# Summary The Codex standalone installers can pause after installation to ask about an older managed install or launching Codex. That makes unattended bootstrap and update flows hard to complete reliably. This PR adds noninteractive installer control on macOS/Linux and Windows through `CODEX_NON_INTERACTIVE=1`. Noninteractive operation is environment-only, which gives automated callers one stable way to suppress prompts. When a noninteractive install leaves an older npm, bun, or brew-managed Codex installed, the standalone bin is configured ahead of that command on `PATH` so the newly installed Codex is the one future launches select. It also supports `CODEX_RELEASE` for callers that select a release through environment variables while retaining the existing explicit release inputs. Release selection accepts `latest`, stable `x.y.z` versions, and Codex prereleases written as `rust-v0.134.0-alpha.3`, `v0.134.0-alpha.3`, or `0.134.0-alpha.3`; it validates that shape before constructing release requests. # Stack 1. [#21567](https://github.com/openai/codex/pull/21567) - Adds release and noninteractive environment controls to the installers. (current) 2. [#24637](https://github.com/openai/codex/pull/24637) - Runs standalone updater installs with `CODEX_NON_INTERACTIVE=1`. 3. [#24639](https://github.com/openai/codex/pull/24639) - Removes explicit release argument inputs in favor of `CODEX_RELEASE`. # Evidence | Before | After | | --- | --- | |  |  | Environment-controlled macOS install with an existing npm-managed Codex on `PATH`: https://github.com/user-attachments/assets/442e0b5b-4a32-4bf5-996b-68784777380d # Design decisions Windows installs using the older standalone bin layout still require an interactive migration confirmation. Noninteractive mode does not auto-migrate that existing directory because replacing it is a destructive transition for an early, limited-use layout; unattended installs on that layout fail with an instruction to rerun interactively. # Testing Tests: installer syntax validation, release-selector acceptance and rejection coverage including PowerShell `Latest` compatibility, macOS live-terminal installer smoke testing with environment-controlled stable and prerelease installation and competing PATH precedence, shell rejection of the omitted noninteractive flag, and Windows ARM64 PowerShell smoke testing with environment-only noninteractive behavior, retained release input, and competing PATH precedence through Parallels. |
||
|
|
c2141c7ce0 |
fix: serialize unix app-server startup (#23516)
# Summary Unix-socket app-server startup can currently race when multiple launch attempts target the same `CODEX_HOME`. Those processes can overlap before the control socket exists, which lets them enter SQLite state initialization concurrently and reproduce the startup corruption pattern seen in SSH mode. This change makes the app-server own that singleton startup guarantee. Unix-socket startup now takes a `CODEX_HOME`-scoped advisory lock before SQLite initialization, runs the existing control-socket preparation check while holding that lock, returns the established `AddrInUse` error when another live listener already owns the socket, and releases the lock once the new listener has bound its socket. # Design decisions - The singleton rule lives in `app-server --listen unix://`, not in a desktop-only caller path, so every Unix-socket launch gets the same race protection. - A duplicate raw app-server launch returns an error instead of silently succeeding. The attach operation remains `app-server proxy`, which continues to connect to an already-running listener. - The lock is held only across the dangerous startup window: socket preparation, SQLite initialization, and socket bind. It is not held for the app-server lifetime. - Listener detection stays in `prepare_control_socket_path(...)`, so the preexisting live-listener and stale-socket behavior remains the single source of truth. # Testing Tests: targeted Unix-socket transport tests on the branch checkout, full `codex-cli` build on `efrazer-db10`, and an SSH-style smoke on `efrazer-db10` covering concurrent app-server starts, explicit duplicate-start errors, and absence of SQLite startup-error matches in launch logs. |
||
|
|
d32cb2c6ac |
fix: harden plugin creator sharing validation (#22893)
# Summary Before this change, the sample plugin creator could emit placeholder-heavy manifests that fail workspace sharing, and it chose a repo-local marketplace implicitly whenever it ran from inside a git checkout. This PR makes generated plugins share-ready by default. It switches creation to the personal marketplace unless the caller explicitly opts into repo-local paths, adds a validator that mirrors the workspace plugin ingestion contract, and updates the skill prompt and docs to describe the real flow. The goal is to stop malformed generated plugins before they reach sharing and to make the default placement match the personal marketplace behavior users expect. ## Changes - Generate share-safe plugin manifests instead of `[TODO: ...]` placeholder payloads. - Default plugin and marketplace creation to `~/plugins` and `~/.agents/plugins/marketplace.json`. - Keep repo-local marketplace creation available through explicit `--path` and `--marketplace-path` arguments. - Add `validate_plugin.py` to check manifests, companion files, skill frontmatter, skill agent YAML, asset paths, and backend-shaped contracts before sharing. - Refresh the plugin creator skill text, reference docs, and default prompt to describe validation and the personal default. ## Design decisions - The validator tracks the workspace ingestion schema directly, including the required `defaultPrompt` alias handling and skill `agents/openai.yaml` checks. - The validator keeps one intentional extra preflight rule: leftover `[TODO: ...]` placeholders are rejected before sharing even when a single placeholder would not independently violate backend type validation. - Repo-local creation stays possible, but it is now explicit instead of cwd-sensitive. ## Testing Tests: targeted Python syntax checks, plugin skill validation, staged diff whitespace validation, 15 generated plugin smoke runs, backend manifest-schema acceptance for all 15 generated bundles, and a git-repo cwd regression proving the creator still writes to the personal marketplace by default. |
||
|
|
01b4817bac |
docs(skills): simplify plugin creator deeplink shape (#22240)
## Summary Plugin Creator now documents the shorter local-plugin handoff URL that the app can interpret directly. [#22221](https://github.com/openai/codex/pull/22221) teaches the skill to end marketplace-backed creation flows with named View and Share links; this follow-up updates those examples so the skill only emits the normalized plugin name, the absolute marketplace path, and optional share mode. The documented shape is: ```txt codex://plugins/<normalized-plugin-name>?marketplacePath=<absolute-marketplace-json-path> codex://plugins/<normalized-plugin-name>?marketplacePath=<absolute-marketplace-json-path>&mode=share ``` The skill text now states exactly where the normalized plugin name belongs, exactly where the absolute marketplace path belongs, and that it should not add `pluginName` or `hostId` query parameters. ## Testing Tests: plugin-creator skill validation. |
||
|
|
fd24c00b0b |
feat(skills): default plugin creator to personal share flow (#22221)
## Summary Plugin creation now defaults to the personal marketplace path and ends with a readable handoff back into Codex after a marketplace-backed scaffold. Before this change, `plugin-creator` centered repo-local marketplace updates and did not clearly guide the agent to return the user to the created plugin afterward. This PR updates the bundled system skill so marketplace-backed scaffolds default to `~/plugins/<plugin-name>` plus `~/.agents/plugins/marketplace.json`, ask for user intent only when an existing repo marketplace makes personal vs team scope ambiguous, and end with named Markdown deeplinks labeled `View <plugin-name>` and `Share <plugin-name>`. ## What changed - default marketplace-backed creation to the personal plugin location - document the explicit repo/team override path for codebases that should own the plugin entry - ask personal vs team only when the current Git repo already has `.agents/plugins/marketplace.json` and the user has not stated scope - require named Markdown deeplinks after marketplace-backed creation so the final response returns the user to the exact plugin cleanly - keep the deeplink targets precise with real absolute `marketplacePath` and normalized `pluginName` values - align the bundled prompt, scaffold help text, and marketplace reference spec with the new default ## Testing Tests: targeted skill validation, Python compile checks, personal-default scaffold smoke, repo-override scaffold smoke, and whitespace checks. |
||
|
|
f6797c3ac6 | feat: verify agent identity JWTs with JWKS (#19764) | ||
|
|
0e8d6b8765 |
fix: configure AgentIdentity AuthAPI base URL (#19904)
## Summary AgentIdentity runtime loading currently registers tasks against a single hardcoded AuthAPI base URL. That works for production, but local and staging validation may need registration to target a different authapi-login-provider without baking internal staging service URLs into the OSS binary. This PR adds a small config surface for `agent_identity_authapi_base_url` and threads it through the existing auth-loading path as a direct argument. Explicit config wins. Without config, task registration keeps using the production AuthAPI URL, matching the current default behavior. ## Stack 1. openai/codex#19762 - `refactor: make auth loading async` (merged) 2. openai/codex#19763 - `refactor: load agent identity runtime eagerly` 3. This PR - `fix: configure AgentIdentity AuthAPI base URL` 4. openai/codex#19764 - `feat: verify agent identity JWTs with JWKS` ## Design decisions - Keep the existing auth-loading shape and pass the new value as an argument. This avoids another wrapper loader and keeps the call path readable. - Add config instead of embedding internal staging URLs. Environments that need a non-production AuthAPI can configure it explicitly. - Keep the default AuthAPI registration URL as production. `chatgpt_base_url` remains separate and is used by the follow-up JWKS verification PR for fetching public keys from the ChatGPT backend route. - Resolve the AuthAPI base URL inside AgentIdentity loading, because task registration is the only consumer of this value. ## Testing Tests: targeted Rust checks, AgentIdentity auth tests, config schema regeneration, formatter/fix pass, and whitespace diff check. |
||
|
|
c08177f7d0 |
refactor: load agent identity runtime eagerly (#19763)
## Summary AgentIdentity auth previously registered the process task lazily behind a `OnceCell`. That meant the auth object could be constructed before its runtime task binding was known. This PR makes AgentIdentity auth load the runtime task at auth load time and stores the resulting process task id directly on the auth object. The model-provider call path can then read a concrete task id instead of handling a missing lazy value. ## Stack 1. [refactor: make auth loading async](https://github.com/openai/codex/pull/19762) (merged) 2. **This PR:** [refactor: load AgentIdentity runtime eagerly](https://github.com/openai/codex/pull/19763) 3. [fix: configure AgentIdentity AuthAPI base URL](https://github.com/openai/codex/pull/19904) 4. [feat: verify AgentIdentity JWTs with JWKS](https://github.com/openai/codex/pull/19764) ## Important call sites | Area | Change | | --- | --- | | `AgentIdentityAuth::load` | Registers the process task during auth loading and stores `process_task_id`. | | `CodexAuth::from_agent_identity_jwt` | Awaits AgentIdentity auth loading. | | model-provider auth | Reads a concrete `process_task_id` instead of an optional lazy value. | | AgentIdentity auth tests | Mock task registration now covers eager runtime allocation. | ## Design decisions AgentIdentity auth now treats task registration as part of constructing a usable auth object. That matches how callers use the value: once auth is present, the model-provider path expects the task-scoped assertion data to be ready. ## Testing Tests: targeted Rust auth test compilation, formatter, scoped Clippy fix, and Bazel lock check. |
||
|
|
2009f6e894 |
refactor: make auth loading async (#19762)
## Summary Auth loading used to expose synchronous construction helpers in several places even though some auth sources now need async work. This PR makes the auth-loading surface async and updates the callers to await it. This is intentionally only plumbing. It does not change how AgentIdentity tokens are decoded, how task runtime ids are allocated, or how JWT signatures are verified. ## Stack 1. **This PR:** [refactor: make auth loading async](https://github.com/openai/codex/pull/19762) 2. [refactor: load AgentIdentity runtime eagerly](https://github.com/openai/codex/pull/19763) 3. [feat: verify AgentIdentity JWTs with JWKS](https://github.com/openai/codex/pull/19764) ## Important call sites | Area | Change | | --- | --- | | `codex-login` auth loading | `CodexAuth` and `AuthManager` construction paths now await auth loading. | | app-server startup | Auth manager construction is awaited during initialization. | | CLI/TUI/exec/MCP/chatgpt callers | Existing auth-loading calls now await the same behavior. | | cloud requirements storage loader | The loader becomes async so it can share the same auth construction path. | | auth tests | Tests that load auth now run in async contexts. | ## Testing Tests: targeted Rust auth test compilation, formatter, scoped Clippy fix, and Bazel lock check. |
||
|
|
fed0a8f4fa |
feat: load AgentIdentity from JWT login/env (#18904)
## Summary This PR lets programmatic AgentIdentity users provide one token through either stdin login or environment auth. `codex login --with-agent-identity` reads an Agent Identity JWT from stdin, validates that it has the required claims, and stores that token as the `agent_identity` value in `auth.json`. The file format is token-only; the decoded account and key fields are runtime state, not hand-authored auth.json fields. The Agent Identity JWT claim shape and decoder live in `codex-agent-identity`; `codex-login` only owns env/storage precedence and conversion into `CodexAuth::AgentIdentity`. When env auth is enabled, `CODEX_AGENT_IDENTITY` can provide the same JWT without writing auth state to disk. `CODEX_API_KEY` still wins if both env vars are set. Reference old stack: https://github.com/openai/codex/pull/17387/changes Reference JWT/env stack: https://github.com/openai/codex/pull/18176 ## Stack 1. https://github.com/openai/codex/pull/18757: full revert 2. https://github.com/openai/codex/pull/18871: isolated Agent Identity crate 3. https://github.com/openai/codex/pull/18785: explicit AgentIdentity auth mode and startup task allocation 4. https://github.com/openai/codex/pull/18811: migrate Codex backend auth callsites through AuthProvider 5. This PR: accept AgentIdentity JWTs through login/env ## Testing Tests: targeted login and Agent Identity crate tests, CLI checks, scoped formatter/linter cleanup, and CI. --------- Co-authored-by: Shijie Rao <shijie.rao@openai.com> |
||
|
|
5882f3f95e |
refactor: route Codex auth through AuthProvider (#18811)
## Summary This PR moves Codex backend request authentication from direct bearer-token handling to `AuthProvider`. The new `codex-auth-provider` crate defines the shared request-auth trait. `CodexAuth::provider()` returns a provider that can apply all headers needed for the selected auth mode. This lets ChatGPT token auth and AgentIdentity auth share the same callsite path: - ChatGPT token auth applies bearer auth plus account/FedRAMP headers where needed. - AgentIdentity auth applies AgentAssertion plus account/FedRAMP headers where needed. Reference old stack: https://github.com/openai/codex/pull/17387/changes ## Callsite Migration | Area | Change | | --- | --- | | backend-client | accepts an `AuthProvider` instead of a raw token/header | | chatgpt client/connectors | applies auth through `CodexAuth::provider()` | | cloud tasks | keeps Codex-backend gating, applies auth through provider | | cloud requirements | uses Codex-backend auth checks and provider headers | | app-server remote control | applies provider headers for backend calls | | MCP Apps/connectors | gates on `uses_codex_backend()` and keys caches from generic account getters | | model refresh | treats AgentIdentity as Codex-backend auth | | OpenAI file upload path | rejects non-Codex-backend auth before applying headers | | core client setup | keeps model-provider auth flow and allows AgentIdentity through provider-backed OpenAI auth | ## Stack 1. https://github.com/openai/codex/pull/18757: full revert 2. https://github.com/openai/codex/pull/18871: isolated Agent Identity crate 3. https://github.com/openai/codex/pull/18785: explicit AgentIdentity auth mode and startup task allocation 4. This PR: migrate Codex backend auth callsites through AuthProvider 5. https://github.com/openai/codex/pull/18904: accept AgentIdentity JWTs and load `CODEX_AGENT_IDENTITY` ## Testing Tests: targeted Rust checks, cargo-shear, Bazel lock check, and CI. |
||
|
|
69c8913e24 |
feat: add explicit AgentIdentity auth mode (#18785)
## Summary This PR adds `CodexAuth::AgentIdentity` as an explicit auth mode. An AgentIdentity auth record is a standalone `auth.json` mode. When `AuthManager::auth().await` loads that mode, it registers one process-scoped task and stores it in runtime-only state on the auth value. Header creation stays synchronous after that because the task is initialized before callers receive the auth object. This PR also removes the old feature flag path. AgentIdentity is selected by explicit auth mode, not by a hidden flag or lazy mutation of ChatGPT auth records. Reference old stack: https://github.com/openai/codex/pull/17387/changes ## Design Decisions - AgentIdentity is a real auth enum variant because it can be the only credential in `auth.json`. - The process task is ephemeral runtime state. It is not serialized and is not stored in rollout/session data. - Account/user metadata needed by existing Codex backend checks lives on the AgentIdentity record for now. - `is_chatgpt_auth()` remains token-specific. - `uses_codex_backend()` is the broader predicate for ChatGPT-token auth and AgentIdentity auth. ## Stack 1. https://github.com/openai/codex/pull/18757: full revert 2. https://github.com/openai/codex/pull/18871: isolated Agent Identity crate 3. This PR: explicit AgentIdentity auth mode and startup task allocation 4. https://github.com/openai/codex/pull/18811: migrate Codex backend auth callsites through AuthProvider 5. https://github.com/openai/codex/pull/18904: accept AgentIdentity JWTs and load `CODEX_AGENT_IDENTITY` ## Testing Tests: targeted Rust checks, cargo-shear, Bazel lock check, and CI. |
||
|
|
564860e8bd |
refactor: add agent identity crate (#18871)
## Summary This PR adds `codex-agent-identity` as an isolated crate for Agent Identity business logic. The crate owns: - AgentAssertion construction. - Agent task registration. - private-key assertion signing. - bounded blocking HTTP for task registration. It does not wire AgentIdentity into `auth.json`, `AuthManager`, rollout state, or request callsites. That integration happens in later PRs. Reference old stack: https://github.com/openai/codex/pull/17387/changes ## Stack 1. https://github.com/openai/codex/pull/18757: full revert 2. This PR: isolated Agent Identity crate 3. https://github.com/openai/codex/pull/18785: explicit AgentIdentity auth mode and startup task allocation 4. https://github.com/openai/codex/pull/18811: migrate Codex backend auth callsites through AuthProvider 5. https://github.com/openai/codex/pull/18904: accept AgentIdentity JWTs and load `CODEX_AGENT_IDENTITY` ## Testing Tests: targeted Rust checks, cargo-shear, Bazel lock check, and CI. |
||
|
|
be75785504 |
fix: fully revert agent identity runtime wiring (#18757)
## Summary This PR fully reverts the previously merged Agent Identity runtime integration from the old stack: https://github.com/openai/codex/pull/17387/changes It removes the Codex-side task lifecycle wiring, rollout/session persistence, feature flag plumbing, lazy `auth.json` mutation, background task auth paths, and request callsite changes introduced by that stack. This leaves the repo in a clean pre-AgentIdentity integration state so the follow-up PRs can reintroduce the pieces in smaller reviewable layers. ## Stack 1. This PR: full revert 2. https://github.com/openai/codex/pull/18871: move Agent Identity business logic into a crate 3. https://github.com/openai/codex/pull/18785: add explicit AgentIdentity auth mode and startup task allocation 4. https://github.com/openai/codex/pull/18811: migrate auth callsites through AuthProvider ## Testing Tests: targeted Rust checks, cargo-shear, Bazel lock check, and CI. |
||
|
|
b885c3f8b1 |
Filter Windows sandbox roots from SSH config dependencies (#18493)
## Stack 1. Base PR: #18443 stops granting ACLs on `USERPROFILE`. 2. This PR: filters additional SSH-owned profile roots discovered from SSH config. ## Bug The base PR removes the broadest bad grant: `USERPROFILE` itself. That still leaves one important case. A user profile child can be SSH-owned even when its name is not one of our fixed exclusions. For example: ```sshconfig Host devbox IdentityFile ~/.keys/devbox CertificateFile ~/.certs/devbox-cert.pub UserKnownHostsFile ~/.known_hosts_custom Include ~/.ssh/conf.d/*.conf ``` After profile expansion, the sandbox might see these as normal profile children: ```text C:\Users\me\.keys C:\Users\me\.certs C:\Users\me\.known_hosts_custom C:\Users\me\.ssh ``` Those paths have another owner: OpenSSH and the tools that manage SSH identity and host-key state. Codex should not add sandbox ACLs to them. OpenSSH describes this dependency tree in [`ssh_config(5)`](https://man.openbsd.org/ssh_config.5), and the client parser follows the same shape in `readconf.c`: - `Include` recursively reads more config files and expands globs - `IdentityFile` and `CertificateFile` name authentication files - `UserKnownHostsFile`, `GlobalKnownHostsFile`, and `RevokedHostKeys` name host-key files - `ControlPath` and `IdentityAgent` can name profile-owned sockets or control files - these path directives can use forms such as `~`, `%d`, and `${HOME}` ## Change This PR adds a small SSH config dependency scanner. It starts at: ```text ~/.ssh/config ``` Then it returns concrete paths named by `Include` and by path-valued SSH config directives: ```text IdentityFile CertificateFile UserKnownHostsFile GlobalKnownHostsFile RevokedHostKeys ControlPath IdentityAgent ``` For example: ```sshconfig IdentityFile ~/.keys/devbox CertificateFile ~/.certs/devbox-cert.pub Include ~/.ssh/conf.d/*.conf ``` returns paths like: ```text C:\Users\me\.keys\devbox C:\Users\me\.certs\devbox-cert.pub C:\Users\me\.ssh\conf.d\devbox.conf ``` The setup code then maps those paths back to their top-level `USERPROFILE` child and filters matching sandbox roots out of both the writable and readable root lists. ## Why this shape The parser reports what SSH config references. The sandbox setup code decides which `USERPROFILE` roots are unsafe to grant. That keeps the policy simple: 1. expand broad profile grants 2. remove the profile root 3. remove fixed sensitive profile folders 4. remove profile folders referenced by SSH config dependencies If a path has two possible owners, the sandbox steps back. SSH keeps control of SSH config, keys, certificates, known-hosts files, sockets, and included config files. ## Tests - `cargo test -p codex-windows-sandbox --lib` - `just bazel-lock-check` - `just fix -p codex-windows-sandbox` - `git diff --check` |
||
|
|
715fafa23c |
Do not grant Windows sandbox ACLs on USERPROFILE (#18443)
## Stack 1. This PR: expand and filter `USERPROFILE` roots. 2. Follow-up: #18493 filters SSH config dependency roots on top of this base. ## Bug On Windows, Codex can grant the sandbox ACL access to the whole user profile directory. That means the sandbox ACL can be applied under paths like: ```text C:\Users\me\.ssh C:\Users\me\.tsh ``` This breaks SSH. Windows OpenSSH checks permissions on SSH config and key material. If Codex adds a sandbox group ACL to those files, OpenSSH can reject the config or keys. The bad interaction is: 1. Codex asks the Windows sandbox to grant access to `USERPROFILE`. 2. The sandbox applies ACLs under that root. 3. SSH-owned files get an extra ACL entry. 4. OpenSSH rejects those files because their permissions are no longer strict enough. ## Why this happens more now Codex now has more flows that naturally start in the user profile: - a new chat can start in the user directory - a project can be rooted in the user directory - a user can start the Codex CLI from the user directory Those are valid user actions. The bug is that `USERPROFILE` is too broad a sandbox root. ## Change This PR keeps the useful behavior of starting from the user profile without granting the profile root itself. The new flow is: 1. collect the normal read and write roots 2. if a root is exactly `USERPROFILE`, replace it with the direct children of `USERPROFILE` 3. remove `USERPROFILE` itself from the final root list 4. apply the existing user-profile read exclusions to both read and write roots 5. add `.tsh` and `.brev` to that exclusion list So this input: ```text C:\Users\me ``` becomes roots like: ```text C:\Users\me\Desktop C:\Users\me\Documents C:\Users\me\Downloads ``` and does not include: ```text C:\Users\me C:\Users\me\.ssh C:\Users\me\.tsh C:\Users\me\.brev ``` If `USERPROFILE` cannot be listed, expansion falls back to the profile root and the later filter removes it. That keeps the failure mode closed for this bug. ## Why this shape The sandbox still gets access to ordinary profile folders when the user starts from home. The sandbox no longer grants access to the profile root itself. All filtering happens after expansion, for both read and write roots. That gives us one simple rule: expand broad profile grants first, then remove roots the sandbox must not own. ## Tests - `just fmt` - `cargo test -p codex-windows-sandbox` - `just fix -p codex-windows-sandbox` - `git diff --check` |
||
|
|
9d1bf002c6 |
Significantly improve standalone installer (#17022)
## Summary
This PR significantly improves the standalone installer experience.
The main changes are:
1. We now install the codex binary and other dependencies in a
subdirectory under CODEX_HOME.
(`CODEX_HOME/packages/standalone/releases/...`)
2. We replace the `codex.js` launcher that npm/bun rely on with logic in
the Rust binary that automatically resolves its dependencies (like
ripgrep)
## Motivation
A few design constraints pushed this work.
1. Currently, the entrypoint to codex is through `codex.js`, which
forces a node dependency to kick off our rust app. We want to move away
from this so that the entrypoint to codex does not rely on node or
external package managers.
2. Right now, the native script adds codex and its dependencies directly
to user PATH. Given that codex is likely to add more binary dependencies
than ripgrep, we want a solution which does not add arbitrary binaries
to user PATH -- the only one we want to add is the `codex` command
itself.
3. We want upgrades to be atomic. We do not want scenarios where
interrupting an upgrade command can move codex into undefined state (for
example, having a new codex binary but an old ripgrep binary). This was
~possible with the old script.
4. Currently, the Rust binary uses heuristics to determine which
installer created it. These heuristics are flaky and are tied to the
`codex.js` launcher. We need a more stable/deterministic way to
determine how the binary was installed for standalone.
5. We do not want conflicting codex installations on PATH. For example,
the user installing via npm, then installing via brew, then installing
via standalone would make it unclear which version of codex is being
launched and make it tough for us to determine the right upgrade
command.
## Design
### Standalone package layout
Standalone installs now live under `CODEX_HOME/packages/standalone`:
```text
$CODEX_HOME/
packages/
standalone/
current -> releases/0.111.0-x86_64-unknown-linux-musl
releases/
0.111.0-x86_64-unknown-linux-musl/
codex
codex-resources/
rg
```
where `standalone/current` is a symlink to a release directory.
On Windows, the release directory has the same shape, with `.exe` names
and Windows helpers in `codex-resources`:
```text
%CODEX_HOME%\
packages\
standalone\
current -> releases\0.111.0-x86_64-pc-windows-msvc
releases\
0.111.0-x86_64-pc-windows-msvc\
codex.exe
codex-resources\
rg.exe
codex-command-runner.exe
codex-windows-sandbox-setup.exe
```
This gives us:
- atomic upgrades because we can fully stage a release before switching
`standalone/current`
- a stable way for the binary to recognize a standalone install from its
canonical `current_exe()` path under CODEX_HOME
- a clean place for binary dependencies like `rg`, Windows sandbox
helpers, and, in the future, our custom `zsh` etc
### Command location
On Unix, we add a symlink at `~/.local/bin/codex` which points directly
to the `$CODEX_HOME/packages/standalone/current/codex` binary. This
becomes the main entrypoint for the CLI.
On Windows, we store the link at
`%LOCALAPPDATA%\Programs\OpenAI\Codex\bin`.
### PATH persistence
This is a tricky part of the PR, as there's no ~super reliable way to
ensure that we end up on PATH without significant tradeoffs.
Most Unix variants will have `~/.local/bin` on PATH already, which means
we *should* be fine simply registering the command there in most cases.
However, there are cases where this is not the case. In these cases, we
directly edit the profile depending on the shell we're in.
- macOS zsh: `~/.zprofile`
- macOS bash: `~/.bash_profile`
- Linux zsh: `~/.zshrc`
- Linux bash: `~/.bashrc`
- fallback: `~/.profile`
On Windows, we update the User `Path` environment variable directly and
we don't need to worry about shell profiles.
### Standalone runtime detection
This PR adds a new shared crate, `codex-install-context`, which computes
install ownership once per process and caches it in a `OnceLock`.
That context includes:
- install manager (`Standalone`, `Npm`, `Bun`, `Brew`, `Other`)
- the managed standalone release directory, when applicable
- the managed standalone `codex-resources` directory, when present
- the resolved `rg_command`
The standalone path is detected by canonicalizing `current_exe()`,
canonicalizing CODEX_HOME via `find_codex_home()`, and checking whether
the binary is running from under
`$CODEX_HOME/packages/standalone/releases`.
We intentionally do not use a release metadata file. The binary path is
the source of truth.
### Dependency resolution
For standalone installs, `grep_files` now resolves bundled `rg` from
`codex-resources` next to the Codex binary.
For npm/bun/brew/other installs, `grep_files` falls back to resolving
`rg` from PATH.
For Windows standalone installs, Windows sandbox helpers are still found
as direct siblings when present. If they are not direct siblings, the
lookup also checks the sibling `codex-resources` directory.
### TUI update path
The TUI now has `UpdateAction::StandaloneUnix` and
`UpdateAction::StandaloneWindows`, which rerun the standalone install
commands.
Unix update command:
```sh
sh -c "curl -fsSL https://chatgpt.com/codex/install.sh | sh"
```
Windows update command:
```powershell
powershell -c "irm https://chatgpt.com/codex/install.ps1|iex"
```
The Windows updater runs PowerShell directly. We do this because `cmd
/C` would parse the `|iex` as a cmd pipeline instead of passing it to
PowerShell.
## Additional installer behavior
- standalone installs now warn about conflicting npm/bun/brew-managed
`codex` installs and offer to uninstall them
- same-version reruns do not redownload the release if it is already
staged locally
## Testing
Installer smoke tests run:
- macOS: fresh install into isolated `HOME` and `CODEX_HOME` with
`scripts/install/install.sh --release latest`
- macOS: reran the installer against the same isolated install to verify
the same-version/update path and PATH block idempotence
- macOS: verified the installed `codex --version` and bundled
`codex-resources/rg --version`
- Windows: parsed `scripts/install/install.ps1` with PowerShell via
`[scriptblock]::Create(...)`
- Windows: verified the standalone update action builds a direct
PowerShell command and does not route the `irm ...|iex` command through
`cmd /C`
---------
Co-authored-by: Codex <noreply@openai.com>
|
||
|
|
168e35b6f2 |
Add Windows direct install script (#12741)
## Summary - add a direct install script for Windows at `scripts/install/install.ps1` - extend release staging so `install.ps1` is published alongside `install.sh` - install the Windows runtime payload (`codex.exe`, `rg.exe`, and helper binaries) from the existing platform npm package ## Dependencies - Depends on https://github.com/openai/codex/pull/12740 ## Testing - Smoke-tested with powershell |
||
|
|
a1cd78c818 |
Add macOS and Linux direct install script (#12740)
## Summary - add a direct install script for macOS and Linux at `scripts/install/install.sh` - stage `install.sh` into `dist/` during release so it is published as a GitHub release asset - reuse the existing platform npm payload so the installer includes both `codex` and `rg` ## Testing - `bash -n scripts/install/install.sh` - local macOS `curl | sh` smoke test against a locally served copy of the script |