#[cfg(unix)]
use std::ffi::CString;
use std::fs::{self, File};
use std::io::{Read as _, Write as _};
use std::path::{Component, Path, PathBuf};
#[cfg(not(windows))]
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Mutex, OnceLock};
#[cfg(not(windows))]
use std::time::{SystemTime, UNIX_EPOCH};
use anyhow::{Context, Result, bail};
pub const XAI_OAUTH_GENERATION_PREFIX: &str = "xai-auth-";
pub const XAI_OAUTH_GENERATION_SUFFIX: &str = ".json";
pub const LEGACY_XAI_OAUTH_FILE_NAME: &str = "xai-auth.json";
const XAI_OAUTH_LIFECYCLE_LOCK_FILE_NAME: &str = ".xai-oauth.lock";
const XAI_OAUTH_FILE_LIMIT: u64 = 1024 * 1024;
#[derive(Debug)]
pub struct XaiOAuthCredentialStore {
directory: PathBuf,
#[cfg(unix)]
directory_handle: File,
#[cfg(windows)]
_component_handles: Vec<File>,
}
#[derive(Debug)]
pub struct XaiOAuthRevocation {
retired: Vec<(String, String)>,
}
#[must_use]
pub fn is_valid_xai_oauth_generation(value: &str) -> bool {
let path = Path::new(value);
if path.components().count() != 1
|| !matches!(path.components().next(), Some(Component::Normal(_)))
|| path.file_name().and_then(|name| name.to_str()) != Some(value)
{
return false;
}
let Some(id) = value
.strip_prefix(XAI_OAUTH_GENERATION_PREFIX)
.and_then(|value| value.strip_suffix(XAI_OAUTH_GENERATION_SUFFIX))
else {
return false;
};
id.len() == 32
&& id
.bytes()
.all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte))
}
pub fn validate_xai_oauth_generation(value: &str) -> Result<&str> {
if !is_valid_xai_oauth_generation(value) {
bail!(
"invalid Codewhale-owned xAI OAuth generation; expected xai-auth-<32 lowercase hex>.json"
);
}
Ok(value)
}
pub fn xai_oauth_credentials_dir() -> Result<PathBuf> {
lexical_absolute_path(&crate::codewhale_home()?.join("credentials"))
}
fn lexical_absolute_path(path: &Path) -> Result<PathBuf> {
let absolute = if path.is_absolute() {
path.to_path_buf()
} else {
std::env::current_dir()
.context("resolving the Codewhale credentials directory")?
.join(path)
};
if absolute
.components()
.any(|component| matches!(component, Component::CurDir | Component::ParentDir))
{
bail!(
"Codewhale credentials directory must be lexically normalized: {}",
crate::quote_os_path(&absolute)
);
}
Ok(absolute)
}
pub fn xai_oauth_generation_path(generation: &str) -> Result<PathBuf> {
Ok(xai_oauth_credentials_dir()?.join(validate_xai_oauth_generation(generation)?))
}
pub fn legacy_xai_oauth_path() -> Result<PathBuf> {
Ok(xai_oauth_credentials_dir()?.join(LEGACY_XAI_OAUTH_FILE_NAME))
}
pub fn with_xai_oauth_lifecycle_lock<T>(
operation: impl FnOnce(&XaiOAuthCredentialStore) -> Result<T>,
) -> Result<T> {
static PROCESS_LOCK: OnceLock<Mutex<()>> = OnceLock::new();
let _process_guard = PROCESS_LOCK
.get_or_init(|| Mutex::new(()))
.lock()
.map_err(|_| anyhow::anyhow!("xAI OAuth lifecycle lock was poisoned"))?;
let store = XaiOAuthCredentialStore::open()?;
let lock_file = store.open_lock_file()?;
let mut lock = fd_lock::RwLock::new(lock_file);
let _guard = lock.write().with_context(|| {
format!(
"failed to acquire xAI OAuth lifecycle lock in {}",
crate::quote_os_path(store.directory())
)
})?;
operation(&store)
}
pub fn with_xai_oauth_revocation_transaction<T>(
operation: impl FnOnce() -> Result<T>,
) -> Result<T> {
with_xai_oauth_lifecycle_lock(|store| {
let revocation = store.stage_revocation()?;
match operation() {
Ok(value) => {
revocation.commit(store).context(
"xAI OAuth authority changed, but retired owned credentials could not be removed",
)?;
Ok(value)
}
Err(error) => {
if let Err(rollback) = revocation.rollback(store) {
return Err(error).context(format!(
"also failed to restore the prior xAI OAuth epoch: {rollback:#}"
));
}
Err(error)
}
}
})
}
impl XaiOAuthCredentialStore {
fn open() -> Result<Self> {
let directory = xai_oauth_credentials_dir()?;
open_owned_credentials_directory(&directory)
}
#[must_use]
pub fn directory(&self) -> &Path {
&self.directory
}
pub fn path_for(&self, name: &str) -> Result<PathBuf> {
validate_owned_auth_name(name)?;
Ok(self.directory.join(name))
}
pub fn read_to_string(&self, name: &str) -> Result<Option<String>> {
validate_owned_auth_name(name)?;
let Some(mut file) = self.open_owned_file_for_read(name)? else {
return Ok(None);
};
let metadata = validate_owned_file_handle(&file, &self.directory.join(name))?;
if metadata.len() > XAI_OAUTH_FILE_LIMIT {
bail!(
"Codewhale-owned xAI OAuth file {} exceeds the {} byte limit",
crate::quote_os_path(&self.directory.join(name)),
XAI_OAUTH_FILE_LIMIT
);
}
let mut bytes = Vec::with_capacity(metadata.len() as usize);
(&mut file)
.take(XAI_OAUTH_FILE_LIMIT + 1)
.read_to_end(&mut bytes)
.with_context(|| {
format!(
"reading Codewhale-owned xAI OAuth file {}",
crate::quote_os_path(&self.directory.join(name))
)
})?;
if bytes.len() as u64 > XAI_OAUTH_FILE_LIMIT {
bail!(
"Codewhale-owned xAI OAuth file {} exceeds the {} byte limit",
crate::quote_os_path(&self.directory.join(name)),
XAI_OAUTH_FILE_LIMIT
);
}
String::from_utf8(bytes).map(Some).map_err(|_| {
anyhow::anyhow!(
"Codewhale-owned xAI OAuth file {} is not valid UTF-8",
crate::quote_os_path(&self.directory.join(name))
)
})
}
pub fn write(&self, name: &str, bytes: &[u8], allow_replace: bool) -> Result<()> {
validate_owned_auth_name(name)?;
anyhow::ensure!(
bytes.len() as u64 <= XAI_OAUTH_FILE_LIMIT,
"refusing oversized xAI OAuth credential payload"
);
self.write_owned_file(name, bytes, allow_replace)
}
pub fn remove(&self, name: &str) -> Result<bool> {
validate_owned_auth_name(name)?;
self.remove_raw(name)
}
pub fn clear_all(&self) -> Result<usize> {
let mut removed = 0;
for name in self.owned_auth_names()? {
if self.remove(&name)? {
removed += 1;
}
}
Ok(removed)
}
pub fn stage_revocation(&self) -> Result<XaiOAuthRevocation> {
#[cfg(windows)]
{
Ok(XaiOAuthRevocation {
retired: self
.owned_auth_names()?
.into_iter()
.map(|name| (name, String::new()))
.collect(),
})
}
#[cfg(not(windows))]
{
static COUNTER: AtomicU64 = AtomicU64::new(0);
let nonce = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_nanos();
let mut retired = Vec::new();
for (index, name) in self.owned_auth_names()?.into_iter().enumerate() {
let tombstone = format!(
".xai-oauth-retired-{}-{nonce}-{}-{index}.tmp",
std::process::id(),
COUNTER.fetch_add(1, Ordering::Relaxed)
);
if let Err(error) = self.rename_raw(&name, &tombstone) {
let rollback = XaiOAuthRevocation { retired };
if let Err(rollback_error) = rollback.rollback(self) {
return Err(error).context(format!(
"also failed to restore previously retired xAI OAuth files: {rollback_error:#}"
));
}
return Err(error);
}
retired.push((name, tombstone));
}
Ok(XaiOAuthRevocation { retired })
}
}
fn owned_auth_names(&self) -> Result<Vec<String>> {
owned_auth_names_in_store(self)
}
fn open_lock_file(&self) -> Result<File> {
self.open_internal_file(XAI_OAUTH_LIFECYCLE_LOCK_FILE_NAME)
}
}
#[cfg(unix)]
fn owned_auth_names_in_store(store: &XaiOAuthCredentialStore) -> Result<Vec<String>> {
use std::ffi::CStr;
use std::os::fd::AsRawFd as _;
let duplicated =
unsafe { libc::fcntl(store.directory_handle.as_raw_fd(), libc::F_DUPFD_CLOEXEC, 0) };
if duplicated < 0 {
return Err(std::io::Error::last_os_error())
.context("duplicating Codewhale credentials directory handle");
}
let stream = unsafe { libc::fdopendir(duplicated) };
if stream.is_null() {
let error = std::io::Error::last_os_error();
unsafe { libc::close(duplicated) };
return Err(error).context("enumerating Codewhale credentials directory");
}
let mut names = Vec::new();
loop {
let entry = unsafe { libc::readdir(stream) };
if entry.is_null() {
break;
}
let name = unsafe { CStr::from_ptr((*entry).d_name.as_ptr()) };
let Ok(name) = name.to_str() else {
continue;
};
if name == LEGACY_XAI_OAUTH_FILE_NAME || is_valid_xai_oauth_generation(name) {
names.push(name.to_string());
}
}
if unsafe { libc::closedir(stream) } != 0 {
return Err(std::io::Error::last_os_error())
.context("closing Codewhale credentials directory enumeration");
}
names.sort();
Ok(names)
}
#[cfg(not(unix))]
fn owned_auth_names_in_store(store: &XaiOAuthCredentialStore) -> Result<Vec<String>> {
let mut names = Vec::new();
let entries = fs::read_dir(&store.directory).with_context(|| {
format!(
"failed to inspect Codewhale credentials directory {}",
crate::quote_os_path(&store.directory)
)
})?;
for entry in entries {
let entry = entry.with_context(|| {
format!(
"failed to inspect Codewhale credentials directory {}",
crate::quote_os_path(&store.directory)
)
})?;
let name = entry.file_name();
let Some(name) = name.to_str() else {
continue;
};
if name == LEGACY_XAI_OAUTH_FILE_NAME || is_valid_xai_oauth_generation(name) {
names.push(name.to_string());
}
}
names.sort();
Ok(names)
}
impl XaiOAuthRevocation {
pub fn rollback(self, store: &XaiOAuthCredentialStore) -> Result<()> {
#[cfg(windows)]
{
let _ = store;
Ok(())
}
#[cfg(not(windows))]
{
let mut first_error = None;
for (original, tombstone) in self.retired.into_iter().rev() {
let result = store.rename_raw(&tombstone, &original).with_context(|| {
format!(
"restoring retired xAI OAuth file {}",
crate::quote_os_path(&store.directory.join(original))
)
});
if result.is_err() && first_error.is_none() {
first_error = result.err();
}
}
if let Some(error) = first_error {
return Err(error);
}
Ok(())
}
}
pub fn commit(self, store: &XaiOAuthCredentialStore) -> Result<usize> {
let mut removed = 0;
for (_original, _tombstone) in self.retired {
#[cfg(windows)]
let target = _original;
#[cfg(not(windows))]
let target = _tombstone;
if store.remove_raw(&target)? {
removed += 1;
}
}
Ok(removed)
}
}
fn validate_owned_auth_name(name: &str) -> Result<()> {
anyhow::ensure!(
name == LEGACY_XAI_OAUTH_FILE_NAME || is_valid_xai_oauth_generation(name),
"invalid Codewhale-owned xAI OAuth basename"
);
Ok(())
}
fn validate_private_basename(name: &str) -> Result<()> {
let path = Path::new(name);
anyhow::ensure!(
path.components().count() == 1
&& matches!(path.components().next(), Some(Component::Normal(_)))
&& path.file_name().and_then(|value| value.to_str()) == Some(name),
"xAI OAuth private basename must be one UTF-8 path component"
);
Ok(())
}
#[cfg(unix)]
fn open_owned_credentials_directory(directory: &Path) -> Result<XaiOAuthCredentialStore> {
use std::os::fd::FromRawFd as _;
use std::os::unix::fs::{MetadataExt as _, PermissionsExt as _};
anyhow::ensure!(
directory.is_absolute(),
"xAI OAuth credentials directory must be absolute"
);
let root_fd = unsafe {
libc::open(
c"/".as_ptr(),
libc::O_RDONLY | libc::O_DIRECTORY | libc::O_CLOEXEC | libc::O_NOFOLLOW,
)
};
if root_fd < 0 {
return Err(std::io::Error::last_os_error()).context("opening filesystem root");
}
let mut current = unsafe { File::from_raw_fd(root_fd) };
for component in directory.components() {
let Component::Normal(name) = component else {
if matches!(component, Component::RootDir) {
continue;
}
bail!(
"Codewhale credentials directory has an unsupported component: {}",
crate::quote_os_path(directory)
);
};
let name = cstring_from_os_str(name)?;
let mut fd = unsafe {
libc::openat(
std::os::fd::AsRawFd::as_raw_fd(¤t),
name.as_ptr(),
libc::O_RDONLY | libc::O_DIRECTORY | libc::O_CLOEXEC | libc::O_NOFOLLOW,
)
};
if fd < 0 && std::io::Error::last_os_error().kind() == std::io::ErrorKind::NotFound {
let created = unsafe {
libc::mkdirat(
std::os::fd::AsRawFd::as_raw_fd(¤t),
name.as_ptr(),
0o700,
)
};
if created != 0 {
let error = std::io::Error::last_os_error();
if error.kind() != std::io::ErrorKind::AlreadyExists {
return Err(error).with_context(|| {
format!(
"creating a component of Codewhale credentials directory {}",
crate::quote_os_path(directory)
)
});
}
}
fd = unsafe {
libc::openat(
std::os::fd::AsRawFd::as_raw_fd(¤t),
name.as_ptr(),
libc::O_RDONLY | libc::O_DIRECTORY | libc::O_CLOEXEC | libc::O_NOFOLLOW,
)
};
}
if fd < 0 {
return Err(std::io::Error::last_os_error()).with_context(|| {
format!(
"opening Codewhale credentials directory without following links: {}",
crate::quote_os_path(directory)
)
});
}
current = unsafe { File::from_raw_fd(fd) };
}
let metadata = current.metadata().with_context(|| {
format!(
"inspecting Codewhale credentials directory {}",
crate::quote_os_path(directory)
)
})?;
anyhow::ensure!(
metadata.is_dir(),
"Codewhale credentials path must be a directory"
);
anyhow::ensure!(
metadata.uid() == unsafe { libc::geteuid() },
"Codewhale credentials directory must be owned by the current user"
);
current
.set_permissions(fs::Permissions::from_mode(0o700))
.with_context(|| {
format!(
"securing Codewhale credentials directory {}",
crate::quote_os_path(directory)
)
})?;
Ok(XaiOAuthCredentialStore {
directory: directory.to_path_buf(),
directory_handle: current,
})
}
#[cfg(unix)]
fn cstring_from_os_str(value: &std::ffi::OsStr) -> Result<CString> {
use std::os::unix::ffi::OsStrExt as _;
CString::new(value.as_bytes()).context("owned xAI OAuth path contains an interior NUL")
}
#[cfg(unix)]
impl XaiOAuthCredentialStore {
fn open_at(&self, name: &str, flags: i32, mode: libc::mode_t) -> Result<Option<File>> {
use std::os::fd::AsRawFd as _;
use std::os::fd::FromRawFd as _;
validate_private_basename(name)?;
let name = CString::new(name).context("xAI OAuth basename contains an interior NUL")?;
let fd = unsafe {
libc::openat(
self.directory_handle.as_raw_fd(),
name.as_ptr(),
flags | libc::O_CLOEXEC | libc::O_NOFOLLOW,
libc::c_uint::from(mode),
)
};
if fd < 0 {
let error = std::io::Error::last_os_error();
if error.kind() == std::io::ErrorKind::NotFound {
return Ok(None);
}
return Err(error).with_context(|| {
format!(
"opening Codewhale-owned xAI OAuth path {}",
crate::quote_os_path(&self.directory.join(name.to_string_lossy().as_ref()))
)
});
}
Ok(Some(unsafe { File::from_raw_fd(fd) }))
}
fn open_owned_file_for_read(&self, name: &str) -> Result<Option<File>> {
self.open_at(name, libc::O_RDONLY, 0)
}
fn open_internal_file(&self, name: &str) -> Result<File> {
use std::os::unix::fs::PermissionsExt as _;
let file = self
.open_at(name, libc::O_RDWR | libc::O_CREAT, 0o600)?
.context("xAI OAuth lifecycle lock disappeared while opening")?;
validate_owned_file_handle(&file, &self.directory.join(name))?;
file.set_permissions(fs::Permissions::from_mode(0o600))?;
Ok(file)
}
fn write_owned_file(&self, name: &str, bytes: &[u8], allow_replace: bool) -> Result<()> {
use std::os::fd::AsRawFd as _;
use std::os::unix::fs::PermissionsExt as _;
let temp_name = format!(
".xai-oauth-write-{}-{}.tmp",
std::process::id(),
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_nanos()
);
let mut temp = self
.open_at(
&temp_name,
libc::O_WRONLY | libc::O_CREAT | libc::O_EXCL,
0o600,
)?
.context("creating private xAI OAuth temporary file")?;
let result = (|| -> Result<()> {
temp.write_all(bytes)
.context("writing xAI OAuth temporary file")?;
temp.flush().context("flushing xAI OAuth temporary file")?;
temp.set_permissions(fs::Permissions::from_mode(0o600))?;
temp.sync_all()
.context("syncing xAI OAuth temporary file")?;
let target =
CString::new(name).context("xAI OAuth basename contains an interior NUL")?;
let temporary =
CString::new(temp_name.as_str()).context("temporary basename contains NUL")?;
if allow_replace {
if let Some(existing) = self.open_owned_file_for_read(name)? {
validate_owned_file_handle(&existing, &self.directory.join(name))?;
}
if unsafe {
libc::renameat(
self.directory_handle.as_raw_fd(),
temporary.as_ptr(),
self.directory_handle.as_raw_fd(),
target.as_ptr(),
)
} != 0
{
return Err(std::io::Error::last_os_error())
.context("atomically replacing xAI OAuth credentials");
}
} else {
if unsafe {
libc::linkat(
self.directory_handle.as_raw_fd(),
temporary.as_ptr(),
self.directory_handle.as_raw_fd(),
target.as_ptr(),
0,
)
} != 0
{
return Err(std::io::Error::last_os_error())
.context("installing a new xAI OAuth generation without replacement");
}
if unsafe {
libc::unlinkat(self.directory_handle.as_raw_fd(), temporary.as_ptr(), 0)
} != 0
{
let error = std::io::Error::last_os_error();
unsafe {
libc::unlinkat(self.directory_handle.as_raw_fd(), target.as_ptr(), 0)
};
return Err(error).context("removing xAI OAuth generation staging link");
}
}
self.directory_handle
.sync_all()
.context("syncing Codewhale credentials directory")?;
Ok(())
})();
drop(temp);
if result.is_err() {
let _ = self.remove_raw(&temp_name);
}
result
}
fn remove_raw(&self, name: &str) -> Result<bool> {
use std::os::fd::AsRawFd as _;
validate_private_basename(name)?;
let Some(file) = self.open_owned_file_for_read(name)? else {
return Ok(false);
};
validate_owned_file_handle(&file, &self.directory.join(name))?;
drop(file);
let name = CString::new(name).context("xAI OAuth basename contains an interior NUL")?;
if unsafe { libc::unlinkat(self.directory_handle.as_raw_fd(), name.as_ptr(), 0) } != 0 {
let error = std::io::Error::last_os_error();
if error.kind() == std::io::ErrorKind::NotFound {
return Ok(false);
}
return Err(error).context("removing Codewhale-owned xAI OAuth file");
}
Ok(true)
}
fn rename_raw(&self, from: &str, to: &str) -> Result<()> {
use std::os::fd::AsRawFd as _;
validate_private_basename(from)?;
validate_private_basename(to)?;
let source = self
.open_owned_file_for_read(from)?
.context("xAI OAuth source disappeared before retirement")?;
validate_owned_file_handle(&source, &self.directory.join(from))?;
anyhow::ensure!(
self.open_owned_file_for_read(to)?.is_none(),
"refusing to replace an existing xAI OAuth retirement path"
);
drop(source);
let from = CString::new(from).context("xAI OAuth basename contains an interior NUL")?;
let to = CString::new(to).context("xAI OAuth basename contains an interior NUL")?;
if unsafe {
libc::renameat(
self.directory_handle.as_raw_fd(),
from.as_ptr(),
self.directory_handle.as_raw_fd(),
to.as_ptr(),
)
} != 0
{
return Err(std::io::Error::last_os_error()).context("retiring xAI OAuth file");
}
Ok(())
}
}
#[cfg(unix)]
fn validate_owned_file_handle(file: &File, path: &Path) -> Result<fs::Metadata> {
use std::os::unix::fs::MetadataExt as _;
let metadata = file.metadata().with_context(|| {
format!(
"inspecting Codewhale-owned xAI OAuth file {}",
crate::quote_os_path(path)
)
})?;
anyhow::ensure!(metadata.is_file(), "xAI OAuth path must be a regular file");
anyhow::ensure!(
metadata.uid() == unsafe { libc::geteuid() },
"xAI OAuth file must be owned by the current user"
);
anyhow::ensure!(
metadata.nlink() == 1,
"xAI OAuth file must not have multiple filesystem links"
);
Ok(metadata)
}
#[cfg(windows)]
fn open_owned_credentials_directory(directory: &Path) -> Result<XaiOAuthCredentialStore> {
use std::os::windows::fs::OpenOptionsExt as _;
use windows_sys::Win32::Storage::FileSystem::{
FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT, FILE_GENERIC_READ,
FILE_SHARE_READ, FILE_SHARE_WRITE, WRITE_DAC, WRITE_OWNER,
};
anyhow::ensure!(
directory.is_absolute(),
"xAI OAuth credentials directory must be absolute"
);
let mut current = PathBuf::new();
let mut handles = Vec::new();
for component in directory.components() {
match component {
Component::Prefix(prefix) => current.push(prefix.as_os_str()),
Component::RootDir => current.push(Path::new(r"\")),
Component::Normal(name) => {
current.push(name);
match fs::create_dir(¤t) {
Ok(()) => {}
Err(error) if error.kind() == std::io::ErrorKind::AlreadyExists => {}
Err(error) => {
return Err(error).with_context(|| {
format!(
"creating a component of Codewhale credentials directory {}",
crate::quote_os_path(directory)
)
});
}
}
let mut options = fs::OpenOptions::new();
options
.read(true)
.share_mode(FILE_SHARE_READ | FILE_SHARE_WRITE)
.custom_flags(FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT);
let handle = options.open(¤t).with_context(|| {
format!(
"opening Codewhale credentials directory component {}",
crate::quote_os_path(¤t)
)
})?;
validate_windows_handle_path(&handle, ¤t, true)?;
handles.push(handle);
}
Component::CurDir | Component::ParentDir => bail!(
"Codewhale credentials directory must be lexically normalized: {}",
crate::quote_os_path(directory)
),
}
}
anyhow::ensure!(
!handles.is_empty(),
"Codewhale credentials directory cannot be a volume root"
);
let mut secure_options = fs::OpenOptions::new();
secure_options
.access_mode(FILE_GENERIC_READ | WRITE_DAC | WRITE_OWNER)
.share_mode(FILE_SHARE_READ | FILE_SHARE_WRITE)
.custom_flags(FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT);
let final_directory = secure_options.open(directory).with_context(|| {
format!(
"opening Codewhale credentials directory for owner-only security: {}",
crate::quote_os_path(directory)
)
})?;
validate_windows_handle_path(&final_directory, directory, true)?;
secure_windows_owner_only_handle(&final_directory, true)
.context("securing Codewhale credentials directory for the current user")?;
verify_windows_owner_only_handle(&final_directory)
.context("verifying Codewhale credentials directory ownership")?;
handles.push(final_directory);
Ok(XaiOAuthCredentialStore {
directory: directory.to_path_buf(),
_component_handles: handles,
})
}
#[cfg(windows)]
impl XaiOAuthCredentialStore {
fn open_windows_file(&self, name: &str, read: bool, write: bool) -> Result<Option<File>> {
use std::os::windows::fs::OpenOptionsExt as _;
use windows_sys::Win32::Storage::FileSystem::{
FILE_FLAG_OPEN_REPARSE_POINT, FILE_SHARE_READ, FILE_SHARE_WRITE,
};
validate_private_basename(name)?;
let path = self.directory.join(name);
let mut options = fs::OpenOptions::new();
options
.read(read)
.write(write)
.share_mode(FILE_SHARE_READ | FILE_SHARE_WRITE)
.custom_flags(FILE_FLAG_OPEN_REPARSE_POINT);
match options.open(&path) {
Ok(file) => {
validate_owned_file_handle(&file, &path)?;
Ok(Some(file))
}
Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(None),
Err(error) => Err(error).with_context(|| {
format!(
"opening Codewhale-owned xAI OAuth path {}",
crate::quote_os_path(&path)
)
}),
}
}
fn open_owned_file_for_read(&self, name: &str) -> Result<Option<File>> {
self.open_windows_file(name, true, false)
}
fn open_internal_file(&self, name: &str) -> Result<File> {
use std::os::windows::fs::OpenOptionsExt as _;
use windows_sys::Win32::Storage::FileSystem::{
DELETE, FILE_FLAG_OPEN_REPARSE_POINT, FILE_GENERIC_READ, FILE_GENERIC_WRITE,
FILE_SHARE_READ, FILE_SHARE_WRITE, WRITE_DAC, WRITE_OWNER,
};
validate_private_basename(name)?;
let path = self.directory.join(name);
for _ in 0..8 {
if let Some(existing) = self.open_windows_file(name, true, true)? {
return Ok(existing);
}
let mut options = fs::OpenOptions::new();
options
.write(true)
.access_mode(
FILE_GENERIC_READ | FILE_GENERIC_WRITE | WRITE_DAC | WRITE_OWNER | DELETE,
)
.create_new(true)
.share_mode(FILE_SHARE_READ | FILE_SHARE_WRITE)
.custom_flags(FILE_FLAG_OPEN_REPARSE_POINT);
let file = match options.open(&path) {
Ok(file) => file,
Err(error) if error.kind() == std::io::ErrorKind::AlreadyExists => continue,
Err(error) => {
return Err(error).with_context(|| {
format!(
"creating Codewhale-owned xAI OAuth lifecycle lock {}",
crate::quote_os_path(&path)
)
});
}
};
let secured = (|| -> Result<()> {
validate_windows_file_shape(&file, &path)?;
secure_windows_owner_only_handle(&file, false)
.context("securing a new xAI OAuth lifecycle lock")?;
validate_owned_file_handle(&file, &path)?;
Ok(())
})();
if let Err(error) = secured {
let cleanup = mark_windows_file_handle_for_deletion(&file);
return match cleanup {
Ok(()) => Err(error),
Err(cleanup) => Err(error).context(format!(
"also failed to delete the empty lifecycle lock: {cleanup:#}"
)),
};
}
return Ok(file);
}
bail!("xAI OAuth lifecycle lock changed repeatedly while opening")
}
fn write_owned_file(&self, name: &str, bytes: &[u8], allow_replace: bool) -> Result<()> {
let path = self.directory.join(name);
if let Some(existing) = self.open_owned_file_for_read(name)? {
anyhow::ensure!(
allow_replace,
"refusing to replace an existing xAI OAuth generation"
);
drop(existing);
}
let mut temporary = tempfile::NamedTempFile::new_in(&self.directory)
.context("creating private xAI OAuth temporary file")?;
let temporary_path = temporary.path().to_path_buf();
let security_handle =
reopen_windows_file_for_owner_security(temporary.as_file(), &temporary_path)?;
secure_windows_owner_only_handle(&security_handle, false)
.context("securing a new xAI OAuth temporary file before writing credentials")?;
validate_owned_file_handle(&security_handle, &temporary_path)
.context("verifying a new xAI OAuth temporary file before writing credentials")?;
let write_result = (|| -> Result<()> {
temporary
.write_all(bytes)
.context("writing xAI OAuth temporary file")?;
temporary
.flush()
.context("flushing xAI OAuth temporary file")?;
temporary
.as_file()
.sync_all()
.context("syncing xAI OAuth temporary file")?;
Ok(())
})();
if let Err(error) = write_result {
return Err(cleanup_windows_secret_after_error(
&security_handle,
error,
"temporary file",
));
}
let persisted = if allow_replace {
match temporary.persist(&path) {
Ok(file) => file,
Err(error) => {
let tempfile::PersistError { error, file } = error;
let persistence_error = anyhow::Error::new(error)
.context("atomically replacing xAI OAuth credentials");
let error = cleanup_windows_secret_after_error(
&security_handle,
persistence_error,
"temporary file",
);
drop(file);
return Err(error);
}
}
} else {
match temporary.persist_noclobber(&path) {
Ok(file) => file,
Err(error) => {
let tempfile::PersistError { error, file } = error;
let persistence_error = anyhow::Error::new(error)
.context("installing a new xAI OAuth generation without replacement");
let error = cleanup_windows_secret_after_error(
&security_handle,
persistence_error,
"temporary file",
);
drop(file);
return Err(error);
}
}
};
if let Err(error) = validate_persisted_windows_owned_file(&persisted, &path) {
return Err(cleanup_windows_secret_after_error(
&security_handle,
error,
"rejected generation",
));
}
Ok(())
}
fn remove_raw(&self, name: &str) -> Result<bool> {
use std::os::windows::fs::OpenOptionsExt as _;
use windows_sys::Win32::Storage::FileSystem::{
DELETE, FILE_FLAG_OPEN_REPARSE_POINT, FILE_GENERIC_READ, FILE_SHARE_DELETE,
FILE_SHARE_READ, FILE_SHARE_WRITE,
};
validate_private_basename(name)?;
let path = self.directory.join(name);
let mut options = fs::OpenOptions::new();
options
.access_mode(FILE_GENERIC_READ | DELETE)
.share_mode(FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE)
.custom_flags(FILE_FLAG_OPEN_REPARSE_POINT);
let file = match options.open(&path) {
Ok(file) => file,
Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(false),
Err(error) => return Err(error).context("opening xAI OAuth file for exact deletion"),
};
validate_owned_file_handle(&file, &path)?;
mark_windows_file_handle_for_deletion(&file)?;
drop(file);
Ok(true)
}
}
#[cfg(windows)]
fn reopen_windows_file_for_owner_security(file: &File, path: &Path) -> Result<File> {
use std::os::windows::io::{AsRawHandle as _, FromRawHandle as _};
use windows_sys::Win32::Foundation::INVALID_HANDLE_VALUE;
use windows_sys::Win32::Storage::FileSystem::{
DELETE, FILE_FLAG_OPEN_REPARSE_POINT, FILE_GENERIC_READ, FILE_GENERIC_WRITE,
FILE_SHARE_DELETE, FILE_SHARE_READ, FILE_SHARE_WRITE, ReOpenFile, WRITE_DAC, WRITE_OWNER,
};
let handle = unsafe {
ReOpenFile(
file.as_raw_handle(),
FILE_GENERIC_READ | FILE_GENERIC_WRITE | WRITE_DAC | WRITE_OWNER | DELETE,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
FILE_FLAG_OPEN_REPARSE_POINT,
)
};
if handle == INVALID_HANDLE_VALUE {
return Err(std::io::Error::last_os_error())
.context("reopening a new xAI OAuth temporary file for owner-only security");
}
let reopened = unsafe { File::from_raw_handle(handle) };
validate_windows_file_shape(&reopened, path)?;
Ok(reopened)
}
#[cfg(windows)]
fn mark_windows_file_handle_for_deletion(file: &File) -> Result<()> {
use std::os::windows::io::AsRawHandle as _;
use windows_sys::Win32::Storage::FileSystem::{
FILE_DISPOSITION_INFO, FileDispositionInfo, SetFileInformationByHandle,
};
let disposition = FILE_DISPOSITION_INFO { DeleteFile: true };
if unsafe {
SetFileInformationByHandle(
file.as_raw_handle(),
FileDispositionInfo,
(&raw const disposition).cast(),
std::mem::size_of::<FILE_DISPOSITION_INFO>() as u32,
)
} == 0
{
return Err(std::io::Error::last_os_error())
.context("marking exact xAI OAuth file handle for deletion");
}
Ok(())
}
#[cfg(windows)]
fn cleanup_windows_secret_after_error(
file: &File,
error: anyhow::Error,
label: &str,
) -> anyhow::Error {
match mark_windows_file_handle_for_deletion(file) {
Ok(()) => error,
Err(cleanup) => error.context(format!(
"also failed to delete the xAI OAuth {label} by exact handle: {cleanup:#}"
)),
}
}
#[cfg(all(windows, test))]
static WINDOWS_POST_PERSIST_VALIDATION_FAILURE: Mutex<Option<PathBuf>> = Mutex::new(None);
#[cfg(windows)]
fn validate_persisted_windows_owned_file(file: &File, path: &Path) -> Result<fs::Metadata> {
#[cfg(test)]
{
let mut injected = WINDOWS_POST_PERSIST_VALIDATION_FAILURE
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if injected.as_deref() == Some(path) {
*injected = None;
bail!("injected post-persistence xAI OAuth validation failure");
}
}
validate_owned_file_handle(file, path)
}
#[cfg(all(windows, test))]
fn fail_next_windows_post_persist_validation(path: &Path) {
*WINDOWS_POST_PERSIST_VALIDATION_FAILURE
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) = Some(path.to_path_buf());
}
#[cfg(windows)]
fn validate_owned_file_handle(file: &File, path: &Path) -> Result<fs::Metadata> {
let metadata = validate_windows_file_shape(file, path)?;
verify_windows_owner_only_handle(file)
.context("Codewhale-owned xAI OAuth file is not current-user-only")?;
Ok(metadata)
}
#[cfg(windows)]
fn validate_windows_file_shape(file: &File, path: &Path) -> Result<fs::Metadata> {
use std::os::windows::io::AsRawHandle as _;
use windows_sys::Win32::Storage::FileSystem::{
BY_HANDLE_FILE_INFORMATION, GetFileInformationByHandle,
};
let metadata = validate_windows_handle_path(file, path, false)?;
let mut information = BY_HANDLE_FILE_INFORMATION::default();
if unsafe { GetFileInformationByHandle(file.as_raw_handle(), &mut information) } == 0 {
return Err(std::io::Error::last_os_error())
.context("inspecting xAI OAuth file link count");
}
anyhow::ensure!(
information.nNumberOfLinks == 1,
"xAI OAuth file must not have multiple filesystem links"
);
Ok(metadata)
}
#[cfg(windows)]
fn validate_windows_handle_path(
file: &File,
expected: &Path,
expect_directory: bool,
) -> Result<fs::Metadata> {
use std::ffi::OsString;
use std::os::windows::ffi::OsStringExt as _;
use std::os::windows::fs::MetadataExt as _;
use std::os::windows::io::AsRawHandle as _;
use windows_sys::Win32::Storage::FileSystem::{
FILE_ATTRIBUTE_REPARSE_POINT, FILE_NAME_NORMALIZED, GetFinalPathNameByHandleW,
VOLUME_NAME_DOS,
};
let metadata = file.metadata().with_context(|| {
format!(
"inspecting Codewhale-owned path {}",
crate::quote_os_path(expected)
)
})?;
anyhow::ensure!(
metadata.file_attributes() & FILE_ATTRIBUTE_REPARSE_POINT == 0,
"Codewhale-owned xAI OAuth path must not be a reparse point"
);
anyhow::ensure!(
if expect_directory {
metadata.is_dir()
} else {
metadata.is_file()
},
"Codewhale-owned xAI OAuth path has the wrong filesystem type"
);
let flags = FILE_NAME_NORMALIZED | VOLUME_NAME_DOS;
let handle = file.as_raw_handle();
let needed = unsafe { GetFinalPathNameByHandleW(handle, std::ptr::null_mut(), 0, flags) };
if needed == 0 {
return Err(std::io::Error::last_os_error())
.context("resolving Codewhale-owned xAI OAuth handle path");
}
let mut buffer = vec![0u16; needed as usize + 1];
let written = unsafe {
GetFinalPathNameByHandleW(handle, buffer.as_mut_ptr(), buffer.len() as u32, flags)
};
if written == 0 || written as usize >= buffer.len() {
return Err(std::io::Error::last_os_error())
.context("resolving Codewhale-owned xAI OAuth handle path");
}
let actual = OsString::from_wide(&buffer[..written as usize]);
anyhow::ensure!(
normalize_windows_path_for_comparison(Path::new(&actual))?
== normalize_windows_path_for_comparison(expected)?,
"Codewhale-owned xAI OAuth path was redirected while opening"
);
Ok(metadata)
}
#[cfg(windows)]
fn normalize_windows_path_for_comparison(path: &Path) -> Result<String> {
let text = path.to_str().ok_or_else(|| {
anyhow::anyhow!(
"xAI OAuth path {} contains invalid Unicode and cannot be compared safely",
crate::quote_os_path(path)
)
})?;
let without_device_prefix = text.strip_prefix(r"\\?\").unwrap_or(text);
let normalized_prefix = without_device_prefix.strip_prefix("UNC\\").map_or_else(
|| without_device_prefix.to_string(),
|rest| format!(r"\\{rest}"),
);
Ok(normalized_prefix
.replace('/', "\\")
.trim_end_matches('\\')
.to_lowercase())
}
#[cfg(windows)]
fn secure_windows_owner_only_handle(file: &File, inherit_to_children: bool) -> Result<()> {
use std::os::windows::io::AsRawHandle as _;
use windows_sys::Win32::Foundation::ERROR_SUCCESS;
use windows_sys::Win32::Security::Authorization::{
EXPLICIT_ACCESS_W, SE_FILE_OBJECT, SET_ACCESS, SetEntriesInAclW, SetSecurityInfo,
TRUSTEE_IS_SID, TRUSTEE_IS_USER, TRUSTEE_W,
};
use windows_sys::Win32::Security::{
DACL_SECURITY_INFORMATION, NO_INHERITANCE, OWNER_SECURITY_INFORMATION,
PROTECTED_DACL_SECURITY_INFORMATION, SUB_CONTAINERS_AND_OBJECTS_INHERIT,
};
use windows_sys::Win32::Storage::FileSystem::FILE_ALL_ACCESS;
let user = CurrentWindowsUser::open()?;
let entry = EXPLICIT_ACCESS_W {
grfAccessPermissions: FILE_ALL_ACCESS,
grfAccessMode: SET_ACCESS,
grfInheritance: if inherit_to_children {
SUB_CONTAINERS_AND_OBJECTS_INHERIT
} else {
NO_INHERITANCE
},
Trustee: TRUSTEE_W {
pMultipleTrustee: std::ptr::null_mut(),
MultipleTrusteeOperation: 0,
TrusteeForm: TRUSTEE_IS_SID,
TrusteeType: TRUSTEE_IS_USER,
ptstrName: user.sid().cast::<u16>(),
},
};
let mut acl = std::ptr::null_mut();
let result = unsafe { SetEntriesInAclW(1, &raw const entry, std::ptr::null(), &mut acl) };
if result != ERROR_SUCCESS {
return Err(std::io::Error::from_raw_os_error(result as i32))
.context("building a current-user-only DACL for Codewhale-owned xAI OAuth storage");
}
let _acl = WindowsLocalAllocation(acl.cast());
let result = unsafe {
SetSecurityInfo(
file.as_raw_handle(),
SE_FILE_OBJECT,
OWNER_SECURITY_INFORMATION
| DACL_SECURITY_INFORMATION
| PROTECTED_DACL_SECURITY_INFORMATION,
user.sid(),
std::ptr::null_mut(),
acl,
std::ptr::null(),
)
};
if result != ERROR_SUCCESS {
return Err(std::io::Error::from_raw_os_error(result as i32))
.context("applying a current-user-only DACL to Codewhale-owned xAI OAuth storage");
}
Ok(())
}
#[cfg(windows)]
fn verify_windows_owner_only_handle(file: &File) -> Result<()> {
use std::os::windows::io::AsRawHandle as _;
use windows_sys::Win32::Foundation::ERROR_SUCCESS;
use windows_sys::Win32::Security::Authorization::{
EXPLICIT_ACCESS_W, GRANT_ACCESS, GetExplicitEntriesFromAclW, GetSecurityInfo,
SE_FILE_OBJECT, SET_ACCESS, TRUSTEE_IS_SID,
};
use windows_sys::Win32::Security::{
ACL, DACL_SECURITY_INFORMATION, EqualSid, OWNER_SECURITY_INFORMATION, PSECURITY_DESCRIPTOR,
PSID,
};
use windows_sys::Win32::Storage::FileSystem::FILE_ALL_ACCESS;
let user = CurrentWindowsUser::open()?;
let mut owner: PSID = std::ptr::null_mut();
let mut dacl: *mut ACL = std::ptr::null_mut();
let mut descriptor: PSECURITY_DESCRIPTOR = std::ptr::null_mut();
let result = unsafe {
GetSecurityInfo(
file.as_raw_handle(),
SE_FILE_OBJECT,
OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION,
&mut owner,
std::ptr::null_mut(),
&mut dacl,
std::ptr::null_mut(),
&mut descriptor,
)
};
if result != ERROR_SUCCESS {
return Err(std::io::Error::from_raw_os_error(result as i32))
.context("reading Codewhale-owned xAI OAuth security descriptor");
}
let _descriptor = WindowsLocalAllocation(descriptor.cast());
anyhow::ensure!(
!owner.is_null() && unsafe { EqualSid(owner, user.sid()) } != 0,
"Codewhale-owned xAI OAuth storage owner is not the current user"
);
anyhow::ensure!(
!dacl.is_null(),
"Codewhale-owned xAI OAuth storage must have an owner-only DACL"
);
let mut count = 0;
let mut entries: *mut EXPLICIT_ACCESS_W = std::ptr::null_mut();
let result = unsafe { GetExplicitEntriesFromAclW(dacl, &mut count, &mut entries) };
if result != ERROR_SUCCESS {
return Err(std::io::Error::from_raw_os_error(result as i32))
.context("reading Codewhale-owned xAI OAuth DACL entries");
}
let _entries = WindowsLocalAllocation(entries.cast());
anyhow::ensure!(
count == 1 && !entries.is_null(),
"Codewhale-owned xAI OAuth DACL must grant only one user"
);
let entry = unsafe { &*entries };
let trustee_sid: PSID = entry.Trustee.ptstrName.cast();
anyhow::ensure!(
entry.Trustee.TrusteeForm == TRUSTEE_IS_SID
&& !trustee_sid.is_null()
&& unsafe { EqualSid(trustee_sid, user.sid()) } != 0
&& matches!(entry.grfAccessMode, SET_ACCESS | GRANT_ACCESS)
&& entry.grfAccessPermissions == FILE_ALL_ACCESS,
"Codewhale-owned xAI OAuth DACL is not current-user-only"
);
Ok(())
}
#[cfg(windows)]
struct CurrentWindowsUser {
token: windows_sys::Win32::Foundation::HANDLE,
token_info: Vec<usize>,
}
#[cfg(windows)]
impl CurrentWindowsUser {
fn open() -> Result<Self> {
use windows_sys::Win32::Foundation::{CloseHandle, GetLastError, HANDLE};
use windows_sys::Win32::Security::{
GetTokenInformation, TOKEN_QUERY, TOKEN_USER, TokenUser,
};
use windows_sys::Win32::System::Threading::{GetCurrentProcess, OpenProcessToken};
let mut token: HANDLE = std::ptr::null_mut();
if unsafe { OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &mut token) } == 0 {
return Err(std::io::Error::last_os_error())
.context("opening current Windows user token");
}
let mut needed = 0;
let _ =
unsafe { GetTokenInformation(token, TokenUser, std::ptr::null_mut(), 0, &mut needed) };
if needed == 0 {
let error = std::io::Error::from_raw_os_error(unsafe { GetLastError() } as i32);
unsafe { CloseHandle(token) };
return Err(error).context("sizing current Windows user token information");
}
let words = (needed as usize).div_ceil(std::mem::size_of::<usize>());
let mut token_info = vec![0usize; words];
if unsafe {
GetTokenInformation(
token,
TokenUser,
token_info.as_mut_ptr().cast(),
needed,
&mut needed,
)
} == 0
{
let error = std::io::Error::last_os_error();
unsafe { CloseHandle(token) };
return Err(error).context("reading current Windows user token information");
}
let user = unsafe { &*token_info.as_ptr().cast::<TOKEN_USER>() };
if user.User.Sid.is_null() {
unsafe { CloseHandle(token) };
bail!("current Windows user token has no SID");
}
Ok(Self { token, token_info })
}
fn sid(&self) -> windows_sys::Win32::Security::PSID {
use windows_sys::Win32::Security::TOKEN_USER;
unsafe { (*self.token_info.as_ptr().cast::<TOKEN_USER>()).User.Sid }
}
}
#[cfg(windows)]
impl Drop for CurrentWindowsUser {
fn drop(&mut self) {
unsafe { windows_sys::Win32::Foundation::CloseHandle(self.token) };
}
}
#[cfg(windows)]
struct WindowsLocalAllocation(*mut core::ffi::c_void);
#[cfg(windows)]
impl Drop for WindowsLocalAllocation {
fn drop(&mut self) {
if !self.0.is_null() {
unsafe { windows_sys::Win32::Foundation::LocalFree(self.0) };
}
}
}
#[cfg(not(any(unix, windows)))]
fn open_owned_credentials_directory(directory: &Path) -> Result<XaiOAuthCredentialStore> {
fs::create_dir_all(directory)?;
let metadata = fs::symlink_metadata(directory)?;
anyhow::ensure!(
metadata.is_dir(),
"Codewhale credentials path must be a directory"
);
Ok(XaiOAuthCredentialStore {
directory: directory.to_path_buf(),
})
}
#[cfg(not(any(unix, windows)))]
impl XaiOAuthCredentialStore {
fn open_owned_file_for_read(&self, name: &str) -> Result<Option<File>> {
validate_private_basename(name)?;
match File::open(self.directory.join(name)) {
Ok(file) => Ok(Some(file)),
Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(None),
Err(error) => Err(error.into()),
}
}
fn open_internal_file(&self, name: &str) -> Result<File> {
validate_private_basename(name)?;
Ok(fs::OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(self.directory.join(name))?)
}
fn write_owned_file(&self, name: &str, bytes: &[u8], allow_replace: bool) -> Result<()> {
let path = self.directory.join(name);
anyhow::ensure!(
allow_replace || !path.exists(),
"refusing to replace xAI OAuth generation"
);
crate::persistence::atomic_write(&path, bytes)
}
fn remove_raw(&self, name: &str) -> Result<bool> {
validate_private_basename(name)?;
match fs::remove_file(self.directory.join(name)) {
Ok(()) => Ok(true),
Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(false),
Err(error) => Err(error.into()),
}
}
fn rename_raw(&self, from: &str, to: &str) -> Result<()> {
validate_private_basename(from)?;
validate_private_basename(to)?;
fs::rename(self.directory.join(from), self.directory.join(to))?;
Ok(())
}
}
#[cfg(not(any(unix, windows)))]
fn validate_owned_file_handle(file: &File, _path: &Path) -> Result<fs::Metadata> {
let metadata = file.metadata()?;
anyhow::ensure!(metadata.is_file(), "xAI OAuth path must be a regular file");
Ok(metadata)
}
pub fn remove_xai_oauth_generation(generation: &str) -> Result<bool> {
let generation = validate_xai_oauth_generation(generation)?;
with_xai_oauth_lifecycle_lock(|store| store.remove(generation))
}
pub fn clear_all_xai_oauth_credentials() -> Result<usize> {
with_xai_oauth_lifecycle_lock(XaiOAuthCredentialStore::clear_all)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn credentials_directory_preserves_lexical_identity() {
let directory = tempfile::tempdir().expect("temp dir");
let lexical = directory.path().join("missing").join("credentials");
assert_eq!(
lexical_absolute_path(&lexical).expect("preserve lexical path"),
lexical
);
assert!(!lexical.exists());
assert!(
lexical_absolute_path(&directory.path().join("missing/../escape")).is_err(),
"owned credential roots must reject traversal components"
);
}
#[test]
fn generation_names_are_strict_basenames() {
let valid = "xai-auth-0123456789abcdef0123456789abcdef.json";
assert!(is_valid_xai_oauth_generation(valid));
for invalid in [
"../xai-auth-0123456789abcdef0123456789abcdef.json",
"/tmp/xai-auth-0123456789abcdef0123456789abcdef.json",
"xai-auth-0123456789ABCDEF0123456789ABCDEF.json",
"xai-auth-short.json",
"xai-auth.json",
] {
assert!(!is_valid_xai_oauth_generation(invalid), "{invalid}");
}
}
#[test]
fn logout_cleanup_removes_only_owned_xai_files() {
let directory = tempfile::tempdir().expect("temp dir");
let directory = directory.path().canonicalize().expect("canonical temp dir");
let store = open_owned_credentials_directory(&directory).expect("open store");
let generation = "xai-auth-0123456789abcdef0123456789abcdef.json";
store
.write(generation, b"secret", false)
.expect("generation");
store
.write(LEGACY_XAI_OAUTH_FILE_NAME, b"legacy", false)
.expect("legacy");
fs::write(directory.join("other-provider.json"), "keep").expect("other provider");
assert_eq!(store.clear_all().expect("clear"), 2);
assert!(directory.join("other-provider.json").exists());
assert!(!directory.join(generation).exists());
assert!(!directory.join("xai-auth.json").exists());
}
#[cfg(unix)]
#[test]
fn unix_store_pins_directory_identity_across_lexical_path_swap() {
use std::os::unix::fs::symlink;
let root = tempfile::tempdir().expect("temp dir");
let root = root.path().canonicalize().expect("canonical temp root");
let credentials = root.join("credentials");
fs::create_dir(&credentials).expect("credentials");
let store = open_owned_credentials_directory(&credentials).expect("open pinned store");
let parked = root.join("parked-credentials");
let external = root.join("external-owner");
fs::create_dir(&external).expect("external directory");
fs::rename(&credentials, &parked).expect("park credentials");
symlink(&external, &credentials).expect("replace lexical path with symlink");
let generation = "xai-auth-0123456789abcdef0123456789abcdef.json";
store
.write(generation, b"pinned bytes", false)
.expect("write through pinned directory handle");
assert_eq!(fs::read(parked.join(generation)).unwrap(), b"pinned bytes");
assert!(!external.join(generation).exists());
assert_eq!(
store.read_to_string(generation).unwrap().as_deref(),
Some("pinned bytes")
);
}
#[cfg(unix)]
#[test]
fn unix_store_rejects_symlinked_root_component_and_hardlinked_leaf() {
use std::os::unix::fs::symlink;
let root = tempfile::tempdir().expect("temp dir");
let root = root.path().canonicalize().expect("canonical temp root");
let real = root.join("real-home");
fs::create_dir(&real).expect("real home");
let linked = root.join("linked-home");
symlink(&real, &linked).expect("home symlink");
assert!(
open_owned_credentials_directory(&linked.join("credentials")).is_err(),
"owned roots must reject every symlink component"
);
let credentials = real.join("credentials");
let store = open_owned_credentials_directory(&credentials).expect("safe store");
let generation = "xai-auth-fedcba9876543210fedcba9876543210.json";
store
.write(generation, b"secret", false)
.expect("seed generation");
fs::hard_link(
credentials.join(generation),
credentials.join("attacker-hardlink"),
)
.expect("hardlink fixture");
assert!(
store.read_to_string(generation).is_err(),
"owned reads must reject multiply-linked credential files"
);
}
#[cfg(windows)]
#[test]
fn windows_owned_path_identity_is_case_insensitive_and_lossless() {
use std::ffi::OsString;
use std::os::windows::ffi::OsStringExt as _;
assert_eq!(
normalize_windows_path_for_comparison(Path::new(r"C:\Users\Alice\Credentials"))
.unwrap(),
normalize_windows_path_for_comparison(Path::new(r"\\?\c:\users\ALICE\credentials"))
.unwrap()
);
let invalid = PathBuf::from(OsString::from_wide(&[
b'C' as u16,
b':' as u16,
b'\\' as u16,
0xd800,
]));
assert!(normalize_windows_path_for_comparison(&invalid).is_err());
}
#[cfg(windows)]
#[test]
fn windows_post_persist_failure_exact_deletes_new_and_replacement_generations() {
fn assert_directory_empty(path: &Path) {
let entries = fs::read_dir(path)
.expect("read credentials directory")
.collect::<std::io::Result<Vec<_>>>()
.expect("read credential entries");
assert!(
entries.is_empty(),
"rejected credential bytes must leave no durable file: {entries:?}"
);
}
let root = tempfile::tempdir().expect("temp dir");
let root = root.path().canonicalize().expect("canonical temp root");
let credentials = root.join("new-credentials");
let store = open_owned_credentials_directory(&credentials).expect("open secure store");
let generation = "xai-auth-0123456789abcdef0123456789abcdef.json";
let generation_path = credentials.join(generation);
fail_next_windows_post_persist_validation(&generation_path);
let error = store
.write(generation, b"new credential bytes", false)
.expect_err("post-persist validation must fail");
assert!(error.to_string().contains("injected post-persistence"));
assert_directory_empty(&credentials);
let replacement_credentials = root.join("replacement-credentials");
let replacement_store = open_owned_credentials_directory(&replacement_credentials)
.expect("open replacement store");
let replacement_path = replacement_credentials.join(generation);
replacement_store
.write(generation, b"prior credential bytes", false)
.expect("seed prior generation");
fail_next_windows_post_persist_validation(&replacement_path);
let error = replacement_store
.write(generation, b"replacement credential bytes", true)
.expect_err("replacement validation must fail");
assert!(error.to_string().contains("injected post-persistence"));
assert!(
!replacement_path.exists(),
"a rejected replacement deliberately fails closed instead of restoring by path"
);
assert_directory_empty(&replacement_credentials);
}
#[cfg(windows)]
#[test]
fn windows_store_secures_every_new_owned_object_for_the_current_user() {
let root = tempfile::tempdir().expect("temp dir");
let root = root.path().canonicalize().expect("canonical temp root");
let credentials = root.join("credentials");
let store = open_owned_credentials_directory(&credentials).expect("open secure store");
let directory = store
._component_handles
.last()
.expect("final credentials directory handle");
verify_windows_owner_only_handle(directory).expect("current-user-only directory");
let lock = store.open_lock_file().expect("create lifecycle lock");
validate_owned_file_handle(&lock, &credentials.join(XAI_OAUTH_LIFECYCLE_LOCK_FILE_NAME))
.expect("current-user-only lifecycle lock");
let generation = "xai-auth-0123456789abcdef0123456789abcdef.json";
store
.write(generation, b"credential bytes", false)
.expect("write secure generation");
let generation_file = store
.open_owned_file_for_read(generation)
.expect("open generation")
.expect("generation exists");
validate_owned_file_handle(&generation_file, &credentials.join(generation))
.expect("current-user-only generation");
}
#[cfg(windows)]
#[test]
fn windows_store_rejects_reparse_component_and_hardlinked_leaf() {
let root = tempfile::tempdir().expect("temp dir");
let root = root.path().canonicalize().expect("canonical temp root");
let real = root.join("real-home");
fs::create_dir(&real).expect("real home");
let linked = root.join("linked-home");
if std::os::windows::fs::symlink_dir(&real, &linked).is_ok() {
assert!(
open_owned_credentials_directory(&linked.join("credentials")).is_err(),
"owned roots must reject junctions and directory reparse points"
);
}
let credentials = real.join("credentials");
let store = open_owned_credentials_directory(&credentials).expect("safe store");
let generation = "xai-auth-fedcba9876543210fedcba9876543210.json";
store
.write(generation, b"secret", false)
.expect("seed generation");
fs::hard_link(
credentials.join(generation),
credentials.join("attacker-hardlink"),
)
.expect("hardlink fixture");
assert!(
store.read_to_string(generation).is_err(),
"owned reads must reject multiply-linked credential files"
);
}
}