recover stale Windows sandbox credentials (#27944)

## Why

The elevated Windows sandbox persists dedicated sandbox account
credentials so later commands can launch without reprovisioning. If
those persisted credentials drift from the actual Windows account
password, `CreateProcessWithLogonW` fails with `ERROR_LOGON_FAILURE` and
Codex currently surfaces that as a hard runner launch failure.

This change makes that failure self-healing. When Windows specifically
rejects the sandbox login, Codex now treats the persisted sandbox
credentials as stale, regenerates them through the existing setup path,
and retries the runner launch once.

## What Changed

- Preserve `CreateProcessWithLogonW` failures as a typed runner logon
error so callers can distinguish `ERROR_LOGON_FAILURE` from unrelated
launch failures.
- Add a sandbox credential refresh helper that deletes the persisted
`sandbox_users.json` record and reuses `require_logon_sandbox_creds()`
to reprovision credentials through the established setup flow.
- Retry elevated runner startup after stale-credential failures in both
the legacy elevated capture path and unified exec elevated backend.
- Add focused tests for stale logon failure detection and persisted
sandbox user file removal.

## Validation

- `git diff --check`
- `cargo test -p codex-windows-sandbox`
This commit is contained in:
iceweasel-oai authored and GitHub committed 2026-06-15 13:54:24 -07:00
1 parent fc1fb682a7
commit c0b36d234a
4 files changed
+201 -18

No files matched your search

@@ -3,12 +3,16 @@ use super::windows_common::make_runner_resizer;
use super::windows_common::start_runner_pipe_writer;
use super::windows_common::start_runner_stdin_writer;
use super::windows_common::start_runner_stdout_reader;
use crate::identity::SandboxCreds;
use crate::identity::refresh_logon_sandbox_creds;
use crate::ipc_framed::EmptyPayload;
use crate::ipc_framed::FramedMessage;
use crate::ipc_framed::IPC_PROTOCOL_VERSION;
use crate::ipc_framed::Message;
use crate::ipc_framed::SpawnRequest;
use crate::resolved_permissions::ResolvedWindowsSandboxPermissions;
use crate::runner_client::RunnerTransport;
use crate::runner_client::is_stale_sandbox_creds_error;
use crate::runner_client::spawn_runner_transport;
use crate::spawn_prep::prepare_elevated_spawn_context_for_permissions;
use anyhow::Result;
@@ -23,6 +27,26 @@ use tokio::sync::broadcast;
use tokio::sync::mpsc;
use tokio::sync::oneshot;
async fn spawn_runner_transport_task(
codex_home: PathBuf,
cwd: PathBuf,
sandbox_creds: SandboxCreds,
logs_base_dir: Option<PathBuf>,
spawn_request: SpawnRequest,
) -> Result<RunnerTransport> {
tokio::task::spawn_blocking(move || -> Result<_> {
spawn_runner_transport(
&codex_home,
&cwd,
&sandbox_creds,
logs_base_dir.as_deref(),
spawn_request,
)
})
.await
.map_err(|err| anyhow::anyhow!("runner handshake task failed: {err}"))?
}
#[allow(clippy::too_many_arguments)]
pub(crate) async fn spawn_windows_sandbox_session_elevated_for_permission_profile(
permission_profile: &PermissionProfile,
@@ -55,7 +79,7 @@ pub(crate) async fn spawn_windows_sandbox_session_elevated_for_permission_profil
workspace_roots,
)?;
let elevated = prepare_elevated_spawn_context_for_permissions(
permissions,
permissions.clone(),
codex_home,
cwd,
&mut env_map,
@@ -83,19 +107,42 @@ pub(crate) async fn spawn_windows_sandbox_session_elevated_for_permission_profil
};
let codex_home = codex_home.to_path_buf();
let cwd = cwd.to_path_buf();
let sandbox_creds = elevated.sandbox_creds.clone();
let sandbox_creds = elevated.sandbox_creds;
let logs_base_dir = elevated.logs_base_dir.clone();
let transport = tokio::task::spawn_blocking(move || -> Result<_> {
spawn_runner_transport(
&codex_home,
&cwd,
&sandbox_creds,
logs_base_dir.as_deref(),
spawn_request,
)
})
let transport = match spawn_runner_transport_task(
codex_home.clone(),
cwd.clone(),
sandbox_creds,
logs_base_dir.clone(),
spawn_request.clone(),
)
.await
.map_err(|err| anyhow::anyhow!("runner handshake task failed: {err}"))??;
{
Ok(transport) => transport,
Err(err) if is_stale_sandbox_creds_error(&err) => {
let sandbox_creds = refresh_logon_sandbox_creds(
&permissions,
&cwd,
&env_map,
&codex_home,
read_roots_override,
read_roots_include_platform_defaults,
write_roots_override,
&deny_read_paths_override,
&deny_write_paths_override,
/*proxy_enforced*/ false,
)?;
spawn_runner_transport_task(
codex_home,
cwd,
sandbox_creds,
logs_base_dir,
spawn_request,
)
.await?
}
Err(err) => return Err(err),
};
let (pipe_write, pipe_read) = transport.into_files();
let (writer_tx, writer_rx) = mpsc::channel::<Vec<u8>>(128);