File size: 5,660 Bytes
afa0cbf | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 | //! Executor-context policy operations that never consult controller paths or environment.
use super::FileSystemPath;
use super::FileSystemSandboxPolicy;
use super::FileSystemSandboxPolicyContext;
use super::FileSystemSpecialPath;
use super::InvalidDenyReadGlobBehavior;
use super::ReadDenyMatcher;
use super::deny_read_validator::DenyReadValidator;
use super::deny_read_validator::has_context_read_denials;
use codex_utils_path_uri::PathConvention;
use codex_utils_path_uri::PathUri;
const INVALID_PATH: &str = "managed filesystem denial cannot be materialized safely";
const SHADOWED: &str = "managed filesystem denial is shadowed by a selected profile grant";
impl ReadDenyMatcher {
/// Builds a strict matcher using only the supplied execution-host paths.
pub fn try_new_with_context(
policy: &FileSystemSandboxPolicy,
context: &FileSystemSandboxPolicyContext<'_>,
) -> Result<Option<Self>, String> {
if !has_context_read_denials(policy, context) {
return Ok(None);
}
validate_context_paths(policy, context)?;
let prepared =
policy.prepare_deny_read_matcher(context, InvalidDenyReadGlobBehavior::ReturnError)?;
Ok(Some(Self {
native_cwd: None,
user_home_dir: None,
temporary_directories: Vec::new(),
prepared,
}))
}
}
impl FileSystemSandboxPolicy {
/// Checks exact-root read access using paths owned by the execution host.
/// Deny glob enforcement is checked separately with [`ReadDenyMatcher`].
pub fn can_read_path(
&self,
path: &PathUri,
context: &FileSystemSandboxPolicyContext<'_>,
) -> bool {
self.resolve_access(path, context).can_read()
}
/// Preserves workspace-root symbols and adds their executor-owned concrete paths.
/// Legacy home-relative workspace denials clear all grants when their target is unknown.
pub fn with_materialized_project_roots_for_path_uris(mut self, roots: &[PathUri]) -> Self {
let Some(materialized) = self
.clone()
.try_materialize_project_roots_with_path_uris(roots)
else {
return Self::restricted(Vec::new());
};
for entry in materialized.entries {
if !self.entries.contains(&entry) {
self.entries.push(entry);
}
}
self
}
/// Builds workspace-write defaults without inspecting the execution host's filesystem.
pub fn workspace_write_with_path_uris(
roots: &[PathUri],
exclude_tmpdir_env_var: bool,
exclude_slash_tmp: bool,
) -> Self {
Self::workspace_write(&[], exclude_tmpdir_env_var, exclude_slash_tmp)
.with_materialized_project_roots_for_path_uris(roots)
}
/// Resolves deny glob text using the execution host's convention and home.
pub fn get_unreadable_globs_with_context(
&self,
context: &FileSystemSandboxPolicyContext<'_>,
) -> Result<Vec<String>, String> {
self.deny_read_globs(context)
}
/// Checks the existing Core rules for required entries and readable concrete paths.
///
/// Sandbox-mode selection and native glob expansion remain caller-owned.
pub fn validate_managed_deny_read(
&self,
required: &Self,
context: &FileSystemSandboxPolicyContext<'_>,
) -> Result<(), String> {
DenyReadValidator::new(required, context)?
.validate(self, context)
.map_err(|_| SHADOWED.to_string())
}
}
fn validate_context_paths(
policy: &FileSystemSandboxPolicy,
context: &FileSystemSandboxPolicyContext<'_>,
) -> Result<(), String> {
let convention = context.cwd.infer_path_convention().ok_or(INVALID_PATH)?;
let valid = |path: &PathUri| {
path.infer_path_convention() == Some(convention) && path.lexical_depth().is_some()
};
if !valid(context.cwd) || context.workspace_roots.iter().any(|path| !valid(path)) {
return Err(INVALID_PATH.into());
}
for entry in &policy.entries {
match &entry.path {
FileSystemPath::Path { path } if !valid(path) => return Err(INVALID_PATH.into()),
FileSystemPath::Special {
value: FileSystemSpecialPath::Tmpdir,
} => {
let directories = context.temporary_directories.ok_or(
"executor temporary-directory metadata is unavailable for managed filesystem denials",
)?;
if directories.iter().any(|path| !valid(path)) {
return Err(
"executor temporary-directory metadata cannot be materialized safely"
.into(),
);
}
}
FileSystemPath::Special {
value:
FileSystemSpecialPath::ProjectRoots {
subpath: Some(subpath),
},
} if context
.workspace_roots
.iter()
.any(|root| root.join(subpath).is_err()) =>
{
return Err(INVALID_PATH.into());
}
FileSystemPath::GlobPattern { pattern }
if (pattern.starts_with("~/")
|| convention == PathConvention::Windows && pattern.starts_with("~\\"))
&& !context.user_home_dir.is_some_and(valid) =>
{
return Err(INVALID_PATH.into());
}
_ => {}
}
}
Ok(())
}
#[cfg(test)]
#[path = "target_tests.rs"]
mod tests;
|