How to give Codex access to your API without giving it your keys
Codex runs commands and calls tools with the permissions it is given. A production key in its config or an .env file turns a helpful command into a call nobody approved.
Porticol Guard keeps the key in the keychain or on the gateway, serves the API over MCP, and holds a risky call until a person answers.
1. Install
macOS or Linux:
curl -fsSL https://porticol.app/install.sh | sh
Windows, in PowerShell:
irm https://porticol.app/install.ps1 | iex
Open Porticol. Local use needs no account and no sign-in. An account is asked for only when you invite someone, sync, share or use a team gateway.
2. Connect Codex
On the start screen, choose Connect my coding agent. Porticol finds Codex, shows the exact lines it will add to ~/.codex/config.toml, and writes nothing until you click Add Porticol. Remove restores the file.
By hand, add the server to ~/.codex/config.toml (%USERPROFILE%\.codex\config.toml on Windows):
[mcp_servers.porticol]
command = "porticol-cli"
args = ["mcp"]
Restart Codex, then ask it to list your Porticol environments. The first call proves the connection.
For a network path, Codex's experimental [features.network_proxy] filters sandboxed commands and chains to an upstream proxy from the environment. Porticol turns it on and points the upstream variables at its local proxy. It also adds a PreToolUse command hook in ~/.codex/hooks.json; Codex hooks can deny or leave a command alone, but cannot ask, and have no fail-closed setting.
3. Write a policy
Guard reads one YAML policy. Start in shadow mode, where Guard logs what it would do and lets the call through, then switch to enforce when the rules match how your team works. The policy below is in enforce, because the walkthrough holds a call.
version: 1
mode: enforce
hosts:
api.example.com: { env: production, key: "{{api_key}}" }
rules:
- match: { method: [GET, HEAD] }
action: allow
- match: { env: production, method: [POST, PUT, PATCH] }
action: ask
agents:
codex: { up_to: production }
"*": { up_to: staging }
{{api_key}} names a secret variable of the production environment. Its value stays in the keychain, and Porticol adds it to the request.
The first matching rule wins. A call above the agent's ceiling is denied before any rule. On a protected environment, a call no rule matches asks by default, so a DELETE to production is held.
4. The held call
When Codex sends a DELETE to production, send returns:
{
"status": "pending",
"approval_id": "8f0c…",
"reason": "DELETE to production needs a person.",
"message": "A person must approve this call in Porticol. Call send again with the same request and approval_id once they have. Do not loop and do not ask for a secret."
}
The approval card shows the agent's name, the environment, the exact request with keys masked, and the agent's reason in its own words. Choose Allow once, Allow for today or Deny. A system notification fires while the app is in the background. From a terminal, porticol-cli approve <id> takes the four-letter code shown on the card; the agent's shell has no terminal, so it cannot approve its own call.
The approval is bound to a SHA-256 of the request the policy decided: the method, URL, environment, headers, auth, protocol, settings and body. It is single use and expires after 10 minutes. Codex retries once with the same request and approval_id; a changed request does not match. A denied call returns {"status": "denied"} and is not retried.
5. Read the log
Every call lands in Agent activity: the agent, method, URL, environment, decision and the rule behind it. The same entries sit in a hash-chained log. porticol-cli guard log verify checks the chain, and the log exports as JSON or CSV. Free keeps 30 days on this computer; on the server, Team keeps 90 days and Business 180, with export.
The honest limit for Codex
Level 2 is weaker on Codex than on Claude Code. The network proxy is experimental, filters sandboxed commands only, and is reached through environment variables rather than a setting that forces every command through Porticol. The hooks can deny but cannot ask, and a non-managed hook needs trust through /hooks. So Codex sits at level 1 with a best-effort proxy, not a wall. Level 1 stops mistakes, not an agent that finds a key and runs curl itself. An administrator can put requirements.toml in place to allow managed hooks only. Windows runs levels 1 and 3 only. Codex's name in the log is self-reported.
Questions
Can Codex ask for approval? For a send or run that needs a person, yes: it returns pending with an approval id. A shell command whose guarded activity is HTTP is left to Porticol's proxy, which holds a call the policy asks about for a person. A command the hook cannot read, or that changes how its calls reach the proxy, is denied, because Codex hooks have no ask decision. If the command was reaching for an API, the agent can make the same call through Porticol's MCP tools, where approval works.
What if the tool call times out? Codex's default tool timeout is 60 seconds. Porticol holds the MCP call for up to 45 seconds, so a pending answer arrives inside it, with the approval id to retry once.
Does Codex see the key? No. Porticol injects the key when the request is sent and masks every result. A key already on disk is a separate problem, and porticol-cli guard scan finds those.