mirror of
https://github.com/openai/codex.git
synced 2026-11-13 06:45:07 +00:00
Keep credentials out of app-server logs (#39993)
## Why App-server logs can be persisted or included in submitted diagnostics, so credentials used by model providers, authentication refreshes, and attestation requests must not appear in diagnostic output. ## What changed - Add `RedactedString`, which preserves serialization and string access while replacing debug output with `<redacted>`. - Use it for model-provider bearer tokens, header and query values, authentication command arguments, and attestation tokens. - Avoid logging JSON-RPC error payloads and parser or authentication errors that may echo credentials; retain safe context such as error codes and categories. ## Testing - Add an app-server regression test that exercises provider credentials, refreshed authentication tokens, and attestation tokens, then verifies none appear in persisted SQLite or submitted diagnostic logs. GitOrigin-RevId: 8c50408adf94d93847658b1320682cf3b637d2cc
This commit is contained in:
1 parent
45a3edc02a
commit
79b7606803
36 files changed
+446
-108
No files matched your search
@@ -15,6 +15,7 @@ workspace = true
|
||||
[dependencies]
|
||||
codex-api = { workspace = true }
|
||||
codex-protocol = { workspace = true }
|
||||
codex-utils-redacted-string = { workspace = true }
|
||||
http = { workspace = true }
|
||||
schemars = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
|
||||
@@ -12,6 +12,7 @@ use codex_protocol::config_types::ModelProviderAuthInfo;
|
||||
use codex_protocol::error::CodexErr;
|
||||
use codex_protocol::error::EnvVarError;
|
||||
use codex_protocol::error::Result as CodexResult;
|
||||
use codex_utils_redacted_string::RedactedString;
|
||||
use http::HeaderMap;
|
||||
use http::header::HeaderName;
|
||||
use http::header::HeaderValue;
|
||||
@@ -107,7 +108,7 @@ pub struct ModelProviderInfo {
|
||||
/// Value to use with `Authorization: Bearer <token>` header. Use of this
|
||||
/// config is discouraged in favor of `env_key` for security reasons, but
|
||||
/// this may be necessary when using this programmatically.
|
||||
pub experimental_bearer_token: Option<String>,
|
||||
pub experimental_bearer_token: Option<RedactedString>,
|
||||
/// Command-backed bearer-token configuration for this provider.
|
||||
pub auth: Option<ModelProviderAuthInfo>,
|
||||
/// AWS SigV4 auth configuration for this provider.
|
||||
@@ -116,10 +117,10 @@ pub struct ModelProviderInfo {
|
||||
#[serde(default)]
|
||||
pub wire_api: WireApi,
|
||||
/// Optional query parameters to append to the base URL.
|
||||
pub query_params: Option<HashMap<String, String>>,
|
||||
pub query_params: Option<HashMap<String, RedactedString>>,
|
||||
/// Additional HTTP headers to include in requests to this provider where
|
||||
/// the (key, value) pairs are the header name and value.
|
||||
pub http_headers: Option<HashMap<String, String>>,
|
||||
pub http_headers: Option<HashMap<String, RedactedString>>,
|
||||
/// Optional HTTP headers to include in requests to this provider where the
|
||||
/// (key, value) pairs are the header name and _environment variable_ whose
|
||||
/// value should be used. If the environment variable is not set, or the
|
||||
@@ -169,7 +170,7 @@ pub struct AwsAuthRefreshConfig {
|
||||
pub command: String,
|
||||
/// Arguments passed to the refresh command.
|
||||
#[serde(default)]
|
||||
pub args: Vec<String>,
|
||||
pub args: Vec<RedactedString>,
|
||||
/// Maximum time to wait for the refresh command to complete.
|
||||
#[serde(default = "default_aws_auth_refresh_timeout_ms")]
|
||||
pub timeout_ms: NonZeroU64,
|
||||
@@ -265,7 +266,9 @@ impl ModelProviderInfo {
|
||||
let mut headers = HeaderMap::with_capacity(capacity);
|
||||
if let Some(extra) = &self.http_headers {
|
||||
for (k, v) in extra {
|
||||
if let (Ok(name), Ok(value)) = (HeaderName::try_from(k), HeaderValue::try_from(v)) {
|
||||
if let (Ok(name), Ok(value)) =
|
||||
(HeaderName::try_from(k), HeaderValue::try_from(v.as_str()))
|
||||
{
|
||||
headers.insert(name, value);
|
||||
}
|
||||
}
|
||||
@@ -318,7 +321,12 @@ impl ModelProviderInfo {
|
||||
Ok(ApiProvider {
|
||||
name: self.name.clone(),
|
||||
base_url,
|
||||
query_params: self.query_params.clone(),
|
||||
query_params: self.query_params.clone().map(|params| {
|
||||
params
|
||||
.into_iter()
|
||||
.map(|(name, value)| (name, value.into_inner()))
|
||||
.collect()
|
||||
}),
|
||||
headers,
|
||||
retry,
|
||||
stream_idle_timeout: self.stream_idle_timeout(),
|
||||
@@ -386,7 +394,7 @@ impl ModelProviderInfo {
|
||||
wire_api: WireApi::Responses,
|
||||
query_params: None,
|
||||
http_headers: Some(
|
||||
[("version".to_string(), env!("CARGO_PKG_VERSION").to_string())]
|
||||
[("version".to_string(), env!("CARGO_PKG_VERSION").into())]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
),
|
||||
@@ -434,7 +442,7 @@ impl ModelProviderInfo {
|
||||
query_params: None,
|
||||
http_headers: Some(HashMap::from([(
|
||||
AMAZON_BEDROCK_MANTLE_CLIENT_AGENT_HEADER.to_string(),
|
||||
AMAZON_BEDROCK_MANTLE_CLIENT_AGENT_VALUE.to_string(),
|
||||
AMAZON_BEDROCK_MANTLE_CLIENT_AGENT_VALUE.into(),
|
||||
)])),
|
||||
env_http_headers: None,
|
||||
request_max_retries: None,
|
||||
|
||||
@@ -54,7 +54,7 @@ query_params = { api-version = "2025-04-01-preview" }
|
||||
aws: None,
|
||||
wire_api: WireApi::Responses,
|
||||
query_params: Some(maplit::hashmap! {
|
||||
"api-version".to_string() => "2025-04-01-preview".to_string(),
|
||||
"api-version".to_string() => "2025-04-01-preview".into(),
|
||||
}),
|
||||
http_headers: None,
|
||||
env_http_headers: None,
|
||||
@@ -92,7 +92,7 @@ supports_standalone_web_search = true
|
||||
wire_api: WireApi::Responses,
|
||||
query_params: None,
|
||||
http_headers: Some(maplit::hashmap! {
|
||||
"X-Example-Header".to_string() => "example-value".to_string(),
|
||||
"X-Example-Header".to_string() => "example-value".into(),
|
||||
}),
|
||||
env_http_headers: Some(maplit::hashmap! {
|
||||
"X-Example-Env-Header".to_string() => "EXAMPLE_ENV_VAR".to_string(),
|
||||
@@ -158,7 +158,7 @@ fn test_header_auth_uses_chatgpt_codex_base_url() {
|
||||
fn test_uses_openai_actor_authorization() {
|
||||
let mut provider = ModelProviderInfo {
|
||||
http_headers: Some(maplit::hashmap! {
|
||||
"X-OpenAI-Actor-Authorization".to_string() => "actor-token".to_string(),
|
||||
"X-OpenAI-Actor-Authorization".to_string() => "actor-token".into(),
|
||||
}),
|
||||
..ModelProviderInfo::default()
|
||||
};
|
||||
@@ -168,12 +168,12 @@ fn test_uses_openai_actor_authorization() {
|
||||
assert!(!provider.uses_openai_actor_authorization());
|
||||
|
||||
provider.http_headers = Some(maplit::hashmap! {
|
||||
OPENAI_ACTOR_AUTHORIZATION_HEADER.to_string() => " ".to_string(),
|
||||
OPENAI_ACTOR_AUTHORIZATION_HEADER.to_string() => " ".into(),
|
||||
});
|
||||
assert!(!provider.uses_openai_actor_authorization());
|
||||
|
||||
provider.http_headers = Some(maplit::hashmap! {
|
||||
OPENAI_ACTOR_AUTHORIZATION_HEADER.to_string() => "actor-token".to_string(),
|
||||
OPENAI_ACTOR_AUTHORIZATION_HEADER.to_string() => "actor-token".into(),
|
||||
});
|
||||
provider.requires_openai_auth = true;
|
||||
assert!(!provider.uses_openai_actor_authorization());
|
||||
@@ -199,7 +199,7 @@ args = ["--format=text"]
|
||||
provider.auth,
|
||||
Some(ModelProviderAuthInfo {
|
||||
command: "./scripts/print-token".to_string(),
|
||||
args: vec!["--format=text".to_string()],
|
||||
args: vec!["--format=text".into()],
|
||||
timeout_ms: NonZeroU64::new(5_000).unwrap(),
|
||||
refresh_interval_ms: 300_000,
|
||||
cwd: AbsolutePathBuf::resolve_path_against_base(".", base_dir.path()),
|
||||
@@ -231,11 +231,7 @@ args = ["login", "--profile", "codex-bedrock"]
|
||||
region: Some("us-west-2".to_string()),
|
||||
auth_refresh: Some(AwsAuthRefreshConfig {
|
||||
command: "aws".to_string(),
|
||||
args: vec![
|
||||
"login".to_string(),
|
||||
"--profile".to_string(),
|
||||
"codex-bedrock".to_string(),
|
||||
],
|
||||
args: vec!["login".into(), "--profile".into(), "codex-bedrock".into()],
|
||||
timeout_ms: NonZeroU64::new(300_000).expect("timeout should be non-zero"),
|
||||
}),
|
||||
})
|
||||
@@ -262,7 +258,7 @@ fn test_create_amazon_bedrock_provider() {
|
||||
query_params: None,
|
||||
http_headers: Some(maplit::hashmap! {
|
||||
AMAZON_BEDROCK_MANTLE_CLIENT_AGENT_HEADER.to_string() =>
|
||||
AMAZON_BEDROCK_MANTLE_CLIENT_AGENT_VALUE.to_string(),
|
||||
AMAZON_BEDROCK_MANTLE_CLIENT_AGENT_VALUE.into(),
|
||||
}),
|
||||
env_http_headers: None,
|
||||
request_max_retries: None,
|
||||
@@ -320,7 +316,7 @@ fn test_create_amazon_bedrock_runtime_provider_with_aws_configuration() {
|
||||
fn provider_auth_for_test() -> ModelProviderAuthInfo {
|
||||
ModelProviderAuthInfo {
|
||||
command: "token-fetcher".to_string(),
|
||||
args: vec!["fetch".to_string()],
|
||||
args: vec!["fetch".into()],
|
||||
timeout_ms: NonZeroU64::new(5_000).expect("timeout should be non-zero"),
|
||||
refresh_interval_ms: 300_000,
|
||||
cwd: std::env::current_dir()
|
||||
@@ -408,11 +404,7 @@ fn test_merge_configured_model_providers_adds_custom_provider() {
|
||||
fn test_merge_configured_model_providers_applies_amazon_bedrock_aws_override() {
|
||||
let auth_refresh = AwsAuthRefreshConfig {
|
||||
command: "aws".to_string(),
|
||||
args: vec![
|
||||
"login".to_string(),
|
||||
"--profile".to_string(),
|
||||
"codex-bedrock".to_string(),
|
||||
],
|
||||
args: vec!["login".into(), "--profile".into(), "codex-bedrock".into()],
|
||||
timeout_ms: NonZeroU64::new(10_000).expect("timeout should be non-zero"),
|
||||
};
|
||||
let configured_model_providers = std::collections::HashMap::from([(
|
||||
@@ -491,7 +483,7 @@ fn test_merge_configured_model_providers_applies_amazon_bedrock_transport_overri
|
||||
auth_refresh: None,
|
||||
}),
|
||||
http_headers: Some(maplit::hashmap! {
|
||||
"x-example-header".to_string() => "value".to_string(),
|
||||
"x-example-header".to_string() => "value".into(),
|
||||
}),
|
||||
..ModelProviderInfo::default()
|
||||
},
|
||||
@@ -511,7 +503,7 @@ fn test_merge_configured_model_providers_applies_amazon_bedrock_transport_overri
|
||||
expected_provider
|
||||
.http_headers
|
||||
.get_or_insert_default()
|
||||
.insert("x-example-header".to_string(), "value".to_string());
|
||||
.insert("x-example-header".to_string(), "value".into());
|
||||
|
||||
assert_eq!(
|
||||
merge_configured_model_providers(
|
||||
|
||||
Reference in new issue
Block a user