Merge 1e4cc1c6a8 into sapling-pr-archive-bolinfest

This commit is contained in:
Michael Bolin
2026-04-23 17:02:09 -07:00
committed by GitHub
5 changed files with 65 additions and 56 deletions
@@ -1526,22 +1526,7 @@ mod tests {
"type": "dangerFullAccess"
},
"permissionProfile": {
"network": {
"enabled": true,
},
"fileSystem": {
"entries": [
{
"path": {
"type": "special",
"value": {
"kind": "root",
},
},
"access": "write",
},
],
},
"type": "disabled"
},
"reasoningEffort": null
}
+2 -1
View File
@@ -837,7 +837,8 @@ Run a standalone command (argv vector) in the server’s sandbox without creatin
"env": { "FOO": "override" }, // optional; merges into the server env and overrides matching names
"size": { "rows": 40, "cols": 120 }, // optional; PTY size in character cells, only valid with tty=true
"permissionProfile": { // optional; defaults to user config
"fileSystem": { "entries": [
"type": "managed",
"fileSystem": { "type": "restricted", "entries": [
{ "path": { "type": "special", "value": { "kind": "root" } }, "access": "read" },
{ "path": { "type": "special", "value": { "kind": "current_working_directory" } }, "access": "write" }
] },
@@ -314,7 +314,6 @@ use codex_protocol::error::CodexErr;
use codex_protocol::error::Result as CodexResult;
use codex_protocol::items::TurnItem;
use codex_protocol::models::ResponseItem;
use codex_protocol::permissions::FileSystemAccessMode;
use codex_protocol::permissions::FileSystemSandboxPolicy;
use codex_protocol::protocol::AgentStatus;
use codex_protocol::protocol::ConversationAudioParams;
@@ -2361,24 +2360,8 @@ impl CodexMessageProcessor {
file_system_sandbox_policy: &mut FileSystemSandboxPolicy,
configured_file_system_sandbox_policy: &FileSystemSandboxPolicy,
) {
if file_system_sandbox_policy.glob_scan_max_depth.is_none() {
file_system_sandbox_policy.glob_scan_max_depth =
configured_file_system_sandbox_policy.glob_scan_max_depth;
}
for deny_entry in configured_file_system_sandbox_policy
.entries
.iter()
.filter(|entry| entry.access == FileSystemAccessMode::None)
{
if !file_system_sandbox_policy
.entries
.iter()
.any(|entry| entry == deny_entry)
{
file_system_sandbox_policy.entries.push(deny_entry.clone());
}
}
file_system_sandbox_policy
.preserve_deny_read_restrictions_from(configured_file_system_sandbox_policy);
}
async fn command_exec_write(
@@ -10239,6 +10222,7 @@ mod tests {
use codex_model_provider_info::WireApi;
use codex_protocol::ThreadId;
use codex_protocol::openai_models::ReasoningEffort;
use codex_protocol::permissions::FileSystemAccessMode;
use codex_protocol::permissions::FileSystemPath;
use codex_protocol::permissions::FileSystemSandboxEntry;
use codex_protocol::protocol::AskForApproval;
+2 -20
View File
@@ -176,26 +176,8 @@ impl SessionConfiguration {
next_configuration.sandbox_policy.set(sandbox_policy)?;
let (mut file_system_sandbox_policy, network_sandbox_policy) =
permission_profile.to_runtime_permissions();
if file_system_sandbox_policy.glob_scan_max_depth.is_none() {
file_system_sandbox_policy.glob_scan_max_depth =
self.file_system_sandbox_policy.glob_scan_max_depth;
}
for deny_entry in self
.file_system_sandbox_policy
.entries
.iter()
.filter(|entry| {
entry.access == codex_protocol::permissions::FileSystemAccessMode::None
})
{
if !file_system_sandbox_policy
.entries
.iter()
.any(|entry| entry == deny_entry)
{
file_system_sandbox_policy.entries.push(deny_entry.clone());
}
}
file_system_sandbox_policy
.preserve_deny_read_restrictions_from(&self.file_system_sandbox_policy);
next_configuration.file_system_sandbox_policy = file_system_sandbox_policy;
next_configuration.network_sandbox_policy = network_sandbox_policy;
} else if let Some(sandbox_policy) = updates.sandbox_policy.clone() {
+57
View File
@@ -340,6 +340,41 @@ impl FileSystemSandboxPolicy {
rebuilt
}
/// Preserve explicit read-deny rules from `existing` when a caller
/// replaces the allow side of a policy.
pub fn preserve_deny_read_restrictions_from(&mut self, existing: &Self) {
let has_deny_read_entries = existing
.entries
.iter()
.any(|entry| entry.access == FileSystemAccessMode::None);
if matches!(self.kind, FileSystemSandboxKind::Unrestricted) && has_deny_read_entries {
*self = Self::restricted(vec![FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::Root,
},
access: FileSystemAccessMode::Write,
}]);
}
if !matches!(self.kind, FileSystemSandboxKind::Restricted) {
return;
}
if self.glob_scan_max_depth.is_none() {
self.glob_scan_max_depth = existing.glob_scan_max_depth;
}
for deny_entry in existing
.entries
.iter()
.filter(|entry| entry.access == FileSystemAccessMode::None)
{
if !self.entries.iter().any(|entry| entry == deny_entry) {
self.entries.push(deny_entry.clone());
}
}
}
/// Returns true when a restricted policy contains any entry that really
/// reduces a broader `:root = write` grant.
///
@@ -2297,6 +2332,28 @@ mod tests {
);
}
#[test]
fn preserving_deny_entries_keeps_unrestricted_policy_enforceable() {
let deny_entry = unreadable_glob_entry("/tmp/project/**/*.env".to_string());
let mut existing = FileSystemSandboxPolicy::restricted(vec![deny_entry.clone()]);
existing.glob_scan_max_depth = Some(2);
let mut replacement = FileSystemSandboxPolicy::unrestricted();
replacement.preserve_deny_read_restrictions_from(&existing);
let mut expected = FileSystemSandboxPolicy::restricted(vec![
FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::Root,
},
access: FileSystemAccessMode::Write,
},
deny_entry,
]);
expected.glob_scan_max_depth = Some(2);
assert_eq!(replacement, expected);
}
fn deny_policy(path: &Path) -> FileSystemSandboxPolicy {
FileSystemSandboxPolicy::restricted(vec![FileSystemSandboxEntry {
path: FileSystemPath::Path {