mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
permissions: remove cwd special path (#19841)
## Why
The experimental `PermissionProfile` API had both `:cwd` and
`:project_roots` special filesystem paths, which made the permission
root ambiguous. This PR removes the unstable `current_working_directory`
special path before the permissions API is stabilized, so callers use
`:project_roots` for symbolic project-root access.
## What changed
- Removes `FileSystemSpecialPath::CurrentWorkingDirectory` from protocol
and app-server protocol models, plus regenerated app-server
JSON/TypeScript schemas.
- Replaces internal `:cwd` permission entries with `:project_roots`
entries.
- Keeps the existing cwd-update behavior for legacy-shaped
workspace-write profiles, while removing the deleted
`CurrentWorkingDirectory` case from that compatibility path.
- Keeps `PermissionProfile::workspace_write()` as the reusable symbolic
workspace-write helper, with docs noting that `:project_roots` entries
resolve at enforcement time.
- Updates app-server docs/examples and approval UI labeling to stop
advertising `:cwd` as a permission token.
## Compatibility
Persisted rollout items may contain the old
`{"kind":"current_working_directory"}` tag from earlier experimental
`permissionProfile` snapshots. This PR keeps that tag as a
deserialize-only alias for `ProjectRoots { subpath: None }`, while
continuing to serialize only the new `project_roots` tag.
## Follow-up
This PR intentionally does not introduce an explicit project-root set on
`SessionConfiguration` or runtime sandbox resolution. Today, the
resolver still uses the active cwd as the single implicit project root.
A follow-up should model project roots separately from tool cwd so
`:project_roots` entries can resolve against the configured project
roots, and resolve to no entries when there are no project roots.
## Verification
- `cargo test -p codex-protocol permissions:: --lib`
- `cargo test -p codex-app-server-protocol`
- `cargo test -p codex-sandboxing -p codex-exec-server --lib`
- `cargo test -p codex-core session_configuration_apply_ --lib`
- `cargo test -p codex-app-server
command_exec_permission_profile_project_roots_use_command_cwd --test
all`
- `cargo test -p codex-tui
thread_read_session_state_does_not_reuse_primary_permission_profile
--lib`
- `cargo test -p codex-tui
preset_matching_accepts_workspace_write_with_extra_roots --lib`
- `cargo test -p codex-config --lib`
This commit is contained in:
@@ -402,7 +402,11 @@ impl PermissionProfile {
|
||||
}
|
||||
}
|
||||
|
||||
/// Managed workspace-write filesystem access with restricted network access.
|
||||
/// Managed workspace-write filesystem access 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 workspace_write() -> Self {
|
||||
Self::Managed {
|
||||
file_system: ManagedFileSystemPermissions::Restricted {
|
||||
@@ -415,7 +419,7 @@ impl PermissionProfile {
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::CurrentWorkingDirectory,
|
||||
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
},
|
||||
|
||||
@@ -80,7 +80,7 @@ impl FileSystemAccessMode {
|
||||
pub enum FileSystemSpecialPath {
|
||||
Root,
|
||||
Minimal,
|
||||
CurrentWorkingDirectory,
|
||||
#[serde(alias = "current_working_directory")]
|
||||
ProjectRoots {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
@@ -414,9 +414,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 +571,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 +889,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 +1018,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 +1116,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 +1215,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 +1551,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 +1567,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 +1586,7 @@ mod tests {
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::CurrentWorkingDirectory,
|
||||
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
},
|
||||
@@ -1611,6 +1612,27 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn legacy_current_working_directory_special_path_deserializes_as_project_roots()
|
||||
-> serde_json::Result<()> {
|
||||
let value = serde_json::json!({
|
||||
"kind": "current_working_directory",
|
||||
});
|
||||
|
||||
let special_path = serde_json::from_value::<FileSystemSpecialPath>(value)?;
|
||||
assert_eq!(
|
||||
special_path,
|
||||
FileSystemSpecialPath::project_roots(/*subpath*/ None)
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::to_value(&special_path)?,
|
||||
serde_json::json!({
|
||||
"kind": "project_roots",
|
||||
})
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn writable_roots_skip_default_dot_codex_when_explicit_user_rule_exists() {
|
||||
@@ -1624,7 +1646,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 +1725,7 @@ mod tests {
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::CurrentWorkingDirectory,
|
||||
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
},
|
||||
@@ -1774,7 +1796,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 +1826,7 @@ mod tests {
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::CurrentWorkingDirectory,
|
||||
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
},
|
||||
@@ -2094,7 +2116,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 +2163,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 +2340,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 +2358,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 +2378,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 +2390,7 @@ mod tests {
|
||||
FileSystemSandboxPolicy::restricted(vec![
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::CurrentWorkingDirectory,
|
||||
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
},
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user