permissions: remove cwd special path

This commit is contained in:
Michael Bolin
2026-04-27 10:42:51 -07:00
parent a6ca39c630
commit 015d927617
46 changed files with 147 additions and 426 deletions
+1 -16
View File
@@ -1028,21 +1028,6 @@
"title": "MinimalFileSystemSpecialPath",
"type": "object"
},
{
"properties": {
"kind": {
"enum": [
"current_working_directory"
],
"type": "string"
}
},
"required": [
"kind"
],
"title": "CurrentWorkingDirectoryFileSystemSpecialPath",
"type": "object"
},
{
"properties": {
"kind": {
@@ -6124,4 +6109,4 @@
}
],
"title": "ClientRequest"
}
}
@@ -392,21 +392,6 @@
"title": "MinimalFileSystemSpecialPath",
"type": "object"
},
{
"properties": {
"kind": {
"enum": [
"current_working_directory"
],
"type": "string"
}
},
"required": [
"kind"
],
"title": "CurrentWorkingDirectoryFileSystemSpecialPath",
"type": "object"
},
{
"properties": {
"kind": {
@@ -177,21 +177,6 @@
"title": "MinimalFileSystemSpecialPath",
"type": "object"
},
{
"properties": {
"kind": {
"enum": [
"current_working_directory"
],
"type": "string"
}
},
"required": [
"kind"
],
"title": "CurrentWorkingDirectoryFileSystemSpecialPath",
"type": "object"
},
{
"properties": {
"kind": {
@@ -177,21 +177,6 @@
"title": "MinimalFileSystemSpecialPath",
"type": "object"
},
{
"properties": {
"kind": {
"enum": [
"current_working_directory"
],
"type": "string"
}
},
"required": [
"kind"
],
"title": "CurrentWorkingDirectoryFileSystemSpecialPath",
"type": "object"
},
{
"properties": {
"kind": {
@@ -1199,21 +1199,6 @@
"title": "MinimalFileSystemSpecialPath",
"type": "object"
},
{
"properties": {
"kind": {
"enum": [
"current_working_directory"
],
"type": "string"
}
},
"required": [
"kind"
],
"title": "CurrentWorkingDirectoryFileSystemSpecialPath",
"type": "object"
},
{
"properties": {
"kind": {
@@ -731,21 +731,6 @@
"title": "MinimalFileSystemSpecialPath",
"type": "object"
},
{
"properties": {
"kind": {
"enum": [
"current_working_directory"
],
"type": "string"
}
},
"required": [
"kind"
],
"title": "CurrentWorkingDirectoryFileSystemSpecialPath",
"type": "object"
},
{
"properties": {
"kind": {
@@ -8573,21 +8573,6 @@
"title": "MinimalFileSystemSpecialPath",
"type": "object"
},
{
"properties": {
"kind": {
"enum": [
"current_working_directory"
],
"type": "string"
}
},
"required": [
"kind"
],
"title": "CurrentWorkingDirectoryFileSystemSpecialPath",
"type": "object"
},
{
"properties": {
"kind": {
@@ -17752,4 +17737,4 @@
},
"title": "CodexAppServerProtocol",
"type": "object"
}
}
@@ -5092,21 +5092,6 @@
"title": "MinimalFileSystemSpecialPath",
"type": "object"
},
{
"properties": {
"kind": {
"enum": [
"current_working_directory"
],
"type": "string"
}
},
"required": [
"kind"
],
"title": "CurrentWorkingDirectoryFileSystemSpecialPath",
"type": "object"
},
{
"properties": {
"kind": {
@@ -15637,4 +15622,4 @@
},
"title": "CodexAppServerProtocolV2",
"type": "object"
}
}
@@ -146,21 +146,6 @@
"title": "MinimalFileSystemSpecialPath",
"type": "object"
},
{
"properties": {
"kind": {
"enum": [
"current_working_directory"
],
"type": "string"
}
},
"required": [
"kind"
],
"title": "CurrentWorkingDirectoryFileSystemSpecialPath",
"type": "object"
},
{
"properties": {
"kind": {
@@ -184,21 +184,6 @@
"title": "MinimalFileSystemSpecialPath",
"type": "object"
},
{
"properties": {
"kind": {
"enum": [
"current_working_directory"
],
"type": "string"
}
},
"required": [
"kind"
],
"title": "CurrentWorkingDirectoryFileSystemSpecialPath",
"type": "object"
},
{
"properties": {
"kind": {
@@ -177,21 +177,6 @@
"title": "MinimalFileSystemSpecialPath",
"type": "object"
},
{
"properties": {
"kind": {
"enum": [
"current_working_directory"
],
"type": "string"
}
},
"required": [
"kind"
],
"title": "CurrentWorkingDirectoryFileSystemSpecialPath",
"type": "object"
},
{
"properties": {
"kind": {
@@ -922,4 +922,4 @@
],
"title": "RawResponseItemCompletedNotification",
"type": "object"
}
}
@@ -183,21 +183,6 @@
"title": "MinimalFileSystemSpecialPath",
"type": "object"
},
{
"properties": {
"kind": {
"enum": [
"current_working_directory"
],
"type": "string"
}
},
"required": [
"kind"
],
"title": "CurrentWorkingDirectoryFileSystemSpecialPath",
"type": "object"
},
{
"properties": {
"kind": {
@@ -569,21 +569,6 @@
"title": "MinimalFileSystemSpecialPath",
"type": "object"
},
{
"properties": {
"kind": {
"enum": [
"current_working_directory"
],
"type": "string"
}
},
"required": [
"kind"
],
"title": "CurrentWorkingDirectoryFileSystemSpecialPath",
"type": "object"
},
{
"properties": {
"kind": {
@@ -257,21 +257,6 @@
"title": "MinimalFileSystemSpecialPath",
"type": "object"
},
{
"properties": {
"kind": {
"enum": [
"current_working_directory"
],
"type": "string"
}
},
"required": [
"kind"
],
"title": "CurrentWorkingDirectoryFileSystemSpecialPath",
"type": "object"
},
{
"properties": {
"kind": {
@@ -1452,4 +1437,4 @@
],
"title": "ThreadResumeParams",
"type": "object"
}
}
@@ -569,21 +569,6 @@
"title": "MinimalFileSystemSpecialPath",
"type": "object"
},
{
"properties": {
"kind": {
"enum": [
"current_working_directory"
],
"type": "string"
}
},
"required": [
"kind"
],
"title": "CurrentWorkingDirectoryFileSystemSpecialPath",
"type": "object"
},
{
"properties": {
"kind": {
@@ -209,21 +209,6 @@
"title": "MinimalFileSystemSpecialPath",
"type": "object"
},
{
"properties": {
"kind": {
"enum": [
"current_working_directory"
],
"type": "string"
}
},
"required": [
"kind"
],
"title": "CurrentWorkingDirectoryFileSystemSpecialPath",
"type": "object"
},
{
"properties": {
"kind": {
@@ -569,21 +569,6 @@
"title": "MinimalFileSystemSpecialPath",
"type": "object"
},
{
"properties": {
"kind": {
"enum": [
"current_working_directory"
],
"type": "string"
}
},
"required": [
"kind"
],
"title": "CurrentWorkingDirectoryFileSystemSpecialPath",
"type": "object"
},
{
"properties": {
"kind": {
@@ -218,21 +218,6 @@
"title": "MinimalFileSystemSpecialPath",
"type": "object"
},
{
"properties": {
"kind": {
"enum": [
"current_working_directory"
],
"type": "string"
}
},
"required": [
"kind"
],
"title": "CurrentWorkingDirectoryFileSystemSpecialPath",
"type": "object"
},
{
"properties": {
"kind": {
@@ -2,4 +2,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type FileSystemSpecialPath = { "kind": "root" } | { "kind": "minimal" } | { "kind": "current_working_directory" } | { "kind": "project_roots", subpath: string | null, } | { "kind": "tmpdir" } | { "kind": "slash_tmp" } | { "kind": "unknown", path: string, subpath: string | null, };
export type FileSystemSpecialPath = { "kind": "root" } | { "kind": "minimal" } | { "kind": "project_roots", subpath: string | null, } | { "kind": "tmpdir" } | { "kind": "slash_tmp" } | { "kind": "unknown", path: string, subpath: string | null, };
@@ -1434,7 +1434,6 @@ v2_enum_from_core!(
pub enum FileSystemSpecialPath {
Root,
Minimal,
CurrentWorkingDirectory,
ProjectRoots {
subpath: Option<PathBuf>,
},
@@ -1451,7 +1450,6 @@ impl From<CoreFileSystemSpecialPath> for FileSystemSpecialPath {
match value {
CoreFileSystemSpecialPath::Root => Self::Root,
CoreFileSystemSpecialPath::Minimal => Self::Minimal,
CoreFileSystemSpecialPath::CurrentWorkingDirectory => Self::CurrentWorkingDirectory,
CoreFileSystemSpecialPath::ProjectRoots { subpath } => Self::ProjectRoots { subpath },
CoreFileSystemSpecialPath::Tmpdir => Self::Tmpdir,
CoreFileSystemSpecialPath::SlashTmp => Self::SlashTmp,
@@ -1465,7 +1463,6 @@ impl From<FileSystemSpecialPath> for CoreFileSystemSpecialPath {
match value {
FileSystemSpecialPath::Root => Self::Root,
FileSystemSpecialPath::Minimal => Self::Minimal,
FileSystemSpecialPath::CurrentWorkingDirectory => Self::CurrentWorkingDirectory,
FileSystemSpecialPath::ProjectRoots { subpath } => Self::ProjectRoots { subpath },
FileSystemSpecialPath::Tmpdir => Self::Tmpdir,
FileSystemSpecialPath::SlashTmp => Self::SlashTmp,
+2 -2
View File
@@ -909,7 +909,7 @@ Run a standalone command (argv vector) in the server’s sandbox without creatin
"type": "managed",
"fileSystem": { "type": "restricted", "entries": [
{ "path": { "type": "special", "value": { "kind": "root" } }, "access": "read" },
{ "path": { "type": "special", "value": { "kind": "current_working_directory" } }, "access": "write" }
{ "path": { "type": "special", "value": { "kind": "project_roots", "subpath": null } }, "access": "write" }
] },
"network": { "enabled": false }
},
@@ -1255,7 +1255,7 @@ the client can offer session-scoped and/or persistent approval choices.
### Permission requests
The built-in `request_permissions` tool sends an `item/permissions/requestApproval` JSON-RPC request to the client with the requested permission profile. This v2 payload mirrors the command-execution `additionalPermissions` shape: it can request network access and additional filesystem access. The `cwd` field identifies the directory used to resolve cwd-relative permissions such as `:cwd`, `:project_roots`, and relative deny globs.
The built-in `request_permissions` tool sends an `item/permissions/requestApproval` JSON-RPC request to the client with the requested permission profile. This v2 payload mirrors the command-execution `additionalPermissions` shape: it can request network access and additional filesystem access. The `cwd` field identifies the directory used to resolve project-root permissions and relative deny globs.
```json
{
@@ -4008,7 +4008,7 @@ mod tests {
file_system: Some(CoreFileSystemPermissions {
entries: vec![FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::CurrentWorkingDirectory,
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
},
access: FileSystemAccessMode::Write,
}],
@@ -4054,7 +4054,7 @@ mod tests {
file_system: Some(CoreFileSystemPermissions {
entries: vec![FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::CurrentWorkingDirectory,
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
},
access: FileSystemAccessMode::Write,
}],
@@ -4104,7 +4104,8 @@ mod tests {
"path": {
"type": "special",
"value": {
"kind": "current_working_directory"
"kind": "project_roots",
"subpath": null
}
},
"access": "write"
@@ -10700,7 +10700,8 @@ mod tests {
fn requested_permissions_trust_project_uses_permission_profile_intent() {
let cwd = test_path_buf("/tmp/project").abs();
let full_access_profile = codex_protocol::models::PermissionProfile::Disabled;
let workspace_write_profile = codex_protocol::models::PermissionProfile::workspace_write();
let workspace_write_profile =
codex_protocol::models::PermissionProfile::legacy_workspace_write_template();
let read_only_profile = codex_protocol::models::PermissionProfile::read_only();
let split_write_profile =
codex_protocol::models::PermissionProfile::from_runtime_permissions(
@@ -246,7 +246,7 @@ async fn command_exec_accepts_permission_profile() -> Result<()> {
#[cfg(unix)]
#[tokio::test]
async fn command_exec_permission_profile_cwd_uses_command_cwd() -> Result<()> {
async fn command_exec_permission_profile_project_roots_use_command_cwd() -> Result<()> {
let server = create_mock_responses_server_sequence_unchecked(Vec::new()).await;
let codex_home = TempDir::new()?;
let command_dir = codex_home.path().join("command-cwd");
@@ -264,7 +264,7 @@ async fn command_exec_permission_profile_cwd_uses_command_cwd() -> Result<()> {
};
entries.push(FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::CurrentWorkingDirectory,
value: FileSystemSpecialPath::ProjectRoots { subpath: None },
},
access: FileSystemAccessMode::Write,
});
@@ -298,7 +298,7 @@ async fn command_exec_permission_profile_cwd_uses_command_cwd() -> Result<()> {
let response: CommandExecResponse = to_response(response)?;
assert_eq!(
response.exit_code, 0,
"parent cwd write should fail under command-cwd-scoped profile: {response:?}"
"parent cwd write should fail under command project-root profile: {response:?}"
);
assert_eq!(
std::fs::read_to_string(command_dir.join("child.txt"))?,
@@ -306,7 +306,7 @@ async fn command_exec_permission_profile_cwd_uses_command_cwd() -> Result<()> {
);
assert!(
!codex_home.path().join("parent.txt").exists(),
"permissionProfile :cwd write should not grant the server cwd when command cwd differs"
"permissionProfile :project_roots write should not grant the server cwd when command cwd differs"
);
Ok(())
+1 -1
View File
@@ -947,7 +947,7 @@ async fn permission_profile_override_applies_runtime_roots_to_legacy_projection(
},
FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::CurrentWorkingDirectory,
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
},
access: FileSystemAccessMode::Write,
},
+1 -1
View File
@@ -1004,7 +1004,7 @@ fn managed_cwd_write_profile_is_not_read_only() {
},
FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::CurrentWorkingDirectory,
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
},
access: FileSystemAccessMode::Write,
},
+18 -6
View File
@@ -535,7 +535,9 @@ fn windows_restricted_token_rejects_split_only_filesystem_policies() {
let file_system_policy = FileSystemSandboxPolicy::restricted(vec![
codex_protocol::permissions::FileSystemSandboxEntry {
path: codex_protocol::permissions::FileSystemPath::Special {
value: codex_protocol::permissions::FileSystemSpecialPath::CurrentWorkingDirectory,
value: codex_protocol::permissions::FileSystemSpecialPath::project_roots(
/*subpath*/ None,
),
},
access: codex_protocol::permissions::FileSystemAccessMode::Write,
},
@@ -630,7 +632,9 @@ fn windows_restricted_token_supports_full_read_split_write_read_carveouts() {
},
codex_protocol::permissions::FileSystemSandboxEntry {
path: codex_protocol::permissions::FileSystemPath::Special {
value: codex_protocol::permissions::FileSystemSpecialPath::CurrentWorkingDirectory,
value: codex_protocol::permissions::FileSystemSpecialPath::project_roots(
/*subpath*/ None,
),
},
access: codex_protocol::permissions::FileSystemAccessMode::Write,
},
@@ -720,7 +724,9 @@ fn windows_elevated_supports_split_write_read_carveouts() {
},
codex_protocol::permissions::FileSystemSandboxEntry {
path: codex_protocol::permissions::FileSystemPath::Special {
value: codex_protocol::permissions::FileSystemSpecialPath::CurrentWorkingDirectory,
value: codex_protocol::permissions::FileSystemSpecialPath::project_roots(
/*subpath*/ None,
),
},
access: codex_protocol::permissions::FileSystemAccessMode::Write,
},
@@ -774,7 +780,9 @@ fn windows_elevated_rejects_unreadable_split_carveouts() {
},
codex_protocol::permissions::FileSystemSandboxEntry {
path: codex_protocol::permissions::FileSystemPath::Special {
value: codex_protocol::permissions::FileSystemSpecialPath::CurrentWorkingDirectory,
value: codex_protocol::permissions::FileSystemSpecialPath::project_roots(
/*subpath*/ None,
),
},
access: codex_protocol::permissions::FileSystemAccessMode::Write,
},
@@ -821,7 +829,9 @@ fn windows_elevated_rejects_unreadable_globs() {
},
codex_protocol::permissions::FileSystemSandboxEntry {
path: codex_protocol::permissions::FileSystemPath::Special {
value: codex_protocol::permissions::FileSystemSpecialPath::CurrentWorkingDirectory,
value: codex_protocol::permissions::FileSystemSpecialPath::project_roots(
/*subpath*/ None,
),
},
access: codex_protocol::permissions::FileSystemAccessMode::Write,
},
@@ -870,7 +880,9 @@ fn windows_elevated_rejects_reopened_writable_descendants() {
},
codex_protocol::permissions::FileSystemSandboxEntry {
path: codex_protocol::permissions::FileSystemPath::Special {
value: codex_protocol::permissions::FileSystemSpecialPath::CurrentWorkingDirectory,
value: codex_protocol::permissions::FileSystemSpecialPath::project_roots(
/*subpath*/ None,
),
},
access: codex_protocol::permissions::FileSystemAccessMode::Write,
},
+1 -1
View File
@@ -258,7 +258,7 @@ fn explicit_read_only_subpaths_prevent_auto_approval_for_external_sandbox() {
let file_system_sandbox_policy = FileSystemSandboxPolicy::restricted(vec![
FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::CurrentWorkingDirectory,
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
},
access: FileSystemAccessMode::Write,
},
+3 -37
View File
@@ -1,7 +1,5 @@
use super::*;
use crate::goals::GoalRuntimeState;
use codex_protocol::permissions::FileSystemPath;
use codex_protocol::permissions::FileSystemSpecialPath;
use tokio::sync::Semaphore;
/// Context for an initialized model agent
@@ -138,30 +136,8 @@ impl SessionConfiguration {
pub(crate) fn apply(&self, updates: &SessionSettingsUpdate) -> ConstraintResult<Self> {
let mut next_configuration = self.clone();
let current_sandbox_policy = self.sandbox_policy();
let current_file_system_sandbox_policy = self.file_system_sandbox_policy();
let current_network_sandbox_policy = self.network_sandbox_policy();
let legacy_file_system_projection =
FileSystemSandboxPolicy::from_legacy_sandbox_policy_preserving_deny_entries(
&current_sandbox_policy,
&self.cwd,
&current_file_system_sandbox_policy,
);
let file_system_policy_matches_legacy = current_file_system_sandbox_policy
.is_semantically_equivalent_to(&legacy_file_system_projection, &self.cwd);
let file_system_policy_has_rebindable_cwd_write = current_file_system_sandbox_policy
.entries
.iter()
.any(|entry| {
entry.access.can_write()
&& matches!(
&entry.path,
FileSystemPath::Special {
value: FileSystemSpecialPath::CurrentWorkingDirectory
| FileSystemSpecialPath::ProjectRoots { subpath: None },
}
)
});
if let Some(collaboration_mode) = updates.collaboration_mode.clone() {
next_configuration.collaboration_mode = collaboration_mode;
}
@@ -226,22 +202,12 @@ impl SessionConfiguration {
network_sandbox_policy,
),
)?;
} else if cwd_changed
&& file_system_policy_matches_legacy
&& file_system_policy_has_rebindable_cwd_write
{
// Preserve richer split policies across cwd-only updates; only
// rederive when the session is already using a structurally
// cwd-bound legacy bridge.
} else if cwd_changed {
let file_system_sandbox_policy =
FileSystemSandboxPolicy::from_legacy_sandbox_policy_preserving_deny_entries(
&current_sandbox_policy,
&next_configuration.cwd,
&current_file_system_sandbox_policy,
);
current_file_system_sandbox_policy.materialize_project_roots_with_cwd(&self.cwd);
next_configuration.permission_profile.set(
PermissionProfile::from_runtime_permissions_with_enforcement(
SandboxEnforcement::from_legacy_sandbox_policy(&current_sandbox_policy),
self.permission_profile().enforcement(),
&file_system_sandbox_policy,
current_network_sandbox_policy,
),
+28 -12
View File
@@ -2855,7 +2855,7 @@ fn turn_environments_for_tests(
}
#[tokio::test]
async fn session_configuration_apply_preserves_profile_file_system_policy_on_cwd_only_update() {
async fn session_configuration_apply_materializes_project_roots_on_cwd_only_update() {
let mut session_configuration = make_session_configuration_for_tests().await;
let workspace = tempfile::tempdir().expect("create temp dir");
let project_root = workspace.path().join("project");
@@ -2874,7 +2874,7 @@ async fn session_configuration_apply_preserves_profile_file_system_policy_on_cwd
let file_system_sandbox_policy = FileSystemSandboxPolicy::restricted(vec![
FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::CurrentWorkingDirectory,
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
},
access: FileSystemAccessMode::Write,
},
@@ -2899,9 +2899,23 @@ async fn session_configuration_apply_preserves_profile_file_system_policy_on_cwd
})
.expect("cwd-only update should succeed");
let expected_file_system_policy =
file_system_sandbox_policy.materialize_project_roots_with_cwd(original_cwd.as_path());
assert_eq!(
updated.file_system_sandbox_policy(),
file_system_sandbox_policy
expected_file_system_policy
);
assert!(
updated
.file_system_sandbox_policy()
.can_write_path_with_cwd(original_cwd.as_path(), updated.cwd.as_path()),
"project root grant should remain anchored to the original root"
);
assert!(
!updated
.file_system_sandbox_policy()
.can_write_path_with_cwd(updated.cwd.as_path(), updated.cwd.as_path()),
"cwd-only update must not rebind :project_roots to the new cwd"
);
}
@@ -3090,7 +3104,7 @@ enabled = false
}
#[tokio::test]
async fn session_configuration_apply_rederives_legacy_file_system_policy_on_cwd_update() {
async fn session_configuration_apply_keeps_legacy_project_root_anchored_on_cwd_update() {
let mut session_configuration = make_session_configuration_for_tests().await;
let workspace = tempfile::tempdir().expect("create temp dir");
let project_root = workspace.path().join("project");
@@ -3121,15 +3135,17 @@ async fn session_configuration_apply_rederives_legacy_file_system_policy_on_cwd_
})
.expect("cwd-only update should succeed");
let expected_file_system_policy = FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(
&updated.sandbox_policy(),
&project_root,
let expected_file_system_policy =
file_system_sandbox_policy.materialize_project_roots_with_cwd(original_cwd.as_path());
assert_eq!(
updated.file_system_sandbox_policy(),
expected_file_system_policy
);
assert!(
updated
!updated
.file_system_sandbox_policy()
.is_semantically_equivalent_to(&expected_file_system_policy, &project_root),
"cwd-only update should rederive the legacy filesystem policy for the new cwd"
.can_write_path_with_cwd(project_root.as_path(), project_root.as_path()),
"cwd-only update should not grant write access to the new cwd"
);
}
@@ -3187,7 +3203,7 @@ async fn session_configuration_apply_preserves_absolute_cwd_write_root_on_cwd_up
!updated
.file_system_sandbox_policy()
.can_write_path_with_cwd(next_cwd.as_path(), updated.cwd.as_path()),
"cwd-only update must not reinterpret an absolute old-cwd grant as :cwd"
"cwd-only update must not reinterpret an absolute old-cwd grant as :project_roots"
);
}
@@ -3850,7 +3866,7 @@ async fn request_permissions_response_materializes_session_cwd_grants_before_rec
file_system: Some(FileSystemPermissions {
entries: vec![FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::CurrentWorkingDirectory,
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
},
access: FileSystemAccessMode::Write,
}],
+1 -1
View File
@@ -354,7 +354,7 @@ mod tests {
entries: vec![
FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::CurrentWorkingDirectory,
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
},
access: FileSystemAccessMode::Write,
},
+1 -1
View File
@@ -309,7 +309,7 @@ async fn model_provided_unified_exec_is_blocked_for_windows_sandboxed_policies()
image_generation_tool_auth_allowed: true,
web_search_mode: Some(WebSearchMode::Cached),
session_source: SessionSource::Cli,
permission_profile: &PermissionProfile::workspace_write(),
permission_profile: &PermissionProfile::legacy_workspace_write_template(),
windows_sandbox_level: WindowsSandboxLevel::RestrictedToken,
});
+1 -3
View File
@@ -117,9 +117,7 @@ pub(crate) fn file_system_policy_has_cwd_dependent_entries(
.any(|entry| match &entry.path {
FileSystemPath::GlobPattern { pattern } => !Path::new(pattern).is_absolute(),
FileSystemPath::Special {
value:
FileSystemSpecialPath::CurrentWorkingDirectory
| FileSystemSpecialPath::ProjectRoots { .. },
value: FileSystemSpecialPath::ProjectRoots { .. },
} => true,
FileSystemPath::Path { .. } | FileSystemPath::Special { .. } => false,
})
+4 -4
View File
@@ -118,7 +118,7 @@ fn sandbox_cwd(sandbox: &FileSystemSandboxContext) -> Result<AbsolutePathBuf, JS
let file_system_policy = sandbox.permissions.file_system_sandbox_policy();
if file_system_policy_has_cwd_dependent_entries(&file_system_policy) {
return Err(invalid_request(
"file system sandbox context with cwd-relative permissions requires cwd".to_string(),
"file system sandbox context with dynamic permissions requires cwd".to_string(),
));
}
@@ -467,7 +467,7 @@ mod tests {
let cwd = AbsolutePathBuf::from_absolute_path(std::env::temp_dir().as_path())
.expect("absolute cwd");
let policy = restricted_policy(vec![special_entry(
FileSystemSpecialPath::CurrentWorkingDirectory,
FileSystemSpecialPath::project_roots(/*subpath*/ None),
FileSystemAccessMode::Write,
)]);
let sandbox_context = sandbox_context_with_cwd(&policy, cwd.clone());
@@ -479,7 +479,7 @@ mod tests {
fn sandbox_cwd_rejects_cwd_dependent_profile_without_context_cwd() {
let policy = FileSystemSandboxPolicy::restricted(vec![FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::CurrentWorkingDirectory,
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
},
access: FileSystemAccessMode::Write,
}]);
@@ -491,7 +491,7 @@ mod tests {
assert_eq!(
err.message,
"file system sandbox context with cwd-relative permissions requires cwd"
"file system sandbox context with dynamic permissions requires cwd"
);
}
@@ -252,7 +252,7 @@ mod tests {
fn remote_sandbox_context_preserves_required_cwd() {
let policy = FileSystemSandboxPolicy::restricted(vec![FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::CurrentWorkingDirectory,
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
},
access: FileSystemAccessMode::Write,
}]);
+1 -1
View File
@@ -1143,7 +1143,7 @@ mod tests {
},
FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::CurrentWorkingDirectory,
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
},
access: FileSystemAccessMode::Write,
},
@@ -200,7 +200,9 @@ fn split_only_filesystem_policy_requires_direct_runtime_enforcement() {
let policy = FileSystemSandboxPolicy::restricted(vec![
codex_protocol::permissions::FileSystemSandboxEntry {
path: codex_protocol::permissions::FileSystemPath::Special {
value: codex_protocol::permissions::FileSystemSpecialPath::CurrentWorkingDirectory,
value: codex_protocol::permissions::FileSystemSpecialPath::project_roots(
/*subpath*/ None,
),
},
access: codex_protocol::permissions::FileSystemAccessMode::Write,
},
+8 -4
View File
@@ -402,8 +402,12 @@ impl PermissionProfile {
}
}
/// Managed workspace-write filesystem access with restricted network access.
pub fn workspace_write() -> Self {
/// Managed legacy workspace-write filesystem template with restricted
/// network access.
///
/// The returned profile contains symbolic `:project_roots` entries that
/// must be resolved against the active permission root before enforcement.
pub fn legacy_workspace_write_template() -> Self {
Self::Managed {
file_system: ManagedFileSystemPermissions::Restricted {
entries: vec![
@@ -415,7 +419,7 @@ impl PermissionProfile {
},
FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::CurrentWorkingDirectory,
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
},
access: FileSystemAccessMode::Write,
},
@@ -1831,7 +1835,7 @@ mod tests {
PermissionProfile::from_legacy_sandbox_policy(&SandboxPolicy::new_read_only_policy())
);
assert_eq!(
PermissionProfile::workspace_write(),
PermissionProfile::legacy_workspace_write_template(),
PermissionProfile::from_legacy_sandbox_policy(
&SandboxPolicy::new_workspace_write_policy()
)
+39 -39
View File
@@ -80,7 +80,6 @@ impl FileSystemAccessMode {
pub enum FileSystemSpecialPath {
Root,
Minimal,
CurrentWorkingDirectory,
ProjectRoots {
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
@@ -414,9 +413,9 @@ impl FileSystemSandboxPolicy {
/// Converts a legacy sandbox policy into a cwd-independent filesystem policy.
///
/// `WorkspaceWrite` uses symbolic entries for cwd-scoped access so callers
/// can preserve the active cwd binding until the policy is actually
/// resolved for a turn or command.
/// `WorkspaceWrite` uses symbolic project-root entries so callers can keep
/// the profile independent of the concrete root until it is resolved for a
/// turn or command.
pub fn from_legacy_sandbox_policy(sandbox_policy: &SandboxPolicy) -> Self {
let mut file_system_policy = Self::from(sandbox_policy);
let SandboxPolicy::WorkspaceWrite {
@@ -571,6 +570,28 @@ impl FileSystemSandboxPolicy {
self.resolve_access_with_cwd(path, cwd).can_write()
}
/// Replaces symbolic `:project_roots` entries with absolute paths resolved
/// against `cwd`.
///
/// Use this when a durable permission profile must survive a cwd-only
/// update without rebinding its project-root authority to the new cwd.
pub fn materialize_project_roots_with_cwd(mut self, cwd: &Path) -> Self {
let cwd = AbsolutePathBuf::from_absolute_path(cwd).ok();
for entry in &mut self.entries {
let FileSystemPath::Special {
value: FileSystemSpecialPath::ProjectRoots { .. },
} = &entry.path
else {
continue;
};
if let Some(path) = resolve_file_system_path(&entry.path, cwd.as_ref()) {
entry.path = FileSystemPath::Path { path };
}
}
self
}
pub fn with_additional_readable_roots(
mut self,
cwd: &Path,
@@ -867,11 +888,6 @@ impl FileSystemSandboxPolicy {
}
},
FileSystemSpecialPath::Minimal => {}
FileSystemSpecialPath::CurrentWorkingDirectory => {
if entry.access.can_write() {
workspace_root_writable = true;
}
}
FileSystemSpecialPath::ProjectRoots { subpath } => {
if subpath.is_none() && entry.access.can_write() {
workspace_root_writable = true;
@@ -1001,7 +1017,7 @@ impl From<&SandboxPolicy> for FileSystemSandboxPolicy {
entries.push(FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::CurrentWorkingDirectory,
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
},
access: FileSystemAccessMode::Write,
});
@@ -1099,20 +1115,8 @@ fn special_paths_share_target(left: &FileSystemSpecialPath, right: &FileSystemSp
match (left, right) {
(FileSystemSpecialPath::Root, FileSystemSpecialPath::Root)
| (FileSystemSpecialPath::Minimal, FileSystemSpecialPath::Minimal)
| (
FileSystemSpecialPath::CurrentWorkingDirectory,
FileSystemSpecialPath::CurrentWorkingDirectory,
)
| (FileSystemSpecialPath::Tmpdir, FileSystemSpecialPath::Tmpdir)
| (FileSystemSpecialPath::SlashTmp, FileSystemSpecialPath::SlashTmp) => true,
(
FileSystemSpecialPath::CurrentWorkingDirectory,
FileSystemSpecialPath::ProjectRoots { subpath: None },
)
| (
FileSystemSpecialPath::ProjectRoots { subpath: None },
FileSystemSpecialPath::CurrentWorkingDirectory,
) => true,
(
FileSystemSpecialPath::ProjectRoots { subpath: left },
FileSystemSpecialPath::ProjectRoots { subpath: right },
@@ -1210,10 +1214,6 @@ fn resolve_file_system_special_path(
FileSystemSpecialPath::Root
| FileSystemSpecialPath::Minimal
| FileSystemSpecialPath::Unknown { .. } => None,
FileSystemSpecialPath::CurrentWorkingDirectory => {
let cwd = cwd?;
Some(cwd.clone())
}
FileSystemSpecialPath::ProjectRoots { subpath } => {
let cwd = cwd?;
match subpath.as_ref() {
@@ -1550,7 +1550,7 @@ mod tests {
let policy = FileSystemSandboxPolicy::restricted(vec![FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::CurrentWorkingDirectory,
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
},
access: FileSystemAccessMode::Write,
}]);
@@ -1566,7 +1566,7 @@ mod tests {
}
#[test]
fn legacy_workspace_write_projection_preserves_symbolic_cwd() {
fn legacy_workspace_write_projection_preserves_symbolic_project_root() {
let policy = SandboxPolicy::WorkspaceWrite {
writable_roots: Vec::new(),
network_access: false,
@@ -1585,7 +1585,7 @@ mod tests {
},
FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::CurrentWorkingDirectory,
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
},
access: FileSystemAccessMode::Write,
},
@@ -1624,7 +1624,7 @@ mod tests {
let policy = FileSystemSandboxPolicy::restricted(vec![
FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::CurrentWorkingDirectory,
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
},
access: FileSystemAccessMode::Write,
},
@@ -1703,7 +1703,7 @@ mod tests {
},
FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::CurrentWorkingDirectory,
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
},
access: FileSystemAccessMode::Write,
},
@@ -1774,7 +1774,7 @@ mod tests {
#[cfg(unix)]
#[test]
fn current_working_directory_special_path_preserves_symlinked_cwd() {
fn project_roots_special_path_preserves_symlinked_root() {
let cwd = TempDir::new().expect("tempdir");
let real_root = cwd.path().join("real");
let link_root = cwd.path().join("link");
@@ -1804,7 +1804,7 @@ mod tests {
},
FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::CurrentWorkingDirectory,
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
},
access: FileSystemAccessMode::Write,
},
@@ -2094,7 +2094,7 @@ mod tests {
let policy = FileSystemSandboxPolicy::restricted(vec![
FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::CurrentWorkingDirectory,
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
},
access: FileSystemAccessMode::Write,
},
@@ -2141,7 +2141,7 @@ mod tests {
let policy = FileSystemSandboxPolicy::restricted(vec![
FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::CurrentWorkingDirectory,
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
},
access: FileSystemAccessMode::Write,
},
@@ -2318,7 +2318,7 @@ mod tests {
let cwd_root = AbsolutePathBuf::from_absolute_path(cwd.path()).expect("absolute cwd");
let policy = FileSystemSandboxPolicy::restricted(vec![FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::CurrentWorkingDirectory,
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
},
access: FileSystemAccessMode::Read,
}]);
@@ -2336,7 +2336,7 @@ mod tests {
let cwd_root = AbsolutePathBuf::from_absolute_path(cwd.path()).expect("absolute cwd");
let policy = FileSystemSandboxPolicy::restricted(vec![FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::CurrentWorkingDirectory,
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
},
access: FileSystemAccessMode::Write,
}]);
@@ -2356,7 +2356,7 @@ mod tests {
.expect("resolve extra root");
let policy = FileSystemSandboxPolicy::restricted(vec![FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::CurrentWorkingDirectory,
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
},
access: FileSystemAccessMode::Write,
}]);
@@ -2368,7 +2368,7 @@ mod tests {
FileSystemSandboxPolicy::restricted(vec![
FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::CurrentWorkingDirectory,
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
},
access: FileSystemAccessMode::Write,
},
+2 -2
View File
@@ -4397,7 +4397,7 @@ mod tests {
},
FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::CurrentWorkingDirectory,
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
},
access: FileSystemAccessMode::Write,
},
@@ -4459,7 +4459,7 @@ mod tests {
let policy = FileSystemSandboxPolicy::restricted(vec![
FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::CurrentWorkingDirectory,
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
},
access: FileSystemAccessMode::Write,
},
+1 -6
View File
@@ -350,9 +350,7 @@ fn materialize_cwd_dependent_entry(
) -> FileSystemSandboxEntry {
match &entry.path {
FileSystemPath::Special {
value:
FileSystemSpecialPath::CurrentWorkingDirectory
| FileSystemSpecialPath::ProjectRoots { .. },
value: FileSystemSpecialPath::ProjectRoots { .. },
} => resolve_permission_path(&entry.path, cwd)
.map(|path| FileSystemSandboxEntry {
path: FileSystemPath::Path { path },
@@ -380,9 +378,6 @@ fn resolve_permission_path(path: &FileSystemPath, cwd: &Path) -> Option<Absolute
let root = cwd.ancestors().last()?;
AbsolutePathBuf::from_absolute_path(root).ok()
}
FileSystemSpecialPath::CurrentWorkingDirectory => {
AbsolutePathBuf::from_absolute_path(cwd).ok()
}
FileSystemSpecialPath::ProjectRoots { subpath } => {
let cwd = AbsolutePathBuf::from_absolute_path(cwd).ok()?;
Some(match subpath {
@@ -299,7 +299,7 @@ fn intersect_permission_profiles_accepts_child_path_granted_for_requested_cwd()
file_system: Some(FileSystemPermissions {
entries: vec![FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::CurrentWorkingDirectory,
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
},
access: FileSystemAccessMode::Write,
}],
@@ -332,7 +332,7 @@ fn intersect_permission_profiles_materializes_cwd_grant_for_reuse() {
file_system: Some(FileSystemPermissions {
entries: vec![FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::CurrentWorkingDirectory,
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
},
access: FileSystemAccessMode::Write,
}],
@@ -383,7 +383,7 @@ fn intersect_permission_profiles_deduplicates_materialized_grants() {
entries: vec![
FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::CurrentWorkingDirectory,
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
},
access: FileSystemAccessMode::Write,
},
@@ -425,7 +425,7 @@ fn intersect_permission_profiles_materializes_cwd_deny_entries() {
},
FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::CurrentWorkingDirectory,
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
},
access: FileSystemAccessMode::None,
},
@@ -474,7 +474,7 @@ fn intersect_permission_profiles_drops_deny_entries_without_filesystem_grants()
entries: vec![
FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::CurrentWorkingDirectory,
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
},
access: FileSystemAccessMode::Write,
},
@@ -512,7 +512,7 @@ fn intersect_permission_profiles_rejects_concrete_grants_matched_by_requested_de
entries: vec![
FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::CurrentWorkingDirectory,
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
},
access: FileSystemAccessMode::Write,
},
@@ -550,7 +550,7 @@ fn intersect_permission_profiles_materializes_relative_deny_globs_for_reuse() {
.expect("absolute later cwd");
let cwd_write = FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::CurrentWorkingDirectory,
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
},
access: FileSystemAccessMode::Write,
};
@@ -629,7 +629,7 @@ fn intersect_permission_profiles_drops_broader_cwd_grant_for_requested_child_pat
file_system: Some(FileSystemPermissions {
entries: vec![FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::CurrentWorkingDirectory,
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
},
access: FileSystemAccessMode::Write,
}],
+1 -1
View File
@@ -60,7 +60,7 @@ fn unified_exec_is_blocked_for_windows_managed_profiles_only() {
));
assert!(!unified_exec_allowed_in_environment(
/*is_windows*/ true,
&PermissionProfile::workspace_write(),
&PermissionProfile::legacy_workspace_write_template(),
WindowsSandboxLevel::RestrictedToken,
));
assert!(unified_exec_allowed_in_environment(
+6 -6
View File
@@ -2208,7 +2208,7 @@ async fn inactive_thread_approval_bubbles_into_active_view() -> Result<()> {
ThreadSessionState {
approval_policy: AskForApproval::OnRequest,
sandbox_policy: SandboxPolicy::new_workspace_write_policy(),
permission_profile: Some(PermissionProfile::workspace_write()),
permission_profile: Some(PermissionProfile::legacy_workspace_write_template()),
rollout_path: Some(test_path_buf("/tmp/agent-rollout.jsonl")),
..test_thread_session(agent_thread_id, test_path_buf("/tmp/agent"))
},
@@ -2368,7 +2368,7 @@ async fn side_defers_subagent_approval_overlay_until_side_exits() -> Result<()>
ThreadSessionState {
approval_policy: AskForApproval::OnRequest,
sandbox_policy: SandboxPolicy::new_workspace_write_policy(),
permission_profile: Some(PermissionProfile::workspace_write()),
permission_profile: Some(PermissionProfile::legacy_workspace_write_template()),
rollout_path: Some(test_path_buf("/tmp/agent-rollout.jsonl")),
..test_thread_session(agent_thread_id, test_path_buf("/tmp/agent"))
},
@@ -2591,7 +2591,7 @@ async fn inactive_thread_approval_badge_clears_after_turn_completion_notificatio
ThreadSessionState {
approval_policy: AskForApproval::OnRequest,
sandbox_policy: SandboxPolicy::new_workspace_write_policy(),
permission_profile: Some(PermissionProfile::workspace_write()),
permission_profile: Some(PermissionProfile::legacy_workspace_write_template()),
rollout_path: Some(test_path_buf("/tmp/agent-rollout.jsonl")),
..test_thread_session(agent_thread_id, test_path_buf("/tmp/agent"))
},
@@ -2645,7 +2645,7 @@ async fn inactive_thread_started_notification_initializes_replay_session() -> Re
let primary_session = ThreadSessionState {
approval_policy: AskForApproval::OnRequest,
sandbox_policy: SandboxPolicy::new_workspace_write_policy(),
permission_profile: Some(PermissionProfile::workspace_write()),
permission_profile: Some(PermissionProfile::legacy_workspace_write_template()),
..test_thread_session(main_thread_id, test_path_buf("/tmp/main"))
};
@@ -2758,7 +2758,7 @@ async fn inactive_thread_started_notification_preserves_primary_model_when_path_
let primary_session = ThreadSessionState {
approval_policy: AskForApproval::OnRequest,
sandbox_policy: SandboxPolicy::new_workspace_write_policy(),
permission_profile: Some(PermissionProfile::workspace_write()),
permission_profile: Some(PermissionProfile::legacy_workspace_write_template()),
..test_thread_session(main_thread_id, test_path_buf("/tmp/main"))
};
@@ -2827,7 +2827,7 @@ async fn thread_read_session_state_does_not_reuse_primary_permission_profile() {
let primary_session = ThreadSessionState {
approval_policy: AskForApproval::OnRequest,
sandbox_policy: SandboxPolicy::new_workspace_write_policy(),
permission_profile: Some(PermissionProfile::workspace_write()),
permission_profile: Some(PermissionProfile::legacy_workspace_write_template()),
..test_thread_session(main_thread_id, test_path_buf("/tmp/main"))
};
app.primary_session_configured = Some(primary_session);
@@ -878,7 +878,6 @@ fn special_path_label(value: &FileSystemSpecialPath) -> String {
match value {
FileSystemSpecialPath::Root => ":root".to_string(),
FileSystemSpecialPath::Minimal => ":minimal".to_string(),
FileSystemSpecialPath::CurrentWorkingDirectory => ":cwd".to_string(),
FileSystemSpecialPath::ProjectRoots { subpath } => path_label(":project_roots", subpath),
FileSystemSpecialPath::Tmpdir => ":tmpdir".to_string(),
FileSystemSpecialPath::SlashTmp => "/tmp".to_string(),