- Workspace root is the smallest authorization boundary. One bridge serves
exactly one workspace; every token is bound to
workspace_id; a token for project A returns 403 on project B's bridge. - Workspace content is untrusted. README, comments, diffs may contain prompt injection. Every MCP tool description carries an explicit warning and tools never grant capabilities based on file content.
- The model never sees long-lived credentials. Computer Use only ever handles the one-time pairing code. Access/refresh tokens travel only inside the OAuth redirect/token endpoints between ChatGPT's client and the bridge.
| Threat | Mitigation |
|---|---|
| MCP URL leaks | URL alone is useless: every /mcp request requires a valid bearer token (401 without, 403 wrong workspace) |
| Pairing code brute force | 8 chars from a 31-char CSPRNG alphabet (~40 bits), 5 attempts per session, per-IP rate limit (10/min), 5-minute TTL, one-time use, session destroyed on limit |
| OAuth CSRF | state round-tripped verbatim; authorization requests are server-side records keyed by random ids |
| Code interception | PKCE S256 mandatory (plain rejected); authorization codes are one-time, 5-minute TTL, bound to client + redirect URI |
| Token theft | Opaque high-entropy tokens; stored only as SHA-256 hashes; access tokens live 1 h; refresh tokens rotate on every use (replay of the old one fails); revocation endpoint + c2c unpair |
| Workspace traversal | realpath canonicalization of the deepest existing ancestor; containment check against the canonical root; case-insensitive comparison on macOS/Windows; rejects .., absolute escapes, backslash tricks, null bytes |
| Symlink escape | Canonicalization resolves symlinks before the containment check (file and directory symlinks both covered by tests) |
| Sensitive files | Deny-by-default patterns (.env*, keys, SSH, cloud creds, keychains…) enforced at resolve time — reads, listings, and search all pass through the same gate; git diff adds pathspec excludes; .env.example allowed |
| Oversized file / diff DoS | read_file caps lines and bytes per response; git_diff paginates by byte offset with hard caps; search caps matches and file sizes |
| Tunnel exposure | Bridge binds 127.0.0.1 only (refuses 0.0.0.0); the only public surface is HTTPS via the tunnel, protected by OAuth; /health reveals only a salted workspace hash |
| Admin API abuse | Loopback-only + random admin token (0600 runtime file) + requests with proxy headers (cf-connecting-ip, x-forwarded-for) rejected; unauthenticated probes get 404 |
| Log credential leakage | Logger redacts token prefixes, bearer headers, token-like parameters, and pairing-code-shaped strings before writing |
| Execution output leak | Codex may nominate test/build/lint logs; a local sanitizer redacts tokens, pairing-code-shaped strings and home paths, truncates size, and refuses private-key blocks entirely. Restricted items are listed without a body. ChatGPT still cannot run commands. |
| Checkpoint / resume dump | Session checkpoints store short protocol fields only (capped). Resume uses the existing chat or HANDOFF — no new protocol state, no log paste, no re-pairing. |
Scopes: workspace.read, workspace.search, git.read, execution.read,
offline_access. Tools enforce scopes individually (INSUFFICIENT_SCOPE).
Access tokens: 1 hour. Refresh tokens: 30 days, rotated. All tokens bound to
workspace_id and client_id.
State lives under the OS-convention app dir
(~/Library/Application Support/codex-with-chatgpt on macOS), directories 0700,
files 0600. Named-hostname preference and tunnel metadata live there too
(tunnels/<workspaceId>.json) — never in the project. Only SHA-256 hashes of
tokens are persisted — a stolen state file does not yield usable bearer tokens.
V1 limitation: client registrations and token hashes are file-based rather than OS-keychain-based. Raw tokens are never written anywhere. Keychain integration is a V2 item.
Write files, delete files, run shell commands, commit, install packages — these tools do not exist on the server, so no prompt injection, scope bug, or UI confusion can enable them.