Skip to content

Add Codex App Server example#569

Merged
whoiskatrin merged 8 commits intocloudflare:mainfrom
andreisavu:andreisavu/codex-app-server
Apr 14, 2026
Merged

Add Codex App Server example#569
whoiskatrin merged 8 commits intocloudflare:mainfrom
andreisavu:andreisavu/codex-app-server

Conversation

@andreisavu
Copy link
Copy Markdown
Contributor

@andreisavu andreisavu commented Apr 9, 2026

Summary

  • Adds a complete example running OpenAI Codex inside a Cloudflare Sandbox with three-layer egress control to minimize data exfiltration risk
  • Worker acts as a WebSocket middleman with a composable JSON-RPC handler pipeline (log, enforceModel, enforcePolicy, autoApprove, sandboxSetup, sandboxExec)
  • enableInternet = false — blocks all direct outbound connections at the network level
  • interceptHttps = true — HTTPS traffic intercepted via Cloudflare CA cert injection, flowing through the same egress handlers as HTTP
  • outboundByHost + outbound — application-level allowlist with deny-by-default catch-all:
    • api.openai.com: allowed with API key injection + HTTPS upgrade (container never sees the real key)
    • github.com: allowed with HTTPS upgrade (needed for repo cloning)
    • Everything else: blocked with 403 Forbidden
  • Single-file browser client with dark terminal-meets-chat aesthetic, streaming agent messages, tool call grid, and JSON-RPC debug panel
  • Includes integration test and egress validation test

What's included

File Description
src/index.ts Sandbox subclass (enableInternet=false, interceptHttps=true), egress proxy, WebSocket bridge, sandbox lifecycle
src/rpc.ts JSON-RPC types + composable handler pipeline
public/index.html Browser client (session gate, streaming chat, tool grid)
Dockerfile cloudflare/sandbox:0.8.7 + @openai/codex CLI
test.mjs E2E smoke test (WebSocket handshake, repo clone, prompt round-trip)
test-egress.mjs Egress constraint validation (API key injection, allowed/blocked hosts)

Egress security model

Container (enableInternet=false)
│
├─ Raw TCP to non-allowed hosts ──► BLOCKED (network level)
│
├─ HTTP to any host ──► outboundByHost / outbound handlers
│   ├─ api.openai.com ──► inject API key, upgrade to HTTPS ──► OpenAI
│   ├─ github.com ──► upgrade to HTTPS ──► GitHub
│   └─ * (catch-all) ──► 403 Forbidden
│
└─ HTTPS to any host (interceptHttps=true) ──► same handlers as HTTP
    ├─ api.openai.com ──► inject API key ──► OpenAI
    ├─ github.com ──► forward ──► GitHub
    └─ * (catch-all) ──► 403 Forbidden

Test plan

  • npm run dev starts locally, WebSocket connects, Codex handshake succeeds
  • sandbox/setup clones a repo into /workspace
  • Agent responds to prompts with streaming deltas
  • Egress: api.openai.com returns 200 with injected API key (HTTP + HTTPS)
  • Egress: github.com returns 301 (allowed, HTTP + HTTPS)
  • Egress: example.com and httpbin.org return 403 (blocked, HTTP + HTTPS)
  • Egress: raw TCP to non-allowed hosts times out (enableInternet=false)
  • Egress: DNS resolves but blocked hosts unreachable
  • Container never has real API key (OPENAI_API_KEY=proxy-injected)
  • wrangler deploy succeeds and production endpoint works
  • Git clone via sandbox/setup works with HTTPS interception enabled

🤖 Generated with Claude Code

@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Apr 9, 2026

⚠️ No Changeset found

Latest commit: 8b78b90

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@andreisavu
Copy link
Copy Markdown
Contributor Author

andreisavu commented Apr 9, 2026

The egress network enforcement here is not as comprehensive and granular as I would like it to be. Seeking some advice on how to improve the configs to ensure that the risk of data exfiltration due to prompt injection is minimized.

I've made some attempts at settings enableInternet to false and unsuccessfully tried to enable the upcoming support for https request interception. Setting enableInternet=false kind of breaks it and the container immediately shutdowns after start.

devin-ai-integration[bot]

This comment was marked as resolved.

@scuffi
Copy link
Copy Markdown
Contributor

scuffi commented Apr 9, 2026

The egress network enforcement here is not as comprehensive and granular as I would like it to be. Seeking some advice on how to improve the configs to ensure that the risk of data exfiltration due to prompt injection is minimized.

I've made some attempts at settings enableInternet to false and unsuccessfully tried to enable the upcoming support for https request interception. Setting enableInternet=false kind of breaks it and the container immediately shutdowns after start.

