Merge 92a0893e4f into sapling-pr-archive-bolinfest

This commit is contained in:
Michael Bolin
2026-05-20 12:30:17 -07:00
committed by GitHub
5 changed files with 102 additions and 69 deletions
+1
View File
@@ -2321,6 +2321,7 @@ dependencies = [
"codex-login",
"codex-otel",
"codex-protocol",
"codex-utils-absolute-path",
"hmac",
"pretty_assertions",
"serde",
+1
View File
@@ -17,6 +17,7 @@ codex-core = { workspace = true }
codex-login = { workspace = true }
codex-otel = { workspace = true }
codex-protocol = { workspace = true }
codex-utils-absolute-path = { workspace = true }
hmac = "0.12.1"
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
+76 -25
View File
@@ -25,11 +25,13 @@ use codex_login::AuthManager;
use codex_login::CodexAuth;
use codex_login::RefreshTokenError;
use codex_protocol::account::PlanType;
use codex_utils_absolute_path::AbsolutePathBufGuard;
use hmac::Hmac;
use hmac::Mac;
use serde::Deserialize;
use serde::Serialize;
use sha2::Sha256;
use std::path::Path;
use std::path::PathBuf;
use std::sync::Arc;
use std::sync::Mutex;
@@ -138,10 +140,12 @@ struct CloudRequirementsCacheSignedPayload {
}
impl CloudRequirementsCacheSignedPayload {
fn requirements(&self) -> Option<ConfigRequirementsToml> {
self.contents
.as_deref()
.and_then(|contents| parse_cloud_requirements(contents).ok().flatten())
fn requirements(&self, requirements_base_dir: &Path) -> Option<ConfigRequirementsToml> {
self.contents.as_deref().and_then(|contents| {
parse_cloud_requirements(contents, requirements_base_dir)
.ok()
.flatten()
})
}
}
fn sign_cache_payload(payload_bytes: &[u8]) -> Option<String> {
@@ -258,6 +262,7 @@ impl RequirementsFetcher for BackendRequirementsFetcher {
struct CloudRequirementsService {
auth_manager: Arc<AuthManager>,
fetcher: Arc<dyn RequirementsFetcher>,
requirements_base_dir: PathBuf,
cache_path: PathBuf,
timeout: Duration,
}
@@ -272,6 +277,7 @@ impl CloudRequirementsService {
Self {
auth_manager,
fetcher,
requirements_base_dir: codex_home.clone(),
cache_path: codex_home.join(CLOUD_REQUIREMENTS_CACHE_FILENAME),
timeout,
}
@@ -351,7 +357,7 @@ impl CloudRequirementsService {
path = %self.cache_path.display(),
"Using cached cloud requirements"
);
return Ok(signed_payload.requirements());
return Ok(signed_payload.requirements(&self.requirements_base_dir));
}
Err(cache_load_status) => {
self.log_cache_load_status(&cache_load_status);
@@ -482,24 +488,26 @@ impl CloudRequirementsService {
};
let requirements = match contents.as_deref() {
Some(contents) => match parse_cloud_requirements(contents) {
Ok(requirements) => requirements,
Err(err) => {
tracing::error!(error = %err, "Failed to parse cloud requirements");
emit_fetch_final_metric(
trigger,
"error",
"parse_error",
attempt,
last_status_code,
);
return Err(CloudRequirementsLoadError::new(
CloudRequirementsLoadErrorCode::Parse,
/*status_code*/ None,
format_cloud_requirements_parse_failed_message(contents, &err),
));
Some(contents) => {
match parse_cloud_requirements(contents, &self.requirements_base_dir) {
Ok(requirements) => requirements,
Err(err) => {
tracing::error!(error = %err, "Failed to parse cloud requirements");
emit_fetch_final_metric(
trigger,
"error",
"parse_error",
attempt,
last_status_code,
);
return Err(CloudRequirementsLoadError::new(
CloudRequirementsLoadErrorCode::Parse,
/*status_code*/ None,
format_cloud_requirements_parse_failed_message(contents, &err),
));
}
}
},
}
None => None,
};
@@ -738,11 +746,13 @@ pub async fn cloud_requirements_loader_for_storage(
fn parse_cloud_requirements(
contents: &str,
requirements_base_dir: &Path,
) -> Result<Option<ConfigRequirementsToml>, toml::de::Error> {
if contents.trim().is_empty() {
return Ok(None);
}
let _guard = AbsolutePathBufGuard::new(requirements_base_dir);
let requirements: ConfigRequirementsToml = toml::from_str(contents)?;
if requirements.is_empty() {
Ok(None)
@@ -1038,7 +1048,11 @@ mod tests {
}
fn parse_for_fetch(contents: Option<&str>) -> Option<ConfigRequirementsToml> {
contents.and_then(|contents| parse_cloud_requirements(contents).ok().flatten())
contents.and_then(|contents| {
parse_cloud_requirements(contents, &std::env::temp_dir())
.ok()
.flatten()
})
}
fn request_error() -> FetchAttemptError {
@@ -1391,6 +1405,35 @@ mod tests {
);
}
#[tokio::test]
async fn fetch_cloud_requirements_resolves_relative_deny_read_globs_from_codex_home() {
let codex_home = tempdir().expect("tempdir");
let service = CloudRequirementsService::new(
auth_manager_with_plan("enterprise").await,
Arc::new(StaticFetcher {
contents: Some(
r#"
[permissions.filesystem]
deny_read = ["./sensitive/**/*.txt"]
"#
.to_string(),
),
}),
codex_home.path().to_path_buf(),
CLOUD_REQUIREMENTS_TIMEOUT,
);
let deny_read = format!("{}/sensitive/**/*.txt", codex_home.path().display());
let expected = toml::from_str::<ConfigRequirementsToml>(&format!(
r#"
[permissions.filesystem]
deny_read = [{deny_read:?}]
"#
))
.expect("parse expected cloud requirements");
assert_eq!(service.fetch().await, Ok(Some(expected)));
}
#[tokio::test]
async fn fetch_cloud_requirements_parses_apps_requirements_toml() {
let result = parse_for_fetch(Some(
@@ -2234,7 +2277,11 @@ command = "sample-mcp"
.signed_payload
.contents
.as_deref()
.and_then(|contents| parse_cloud_requirements(contents).ok().flatten()),
.and_then(|contents| {
parse_cloud_requirements(contents, codex_home.path())
.ok()
.flatten()
}),
Some(ConfigRequirementsToml {
allowed_approval_policies: Some(vec![AskForApproval::Never]),
allowed_approvals_reviewers: None,
@@ -2358,7 +2405,11 @@ command = "sample-mcp"
.signed_payload
.contents
.as_deref()
.and_then(|contents| parse_cloud_requirements(contents).ok().flatten()),
.and_then(|contents| {
parse_cloud_requirements(contents, codex_home.path())
.ok()
.flatten()
}),
Some(ConfigRequirementsToml {
allowed_approval_policies: Some(vec![AskForApproval::OnRequest]),
allowed_approvals_reviewers: None,
@@ -16,8 +16,8 @@ use crate::logging::log_note;
use crate::sandbox_bin_dir;
const DEV_BUILD_VERSION_SENTINEL: &str = "0.0.0";
const BIN_DIRNAME: &str = "bin";
const RESOURCES_DIRNAME: &str = "codex-resources";
pub(crate) const BIN_DIRNAME: &str = "bin";
pub(crate) const RESOURCES_DIRNAME: &str = "codex-resources";
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub(crate) enum HelperExecutable {
@@ -52,7 +52,7 @@ pub(crate) fn helper_bin_dir(codex_home: &Path) -> PathBuf {
pub(crate) fn legacy_lookup(kind: HelperExecutable) -> PathBuf {
if let Ok(exe) = std::env::current_exe()
&& let Some(candidate) = source_path_for_exe(&exe, kind.file_name())
&& let Some(candidate) = bundled_executable_path_for_exe(&exe, kind.file_name())
{
return candidate;
}
@@ -179,7 +179,7 @@ fn store_helper_path(cache_key: String, path: PathBuf) {
fn sibling_source_path(kind: HelperExecutable) -> Result<PathBuf> {
let exe = std::env::current_exe().context("resolve current executable for helper lookup")?;
source_path_for_exe(&exe, kind.file_name()).ok_or_else(|| {
bundled_executable_path_for_exe(&exe, kind.file_name()).ok_or_else(|| {
anyhow!(
"helper not found next to current executable or under {RESOURCES_DIRNAME}: {}",
exe.display()
@@ -187,7 +187,7 @@ fn sibling_source_path(kind: HelperExecutable) -> Result<PathBuf> {
})
}
fn source_path_for_exe(exe: &Path, file_name: &str) -> Option<PathBuf> {
pub(crate) fn bundled_executable_path_for_exe(exe: &Path, file_name: &str) -> Option<PathBuf> {
let dir = exe.parent()?;
let direct_candidate = dir.join(file_name);
if direct_candidate.is_file() {
@@ -361,13 +361,13 @@ mod tests {
use super::DEV_BUILD_VERSION_SENTINEL;
use super::HelperExecutable;
use super::RESOURCES_DIRNAME;
use super::bundled_executable_path_for_exe;
use super::copy_from_source_if_needed;
use super::destination_is_fresh;
use super::dev_build_suffix;
use super::helper_bin_dir;
use super::helper_version_suffix;
use super::materialized_file_name;
use super::source_path_for_exe;
use pretty_assertions::assert_eq;
use std::fs;
use std::path::Path;
@@ -469,8 +469,9 @@ mod tests {
fs::write(&exe, b"codex").expect("write exe");
fs::write(&helper, b"runner").expect("write helper");
let resolved = source_path_for_exe(&exe, /*file_name*/ "codex-command-runner.exe")
.expect("helper path");
let resolved =
bundled_executable_path_for_exe(&exe, /*file_name*/ "codex-command-runner.exe")
.expect("helper path");
assert_eq!(resolved, helper);
}
@@ -488,8 +489,9 @@ mod tests {
fs::write(&exe, b"codex").expect("write exe");
fs::write(&helper, b"runner").expect("write helper");
let resolved = source_path_for_exe(&exe, /*file_name*/ "codex-command-runner.exe")
.expect("helper path");
let resolved =
bundled_executable_path_for_exe(&exe, /*file_name*/ "codex-command-runner.exe")
.expect("helper path");
assert_eq!(resolved, helper);
}
@@ -510,8 +512,9 @@ mod tests {
fs::write(&package_helper, b"package runner").expect("write package helper");
fs::write(&bin_helper, b"bin runner").expect("write bin helper");
let resolved = source_path_for_exe(&exe, /*file_name*/ "codex-command-runner.exe")
.expect("helper path");
let resolved =
bundled_executable_path_for_exe(&exe, /*file_name*/ "codex-command-runner.exe")
.expect("helper path");
assert_eq!(resolved, package_helper);
}
@@ -529,8 +532,9 @@ mod tests {
fs::write(&sibling_helper, b"sibling runner").expect("write sibling helper");
fs::write(&resource_helper, b"resource runner").expect("write resource helper");
let resolved = source_path_for_exe(&exe, /*file_name*/ "codex-command-runner.exe")
.expect("helper path");
let resolved =
bundled_executable_path_for_exe(&exe, /*file_name*/ "codex-command-runner.exe")
.expect("helper path");
assert_eq!(resolved, sibling_helper);
}
+6 -30
View File
@@ -3,7 +3,6 @@ use serde::Serialize;
use std::collections::BTreeSet;
use std::collections::HashMap;
use std::collections::HashSet;
use std::ffi::OsStr;
use std::ffi::c_void;
use std::os::windows::process::CommandExt;
use std::path::Path;
@@ -13,6 +12,7 @@ use std::process::Stdio;
use crate::allow::AllowDenyPaths;
use crate::allow::compute_allow_paths;
use crate::helper_materialization::bundled_executable_path_for_exe;
use crate::helper_materialization::helper_bin_dir;
use crate::logging::log_note;
use crate::path_normalization::canonical_path_key;
@@ -40,11 +40,10 @@ use windows_sys::Win32::Security::SECURITY_NT_AUTHORITY;
pub const SETUP_VERSION: u32 = 5;
pub const OFFLINE_USERNAME: &str = "CodexSandboxOffline";
pub const ONLINE_USERNAME: &str = "CodexSandboxOnline";
const BIN_DIRNAME: &str = "bin";
const ERROR_CANCELLED: u32 = 1223;
const SECURITY_BUILTIN_DOMAIN_RID: u32 = 0x0000_0020;
const DOMAIN_ALIAS_RID_ADMINS: u32 = 0x0000_0220;
const RESOURCES_DIRNAME: &str = "codex-resources";
const SETUP_EXE_FILENAME: &str = "codex-windows-sandbox-setup.exe";
const USERPROFILE_ROOT_EXCLUSIONS: &[&str] = &[
".ssh",
".tsh",
@@ -586,34 +585,11 @@ fn find_setup_exe() -> PathBuf {
{
return setup_exe;
}
PathBuf::from("codex-windows-sandbox-setup.exe")
PathBuf::from(SETUP_EXE_FILENAME)
}
fn find_setup_exe_for_current_exe(exe: &Path) -> Option<PathBuf> {
let dir = exe.parent()?;
let candidate = dir.join("codex-windows-sandbox-setup.exe");
if candidate.is_file() {
return Some(candidate);
}
if dir.file_name() == Some(OsStr::new(BIN_DIRNAME))
&& let Some(package_dir) = dir.parent()
{
let package_resource_candidate = package_dir
.join(RESOURCES_DIRNAME)
.join("codex-windows-sandbox-setup.exe");
if package_resource_candidate.is_file() {
return Some(package_resource_candidate);
}
}
// Older standalone installs keep Windows helper binaries under
// `codex-resources/` next to `codex.exe`, so elevation still probes that
// sibling folder before falling back to PATH.
let resource_candidate = dir
.join(RESOURCES_DIRNAME)
.join("codex-windows-sandbox-setup.exe");
resource_candidate.is_file().then_some(resource_candidate)
bundled_executable_path_for_exe(exe, SETUP_EXE_FILENAME)
}
fn report_helper_failure(
@@ -976,8 +952,6 @@ fn filter_sensitive_write_roots(mut roots: Vec<PathBuf>, codex_home: &Path) -> V
#[cfg(test)]
mod tests {
use super::BIN_DIRNAME;
use super::RESOURCES_DIRNAME;
use super::WINDOWS_PLATFORM_DEFAULT_READ_ROOTS;
use super::build_payload_roots;
use super::find_setup_exe_for_current_exe;
@@ -987,6 +961,8 @@ mod tests {
use super::offline_proxy_settings_from_env;
use super::profile_read_roots;
use super::proxy_ports_from_env;
use crate::helper_materialization::BIN_DIRNAME;
use crate::helper_materialization::RESOURCES_DIRNAME;
use crate::helper_materialization::helper_bin_dir;
use crate::policy::SandboxPolicy;
use codex_utils_absolute_path::AbsolutePathBuf;