core-agent-ide/codex-rs/responses-api-proxy
zbarsky-openai 2a06d64bc9
feat: add support for building with Bazel (#8875)
This PR configures Codex CLI so it can be built with
[Bazel](https://bazel.build) in addition to Cargo. The `.bazelrc`
includes configuration so that remote builds can be done using
[BuildBuddy](https://www.buildbuddy.io).

If you are familiar with Bazel, things should work as you expect, e.g.,
run `bazel test //... --keep-going` to run all the tests in the repo,
but we have also added some new aliases in the `justfile` for
convenience:

- `just bazel-test` to run tests locally
- `just bazel-remote-test` to run tests remotely (currently, the remote
build is for x86_64 Linux regardless of your host platform). Note we are
currently seeing the following test failures in the remote build, so we
still need to figure out what is happening here:

```
failures:
    suite::compact::manual_compact_twice_preserves_latest_user_messages
    suite::compact_resume_fork::compact_resume_after_second_compaction_preserves_history
    suite::compact_resume_fork::compact_resume_and_fork_preserve_model_history_view
```

- `just build-for-release` to build release binaries for all
platforms/architectures remotely

To setup remote execution:
- [Create a buildbuddy account](https://app.buildbuddy.io/) (OpenAI
employees should also request org access at
https://openai.buildbuddy.io/join/ with their `@openai.com` email
address.)
- [Copy your API key](https://app.buildbuddy.io/docs/setup/) to
`~/.bazelrc` (add the line `build
--remote_header=x-buildbuddy-api-key=YOUR_KEY`)
- Use `--config=remote` in your `bazel` invocations (or add `common
--config=remote` to your `~/.bazelrc`, or use the `just` commands)

## CI

In terms of CI, this PR introduces `.github/workflows/bazel.yml`, which
uses Bazel to run the tests _locally_ on Mac and Linux GitHub runners
(we are working on supporting Windows, but that is not ready yet). Note
that the failures we are seeing in `just bazel-remote-test` do not occur
on these GitHub CI jobs, so everything in `.github/workflows/bazel.yml`
is green right now.

The `bazel.yml` uses extra config in `.github/workflows/ci.bazelrc` so
that macOS CI jobs build _remotely_ on Linux hosts (using the
`docker://docker.io/mbolin491/codex-bazel` Docker image declared in the
root `BUILD.bazel`) using cross-compilation to build the macOS
artifacts. Then these artifacts are downloaded locally to GitHub's macOS
runner so the tests can be executed natively. This is the relevant
config that enables this:

```
common:macos --config=remote
common:macos --strategy=remote
common:macos --strategy=TestRunner=darwin-sandbox,local
```

Because of the remote caching benefits we get from BuildBuddy, these new
CI jobs can be extremely fast! For example, consider these two jobs that
ran all the tests on Linux x86_64:

- Bazel 1m37s
https://github.com/openai/codex/actions/runs/20861063212/job/59940545209?pr=8875
- Cargo 9m20s
https://github.com/openai/codex/actions/runs/20861063192/job/59940559592?pr=8875

For now, we will continue to run both the Bazel and Cargo jobs for PRs,
but once we add support for Windows and running Clippy, we should be
able to cutover to using Bazel exclusively for PRs, which should still
speed things up considerably. We will probably continue to run the Cargo
jobs post-merge for commits that land on `main` as a sanity check.

Release builds will also continue to be done by Cargo for now.

Earlier attempt at this PR: https://github.com/openai/codex/pull/8832
Earlier attempt to add support for Buck2, now abandoned:
https://github.com/openai/codex/pull/8504

---------

Co-authored-by: David Zbarsky <dzbarsky@gmail.com>
Co-authored-by: Michael Bolin <mbolin@openai.com>
2026-01-09 11:09:43 -08:00
..
npm feat: introduce npm module for codex-responses-api-proxy (#4417) 2025-09-28 19:34:06 -07:00
src feat: add options to responses-api-proxy to support Azure (#6129) 2025-11-03 10:06:00 -08:00
BUILD.bazel feat: add support for building with Bazel (#8875) 2026-01-09 11:09:43 -08:00
Cargo.toml chore: add cargo-deny configuration (#7119) 2025-11-24 12:22:18 -08:00
README.md feat: add options to responses-api-proxy to support Azure (#6129) 2025-11-03 10:06:00 -08:00

codex-responses-api-proxy

A strict HTTP proxy that only forwards POST requests to /v1/responses to the OpenAI API (https://api.openai.com), injecting the Authorization: Bearer $OPENAI_API_KEY header. Everything else is rejected with 403 Forbidden.

Expected Usage

IMPORTANT: codex-responses-api-proxy is designed to be run by a privileged user with access to OPENAI_API_KEY so that an unprivileged user cannot inspect or tamper with the process. Though if --http-shutdown is specified, an unprivileged user can make a GET request to /shutdown to shutdown the server, as an unprivileged user could not send SIGTERM to kill the process.

A privileged user (i.e., root or a user with sudo) who has access to OPENAI_API_KEY would run the following to start the server, as codex-responses-api-proxy reads the auth token from stdin:

printenv OPENAI_API_KEY | env -u OPENAI_API_KEY codex-responses-api-proxy --http-shutdown --server-info /tmp/server-info.json

A non-privileged user would then run Codex as follows, specifying the model_provider dynamically:

PROXY_PORT=$(jq .port /tmp/server-info.json)
PROXY_BASE_URL="http://127.0.0.1:${PROXY_PORT}"
codex exec -c "model_providers.openai-proxy={ name = 'OpenAI Proxy', base_url = '${PROXY_BASE_URL}/v1', wire_api='responses' }" \
    -c model_provider="openai-proxy" \
    'Your prompt here'

When the unprivileged user was finished, they could shutdown the server using curl (since kill -SIGTERM is not an option):

curl --fail --silent --show-error "${PROXY_BASE_URL}/shutdown"

Behavior

  • Reads the API key from stdin. All callers should pipe the key in (for example, printenv OPENAI_API_KEY | codex-responses-api-proxy).
  • Formats the header value as Bearer <key> and attempts to mlock(2) the memory holding that header so it is not swapped to disk.
  • Listens on the provided port or an ephemeral port if --port is not specified.
  • Accepts exactly POST /v1/responses (no query string). The request body is forwarded to https://api.openai.com/v1/responses with Authorization: Bearer <key> set. All original request headers (except any incoming Authorization) are forwarded upstream, with Host overridden to api.openai.com. For other requests, it responds with 403.
  • Optionally writes a single-line JSON file with server info, currently { "port": <u16>, "pid": <u32> }.
  • Optional --http-shutdown enables GET /shutdown to terminate the process with exit code 0. This allows one user (e.g., root) to start the proxy and another unprivileged user on the host to shut it down.

CLI

codex-responses-api-proxy [--port <PORT>] [--server-info <FILE>] [--http-shutdown] [--upstream-url <URL>]
  • --port <PORT>: Port to bind on 127.0.0.1. If omitted, an ephemeral port is chosen.
  • --server-info <FILE>: If set, the proxy writes a single line of JSON with { "port": <PORT>, "pid": <PID> } once listening.
  • --http-shutdown: If set, enables GET /shutdown to exit the process with code 0.
  • --upstream-url <URL>: Absolute URL to forward requests to. Defaults to https://api.openai.com/v1/responses.
  • Authentication is fixed to Authorization: Bearer <key> to match the Codex CLI expectations.

For Azure, for example (ensure your deployment accepts Authorization: Bearer <key>):

printenv AZURE_OPENAI_API_KEY | env -u AZURE_OPENAI_API_KEY codex-responses-api-proxy \
  --http-shutdown \
  --server-info /tmp/server-info.json \
  --upstream-url "https://YOUR_PROJECT_NAME.openai.azure.com/openai/deployments/YOUR_DEPLOYMENT/responses?api-version=2025-04-01-preview"

Notes

  • Only POST /v1/responses is permitted. No query strings are allowed.
  • All request headers are forwarded to the upstream call (aside from overriding Authorization and Host). Response status and content-type are mirrored from upstream.

Hardening Details

Care is taken to restrict access/copying to the value of OPENAI_API_KEY retained in memory:

  • We leverage codex_process_hardening so codex-responses-api-proxy is run with standard process-hardening techniques.
  • At startup, we allocate a 1024 byte buffer on the stack and copy "Bearer " into the start of the buffer.
  • We then read from stdin, copying the contents into the buffer after "Bearer ".
  • After verifying the key matches /^[a-zA-Z0-9_-]+$/ (and does not exceed the buffer), we create a String from that buffer (so the data is now on the heap).
  • We zero out the stack-allocated buffer using https://crates.io/crates/zeroize so it is not optimized away by the compiler.
  • We invoke .leak() on the String so we can treat its contents as a &'static str, as it will live for the rest of the process.
  • On UNIX, we mlock(2) the memory backing the &'static str.
  • When using the &'static str when building an HTTP request, we use HeaderValue::from_static() to avoid copying the &str.
  • We also invoke .set_sensitive(true) on the HeaderValue, which in theory indicates to other parts of the HTTP stack that the header should be treated with "special care" to avoid leakage:

439d1c50d7/src/header/value.rs (L346-L376)