Hey Andrei! Whilst HTTPS is still in development you'll need to pin a specific version of containers which we can remove in future once this is standard in the sandbox-sdk. For now, can you temporarily pin this containers package in your example?

cd examples/codex-app-server
npm i https://pkg.pr.new/cloudflare/containers/@cloudflare/containers@171

Then I've left a little suggestion about how to enable this in the Sandbox, let me know if this works!

Comment thread examples/codex-app-server/src/index.ts Outdated
Comment thread examples/codex-app-server/src/index.ts
Comment thread examples/codex-app-server/src/index.ts Outdated
@andreisavu
Copy link
Copy Markdown
Contributor Author

thanks @scuffi! will try out things locally to see if this gets me closer to what will feel likely a semi-air-gapped environment.

@andreisavu
Copy link
Copy Markdown
Contributor Author

Egress testing results — deployed to Cloudflare

Applied @scuffi's recommendations: Sandbox subclass with enableInternet = false + interceptHttps = true, pinned @cloudflare/containers@171, simplified outbound handlers. Deployed and tested against production.

Configuration

export class Sandbox extends BaseSandbox<Env> {
  enableInternet = false;
  interceptHttps = true;
}

Test results (21 probes from inside the container via sandbox/exec)

Probe Result Verdict
HTTP api.openai.com (port 80) 200 ✅ Allowed — API key injected by proxy
HTTP github.com (port 80) 301 ✅ Allowed — passthrough
HTTP example.com (port 80) 403 ✅ Blocked by deny-by-default handler
HTTP httpbin.org (port 80) 403 ✅ Blocked
HTTPS example.com (port 443) 403 ✅ Intercepted and blocked
HTTPS httpbin.org (port 443) 403 ✅ Intercepted and blocked
HTTPS api.openai.com (port 443) 200 ✅ Intercepted — key injected, works
POST exfil via HTTP 403 ✅ Blocked
POST exfil via HTTPS 403 ✅ Blocked
Raw TCP portquiz.net:8080 (bash /dev/tcp) timeout ✅ Blocked by enableInternet=false
Node net.connect portquiz.net:8080 timeout ✅ Blocked
Raw TCP example.com:80 (bash /dev/tcp) OPEN ⚠️ Connects to egress proxy, gets 403
Raw TCP example.com:443 (bash /dev/tcp) OPEN ⚠️ Connects to HTTPS proxy, gets 403
Node net.connect example.com:443 CONNECTED then ECONNRESET ⚠️ Same — proxy accepts then resets
DNS resolve example.com Resolves ℹ️ DNS is unrestricted
CA cert /etc/cloudflare/certs/cloudflare-containers-ca.crt exists ✅ Injected by runtime

Summary

  • HTTP + HTTPS exfiltration fully blocked — deny-by-default with allowlist for api.openai.com and github.com
  • Raw TCP to non-allowed hosts blockedenableInternet=false prevents connections to hosts not in outboundByHost
  • Raw TCP to allowed hosts on ports 80/443 — connects to the proxy (not the real host), which returns 403/resets. Data doesn't reach the destination.
  • DNS still resolves — but without network access to blocked hosts, resolution alone doesn't enable exfiltration
  • CA cert injected correctly by the Cloudflare runtime, enabling transparent HTTPS interception

The main remaining vector is DNS tunneling (encoding data in DNS queries to attacker-controlled nameservers). For the Codex use case this is a very low practical risk.

Note: interceptHttps requires the preview @cloudflare/containers@171 package — this pin can be removed once HTTPS interception ships in the stable release.

@andreisavu
Copy link
Copy Markdown
Contributor Author

@scuffi would you consider this blocked on the upstream release of containers sdk before it can be merged? I think functionally we are in the right place now.

@andreisavu
Copy link
Copy Markdown
Contributor Author

Screenshot from a manual test:

Screenshot 2026-04-09 at 11 40 34 AM

@gabivlj
Copy link
Copy Markdown

gabivlj commented Apr 9, 2026

@andreisavu @cloudflare/containers@0.3.0 is being published!

@whoiskatrin
Copy link
Copy Markdown
Collaborator

@andreisavu following @gabivlj version release, we've released 0.8.8 with the containers version bumped, you should be able to try again now

@whoiskatrin whoiskatrin added the ok-to-test Maintainer-approved: run full CI on fork PR label Apr 9, 2026
@yasonk
Copy link
Copy Markdown

yasonk commented Apr 12, 2026

This is great! I just find it ironic that Claude Code generated a Codex app server :)

@sandy-bonk sandy-bonk bot removed the ok-to-test Maintainer-approved: run full CI on fork PR label Apr 13, 2026
@sandy-bonk
Copy link
Copy Markdown
Contributor

sandy-bonk bot commented Apr 13, 2026

New commits pushed -- ok-to-test label removed. A maintainer must re-review and re-apply the label to run the full CI pipeline.

