mirror of
https://github.com/openai/codex.git
synced 2026-05-03 10:56:37 +00:00
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:
160
codex-rs/windows-sandbox-rs/src/hide_users.rs
Normal file
160
codex-rs/windows-sandbox-rs/src/hide_users.rs
Normal 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)
|
||||
}
|
||||
Reference in New Issue
Block a user