mirror of
https://github.com/openai/codex.git
synced 2026-11-15 21:50:46 +00:00
Preserve legacy sandbox templates in TUI permission conversions (#60936)
## What changed Add lossless conversions between legacy sandbox templates and `PermissionProfile` without probing workspace metadata, collapsing fixed roots into current-directory access, or deduplicating writable roots. Reject profiles whose restrictions cannot survive the round trip. Use these conversions for sandbox inheritance in `create_thread` and for validating read-only temporary structured threads. ## Testing Extend coverage for duplicate writable roots, existing `.git` metadata, unrepresentable restrictions, and child threads inheriting writable roots, network access, and temporary-directory exclusions. GitOrigin-RevId: cb37acaf615b09f45680b4b0c4152470fbc04abb
This commit is contained in:
@@ -21,6 +21,7 @@ use crate::permissions::FileSystemSandboxEntry;
|
||||
use crate::permissions::FileSystemSandboxKind;
|
||||
use crate::permissions::FileSystemSandboxPolicy;
|
||||
use crate::permissions::FileSystemSpecialPath;
|
||||
use crate::permissions::LegacySandboxPolicyProjection;
|
||||
use crate::permissions::NetworkSandboxPolicy;
|
||||
use crate::permissions::RawFileSystemSandboxEntry;
|
||||
use crate::protocol::SandboxPolicy;
|
||||
@@ -662,6 +663,28 @@ impl PermissionProfile {
|
||||
)
|
||||
}
|
||||
|
||||
/// Decodes a portable legacy template without probing workspace metadata.
|
||||
pub fn from_legacy_sandbox_policy_template(sandbox_policy: &SandboxPolicy) -> Self {
|
||||
if let SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots,
|
||||
exclude_tmpdir_env_var,
|
||||
exclude_slash_tmp,
|
||||
..
|
||||
} = sandbox_policy
|
||||
{
|
||||
Self::from_runtime_permissions(
|
||||
&FileSystemSandboxPolicy::workspace_write_template(
|
||||
writable_roots,
|
||||
*exclude_tmpdir_env_var,
|
||||
*exclude_slash_tmp,
|
||||
),
|
||||
NetworkSandboxPolicy::from(sandbox_policy),
|
||||
)
|
||||
} else {
|
||||
Self::from_legacy_sandbox_policy(sandbox_policy)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_legacy_sandbox_policy_for_cwd(sandbox_policy: &SandboxPolicy, cwd: &Path) -> Self {
|
||||
Self::from_runtime_permissions_with_enforcement(
|
||||
SandboxEnforcement::from_legacy_sandbox_policy(sandbox_policy),
|
||||
@@ -736,6 +759,23 @@ impl PermissionProfile {
|
||||
}
|
||||
}
|
||||
|
||||
/// Encodes an unresolved template without collapsing fixed roots into cwd access.
|
||||
pub fn to_legacy_sandbox_policy_template(&self) -> io::Result<SandboxPolicy> {
|
||||
let policy = self
|
||||
.file_system_sandbox_policy()
|
||||
.to_legacy_sandbox_policy_with_projection(
|
||||
self.network_sandbox_policy(),
|
||||
LegacySandboxPolicyProjection::Template,
|
||||
)?;
|
||||
if Self::from_legacy_sandbox_policy_template(&policy) != *self {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"permission profile cannot be represented by a legacy sandbox template",
|
||||
));
|
||||
}
|
||||
Ok(policy)
|
||||
}
|
||||
|
||||
pub fn to_runtime_permissions(&self) -> (FileSystemSandboxPolicy, NetworkSandboxPolicy) {
|
||||
(
|
||||
self.file_system_sandbox_policy(),
|
||||
|
||||
@@ -40,6 +40,13 @@ pub use local_aliases::LocalFileSystemPolicyMatcher;
|
||||
pub use windows_glob::WindowsDenyReadGlobScan;
|
||||
pub use windows_glob::windows_deny_read_glob_scan;
|
||||
|
||||
/// Selects native execution projection or an unresolved, lossless transport template.
|
||||
#[derive(Clone, Copy)]
|
||||
pub(crate) enum LegacySandboxPolicyProjection<'a> {
|
||||
Cwd(&'a Path),
|
||||
Template,
|
||||
}
|
||||
|
||||
const PROTECTED_METADATA_GIT_PATH_NAME: &str = ".git";
|
||||
const PROTECTED_METADATA_AGENTS_PATH_NAME: &str = ".agents";
|
||||
const PROTECTED_METADATA_CODEX_PATH_NAME: &str = ".codex";
|
||||
@@ -805,6 +812,32 @@ impl FileSystemSandboxPolicy {
|
||||
writable_roots: &[AbsolutePathBuf],
|
||||
exclude_tmpdir_env_var: bool,
|
||||
exclude_slash_tmp: bool,
|
||||
) -> Self {
|
||||
let mut policy = Self::workspace_write_template(
|
||||
writable_roots,
|
||||
exclude_tmpdir_env_var,
|
||||
exclude_slash_tmp,
|
||||
);
|
||||
for writable_root in writable_roots {
|
||||
for protected_path in default_read_only_subpaths_for_writable_root(
|
||||
writable_root,
|
||||
/*protect_missing_dot_codex*/ false,
|
||||
) {
|
||||
append_default_read_only_path_if_no_explicit_rule(
|
||||
&mut policy.entries,
|
||||
protected_path,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
policy
|
||||
}
|
||||
|
||||
/// Builds an unresolved template; admission must bind native workspace metadata.
|
||||
pub(crate) fn workspace_write_template(
|
||||
writable_roots: &[AbsolutePathBuf],
|
||||
exclude_tmpdir_env_var: bool,
|
||||
exclude_slash_tmp: bool,
|
||||
) -> Self {
|
||||
let mut entries = vec![FileSystemSandboxEntry::new(
|
||||
FileSystemPath::Special {
|
||||
@@ -846,16 +879,7 @@ impl FileSystemSandboxPolicy {
|
||||
append_default_read_only_project_root_subpath_if_no_explicit_rule(&mut entries, ".agents");
|
||||
append_default_read_only_project_root_subpath_if_no_explicit_rule(&mut entries, ".codex");
|
||||
append_default_read_only_project_root_subpath_if_no_explicit_rule(&mut entries, ".aws");
|
||||
for writable_root in writable_roots {
|
||||
for protected_path in default_read_only_subpaths_for_writable_root(
|
||||
writable_root,
|
||||
/*protect_missing_dot_codex*/ false,
|
||||
) {
|
||||
append_default_read_only_path_if_no_explicit_rule(&mut entries, protected_path);
|
||||
}
|
||||
}
|
||||
|
||||
FileSystemSandboxPolicy::restricted(entries)
|
||||
Self::restricted(entries)
|
||||
}
|
||||
|
||||
/// Converts a legacy sandbox policy into an equivalent filesystem policy
|
||||
@@ -1899,6 +1923,17 @@ impl FileSystemSandboxPolicy {
|
||||
&self,
|
||||
network_policy: NetworkSandboxPolicy,
|
||||
cwd: &Path,
|
||||
) -> io::Result<SandboxPolicy> {
|
||||
self.to_legacy_sandbox_policy_with_projection(
|
||||
network_policy,
|
||||
LegacySandboxPolicyProjection::Cwd(cwd),
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn to_legacy_sandbox_policy_with_projection(
|
||||
&self,
|
||||
network_policy: NetworkSandboxPolicy,
|
||||
projection: LegacySandboxPolicyProjection<'_>,
|
||||
) -> io::Result<SandboxPolicy> {
|
||||
Ok(match self.kind {
|
||||
FileSystemSandboxKind::ExternalSandbox => SandboxPolicy::ExternalSandbox {
|
||||
@@ -1918,7 +1953,12 @@ impl FileSystemSandboxPolicy {
|
||||
}
|
||||
}
|
||||
FileSystemSandboxKind::Restricted => {
|
||||
let cwd_absolute = AbsolutePathBuf::from_absolute_path(cwd).ok();
|
||||
let cwd = match projection {
|
||||
LegacySandboxPolicyProjection::Cwd(cwd) => Some(cwd),
|
||||
LegacySandboxPolicyProjection::Template => None,
|
||||
};
|
||||
let cwd_absolute =
|
||||
cwd.and_then(|cwd| AbsolutePathBuf::from_absolute_path(cwd).ok());
|
||||
let has_full_disk_write_access = self.has_full_disk_write_access();
|
||||
let mut workspace_root_writable = false;
|
||||
let mut writable_roots = Vec::new();
|
||||
@@ -1976,15 +2016,18 @@ impl FileSystemSandboxPolicy {
|
||||
|
||||
if workspace_root_writable {
|
||||
SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots: dedup_absolute_paths(
|
||||
writable_roots,
|
||||
/*normalize_effective_paths*/ false,
|
||||
),
|
||||
writable_roots: match projection {
|
||||
LegacySandboxPolicyProjection::Cwd(_) => dedup_absolute_paths(
|
||||
writable_roots,
|
||||
/*normalize_effective_paths*/ false,
|
||||
),
|
||||
LegacySandboxPolicyProjection::Template => writable_roots,
|
||||
},
|
||||
network_access: network_policy.is_enabled(),
|
||||
exclude_tmpdir_env_var: !tmpdir_writable,
|
||||
exclude_slash_tmp: !slash_tmp_writable,
|
||||
}
|
||||
} else if self.has_legacy_write_intent(cwd) {
|
||||
} else if cwd.is_some_and(|cwd| self.has_legacy_write_intent(cwd)) {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"permissions profile requests filesystem writes outside the workspace root, which is not supported until the runtime enforces FileSystemSandboxPolicy directly",
|
||||
|
||||
Reference in New Issue
Block a user