mirror of
https://github.com/openai/codex.git
synced 2026-11-04 00:24:43 +00:00
fix: use AbsolutePathBuf for permission profile file roots (#12970)
## Why `PermissionProfile` should describe filesystem roots as absolute paths at the type level. Using `PathBuf` in `FileSystemPermissions` made the shared type too permissive and blurred together three different deserialization cases: - skill metadata in `agents/openai.yaml`, where relative paths should resolve against the skill directory - app-server API payloads, where callers should have to send absolute paths - local tool-call payloads for commands like `shell_command` and `exec_command`, where `additional_permissions.file_system` may legitimately be relative to the command `workdir` This change tightens the shared model without regressing the existing local command flow. ## What Changed - changed `protocol::models::FileSystemPermissions` and the app-server `AdditionalFileSystemPermissions` mirror to use `AbsolutePathBuf` - wrapped skill metadata deserialization in `AbsolutePathBufGuard`, so relative permission roots in `agents/openai.yaml` resolve against the containing skill directory - kept app-server/API deserialization strict, so relative `additionalPermissions.fileSystem.*` paths are rejected at the boundary - restored cwd/workdir-relative deserialization for local tool-call payloads by parsing `shell`, `shell_command`, and `exec_command` arguments under an `AbsolutePathBufGuard` rooted at the resolved command working directory - simplified runtime additional-permission normalization so it only canonicalizes and deduplicates absolute roots instead of trying to recover relative ones later - updated the app-server schema fixtures, `app-server/README.md`, and the affected transport/TUI tests to match the final behavior
This commit is contained in:
1 parent
8cf5b00aef
commit
d09a7535ed
22 files changed
+384
-191
No files matched your search
@@ -92,8 +92,6 @@ pub enum SandboxPreference {
|
||||
pub(crate) enum SandboxTransformError {
|
||||
#[error("missing codex-linux-sandbox executable path")]
|
||||
MissingLinuxSandboxExecutable,
|
||||
#[error("invalid additional permissions path: {0}")]
|
||||
InvalidAdditionalPermissionsPath(String),
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
#[error("seatbelt sandbox is only available on macOS")]
|
||||
SeatbeltUnavailable,
|
||||
@@ -101,19 +99,16 @@ pub(crate) enum SandboxTransformError {
|
||||
|
||||
pub(crate) fn normalize_additional_permissions(
|
||||
additional_permissions: PermissionProfile,
|
||||
command_cwd: &Path,
|
||||
) -> Result<PermissionProfile, String> {
|
||||
let Some(file_system) = additional_permissions.file_system else {
|
||||
return Ok(PermissionProfile::default());
|
||||
};
|
||||
let read = file_system
|
||||
.read
|
||||
.map(|paths| normalize_permission_paths(paths, command_cwd, "file_system.read"))
|
||||
.transpose()?;
|
||||
.map(|paths| normalize_permission_paths(paths, "file_system.read"));
|
||||
let write = file_system
|
||||
.write
|
||||
.map(|paths| normalize_permission_paths(paths, command_cwd, "file_system.write"))
|
||||
.transpose()?;
|
||||
.map(|paths| normalize_permission_paths(paths, "file_system.write"));
|
||||
Ok(PermissionProfile {
|
||||
file_system: Some(FileSystemPermissions { read, write }),
|
||||
..Default::default()
|
||||
@@ -121,48 +116,25 @@ pub(crate) fn normalize_additional_permissions(
|
||||
}
|
||||
|
||||
fn normalize_permission_paths(
|
||||
paths: Vec<PathBuf>,
|
||||
command_cwd: &Path,
|
||||
permission_kind: &str,
|
||||
) -> Result<Vec<PathBuf>, String> {
|
||||
paths: Vec<AbsolutePathBuf>,
|
||||
_permission_kind: &str,
|
||||
) -> Vec<AbsolutePathBuf> {
|
||||
let mut out = Vec::with_capacity(paths.len());
|
||||
let mut seen = HashSet::new();
|
||||
|
||||
for path in paths {
|
||||
if path.as_os_str().is_empty() {
|
||||
return Err(format!("{permission_kind} contains an empty path"));
|
||||
}
|
||||
|
||||
let resolved = if path.is_absolute() {
|
||||
AbsolutePathBuf::from_absolute_path(path.clone()).map_err(|err| {
|
||||
format!(
|
||||
"{permission_kind} path `{}` is invalid: {err}",
|
||||
path.display()
|
||||
)
|
||||
})?
|
||||
} else {
|
||||
AbsolutePathBuf::resolve_path_against_base(&path, command_cwd).map_err(|err| {
|
||||
format!(
|
||||
"{permission_kind} path `{}` cannot be resolved against cwd `{}`: {err}",
|
||||
path.display(),
|
||||
command_cwd.display()
|
||||
)
|
||||
})?
|
||||
};
|
||||
|
||||
let canonicalized = resolved
|
||||
let canonicalized = path
|
||||
.as_path()
|
||||
.canonicalize()
|
||||
.ok()
|
||||
.and_then(|path| AbsolutePathBuf::from_absolute_path(path).ok())
|
||||
.unwrap_or(resolved);
|
||||
let canonicalized = canonicalized.to_path_buf();
|
||||
.unwrap_or(path);
|
||||
if seen.insert(canonicalized.clone()) {
|
||||
out.push(canonicalized);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(out)
|
||||
out
|
||||
}
|
||||
|
||||
fn dedup_absolute_paths(paths: Vec<AbsolutePathBuf>) -> Vec<AbsolutePathBuf> {
|
||||
@@ -178,37 +150,23 @@ fn dedup_absolute_paths(paths: Vec<AbsolutePathBuf>) -> Vec<AbsolutePathBuf> {
|
||||
|
||||
fn additional_permission_roots(
|
||||
additional_permissions: &PermissionProfile,
|
||||
) -> Result<(Vec<AbsolutePathBuf>, Vec<AbsolutePathBuf>), SandboxTransformError> {
|
||||
let to_abs = |paths: &[PathBuf]| {
|
||||
let mut out = Vec::with_capacity(paths.len());
|
||||
for path in paths {
|
||||
let absolute = AbsolutePathBuf::from_absolute_path(path.clone()).map_err(|err| {
|
||||
SandboxTransformError::InvalidAdditionalPermissionsPath(format!(
|
||||
"`{}`: {err}",
|
||||
path.display()
|
||||
))
|
||||
})?;
|
||||
out.push(absolute);
|
||||
}
|
||||
Ok(dedup_absolute_paths(out))
|
||||
};
|
||||
|
||||
Ok((
|
||||
to_abs(
|
||||
) -> (Vec<AbsolutePathBuf>, Vec<AbsolutePathBuf>) {
|
||||
(
|
||||
dedup_absolute_paths(
|
||||
additional_permissions
|
||||
.file_system
|
||||
.as_ref()
|
||||
.and_then(|file_system| file_system.read.as_deref())
|
||||
.and_then(|file_system| file_system.read.clone())
|
||||
.unwrap_or_default(),
|
||||
)?,
|
||||
to_abs(
|
||||
),
|
||||
dedup_absolute_paths(
|
||||
additional_permissions
|
||||
.file_system
|
||||
.as_ref()
|
||||
.and_then(|file_system| file_system.write.as_deref())
|
||||
.and_then(|file_system| file_system.write.clone())
|
||||
.unwrap_or_default(),
|
||||
)?,
|
||||
))
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fn merge_read_only_access_with_additional_reads(
|
||||
@@ -239,7 +197,7 @@ fn sandbox_policy_with_additional_permissions(
|
||||
return Ok(sandbox_policy.clone());
|
||||
}
|
||||
|
||||
let (extra_reads, extra_writes) = additional_permission_roots(additional_permissions)?;
|
||||
let (extra_reads, extra_writes) = additional_permission_roots(additional_permissions);
|
||||
|
||||
let policy = match sandbox_policy {
|
||||
SandboxPolicy::DangerFullAccess | SandboxPolicy::ExternalSandbox { .. } => {
|
||||
|
||||
Reference in new issue
Block a user