Special Topic · Inside OpenAI Codex

Network and Credential Proxy: The Key the Model Never Sees

Codex funnels outbound traffic: the domain is judged first, the real token is swapped only inside the proxy, and the sandbox only allows the proxy port

THE QUESTION THIS PAGE ANSWERS

ANSWER FIRST

What is the key idea behind “Network and Credential Proxy: The Key the Model Never Sees”?

Codex funnels outbound traffic: the domain is judged first, the real token is swapped only inside the proxy, and the sandbox only allows the proxy port

DECISION RULE

Follow the handoffs, not the demo. A system becomes dependable at the boundaries between model, tools, state, permissions, and people. Read each handoff as a place where you can observe, test, and recover.

TRY NEXT

Name the input, owner, approval, and recovery action for one automated step.

WATCH FOR

A successful run that cannot explain what happened or be safely repeated.

Course goalAfter this lesson you can explain three things. How a domain is judged: deny first, then local, then the allowlist. The real token is swapped for a same-shape fake before spawn; on the way out the proxy swaps it back by host. A client that won’t take the proxy is pressed into the same funnel by the OS sandbox.
Try it first · A credentialed request through the proxy
Swap the host, swap the method: see which gate stops the request, and which step swaps the fake for the real value
Host
Method
Mode
First watch the fake become real. Then switch to POST, or let Go skip the proxy.
Client
This request
GET https://api.github.com/repos/you/app/issues
Authorization
Bearer github_pat_dmy8f3a2
Gate 1 · SandboxStandby
Only the proxy port is allowed. A direct loopback dies at this gate.
Gate 2 · DomainStandby
Deny first, then local or private, then the allowlist.
Gate 3 · MethodStandby
Limited only allows GET, HEAD, OPTIONS.
Gate 4 · CredentialsStandby
Only if the request header carries the fake does it swap in the broker’s real token.
Child envGH_TOKEN = github_pat_dmy8f3a2
Broker memoryThe real token is locked here
Model contextCannot see the real value
Hit Play to watch this request walk four gates, and see which step swaps the fake for the real value.
Logic trail · each animation step maps to a stretch of source
  1. Before spawning the command, swap the real token for a same-shape fakecredential_broker.rs L92
  2. The sandbox only allows the proxy port; a direct loopback ends hereseatbelt.rs L325
  3. Resolve the host; deny is judged first and always winsruntime.rs L553
  4. A local literal without an exact allow is blocked as a private addressruntime.rs L578
  5. A name that resolves off the public net is blocked even if it is on the listruntime.rs L582
  6. An empty or miss on the allowlist returns NotAllowedruntime.rs L598
  7. Only NotAllowed asks the decidernetwork_policy.rs L349
  8. Under Limited, anything but GET, HEAD, OPTIONS is blockedconfig.rs L311
  9. HTTPS must see the inner method; missing MITM blocks ithttp_proxy.rs L312
  10. Only if the header carries the fake is the matching real credential injectedmitm.rs L301
Hit Play to watch this request walk four gates, and see which step swaps the fake for the real value.
Domain firstDeny always wins. A local literal needs an exact allow. A name that resolves to the intranet cannot pass even if it is on the list.
The real key never leaves the brokerThe child env and the model context stay fake. The real token is swapped on only at the outbound hop.
The sandbox is the mouth of the funnelWhen Go skips the proxy, the policy function never runs. The OS only allows the proxy port; a direct connect dies at gate one.
Teaching sketch: the list, the fake’s shape, and the resolve result are course settings. Line numbers on the logic trail match openai/codex commit 4f39251a01.
Idea 1 · How a domain is judged
What problem it solves

You ask the model to file a GitHub issue. It writes curl; the env has a real token. A second later the host becomes evil.example, or it hits 169.254.169.254. Block only “hosts not on the list” and deny entries plus intranet addresses leak. If an empty list defaults to allow, forgetting the config is opening everything.

What the idea is

The judge order is written in stone. Deny first, then local or private, then the allowlist. An empty list and a miss both block. The decider can only retry a case the allowlist dropped — not the denylist, not a local segment.

Source:codex-rs/network-proxy/src/runtime.rs lines 549–552;codex-rs/network-proxy/src/network_policy.rs lines 346–378;codex-rs/network-proxy/README.md lines 161–162

*.example.com does not include the apex; **.example.com does. A name that resolves to a private IP is blocked even if it is written exactly on the list. A local literal must be written exactly as localhost or 127.0.0.1; a * glob does not count.

