Skip to content

Conversation

bolinfest
Copy link
Collaborator

@bolinfest bolinfest commented Sep 25, 2025

Details are in responses-api-proxy/README.md, but the key contribution of this PR is a new subcommand, codex responses-api-proxy, which reads the auth token for use with the OpenAI Responses API from stdin at startup and then proxies POST requests to /v1/responses over to https://api.openai.com/v1/responses, injecting the auth token as part of the Authorization header.

The expectation is that codex responses-api-proxy is launched by a privileged user who has access to the auth token so that it can be used by unprivileged users of the Codex CLI on the same host.

If the client only has one user account with sudo, one option is to:

  • run sudo codex responses-api-proxy --http-shutdown --server-info /tmp/server-info.json to start the server
  • record the port written to /tmp/server-info.json
  • relinquish their sudo privileges (which is irreversible!) like so:
sudo deluser $USER sudo || sudo gpasswd -d $USER sudo || true
  • use codex with the proxy (see README.md)
  • when done, make a GET request to the server using the PORT from server-info.json to shut it down:
curl --fail --silent --show-error "http://127.0.0.1:$PORT/shutdown"

To protect the auth token, we:

  • allocate a 1024 byte buffer on the stack and write "Bearer " into it to start
  • we then read from stdin, copying to the contents into the buffer after the prefix
  • after verifying the input looks good, 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 processs
  • 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:

https://github.com/hyperium/http/blob/439d1c50d71e3be3204b6c4a1bf2255ed78e1f93/src/header/value.rs#L346-L376

@bolinfest bolinfest changed the base branch from main to pr4243 September 25, 2025 20:10
Copy link
Contributor

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Comment on lines 131 to 145
for header in req.headers() {
let name_ascii = header.field.as_str();
let name = name_ascii.as_str();
if name.eq_ignore_ascii_case("authorization") {
continue;
}
let header_name = match HeaderName::from_bytes(name.as_bytes()) {
Ok(name) => name,
Err(_) => continue,
};
if let Ok(value) = HeaderValue::from_bytes(header.value.as_bytes()) {
headers.append(header_name, value);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

P1 Badge Preserve client header names when forwarding

The proxy rebuilds the upstream header map by calling HeaderName::from_bytes(name.as_bytes()) for each incoming header. HeaderName only accepts lowercase names, so canonical headers like Content-Type, OpenAI-Beta, or User-Agent—which most clients send capitalised—are skipped with the Err(_) => continue branch. Forwarded requests therefore lack required metadata (e.g. Content-Type: application/json), and OpenAI will respond with 4xx errors even though the client supplied it. Normalize the header names to lowercase (or use HeaderName::from_str after to_ascii_lowercase) before inserting so the proxy preserves all client headers.

Useful? React with 👍 / 👎.

fs::create_dir_all(parent).with_context(|| format!("create_dir_all {parent_display}"))?;
}
let info = ServerInfo { port };
let data = serde_json::to_vec(&info).context("serialize startup info")?;
Copy link
Collaborator

@pakrym-oai pakrym-oai Sep 25, 2025

Choose a reason for hiding this comment

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

can we get automatic backtraces somehow? context looks so busy

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

In general, no, but I'm happy to drop the context() stuff and make this process less likely to spill information.

@bolinfest bolinfest merged commit c549481 into main Sep 26, 2025
38 checks passed
@bolinfest bolinfest deleted the pr4246 branch September 26, 2025 15:19
@github-actions github-actions bot locked and limited conversation to collaborators Sep 26, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants