Files
codex/codex-rs/network-proxy/src/credential_broker/providers/github.rs
T
989f55defa feat(network-proxy): experimental local credential broker (#28034)
## Why

Codex child processes can inherit injectable local credentials directly,
which lets commands read and exfiltrate the real values. This
experimental slice keeps supported workflows working while moving those
credentials behind the managed network proxy.

This PR contains only the proxy-owned broker implementation. The Codex
config and runtime integration is stacked separately in #29752.

## What changed

- discover supported credentials during child setup, retain real values
only in the in-memory proxy broker, and replace them with shaped dummy
values
- require a presented dummy to select a stored credential and preserve
unrelated explicit authorization headers
- bind GitHub cloud, GitHub Enterprise, and OpenAI credentials to their
intended hosts
- inject credentials only into TLS traffic by default; plaintext
injection requires the explicit dangerous opt-in
- use TLS ClientHello routing for CONNECT so non-TLS protocols remain
opaque tunnels
- expose a pure API that identifies environment keys still holding
broker-generated dummies without mutating the caller's environment

## Scope

- supported credentials: `GH_TOKEN`, `GITHUB_TOKEN`,
`GH_ENTERPRISE_TOKEN`, `GITHUB_ENTERPRISE_TOKEN`, and `OPENAI_API_KEY`
- GitHub cloud credentials match `github.com`, `api.github.com`, and
`*.ghe.com`
- GitHub Enterprise credentials match only the normalized non-cloud
`GH_HOST`
- OpenAI API keys match only `api.openai.com`
- this does not cover SSH agents, kube client certificates, filesystem
secret discovery, or context-injected secret scrubbing

## Validation

- `just test -p codex-network-proxy` (191 passed)
- focused opaque CONNECT, plaintext opt-in, dummy-selection, and
child-isolation regressions passed
- scoped Clippy check for `codex-network-proxy` passed

---------

Co-authored-by: viyatb-oai <viyatb@openai.com>
Co-authored-by: Codex <noreply@openai.com>
2026-06-24 13:21:16 -07:00

92 lines
2.8 KiB
Rust

use super::CredentialHostBinding;
use super::CredentialProvider;
use super::CredentialSource;
use super::shaped_dummy_value;
use crate::policy::normalize_host;
use rama_http::HeaderMap;
use rama_http::HeaderValue;
use rama_http::header::AUTHORIZATION;
use std::collections::HashMap;
const GH_HOST_ENV_VAR: &str = "GH_HOST";
const GITHUB_TOKEN_PREFIXES: &[&str] = &["github_pat_", "ghp_", "gho_", "ghu_", "ghs_", "ghr_"];
const GITHUB_TOKEN_MIN_LEN: usize = 40;
const GITHUB_CLOUD_TOKEN_ENV_VARS: &[&str] = &["GH_TOKEN", "GITHUB_TOKEN"];
const GITHUB_ENTERPRISE_TOKEN_ENV_VARS: &[&str] =
&["GH_ENTERPRISE_TOKEN", "GITHUB_ENTERPRISE_TOKEN"];
const GITHUB_CLOUD_HOSTS: &[&str] = &["api.github.com", "github.com"];
const GITHUB_CLOUD_HOST_SUFFIXES: &[&str] = &[".ghe.com"];
pub(super) static PROVIDER: CredentialProvider = CredentialProvider {
context_env_vars: &[GH_HOST_ENV_VAR],
sources: &[
CredentialSource {
env_vars: GITHUB_CLOUD_TOKEN_ENV_VARS,
host_binding: github_cloud_binding,
},
CredentialSource {
env_vars: GITHUB_ENTERPRISE_TOKEN_ENV_VARS,
host_binding: github_enterprise_binding,
},
],
dummy_value,
request_header,
request_header_value,
insert_request_header,
};
fn dummy_value(real_value: &str) -> String {
shaped_dummy_value(
real_value,
github_token_prefix(real_value),
GITHUB_TOKEN_MIN_LEN,
)
}
fn request_header(headers: &HeaderMap) -> Option<&HeaderValue> {
headers.get(AUTHORIZATION)
}
fn request_header_value(value: &str) -> Option<HeaderValue> {
HeaderValue::from_str(&format!("Bearer {value}")).ok()
}
fn insert_request_header(headers: &mut HeaderMap, value: HeaderValue) {
headers.insert(AUTHORIZATION, value);
}
fn github_cloud_binding(_: &HashMap<String, String>) -> Option<CredentialHostBinding> {
Some(CredentialHostBinding::HostPattern {
exact_hosts: GITHUB_CLOUD_HOSTS,
suffixes: GITHUB_CLOUD_HOST_SUFFIXES,
})
}
fn github_enterprise_binding(env: &HashMap<String, String>) -> Option<CredentialHostBinding> {
github_host_hint(env)
.filter(|host| !github_cloud_host(host))
.map(CredentialHostBinding::ExactHost)
}
fn github_cloud_host(host: &str) -> bool {
GITHUB_CLOUD_HOSTS.contains(&host)
|| GITHUB_CLOUD_HOST_SUFFIXES
.iter()
.any(|suffix| host.ends_with(suffix))
}
fn github_token_prefix(value: &str) -> &str {
GITHUB_TOKEN_PREFIXES
.iter()
.copied()
.find(|prefix| value.starts_with(prefix))
.unwrap_or("ghp_")
}
fn github_host_hint(env: &HashMap<String, String>) -> Option<String> {
env.get(GH_HOST_ENV_VAR)
.map(String::as_str)
.map(normalize_host)
.filter(|host| !host.is_empty())
}