Source:codex-rs/network-proxy/src/policy.rs lines 321–331;codex-rs/network-proxy/src/runtime.rs lines 1028–1043

Resolve host A fail also blocks 1 deny A hit stops at once 403 denylist 2 Local or private The literal must be exact 403 allowlist 3 allowlist An empty list also blocks
Teaching diagram: the three-step order is fixed. Denied and NotAllowedLocal never ask the decider.

On a block the command process gets a 403; the header is x-proxy-error, the body is one human sentence. Outwardly not_allowed and not_allowed_local are both blocked-by-allowlist; only the body splits “not on the list” from “the sandbox blocked local”. That 403 becomes exec output fed back to the model; the sample loop does not stop.

Source:codex-rs/network-proxy/src/responses.rs lines 52–83

If the same pattern is both allow and deny, the effective value is the larger. Enum order is None < Allow < Deny.

Source:codex-rs/network-proxy/src/config.rs lines 19–27

Why it lasts

Default deny, deny always wins, the intranet needs an explicit door — that’s the generic SSRF-defense shape. Rewrite it in another language and the same questions still apply: what if the list is empty, who wins a conflict, does a name that resolves to the intranet count as allow?

Idea 2 · The real key only appears inside the proxy
What problem it solves

The token rides the command into the rollout. The next turn the model can still see it; the turn after that it may hit a log. Once a credential enters model context, every later redaction is a patch.

What the idea is

Before spawn, the credential broker swaps GH_TOKEN and OPENAI_API_KEY for fakes of the same length and prefix. If the model printenvs, it sees the fake. A fake in context is useless upstream.

Source:codex-rs/network-proxy/src/credential_broker.rs lines 92–119

On the way out, MITM filters by host. The request header must carry the fake before it is swapped for the real value. A hook’s strip/inject runs after the broker and can peel off the Authorization just injected. A user-edited env value is not restored. The mark is untrusted input. The broker only covers GitHub and OpenAI.

Source:codex-rs/network-proxy/src/mitm.rs lines 297–302

Model Cannot see the real token Child env Same-shape fake Proxy MITM Swap the real value by host Upstream Real token The real value only appears in the proxy process’s memory — not the rollout, not the model’s next-turn context
Teaching timeline: the fake walks every visible surface; the real value appears only on the outbound hop.
The child’s pocket holds a fake stub; the real ticket is locked behind the counter.
Why it lasts

If the outbound policy is wrong, the model can still change its mind from a 403. A credential in context is expensive to revoke. So the real value is taken before spawn, and the put-back happens in the proxy process’s memory. The fake keeps its shape so a client that checks format can still start.

Idea 3 · The sandbox presses traffic into the proxy
What problem it solves

Go’s net/http skips HTTP_PROXY for loopback. You think the traffic entered the proxy; it actually hit 127.0.0.1 directly. Locally there is an admin port, a docker socket. The policy file has a list; the request never reached that layer.

What the idea is

The sandbox only allows the proxy port. Restricted Seatbelt on macOS writes only localhost:{port}. Linux takes ProxyOnly. When allow_local_binding is false, NO_PROXY is written as an empty string, and a loopback literal must also take the proxy. Turning local binding on is admitting loopback no longer goes through the allowlist.

Source:codex-rs/sandboxing/src/seatbelt.rs lines 309–336;codex-rs/network-proxy/src/proxy.rs lines 450–460

Why it lasts

An application-layer proxy cannot stop a client that won’t take it. The next layer must be the OS or a firewall, with one mouth left. The three doors stacked together make a managed network. Any one door alone cannot cover a face another door dropped.

Side-by-side · The valve sits on a different layer

Grok: the valve is at the tool door; loopback is allowed by default

Grok’s web_fetch does a domain list and a post-resolve IP check inside the tool. An empty list blocks every URL — same direction as Codex’s allowlist-first. A path can also be written as a prefix, narrowed to one stretch of docs.

The SSRF check blocks RFC1918, link-local, CGNAT. Loopback is explicitly allowed; the comment says local development. curl in bash does not take this list. Codex puts the valve on every child-process exit, so it has to be tied to the sandbox.

Source checked on both sides · 2026-08-22 · Source:web_fetch/domain.rs lines 110–144;web_fetch/ssrf.rs lines 17–19

DSH: config stores only the variable name; spawn wipes the env by key

DSH makes the secret a reference. The settings file only carries the env-var name; the provider resolves on every operation. The config surface never sees the value.

The child has one more wipe. Keys named like KEY or TOKEN, and every DSH_*, are dropped. That stops a real value appearing in the config file. If the model can still printenv, unless spawn used this wipe, the env may still hold a real token.

Source checked on both sides · 2026-08-22 · Source: packages/credentials/credentials/src/index.ts lines 1–7; packages/subprocess/subprocess/src/index.ts lines 60–66 · DSH · Credentials resolved each time
Classroom Exercise
01

Does a star allow localhost?

allow_local_binding = false, and the allowlist is only *. Call the judge once on 127.0.0.1 — what do you expect? Call it again on the public IP 8.8.8.8 — what then?

Hint: a local literal refuses a glob. Allowlist compile explicitly allows a global *; denylist compile refuses it.

Takeaway:Outbound defaults to deny; deny always wins; the intranet needs an exact door. A real secret must not enter an env the model can see — swap a fake before spawn, swap again outbound. The proxy cannot stop a client that skips it; the next layer must be a sandbox that only allows the proxy port.

The handoffs inside “Try it first · A credentialed request through the proxy”

“You ask the model to file a GitHub issue.” shows that an Agent is not defined by the model alone. Each handoff between model, context, tools, state, permissions, and people affects both progress and recovery.

Write the state before adding capability

Starting from “The judge order is written in stone.”, split the workflow into starting state, next action, tool result, state update, and stop condition. Debugging then means finding the first lost piece of information or authority instead of saying vaguely that the model “got worse”.

  • Before spawning the command, swap the real token for a same-shape fake credential_broker.rs L92
  • The sandbox only allows the proxy port; a direct loopback ends here seatbelt.rs L325
  • Resolve the host; deny is judged first and always wins runtime.rs L553

A happy path is not reliability

Use “Hint: a local literal refuses a glob.” to replay one successful and one failed run. Record the context, tool result, and owner at each turn; the workflow is maintainable when a second person can follow it without the original builder.

From “Try it first · A credentialed request through the proxy” to “Idea 1 · How a domain is judged”

“Try it first · A credentialed request through the proxy” grounds the problem in “Swap the host, swap the method: see which gate stops the request, and which step swaps the fake for the real value Play Step Reset Host api.github.com evil.example localhost Resolves to intranet Method GET POST…”. “Idea 1 · How a domain is judged” then moves it toward “You ask the model to file a GitHub issue. It writes curl ; the env has a real token. A second later the host becomes evil.example , or it hits 169.254.169.254 . Block only “hosts not on the list” and deny entri…”. Together, they show that the lesson is not just a conclusion to remember, but a claim with conditions.

Carry the judgment into the next situation

When analyzing an Agent, trace state, action, tool result, and next step in order. Each handoff should explain where information came from, who confirmed it, and where failure stops.

  • “Try it first · A credentialed request through the proxy”: Swap the host, swap the method: see which gate stops the request, and which step swaps the fake for the real value Play Step Reset Host api.github.com evil.example localhost Resolves to intranet Method GET POST…
  • “Idea 1 · How a domain is judged”: You ask the model to file a GitHub issue. It writes curl ; the env has a real token. A second later the host becomes evil.example , or it hits 169.254.169.254 . Block only “hosts not on the list” and deny entri…
  • “The closing point”: A name that resolves off the public net is blocked even if it is on the list runtime.rs L582

The final “The closing point” brings the discussion to “A name that resolves off the public net is blocked even if it is on the list runtime.rs L582”. The useful thing to carry forward is knowing which judgments must be revisited when input, scale, or risk changes.

Mark as learned Your reading progress updates automatically
← PreviousNext →

Keep reading

The next useful article in the thread.

ARTICLE DISCUSSION

Leave one useful thought here.

Keep the idea that clicked, the question that stayed open, or a small note for the next learner.

Discussing Network and Credential Proxy: The Key the Model Never Sees Inside OpenAI Codex
3discussionsArticle discussion · synced with the Circle
View in the learning circle
AM
Asha MorganContent editor
INSIGHTField note

I turned one judgment from this article into a small experiment I could run today. Knowing what to observe next is more useful than simply remembering the conclusion.

ARTICLE DISCUSSION7 helpful
LH
Lin HarperIndie developer
INSIGHTInsight

After reading this, I first looked for the conditions behind the idea instead of copying the method into a project. That order made the later trade-offs much clearer.

ARTICLE DISCUSSION5 helpful
KM
Kiki MooreProduct operations
QUESTIONQuestion

When this judgment reaches real work, which constraint should be added first? I am curious which step matters most between reading and the first practical attempt.

ARTICLE DISCUSSION4 helpful