andreisavu and others added 6 commits April 13, 2026 16:56
Runs OpenAI Codex inside a Cloudflare Sandbox with a Worker acting as a
WebSocket middleman between the browser and the container.

Worker (src/index.ts):
- Composable JSON-RPC handler pipeline: log, enforceModel, enforcePolicy,
  sandboxSetup, sandboxExec, autoApprove
- HTTP egress control: api.openai.com proxied with API key injection,
  github.com allowed for git clone, everything else blocked with 403
- Fresh sandbox per session — container destroyed and recreated on each
  WebSocket connect
- Single-thread-per-session model for simplicity
- Bearer token auth support via AUTH_TOKEN env var

Browser client (public/index.html):
- Dark terminal-meets-chat aesthetic, single-file vanilla HTML/CSS/JS
- Session gate with name and optional repo URL (persisted in localStorage)
- Streaming agent messages with cursor, tool call grid with command
  execution and file change blocks, collapsible detail panels
- JSON-RPC log side panel for debugging

Tests:
- E2E smoke test (test.mjs): WebSocket handshake, repo clone, prompt
  round-trip with streaming delta collection
- Egress validation test (test-egress.mjs): verifies API key injection,
  allowed/blocked hosts, and 403 response body

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Apply @scuffi's review recommendations:
- Subclass Sandbox with enableInternet = false and interceptHttps = true
- Pin preview @cloudflare/containers@171 for HTTPS interception support
- Simplify outbound handlers by inlining them in outboundByHost/outbound

With enableInternet = false, raw TCP to non-allowed hosts is blocked at the
network level. With interceptHttps = true, HTTPS traffic flows through the
same outbound handlers as HTTP, closing the HTTPS bypass gap.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
With interceptHttps enabled, the proxy terminates TLS and hands the
request to the handler with an http:// URL. Explicitly upgrade to
https:// so git clone works correctly through the egress proxy.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Document the Sandbox subclass with enableInternet = false and
interceptHttps = true. Explain the three-layer egress control model
and note the preview containers package dependency.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Bump @cloudflare/sandbox to ^0.8.8 and @cloudflare/containers to ^0.3.0
  (replaces preview pkg.pr.new dependency)
- Bump Dockerfile base image to cloudflare/sandbox:0.8.8
- Replace /etc/profile.d shell hack with sandbox.setEnvVars() for injecting
  OPENAI_BASE_URL and OPENAI_API_KEY into the container
- Increase max_instances from 1 to 3
- Update egress test to verify env var via echo instead of reading profile

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use "*" like all other examples in the monorepo instead of pinning
to a published version.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@andreisavu andreisavu force-pushed the andreisavu/codex-app-server branch from 1e368d5 to 0da44a4 Compare April 13, 2026 23:57
Align with other examples: use latest sandbox base image and
standard max_instances setting.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
devin-ai-integration[bot]

This comment was marked as resolved.

- Dockerfile reference: 0.8.7 -> 0.8.10
- Remove outdated note about preview @cloudflare/containers dependency
  (now using stable ^0.3.0)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@andreisavu
Copy link
Copy Markdown
Contributor Author

@whoiskatrin this PR is ready for another test

@whoiskatrin whoiskatrin added the ok-to-test Maintainer-approved: run full CI on fork PR label Apr 14, 2026
@andreisavu
Copy link
Copy Markdown
Contributor Author

as far as automated checks go this seems ready to merge! let me know if more changes are needed.

Copy link
Copy Markdown
Contributor

@scuffi scuffi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for the example with the new functionality!

@whoiskatrin whoiskatrin merged commit f14826b into cloudflare:main Apr 14, 2026
20 of 21 checks passed
@andreisavu
Copy link
Copy Markdown
Contributor Author

awesome! thanks for accepting the contribution!

@yasonk
Copy link
Copy Markdown

yasonk commented Apr 16, 2026

@andreisavu I wonder how you example would change with the recent releases of Sandbox features?
https://blog.cloudflare.com/sandbox-ga/

@andreisavu
Copy link
Copy Markdown
Contributor Author

@yasonk not much I think from what I can tell from the GA announcement but there are definitely a lot more interesting things that can be done and built on top.

@yasonk
Copy link
Copy Markdown

yasonk commented Apr 16, 2026

@andreisavu ok, makes sense. Looks like you are using all the relevant features already. Super cool! The only enhancement would be to allow spinning up application on the sandbox and exposing the 3000 or 8080 port to allow e2e testing from the outside... but maybe that's just a matter of taste because you could just spin up an instance through CI automation and test it there.

Would something like this be doable for Claude? Or does Claude not allow this because that would be considered a 3rd party wrapper?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ok-to-test Maintainer-approved: run full CI on fork PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants