Add rotating stdin credentials for remote exec-server (#54434)

## What changed

- Add `--executor-credential-stdin` to read newline-delimited JSON credentials bound to the configured environment. Require an initial credential within 30 seconds and accept subsequent rotations.
- Reject expired credentials on each request, and stop the executor on invalid input or stdin closure. Limit credential frames to 16 KiB.
- Use scoped bearer authentication for registry requests without account credential fallback or WebSocket authentication headers. Skip workload identity loading and configured telemetry exporters in this mode.

## Testing

Add unit and integration coverage for rotation, expiry, malformed frames, environment mismatches, stdin closure, registry-only authentication, credential redaction, and telemetry exporter isolation.

GitOrigin-RevId: 30a94478165395144a765450f5ec6b3697acdd55
This commit is contained in:
viyatb-oai
2026-10-15 22:32:24 +00:00
committed by copyberry
parent 98fd26206d
commit 16e578dd1d
8 changed files with 826 additions and 10 deletions
+41 -10
View File
@@ -26,6 +26,7 @@ use codex_utils_cli::CliConfigOverrides;
use codex_websocket_auth::WebsocketAuthArgs;
use crate::exec_server_auth;
use crate::exec_server_credential::ExecutorCredentialPipe;
use crate::exec_server_telemetry;
/// [EXPERIMENTAL] Run the standalone exec-server service.
@@ -115,6 +116,15 @@ pub(super) struct ExecServerCommand {
)]
pub(super) use_agent_identity_auth: bool,
/// Read rotating executor credentials from stdin; exit when the pipe closes.
#[arg(
long = "executor-credential-stdin",
requires = "exec_server_remote",
conflicts_with_all = ["use_agent_identity_auth", "aws_sigv4"],
global = true
)]
pub(super) executor_credential_stdin: bool,
/// Sign Direct registration and WebSocket handshake requests with AWS SigV4.
#[arg(long = "aws-sigv4", requires = "exec_server_remote", global = true)]
pub(super) aws_sigv4: bool,
@@ -196,12 +206,31 @@ impl ExecServerCommand {
let config = load_exec_server_config(
root_config_overrides,
strict_config,
/*enable_workload_identity*/ true,
/*enable_workload_identity*/ !self.executor_credential_stdin,
)
.await?;
let direct_transport = self.remote_transport == ExecServerRemoteTransport::Direct;
let (_otel, telemetry) = exec_server_telemetry::init(Some(&config));
let auth_provider = if self.aws_sigv4 {
// Configured exporters may contain account-wide credentials or client keys.
// The scoped executor must not initialize those exporters.
let telemetry_config = (!self.executor_credential_stdin).then_some(&config);
let (_otel, telemetry) = exec_server_telemetry::init(telemetry_config);
let mut credential_provider = None;
let mut parent_lifetime = if self.exit_on_stdin_close {
exec_server_telemetry::ParentLifetime::StdinPipe
} else {
exec_server_telemetry::ParentLifetime::Independent
};
let auth_provider: codex_api::SharedAuthProvider = if self.executor_credential_stdin {
let ExecutorCredentialPipe {
provider,
parent_closed,
} = ExecutorCredentialPipe::from_stdin(environment_id.trim().to_string());
provider.wait_for_initial().await?;
parent_lifetime =
exec_server_telemetry::ParentLifetime::MonitoredStdin(parent_closed);
credential_provider = Some(Arc::clone(&provider));
provider
} else if self.aws_sigv4 {
exec_server_auth::aws_sigv4_auth_provider(
codex_aws_auth::AwsAuthConfig {
profile: self.aws_profile,
@@ -230,16 +259,14 @@ impl ExecServerCommand {
auth_provider,
config.http_client_factory(),
)?;
if self.executor_credential_stdin {
remote_config = remote_config.with_executor_credential_auth()?;
}
if let Some(name) = self.name {
remote_config.name = name;
}
remote_config.request_dispatch_mode = self.request_dispatch_mode;
let remote_config = remote_config.with_telemetry(telemetry);
let parent_lifetime = if self.exit_on_stdin_close {
exec_server_telemetry::ParentLifetime::StdinPipe
} else {
exec_server_telemetry::ParentLifetime::Independent
};
let (shutdown_sender, shutdown_receiver) = tokio::sync::oneshot::channel();
#[cfg(target_os = "macos")]
let runtime_paths = runtime_paths.with_allowed_symlinked_codex_home(
@@ -248,7 +275,7 @@ impl ExecServerCommand {
&config.codex_home,
),
);
exec_server_telemetry::run_until_shutdown(
let result = exec_server_telemetry::run_until_shutdown(
async move {
let shutdown = async move {
let _ = shutdown_receiver.await;
@@ -276,7 +303,11 @@ impl ExecServerCommand {
parent_lifetime,
exec_server_telemetry::ShutdownBehavior::Graceful(shutdown_sender),
)
.await
.await;
if let Some(provider) = credential_provider {
provider.check_pipe_failure()?;
}
result
} else {
let config_result = load_exec_server_config(
root_config_overrides,