best effort to "hide" Sandbox users (#8492)

The elevated sandbox creates two new Windows users - CodexSandboxOffline
and CodexSandboxOnline. This is necessary, so this PR does all that it
can to "hide" those users. It uses the registry plus directory flags (on
their home directories) to get them to show up as little as possible.
This commit is contained in:
iceweasel-oai 2026-01-05 12:29:10 -08:00 committed by GitHub
parent 7cf6f1c723
commit 07f077dfb3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 174 additions and 1 deletions

View file

@ -8,6 +8,7 @@ use codex_windows_sandbox::create_process_as_user;
use codex_windows_sandbox::create_readonly_token_with_cap_from;
use codex_windows_sandbox::create_workspace_write_token_with_cap_from;
use codex_windows_sandbox::get_current_token_for_restriction;
use codex_windows_sandbox::hide_current_user_profile_dir;
use codex_windows_sandbox::log_note;
use codex_windows_sandbox::parse_policy;
use codex_windows_sandbox::to_wide;
@ -91,6 +92,7 @@ pub fn main() -> Result<()> {
}
let req: RunnerRequest = serde_json::from_str(&input).context("parse runner request json")?;
let log_dir = Some(req.codex_home.as_path());
hide_current_user_profile_dir(req.codex_home.as_path());
log_note(
&format!(
"runner start cwd={} cmd={:?} real_codex_home={}",

View file

@ -0,0 +1,160 @@
#![cfg(target_os = "windows")]
use crate::logging::log_note;
use crate::winutil::format_last_error;
use crate::winutil::to_wide;
use anyhow::anyhow;
use std::ffi::OsStr;
use std::path::Path;
use std::path::PathBuf;
use windows_sys::Win32::Foundation::GetLastError;
use windows_sys::Win32::Storage::FileSystem::GetFileAttributesW;
use windows_sys::Win32::Storage::FileSystem::SetFileAttributesW;
use windows_sys::Win32::Storage::FileSystem::FILE_ATTRIBUTE_HIDDEN;
use windows_sys::Win32::Storage::FileSystem::FILE_ATTRIBUTE_SYSTEM;
use windows_sys::Win32::Storage::FileSystem::INVALID_FILE_ATTRIBUTES;
use windows_sys::Win32::System::Registry::RegCloseKey;
use windows_sys::Win32::System::Registry::RegCreateKeyExW;
use windows_sys::Win32::System::Registry::RegSetValueExW;
use windows_sys::Win32::System::Registry::HKEY;
use windows_sys::Win32::System::Registry::HKEY_LOCAL_MACHINE;
use windows_sys::Win32::System::Registry::KEY_WRITE;
use windows_sys::Win32::System::Registry::REG_DWORD;
use windows_sys::Win32::System::Registry::REG_OPTION_NON_VOLATILE;
const USERLIST_KEY_PATH: &str =
r"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\SpecialAccounts\UserList";
pub fn hide_newly_created_users(usernames: &[String], log_base: &Path) {
if usernames.is_empty() {
return;
}
if let Err(err) = hide_users_in_winlogon(usernames, log_base) {
log_note(
&format!("hide users: failed to update Winlogon UserList: {err}"),
Some(log_base),
);
}
}
/// Best-effort: hides the current sandbox user's profile directory once it exists.
///
/// Windows only creates profile directories when that user first logs in.
/// This intentionally runs in the command-runner (as the sandbox user) because
/// command running is what causes us to log in as a particular sandbox user.
pub fn hide_current_user_profile_dir(log_base: &Path) {
let Some(profile) = std::env::var_os("USERPROFILE") else {
return;
};
let profile_dir = PathBuf::from(profile);
if !profile_dir.exists() {
return;
}
match hide_directory(&profile_dir) {
Ok(true) => {
// Log only when we actually change attributes, so this stays one-time per profile dir.
log_note(
&format!(
"hide users: profile dir hidden for current user ({})",
profile_dir.display()
),
Some(log_base),
);
}
Ok(false) => {}
Err(err) => {
log_note(
&format!(
"hide users: failed to hide current user profile dir ({}): {err}",
profile_dir.display()
),
Some(log_base),
);
}
}
}
fn hide_users_in_winlogon(usernames: &[String], log_base: &Path) -> anyhow::Result<()> {
let key = create_userlist_key()?;
for username in usernames {
let name_w = to_wide(OsStr::new(username));
let value: u32 = 0;
let status = unsafe {
RegSetValueExW(
key,
name_w.as_ptr(),
0,
REG_DWORD,
&value as *const u32 as *const u8,
std::mem::size_of_val(&value) as u32,
)
};
if status != 0 {
log_note(
&format!(
"hide users: failed to set UserList value for {username}: {status} ({error})",
error = format_last_error(status as i32)
),
Some(log_base),
);
}
}
unsafe {
RegCloseKey(key);
}
Ok(())
}
fn create_userlist_key() -> anyhow::Result<HKEY> {
let key_path = to_wide(USERLIST_KEY_PATH);
let mut key: HKEY = 0;
let status = unsafe {
RegCreateKeyExW(
HKEY_LOCAL_MACHINE,
key_path.as_ptr(),
0,
std::ptr::null_mut(),
REG_OPTION_NON_VOLATILE,
KEY_WRITE,
std::ptr::null_mut(),
&mut key,
std::ptr::null_mut(),
)
};
if status != 0 {
return Err(anyhow!(
"RegCreateKeyExW failed: {status} ({error})",
error = format_last_error(status as i32)
));
}
Ok(key)
}
/// Sets HIDDEN|SYSTEM on `path` if needed, returning whether it changed anything.
fn hide_directory(path: &Path) -> anyhow::Result<bool> {
let wide = to_wide(path);
let attrs = unsafe { GetFileAttributesW(wide.as_ptr()) };
if attrs == INVALID_FILE_ATTRIBUTES {
let err = unsafe { GetLastError() } as i32;
return Err(anyhow!(
"GetFileAttributesW failed for {}: {err} ({error})",
path.display(),
error = format_last_error(err)
));
}
let new_attrs = attrs | FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM;
if new_attrs == attrs {
return Ok(false);
}
let ok = unsafe { SetFileAttributesW(wide.as_ptr(), new_attrs) };
if ok == 0 {
let err = unsafe { GetLastError() } as i32;
return Err(anyhow!(
"SetFileAttributesW failed for {}: {err} ({error})",
path.display(),
error = format_last_error(err)
));
}
Ok(true)
}

View file

@ -5,7 +5,8 @@ macro_rules! windows_modules {
}
windows_modules!(
acl, allow, audit, cap, dpapi, env, identity, logging, policy, process, token, winutil
acl, allow, audit, cap, dpapi, env, hide_users, identity, logging, policy, process, token,
winutil
);
#[cfg(target_os = "windows")]
@ -38,6 +39,10 @@ pub use dpapi::unprotect as dpapi_unprotect;
#[cfg(target_os = "windows")]
pub use elevated_impl::run_windows_sandbox_capture as run_windows_sandbox_capture_elevated;
#[cfg(target_os = "windows")]
pub use hide_users::hide_current_user_profile_dir;
#[cfg(target_os = "windows")]
pub use hide_users::hide_newly_created_users;
#[cfg(target_os = "windows")]
pub use identity::require_logon_sandbox_creds;
#[cfg(target_os = "windows")]
pub use logging::log_note;

View file

@ -9,6 +9,7 @@ use base64::Engine;
use codex_windows_sandbox::convert_string_sid_to_sid;
use codex_windows_sandbox::ensure_allow_mask_aces_with_inheritance;
use codex_windows_sandbox::ensure_allow_write_aces;
use codex_windows_sandbox::hide_newly_created_users;
use codex_windows_sandbox::load_or_create_cap_sids;
use codex_windows_sandbox::log_note;
use codex_windows_sandbox::path_mask_allows;
@ -448,6 +449,11 @@ fn run_setup_full(payload: &Payload, log: &mut File, sbx_dir: &Path) -> Result<(
&payload.online_username,
log,
)?;
let users = vec![
payload.offline_username.clone(),
payload.online_username.clone(),
];
hide_newly_created_users(&users, sbx_dir);
}
let offline_sid = resolve_sid(&payload.offline_username)?;
let offline_sid_str = string_from_sid_bytes(&offline_sid).map_err(anyhow::Error::msg)?;