Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions codex-rs/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions codex-rs/windows-sandbox-rs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tempfile = "3"
tokio = { workspace = true, features = ["sync", "rt"] }
tracing-appender = { workspace = true }
windows = { version = "0.58", features = [
"Win32_Foundation",
"Win32_NetworkManagement_WindowsFirewall",
Expand Down
39 changes: 16 additions & 23 deletions codex-rs/windows-sandbox-rs/src/bin/setup_main/win.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ use anyhow::Result;
use base64::Engine;
use base64::engine::general_purpose::STANDARD as BASE64;
use codex_otel::StatsigMetricsSettings;
use codex_windows_sandbox::LOG_FILE_NAME;
use codex_windows_sandbox::SETUP_VERSION;
use codex_windows_sandbox::SetupErrorCode;
use codex_windows_sandbox::SetupErrorReport;
Expand All @@ -21,6 +20,7 @@ use codex_windows_sandbox::hide_newly_created_users;
use codex_windows_sandbox::install_wfp_filters;
use codex_windows_sandbox::is_command_cwd_root;
use codex_windows_sandbox::log_note;
use codex_windows_sandbox::log_writer;
use codex_windows_sandbox::path_mask_allows;
use codex_windows_sandbox::sandbox_bin_dir;
use codex_windows_sandbox::sandbox_dir;
Expand All @@ -36,7 +36,6 @@ use serde::Serialize;
use std::collections::HashSet;
use std::ffi::OsStr;
use std::ffi::c_void;
use std::fs::File;
use std::io::Write;
use std::os::windows::process::CommandExt;
use std::path::Path;
Expand Down Expand Up @@ -109,7 +108,7 @@ enum SetupMode {
ReadAclsOnly,
}

fn log_line(log: &mut File, msg: &str) -> Result<()> {
fn log_line(log: &mut dyn Write, msg: &str) -> Result<()> {
let ts = chrono::Utc::now().to_rfc3339();
writeln!(log, "[{ts}] {msg}").map_err(|err| {
anyhow::Error::new(SetupFailure::new(
Expand Down Expand Up @@ -156,7 +155,7 @@ fn workspace_write_cap_sids_for_path(
Ok(sid_strs)
}

fn spawn_read_acl_helper(payload: &Payload, _log: &mut File) -> Result<()> {
fn spawn_read_acl_helper(payload: &Payload, _log: &mut dyn Write) -> Result<()> {
let mut read_payload = payload.clone();
read_payload.mode = SetupMode::ReadAclsOnly;
read_payload.refresh_only = true;
Expand All @@ -182,7 +181,7 @@ struct ReadAclSubjects<'a> {
fn apply_read_acls(
read_roots: &[PathBuf],
subjects: &ReadAclSubjects<'_>,
log: &mut File,
log: &mut dyn Write,
refresh_errors: &mut Vec<String>,
access_mask: u32,
access_label: &str,
Expand Down Expand Up @@ -259,7 +258,7 @@ fn read_mask_allows_or_log(
read_mask: u32,
access_label: &str,
refresh_errors: &mut Vec<String>,
log: &mut File,
log: &mut dyn Write,
) -> Result<bool> {
match path_mask_allows(root, psids, read_mask, /*require_all_bits*/ true) {
Ok(has) => Ok(has),
Expand Down Expand Up @@ -294,7 +293,7 @@ fn lock_sandbox_dir(
sandbox_group_access_mode: i32,
sandbox_group_mask: u32,
real_user_mask: u32,
_log: &mut File,
_log: &mut dyn Write,
) -> Result<()> {
std::fs::create_dir_all(dir)?;
let system_sid = resolve_sid("SYSTEM")?;
Expand Down Expand Up @@ -391,8 +390,7 @@ pub fn main() -> Result<()> {
if let Ok(codex_home) = std::env::var("CODEX_HOME") {
let sbx_dir = sandbox_dir(Path::new(&codex_home));
let _ = std::fs::create_dir_all(&sbx_dir);
let log_path = sbx_dir.join(LOG_FILE_NAME);
if let Ok(mut f) = File::options().create(true).append(true).open(&log_path) {
if let Some(mut f) = log_writer(&sbx_dir) {
let _ = writeln!(
f,
"[{}] top-level error: {}",
Expand Down Expand Up @@ -442,17 +440,12 @@ fn real_main() -> Result<()> {
format!("failed to create sandbox dir {}: {err}", sbx_dir.display()),
))
})?;
let log_path = sbx_dir.join(LOG_FILE_NAME);
let mut log = File::options()
.create(true)
.append(true)
.open(&log_path)
.map_err(|err| {
anyhow::Error::new(SetupFailure::new(
SetupErrorCode::HelperLogFailed,
format!("open log {} failed: {err}", log_path.display()),
))
})?;
let mut log = log_writer(&sbx_dir).ok_or_else(|| {
anyhow::Error::new(SetupFailure::new(
SetupErrorCode::HelperLogFailed,
format!("open log in {} failed", sbx_dir.display()),
))
})?;
let result = run_setup(&payload, &mut log, &sbx_dir);
if let Err(err) = &result {
let _ = log_line(&mut log, &format!("setup error: {err:?}"));
Expand Down Expand Up @@ -480,14 +473,14 @@ fn real_main() -> Result<()> {
result
}

fn run_setup(payload: &Payload, log: &mut File, sbx_dir: &Path) -> Result<()> {
fn run_setup(payload: &Payload, log: &mut dyn Write, sbx_dir: &Path) -> Result<()> {
match payload.mode {
SetupMode::ReadAclsOnly => run_read_acl_only(payload, log),
SetupMode::Full => run_setup_full(payload, log, sbx_dir),
}
}

fn run_read_acl_only(payload: &Payload, log: &mut File) -> Result<()> {
fn run_read_acl_only(payload: &Payload, log: &mut dyn Write) -> Result<()> {
let _read_acl_guard = match acquire_read_acl_mutex()? {
Some(guard) => guard,
None => {
Expand Down Expand Up @@ -550,7 +543,7 @@ fn run_read_acl_only(payload: &Payload, log: &mut File) -> Result<()> {
Ok(())
}

fn run_setup_full(payload: &Payload, log: &mut File, sbx_dir: &Path) -> Result<()> {
fn run_setup_full(payload: &Payload, log: &mut dyn Write, sbx_dir: &Path) -> Result<()> {
let refresh_only = payload.refresh_only;
if !refresh_only {
let provision_result = provision_sandbox_users(
Expand Down
19 changes: 13 additions & 6 deletions codex-rs/windows-sandbox-rs/src/bin/setup_main/win/firewall.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use anyhow::Result;
use std::fs::File;
use std::io::Write;

use windows::Win32::Foundation::S_OK;
Expand Down Expand Up @@ -57,7 +56,7 @@ pub fn ensure_offline_proxy_allowlist(
offline_sid: &str,
proxy_ports: &[u16],
allow_local_binding: bool,
log: &mut File,
log: &mut dyn Write,
) -> Result<()> {
let local_user_spec = format!("O:LSD:(A;;CC;;;{offline_sid})");

Expand Down Expand Up @@ -154,7 +153,7 @@ pub fn ensure_offline_proxy_allowlist(
result
}

pub fn ensure_offline_outbound_block(offline_sid: &str, log: &mut File) -> Result<()> {
pub fn ensure_offline_outbound_block(offline_sid: &str, log: &mut dyn Write) -> Result<()> {
let local_user_spec = format!("O:LSD:(A;;CC;;;{offline_sid})");

let hr = unsafe { CoInitializeEx(None, COINIT_APARTMENTTHREADED) };
Expand Down Expand Up @@ -206,7 +205,11 @@ pub fn ensure_offline_outbound_block(offline_sid: &str, log: &mut File) -> Resul
result
}

fn remove_rule_if_present(rules: &INetFwRules, internal_name: &str, log: &mut File) -> Result<()> {
fn remove_rule_if_present(
rules: &INetFwRules,
internal_name: &str,
log: &mut dyn Write,
) -> Result<()> {
let name = BSTR::from(internal_name);
if unsafe { rules.Item(&name) }.is_ok() {
unsafe { rules.Remove(&name) }.map_err(|err| {
Expand Down Expand Up @@ -266,7 +269,11 @@ fn validate_local_policy_modify_result(
)))
}

fn ensure_block_rule(rules: &INetFwRules, spec: &BlockRuleSpec<'_>, log: &mut File) -> Result<()> {
fn ensure_block_rule(
rules: &INetFwRules,
spec: &BlockRuleSpec<'_>,
log: &mut dyn Write,
) -> Result<()> {
let name = BSTR::from(spec.internal_name);
let rule: INetFwRule3 = match unsafe { rules.Item(&name) } {
Ok(existing) => existing.cast().map_err(|err| {
Expand Down Expand Up @@ -453,7 +460,7 @@ fn port_range_string(start: u32, end: u32) -> String {
}
}

fn log_line(log: &mut File, msg: &str) -> Result<()> {
fn log_line(log: &mut dyn Write, msg: &str) -> Result<()> {
let ts = chrono::Utc::now().to_rfc3339();
writeln!(log, "[{ts}] {msg}")?;
Ok(())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use rand::rngs::SmallRng;
use serde::Serialize;
use std::ffi::OsStr;
use std::ffi::c_void;
use std::fs::File;
use std::io::Write;
use std::path::Path;
use std::path::PathBuf;
use windows_sys::Win32::Foundation::ERROR_INSUFFICIENT_BUFFER;
Expand Down Expand Up @@ -49,7 +49,7 @@ const SID_AUTHENTICATED_USERS: &str = "S-1-5-11";
const SID_EVERYONE: &str = "S-1-1-0";
const SID_SYSTEM: &str = "S-1-5-18";

pub fn ensure_sandbox_users_group(log: &mut File) -> Result<()> {
pub fn ensure_sandbox_users_group(log: &mut dyn Write) -> Result<()> {
ensure_local_group(SANDBOX_USERS_GROUP, SANDBOX_USERS_GROUP_COMMENT, log)
}

Expand All @@ -63,7 +63,7 @@ pub fn provision_sandbox_users(
online_username: &str,
proxy_ports: &[u16],
allow_local_binding: bool,
log: &mut File,
log: &mut dyn Write,
) -> Result<()> {
ensure_sandbox_users_group(log)?;
super::log_line(
Expand All @@ -86,13 +86,13 @@ pub fn provision_sandbox_users(
Ok(())
}

pub fn ensure_sandbox_user(username: &str, password: &str, log: &mut File) -> Result<()> {
pub fn ensure_sandbox_user(username: &str, password: &str, log: &mut dyn Write) -> Result<()> {
ensure_local_user(username, password, log)?;
ensure_local_group_member(SANDBOX_USERS_GROUP, username)?;
Ok(())
}

pub fn ensure_local_user(name: &str, password: &str, log: &mut File) -> Result<()> {
pub fn ensure_local_user(name: &str, password: &str, log: &mut dyn Write) -> Result<()> {
let name_w = to_wide(OsStr::new(name));
let pwd_w = to_wide(OsStr::new(password));
unsafe {
Expand Down Expand Up @@ -156,7 +156,7 @@ pub fn ensure_local_user(name: &str, password: &str, log: &mut File) -> Result<(
Ok(())
}

pub fn ensure_local_group(name: &str, comment: &str, log: &mut File) -> Result<()> {
pub fn ensure_local_group(name: &str, comment: &str, log: &mut dyn Write) -> Result<()> {
const ERROR_ALIAS_EXISTS: u32 = 1379;
const NERR_GROUP_EXISTS: u32 = 2223;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use std::ffi::c_void;
use std::fs::File;
use std::io::Write;
use std::path::PathBuf;

use anyhow::Result;
Expand All @@ -13,7 +13,7 @@ use windows_sys::Win32::Storage::FileSystem::FILE_GENERIC_READ;
pub(super) fn ensure_codex_app_runtime_bin_readable(
sandbox_group_psid: *mut c_void,
refresh_errors: &mut Vec<String>,
log: &mut File,
log: &mut dyn Write,
) -> Result<()> {
let local_app_data = std::env::var_os("LOCALAPPDATA")
.map(PathBuf::from)
Expand Down
6 changes: 5 additions & 1 deletion codex-rs/windows-sandbox-rs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -183,10 +183,14 @@ pub use ipc_framed::read_frame;
#[cfg(target_os = "windows")]
pub use ipc_framed::write_frame;
#[cfg(target_os = "windows")]
pub use logging::LOG_FILE_NAME;
pub use logging::current_log_file_path;
#[cfg(target_os = "windows")]
pub use logging::log_file_path_for_utc_date;
#[cfg(target_os = "windows")]
pub use logging::log_note;
#[cfg(target_os = "windows")]
pub use logging::log_writer;
#[cfg(target_os = "windows")]
pub use path_normalization::canonicalize_path;
#[cfg(target_os = "windows")]
pub use policy::SandboxPolicy;
Expand Down
74 changes: 64 additions & 10 deletions codex-rs/windows-sandbox-rs/src/logging.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
use std::fs::OpenOptions;
use std::io::Write;
use std::path::Path;
use std::path::PathBuf;
use std::sync::OnceLock;

use codex_utils_string::take_bytes_at_char_boundary;
use tracing_appender::rolling::RollingFileAppender;
use tracing_appender::rolling::Rotation;

const LOG_COMMAND_PREVIEW_LIMIT: usize = 200;
pub const LOG_FILE_NAME: &str = "sandbox.log";
pub const LOG_FILE_PREFIX: &str = "sandbox";
pub const LOG_FILE_SUFFIX: &str = "log";
pub const MAX_LOG_FILES: usize = 90;

fn exe_label() -> &'static str {
static LABEL: OnceLock<String> = OnceLock::new();
Expand All @@ -28,18 +31,35 @@ fn preview(command: &[String]) -> String {
}
}

fn log_file_path(base_dir: &Path) -> Option<PathBuf> {
if base_dir.is_dir() {
Some(base_dir.join(LOG_FILE_NAME))
} else {
None
pub fn log_file_path_for_utc_date(base_dir: &Path, date: chrono::NaiveDate) -> PathBuf {
base_dir.join(format!(
"{LOG_FILE_PREFIX}.{}.{}",
date.format("%Y-%m-%d"),
LOG_FILE_SUFFIX
))
}

pub fn current_log_file_path(base_dir: &Path) -> PathBuf {
log_file_path_for_utc_date(base_dir, chrono::Utc::now().date_naive())
}

pub fn log_writer(base_dir: &Path) -> Option<RollingFileAppender> {
if !base_dir.is_dir() {
return None;
}

RollingFileAppender::builder()
.rotation(Rotation::DAILY)
.filename_prefix(LOG_FILE_PREFIX)
.filename_suffix(LOG_FILE_SUFFIX)
.max_log_files(MAX_LOG_FILES)
.build(base_dir)
.ok()
}

fn append_line(line: &str, base_dir: Option<&Path>) {
if let Some(dir) = base_dir
&& let Some(path) = log_file_path(dir)
&& let Ok(mut f) = OpenOptions::new().create(true).append(true).open(path)
&& let Some(mut f) = log_writer(dir)
{
let _ = writeln!(f, "{line}");
}
Expand Down Expand Up @@ -68,7 +88,7 @@ pub fn debug_log(msg: &str, base_dir: Option<&Path>) {
}
}

// Unconditional note logging to sandbox.log
// Unconditional note logging to the daily sandbox log.
pub fn log_note(msg: &str, base_dir: Option<&Path>) {
let ts = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f");
append_line(&format!("[{ts} {}] {}", exe_label(), msg), base_dir);
Expand All @@ -88,4 +108,38 @@ mod tests {
let previewed = result.unwrap();
assert!(previewed.len() <= LOG_COMMAND_PREVIEW_LIMIT);
}

#[test]
fn log_note_writes_to_daily_rolling_log() {
let tempdir = tempfile::tempdir().expect("tempdir");

log_note("hello daily log", Some(tempdir.path()));

let entries = std::fs::read_dir(tempdir.path())
.expect("read log dir")
.collect::<Result<Vec<_>, _>>()
.expect("read entries");
assert_eq!(entries.len(), 1);

let log_path = entries[0].path();
let filename = log_path
.file_name()
.and_then(|name| name.to_str())
.expect("utf-8 filename");
assert!(filename.starts_with("sandbox."));
assert!(filename.ends_with(".log"));

let log = std::fs::read_to_string(log_path).expect("read log");
assert!(log.contains("hello daily log"));
}

#[test]
fn log_file_path_for_utc_date_matches_rolling_appender_name() {
let date = chrono::NaiveDate::from_ymd_opt(2026, 5, 21).expect("valid date");

assert_eq!(
log_file_path_for_utc_date(Path::new("logs"), date),
PathBuf::from("logs").join("sandbox.2026-05-21.log")
);
}
}
Loading
Loading