Files
codex/codex-rs/core/src/memory_usage.rs
iceweasel-oai 8ae0c837f0 Avoid PowerShell profiles in elevated Windows sandbox (#21400)
## Why

On Windows, elevated sandboxed commands run under a dedicated sandbox
account while `HOME` / `USERPROFILE` can still point at the real user's
profile directory. For PowerShell login shells, that combination can
make the sandbox account try to load the real user's PowerShell profile
script. If the sandbox account's execution policy differs from the real
user's policy, startup can emit profile-loading errors before the
requested command runs.

For this backend, loading the profile is not a faithful user login
shell: it is cross-account profile execution. Treating these PowerShell
invocations as non-login shells avoids that invalid startup path.

## Why This Happens Late

The normal `login` decision is resolved when shell argv is created, but
that point is too early to make this Windows sandbox-specific decision.
At argv creation time we do not yet know the actual sandbox attempt that
will run the command. A turn can include sandboxed and unsandboxed
attempts, and a broad turn-level override would also affect Full Access
commands where the user's profile should remain available.

Instead, this change carries the selected `ShellType` alongside the argv
and applies the `-NoProfile` adjustment in the shell runtimes once the
`SandboxAttempt` is known. That keeps the override scoped to actual
`WindowsRestrictedToken` attempts with `WindowsSandboxLevel::Elevated`.

The runtime uses the selected shell metadata rather than re-detecting
PowerShell from argv. That avoids brittle parsing and covers PowerShell
invocation shapes such as `-EncodedCommand`.

## What Changed

- Carry selected shell metadata through `exec_command` / unified exec
requests and shell tool requests.
- Insert `-NoProfile` for PowerShell commands only when the runtime is
about to execute a sandboxed elevated Windows attempt.
- Add focused unit coverage for elevated Windows PowerShell,
`-EncodedCommand`, existing `-NoProfile`, legacy restricted-token
attempts, unsandboxed attempts, and non-PowerShell commands.

## Verification

- `cargo test -p codex-core disable_powershell_profile_tests`
- `cargo test -p codex-core test_get_command`
- `cargo clippy --fix --tests --allow-dirty --allow-no-vcs -p
codex-core`

A full `cargo test -p codex-core` run was also attempted during
development, but it still hit an unrelated stack overflow in
`agent::control` tests before reaching this area.
2026-05-13 21:37:50 +00:00

79 lines
3.0 KiB
Rust

use crate::tools::context::ToolInvocation;
use crate::tools::context::ToolPayload;
use crate::tools::flat_tool_name;
use crate::tools::handlers::unified_exec::ExecCommandArgs;
use codex_memories_read::usage::MEMORIES_USAGE_METRIC;
use codex_memories_read::usage::memories_usage_kinds_from_command;
use codex_protocol::models::ShellCommandToolCallParams;
use std::path::PathBuf;
pub(crate) async fn emit_metric_for_tool_read(invocation: &ToolInvocation, success: bool) {
let Some((command, _)) = shell_command_for_invocation(invocation) else {
return;
};
let kinds = memories_usage_kinds_from_command(&command);
if kinds.is_empty() {
return;
}
let success = if success { "true" } else { "false" };
let tool_name = flat_tool_name(&invocation.tool_name);
for kind in kinds {
invocation.turn.session_telemetry.counter(
MEMORIES_USAGE_METRIC,
/*inc*/ 1,
&[
("kind", kind.as_tag()),
("tool", tool_name.as_ref()),
("success", success),
],
);
}
}
fn shell_command_for_invocation(invocation: &ToolInvocation) -> Option<(Vec<String>, PathBuf)> {
let ToolPayload::Function { arguments } = &invocation.payload else {
return None;
};
match (
invocation.tool_name.namespace.as_deref(),
invocation.tool_name.name.as_str(),
) {
(None, "shell_command") => serde_json::from_str::<ShellCommandToolCallParams>(arguments)
.ok()
.map(|params| {
if !invocation.turn.tools_config.allow_login_shell && params.login == Some(true) {
#[allow(deprecated)]
let cwd = invocation.turn.resolve_path(params.workdir).to_path_buf();
return (Vec::new(), cwd);
}
let use_login_shell = params
.login
.unwrap_or(invocation.turn.tools_config.allow_login_shell);
let command = invocation
.session
.user_shell()
.derive_exec_args(&params.command, use_login_shell);
#[allow(deprecated)]
let cwd = invocation.turn.resolve_path(params.workdir).to_path_buf();
(command, cwd)
}),
(None, "exec_command") => serde_json::from_str::<ExecCommandArgs>(arguments)
.ok()
.and_then(|params| {
let command = crate::tools::handlers::unified_exec::get_command(
&params,
invocation.session.user_shell(),
&invocation.turn.tools_config.unified_exec_shell_mode,
invocation.turn.tools_config.allow_login_shell,
)
.ok()?;
#[allow(deprecated)]
let cwd = invocation.turn.resolve_path(params.workdir).to_path_buf();
Some((command.command, cwd))
}),
(Some(_), _) | (None, _) => None,
}
}