mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
cb9178e8b3
## Why Some Windows users do not have local admin access, so they cannot complete the elevated portion of the Windows sandbox setup when Codex first needs it. This adds an alpha provisioning path that an admin or IT deployment script can run ahead of time for the Codex user. The intended managed-deployment shape is: ```powershell codex sandbox setup --elevated --user "$env:COMPUTERNAME\Alice" --codex-home "C:\Users\Alice\.codex" ``` `--elevated` is treated as the requested sandbox setup level, not as proof that the process is elevated. The Windows sandbox setup orchestration still checks that the caller is actually elevated before launching the helper without a UAC prompt. ## What changed - Added `codex sandbox setup --elevated` with explicit user selection via either `--current-user` or `--user ... --codex-home ...`. - Moved the CLI implementation into `cli/src/sandbox_setup.rs` instead of growing `cli/src/main.rs`. - Added a Windows sandbox `ProvisionOnly` helper mode that runs the elevation-required provisioning work without requiring a workspace cwd or runtime sandbox policy. - Reused the existing elevated helper path for creating/updating sandbox users, configuring firewall/WFP rules, and applying sandbox directory ACLs. - Persisted `windows.sandbox = "elevated"` into the target `CODEX_HOME` so the desktop app does not show the initial sandbox setup banner after pre-provisioning succeeds. ## Validation - `cargo fmt -p codex-windows-sandbox -p codex-core -p codex-cli` - `cargo test -p codex-cli sandbox_setup --target-dir target\sandbox-setup-check` - `cargo test -p codex-windows-sandbox payload_accepts_provision_only_mode --target-dir target\sandbox-setup-check` - `git diff --check` - Manual Windows alpha flow with a standard local user (`Mandi Lavida`): ran the new setup command from an admin shell, verified the target `.codex` contents, sandbox marker/secrets, ACLs, firewall rules, and desktop startup without the sandbox setup banner once experimental network proxy requirements were disabled. ## Notes This intentionally does not solve later elevated update coordination for IT-managed deployments. The setup command can still apply provisioning updates when run again, but a broader coordination/process story is out of scope for this alpha.
207 lines
5.7 KiB
Rust
207 lines
5.7 KiB
Rust
use std::path::PathBuf;
|
|
|
|
use clap::ArgAction;
|
|
use clap::ArgGroup;
|
|
use clap::Parser;
|
|
use codex_core::config::edit::ConfigEditsBuilder;
|
|
use codex_core::config::find_codex_home;
|
|
|
|
#[derive(Debug, Parser)]
|
|
#[command(group(
|
|
ArgGroup::new("sandbox_user")
|
|
.required(true)
|
|
.args(["user", "current_user"])
|
|
))]
|
|
pub(crate) struct SandboxSetupCommand {
|
|
/// Set up the elevated Windows sandbox.
|
|
#[arg(long = "elevated", action = ArgAction::SetTrue)]
|
|
elevated_sandbox_level: bool,
|
|
|
|
/// Windows user that will run Codex after managed deployment.
|
|
#[arg(
|
|
long = "user",
|
|
value_name = "USER",
|
|
conflicts_with = "current_user",
|
|
requires = "codex_home"
|
|
)]
|
|
user: Option<String>,
|
|
|
|
/// Use the current Windows user as the Codex user.
|
|
#[arg(
|
|
long = "current-user",
|
|
default_value_t = false,
|
|
conflicts_with = "user"
|
|
)]
|
|
current_user: bool,
|
|
|
|
/// CODEX_HOME for the Codex user. Required with --user.
|
|
#[arg(long = "codex-home", value_name = "DIR")]
|
|
codex_home: Option<PathBuf>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
enum SandboxSetupLevel {
|
|
Elevated,
|
|
}
|
|
|
|
impl SandboxSetupCommand {
|
|
fn setup_level(&self) -> anyhow::Result<SandboxSetupLevel> {
|
|
if self.elevated_sandbox_level {
|
|
Ok(SandboxSetupLevel::Elevated)
|
|
} else {
|
|
anyhow::bail!("`codex sandbox setup` currently requires --elevated");
|
|
}
|
|
}
|
|
}
|
|
|
|
pub(crate) async fn run(cmd: SandboxSetupCommand) -> anyhow::Result<()> {
|
|
match cmd.setup_level()? {
|
|
SandboxSetupLevel::Elevated => run_elevated(cmd).await,
|
|
}
|
|
}
|
|
|
|
pub(crate) fn parse_setup_command(
|
|
sandbox_command: &[String],
|
|
) -> anyhow::Result<Option<SandboxSetupCommand>> {
|
|
if sandbox_command
|
|
.first()
|
|
.is_none_or(|command| command != "setup")
|
|
{
|
|
return Ok(None);
|
|
}
|
|
|
|
SandboxSetupCommand::try_parse_from(sandbox_command.iter().map(String::as_str))
|
|
.map(Some)
|
|
.map_err(anyhow::Error::from)
|
|
}
|
|
|
|
async fn run_elevated(cmd: SandboxSetupCommand) -> anyhow::Result<()> {
|
|
let identity = resolve_sandbox_setup_identity(&cmd)?;
|
|
|
|
codex_core::windows_sandbox::run_elevated_provisioning_setup(
|
|
identity.codex_home.as_path(),
|
|
identity.real_user.as_str(),
|
|
)?;
|
|
ConfigEditsBuilder::new(identity.codex_home.as_path())
|
|
.set_windows_sandbox_mode("elevated")
|
|
.apply()
|
|
.await
|
|
.map_err(|err| {
|
|
anyhow::anyhow!(
|
|
"sandbox provisioning succeeded, but failed to persist elevated sandbox config: {err}"
|
|
)
|
|
})?;
|
|
|
|
println!(
|
|
"Windows elevated sandbox setup completed for {} at {}.",
|
|
identity.real_user,
|
|
identity.codex_home.display()
|
|
);
|
|
Ok(())
|
|
}
|
|
|
|
struct SandboxSetupIdentity {
|
|
real_user: String,
|
|
codex_home: PathBuf,
|
|
}
|
|
|
|
fn resolve_sandbox_setup_identity(
|
|
cmd: &SandboxSetupCommand,
|
|
) -> anyhow::Result<SandboxSetupIdentity> {
|
|
if cmd.current_user {
|
|
let real_user = std::env::var("USERNAME")
|
|
.or_else(|_| std::env::var("USER"))
|
|
.map_err(|err| {
|
|
anyhow::anyhow!("failed to determine current user from environment: {err}")
|
|
})?;
|
|
let codex_home = match cmd.codex_home.clone() {
|
|
Some(codex_home) => codex_home,
|
|
None => find_codex_home()?.to_path_buf(),
|
|
};
|
|
return Ok(SandboxSetupIdentity {
|
|
real_user,
|
|
codex_home,
|
|
});
|
|
}
|
|
|
|
let real_user = cmd
|
|
.user
|
|
.clone()
|
|
.ok_or_else(|| anyhow::anyhow!("--user or --current-user is required"))?;
|
|
let codex_home = cmd
|
|
.codex_home
|
|
.clone()
|
|
.ok_or_else(|| anyhow::anyhow!("--codex-home is required with --user"))?;
|
|
Ok(SandboxSetupIdentity {
|
|
real_user,
|
|
codex_home,
|
|
})
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn parses_managed_user_identity() {
|
|
let command = SandboxSetupCommand::try_parse_from([
|
|
"setup",
|
|
"--elevated",
|
|
"--user",
|
|
"DOMAIN\\alice",
|
|
"--codex-home",
|
|
r"C:\Users\alice\.codex",
|
|
])
|
|
.expect("parse");
|
|
|
|
assert!(command.elevated_sandbox_level);
|
|
assert_eq!(command.user.as_deref(), Some(r"DOMAIN\alice"));
|
|
assert!(!command.current_user);
|
|
assert_eq!(
|
|
command.codex_home.as_deref(),
|
|
Some(std::path::Path::new(r"C:\Users\alice\.codex"))
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn requires_explicit_user_identity() {
|
|
let err = SandboxSetupCommand::try_parse_from(["setup", "--elevated"])
|
|
.expect_err("parse should fail");
|
|
|
|
assert_eq!(err.kind(), clap::error::ErrorKind::MissingRequiredArgument);
|
|
}
|
|
|
|
#[test]
|
|
fn requires_codex_home_for_managed_user() {
|
|
let err =
|
|
SandboxSetupCommand::try_parse_from(["setup", "--elevated", "--user", "DOMAIN\\alice"])
|
|
.expect_err("parse should fail");
|
|
|
|
assert_eq!(err.kind(), clap::error::ErrorKind::MissingRequiredArgument);
|
|
}
|
|
|
|
#[test]
|
|
fn parses_setup_from_sandbox_command_args() {
|
|
let command = parse_setup_command(&[
|
|
"setup".to_string(),
|
|
"--elevated".to_string(),
|
|
"--user".to_string(),
|
|
r"DOMAIN\alice".to_string(),
|
|
"--codex-home".to_string(),
|
|
r"C:\Users\alice\.codex".to_string(),
|
|
])
|
|
.expect("parse")
|
|
.expect("setup command");
|
|
|
|
assert_eq!(command.user.as_deref(), Some(r"DOMAIN\alice"));
|
|
}
|
|
|
|
#[test]
|
|
fn ignores_non_setup_sandbox_command_args() {
|
|
let command =
|
|
parse_setup_command(&["echo".to_string(), "hello".to_string()]).expect("parse");
|
|
|
|
assert!(command.is_none());
|
|
}
|
|
}
|