Files
codex/codex-rs/workload-identity/src/assertion.rs
T
cooper-oai 936f5eb3ee Add workload identity token exchange support (#37610)
## What changed

- Add the `codex-workload-identity` crate for exchanging a file-backed JWT assertion and federation rule ID for short-lived ChatGPT credentials.
- Cache valid access tokens, refresh them before expiry or after rejection, and coalesce concurrent exchanges. Continue using a still-valid cached token when a proactive refresh fails transiently.
- Validate assertion files, token endpoints, and exchange responses; honor outbound proxy policy for HTTPS endpoints and redact access tokens from debug output.

## Testing

- Cover request encoding, assertion rotation, caching, concurrent refreshes, transient-failure fallback, configuration validation, and malformed inputs and responses.

GitOrigin-RevId: 5496851683c2dcf6aaad6840053b97f7c0be076e
2026-08-08 17:18:02 +00:00

36 lines
1.2 KiB
Rust

use std::path::Path;
use tokio::io::AsyncReadExt;
use crate::WorkloadIdentityError;
const MAX_ASSERTION_BYTES: u64 = 16 * 1024;
/// Reopens the assertion file for each exchange so its owner can rotate the credential.
pub(crate) async fn read_assertion(path: &Path) -> Result<String, WorkloadIdentityError> {
let file = tokio::fs::File::open(path).await.map_err(|source| {
WorkloadIdentityError::AssertionFile {
path: path.to_path_buf(),
source: source.into(),
}
})?;
let mut bytes = Vec::new();
file.take(MAX_ASSERTION_BYTES + 1)
.read_to_end(&mut bytes)
.await
.map_err(|source| WorkloadIdentityError::AssertionFile {
path: path.to_path_buf(),
source: source.into(),
})?;
if bytes.len() as u64 > MAX_ASSERTION_BYTES {
return Err(WorkloadIdentityError::AssertionTooLarge);
}
let assertion =
String::from_utf8(bytes).map_err(|_| WorkloadIdentityError::InvalidAssertion)?;
let assertion = assertion.trim();
if assertion.is_empty() || assertion.as_bytes().contains(&0) {
return Err(WorkloadIdentityError::InvalidAssertion);
}
Ok(assertion.to_string())
}