From f57966695fc936a1b4e5da6758ad6d532724fe83 Mon Sep 17 00:00:00 2001 From: jif Date: Fri, 6 Nov 2026 12:53:46 +0000 Subject: [PATCH] Validate implicit API keys for Guardian Decisions (#59905) ## Why Decisions uses a commercial endpoint, so implicitly inherited credentials must exclude FedRAMP API keys. ## What changed Reject empty, whitespace-padded, and FedRAMP keys selected from cached authentication or `OPENAI_API_KEY`, returning a credentials error without rewriting the key. Explicit `CODEX_GUARDIAN_DECISIONS_API_KEY` overrides retain their existing behavior. ## Testing Extend the cached-key precedence test to verify that FedRAMP project and service-account keys, including a whitespace-padded key, are rejected even when `OPENAI_API_KEY` provides a fallback. GitOrigin-RevId: 8904b0e5b30662aaf7eb7535309b01c254bcbb82 --- .../guardian-v2/src/async_scorer/decisions_tests.rs | 11 +++++++++++ codex-rs/ext/guardian-v2/src/async_scorer/startup.rs | 6 ++++++ 2 files changed, 17 insertions(+) diff --git a/codex-rs/ext/guardian-v2/src/async_scorer/decisions_tests.rs b/codex-rs/ext/guardian-v2/src/async_scorer/decisions_tests.rs index 21b2af59a4..512a0ffd9c 100644 --- a/codex-rs/ext/guardian-v2/src/async_scorer/decisions_tests.rs +++ b/codex-rs/ext/guardian-v2/src/async_scorer/decisions_tests.rs @@ -160,6 +160,17 @@ fn decisions_prefers_cached_api_key_over_openai_env() -> anyhow::Result<()> { decisions_api_key("openai", /*base_url*/ None, &cached) == Ok(auth_key.into()), "cached API-key auth must take priority over OPENAI_API_KEY" ); + for auth_key in [ + "sk-proj-fedramp-synthetic", + "sk-svcacct-fedramp-synthetic", + " sk-proj-fedramp-synthetic", + ] { + let cached = AuthManager::from_auth_for_testing(CodexAuth::from_api_key(auth_key)); + assert!(matches!( + decisions_api_key("openai", /*base_url*/ None, &cached), + Err(DecisionsError::Credentials) + )); + } Ok(()) } diff --git a/codex-rs/ext/guardian-v2/src/async_scorer/startup.rs b/codex-rs/ext/guardian-v2/src/async_scorer/startup.rs index b774caa402..9f90e8c14f 100644 --- a/codex-rs/ext/guardian-v2/src/async_scorer/startup.rs +++ b/codex-rs/ext/guardian-v2/src/async_scorer/startup.rs @@ -67,6 +67,7 @@ pub(super) fn decisions_api_key( .and_then(CodexAuth::api_key) .map(str::to_owned) .or_else(codex_login::read_openai_api_key_from_env) + .filter(|key| valid_implicit_decisions_key(key)) .ok_or(DecisionsError::Credentials) } Err(std::env::VarError::NotPresent | std::env::VarError::NotUnicode(_)) => { @@ -75,6 +76,11 @@ pub(super) fn decisions_api_key( } } +// Decisions uses a commercial endpoint. Reject padded keys without rewriting credentials. +pub(super) fn valid_implicit_decisions_key(key: &str) -> bool { + !key.is_empty() && key.trim() == key && !codex_login::auth::is_fedramp_api_key(key) +} + pub(super) async fn sampler_config( input: &ThreadStartInput<'_, Config>, manager: Option>,