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:
andrewgu-oai
2026-08-21 19:04:32 +00:00
committed by copyberry
parent 45a3edc02a
commit 79b7606803
36 changed files with 446 additions and 108 deletions
+5 -7
View File
@@ -998,10 +998,8 @@ env_http_headers = { "x-openai-internal-codex-residency" = "CODEX_TEST_UNSET_RES
.as_ref()
.expect("environment-backed headers should remain configured");
assert_eq!(
static_headers
.get("X-OpenAI-Internal-Codex-Residency")
.map(String::as_str),
Some("request-override")
static_headers.get("X-OpenAI-Internal-Codex-Residency"),
Some(&"request-override".into())
);
assert_eq!(
environment_headers
@@ -1010,8 +1008,8 @@ env_http_headers = { "x-openai-internal-codex-residency" = "CODEX_TEST_UNSET_RES
Some("CODEX_TEST_UNSET_RESIDENCY_HEADER")
);
assert_eq!(
static_headers.get("x-provider-header").map(String::as_str),
Some("preserved")
static_headers.get("x-provider-header"),
Some(&"preserved".into())
);
assert_eq!(
environment_headers
@@ -1140,7 +1138,7 @@ command = "print-token"
expected_provider
.http_headers
.get_or_insert_default()
.insert("X-Custom-Header".to_string(), "value".to_string());
.insert("X-Custom-Header".to_string(), "value".into());
assert_eq!(config.model_provider_id, "amazon-bedrock");
assert_eq!(config.model_provider, expected_provider);
+7 -8
View File
@@ -7,6 +7,7 @@ use codex_config::types::FeedbackConfigToml;
use codex_features::FeatureToml;
use codex_login::default_client::RESIDENCY_HEADER_NAME;
use codex_utils_absolute_path::AbsolutePathBuf;
use std::collections::HashMap;
use std::path::Path;
/// Applies managed requirements to regular config before final config construction.
@@ -50,14 +51,12 @@ pub(super) fn apply_to_config(
);
if requirements.enforce_residency.value().is_some() {
for (provider_name, provider) in &config.model_providers {
let has_residency_header = [&provider.http_headers, &provider.env_http_headers]
.into_iter()
.flatten()
.any(|headers| {
headers
.keys()
.any(|name| name.eq_ignore_ascii_case(RESIDENCY_HEADER_NAME))
});
let has_residency_header = provider
.http_headers
.iter()
.flat_map(HashMap::keys)
.chain(provider.env_http_headers.iter().flat_map(HashMap::keys))
.any(|name| name.eq_ignore_ascii_case(RESIDENCY_HEADER_NAME));
if has_residency_header {
let warning = format!(
+1 -1
View File
@@ -1681,7 +1681,7 @@ fn realtime_api_key(auth: Option<&CodexAuth>, provider: &ModelProviderInfo) -> C
}
if let Some(token) = provider.experimental_bearer_token.clone() {
return Ok(token);
return Ok(token.into_inner());
}
if let Some(api_key) = auth.and_then(CodexAuth::api_key) {