permissions: remove legacy read-only access modes (#19449)

## Why

`ReadOnlyAccess` was a transitional legacy shape on `SandboxPolicy`:
`FullAccess` meant the historical read-only/workspace-write modes could
read the full filesystem, while `Restricted` tried to carry partial
readable roots. The partial-read model now belongs in
`FileSystemSandboxPolicy` and `PermissionProfile`, so keeping it on
`SandboxPolicy` makes every legacy projection reintroduce lossy
read-root bookkeeping and creates unnecessary noise in the rest of the
permissions migration.

This PR makes the legacy policy model narrower and explicit:
`SandboxPolicy::ReadOnly` and `SandboxPolicy::WorkspaceWrite` represent
the old full-read sandbox modes only. Split readable roots, deny-read
globs, and platform-default/minimal read behavior stay in the runtime
permissions model.

## What changed

- Removes `ReadOnlyAccess` from
`codex_protocol::protocol::SandboxPolicy`, including the generated
`access` and `readOnlyAccess` API fields.
- Updates legacy policy/profile conversions so restricted filesystem
reads are represented only by `FileSystemSandboxPolicy` /
`PermissionProfile` entries.
- Keeps app-server v2 compatible with legacy `fullAccess` read-access
payloads by accepting and ignoring that no-op shape, while rejecting
legacy `restricted` read-access payloads instead of silently widening
them to full-read legacy policies.
- Carries Windows sandbox platform-default read behavior with an
explicit override flag instead of depending on
`ReadOnlyAccess::Restricted`.
- Refreshes generated app-server schema/types and updates tests/docs for
the simplified legacy policy shape.

## Verification

- `cargo check -p codex-app-server-protocol --tests`
- `cargo check -p codex-windows-sandbox --tests`
- `cargo test -p codex-app-server-protocol sandbox_policy_`


---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/19449).
* #19395
* #19394
* #19393
* #19392
* #19391
* __->__ #19449
This commit is contained in:
Michael Bolin
2026-04-24 17:16:58 -07:00
committed by GitHub
parent d19de6d150
commit 789f387982
63 changed files with 284 additions and 1506 deletions
@@ -2126,53 +2126,6 @@
],
"type": "object"
},
"ReadOnlyAccess": {
"oneOf": [
{
"properties": {
"includePlatformDefaults": {
"default": true,
"type": "boolean"
},
"readableRoots": {
"default": [],
"items": {
"$ref": "#/definitions/AbsolutePathBuf"
},
"type": "array"
},
"type": {
"enum": [
"restricted"
],
"title": "RestrictedReadOnlyAccessType",
"type": "string"
}
},
"required": [
"type"
],
"title": "RestrictedReadOnlyAccess",
"type": "object"
},
{
"properties": {
"type": {
"enum": [
"fullAccess"
],
"title": "FullAccessReadOnlyAccessType",
"type": "string"
}
},
"required": [
"type"
],
"title": "FullAccessReadOnlyAccess",
"type": "object"
}
]
},
"RealtimeOutputModality": {
"enum": [
"text",
@@ -3056,16 +3009,6 @@
},
{
"properties": {
"access": {
"allOf": [
{
"$ref": "#/definitions/ReadOnlyAccess"
}
],
"default": {
"type": "fullAccess"
}
},
"networkAccess": {
"default": false,
"type": "boolean"
@@ -3122,16 +3065,6 @@
"default": false,
"type": "boolean"
},
"readOnlyAccess": {
"allOf": [
{
"$ref": "#/definitions/ReadOnlyAccess"
}
],
"default": {
"type": "fullAccess"
}
},
"type": {
"enum": [
"workspaceWrite"
@@ -12228,53 +12228,6 @@
"title": "RawResponseItemCompletedNotification",
"type": "object"
},
"ReadOnlyAccess": {
"oneOf": [
{
"properties": {
"includePlatformDefaults": {
"default": true,
"type": "boolean"
},
"readableRoots": {
"default": [],
"items": {
"$ref": "#/definitions/v2/AbsolutePathBuf"
},
"type": "array"
},
"type": {
"enum": [
"restricted"
],
"title": "RestrictedReadOnlyAccessType",
"type": "string"
}
},
"required": [
"type"
],
"title": "RestrictedReadOnlyAccess",
"type": "object"
},
{
"properties": {
"type": {
"enum": [
"fullAccess"
],
"title": "FullAccessReadOnlyAccessType",
"type": "string"
}
},
"required": [
"type"
],
"title": "FullAccessReadOnlyAccess",
"type": "object"
}
]
},
"RealtimeConversationVersion": {
"enum": [
"v1",
@@ -13483,16 +13436,6 @@
},
{
"properties": {
"access": {
"allOf": [
{
"$ref": "#/definitions/v2/ReadOnlyAccess"
}
],
"default": {
"type": "fullAccess"
}
},
"networkAccess": {
"default": false,
"type": "boolean"
@@ -13549,16 +13492,6 @@
"default": false,
"type": "boolean"
},
"readOnlyAccess": {
"allOf": [
{
"$ref": "#/definitions/v2/ReadOnlyAccess"
}
],
"default": {
"type": "fullAccess"
}
},
"type": {
"enum": [
"workspaceWrite"
@@ -8942,53 +8942,6 @@
"title": "RawResponseItemCompletedNotification",
"type": "object"
},
"ReadOnlyAccess": {
"oneOf": [
{
"properties": {
"includePlatformDefaults": {
"default": true,
"type": "boolean"
},
"readableRoots": {
"default": [],
"items": {
"$ref": "#/definitions/AbsolutePathBuf"
},
"type": "array"
},
"type": {
"enum": [
"restricted"
],
"title": "RestrictedReadOnlyAccessType",
"type": "string"
}
},
"required": [
"type"
],
"title": "RestrictedReadOnlyAccess",
"type": "object"
},
{
"properties": {
"type": {
"enum": [
"fullAccess"
],
"title": "FullAccessReadOnlyAccessType",
"type": "string"
}
},
"required": [
"type"
],
"title": "FullAccessReadOnlyAccess",
"type": "object"
}
]
},
"RealtimeConversationVersion": {
"enum": [
"v1",
@@ -10197,16 +10150,6 @@
},
{
"properties": {
"access": {
"allOf": [
{
"$ref": "#/definitions/ReadOnlyAccess"
}
],
"default": {
"type": "fullAccess"
}
},
"networkAccess": {
"default": false,
"type": "boolean"
@@ -10263,16 +10206,6 @@
"default": false,
"type": "boolean"
},
"readOnlyAccess": {
"allOf": [
{
"$ref": "#/definitions/ReadOnlyAccess"
}
],
"default": {
"type": "fullAccess"
}
},
"type": {
"enum": [
"workspaceWrite"
@@ -374,53 +374,6 @@
],
"type": "object"
},
"ReadOnlyAccess": {
"oneOf": [
{
"properties": {
"includePlatformDefaults": {
"default": true,
"type": "boolean"
},
"readableRoots": {
"default": [],
"items": {
"$ref": "#/definitions/AbsolutePathBuf"
},
"type": "array"
},
"type": {
"enum": [
"restricted"
],
"title": "RestrictedReadOnlyAccessType",
"type": "string"
}
},
"required": [
"type"
],
"title": "RestrictedReadOnlyAccess",
"type": "object"
},
{
"properties": {
"type": {
"enum": [
"fullAccess"
],
"title": "FullAccessReadOnlyAccessType",
"type": "string"
}
},
"required": [
"type"
],
"title": "FullAccessReadOnlyAccess",
"type": "object"
}
]
},
"SandboxPolicy": {
"oneOf": [
{
@@ -441,16 +394,6 @@
},
{
"properties": {
"access": {
"allOf": [
{
"$ref": "#/definitions/ReadOnlyAccess"
}
],
"default": {
"type": "fullAccess"
}
},
"networkAccess": {
"default": false,
"type": "boolean"
@@ -507,16 +450,6 @@
"default": false,
"type": "boolean"
},
"readOnlyAccess": {
"allOf": [
{
"$ref": "#/definitions/ReadOnlyAccess"
}
],
"default": {
"type": "fullAccess"
}
},
"type": {
"enum": [
"workspaceWrite"
@@ -1028,53 +1028,6 @@
],
"type": "object"
},
"ReadOnlyAccess": {
"oneOf": [
{
"properties": {
"includePlatformDefaults": {
"default": true,
"type": "boolean"
},
"readableRoots": {
"default": [],
"items": {
"$ref": "#/definitions/AbsolutePathBuf"
},
"type": "array"
},
"type": {
"enum": [
"restricted"
],
"title": "RestrictedReadOnlyAccessType",
"type": "string"
}
},
"required": [
"type"
],
"title": "RestrictedReadOnlyAccess",
"type": "object"
},
{
"properties": {
"type": {
"enum": [
"fullAccess"
],
"title": "FullAccessReadOnlyAccessType",
"type": "string"
}
},
"required": [
"type"
],
"title": "FullAccessReadOnlyAccess",
"type": "object"
}
]
},
"ReasoningEffort": {
"description": "See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#get-started-with-reasoning",
"enum": [
@@ -1107,16 +1060,6 @@
},
{
"properties": {
"access": {
"allOf": [
{
"$ref": "#/definitions/ReadOnlyAccess"
}
],
"default": {
"type": "fullAccess"
}
},
"networkAccess": {
"default": false,
"type": "boolean"
@@ -1173,16 +1116,6 @@
"default": false,
"type": "boolean"
},
"readOnlyAccess": {
"allOf": [
{
"$ref": "#/definitions/ReadOnlyAccess"
}
],
"default": {
"type": "fullAccess"
}
},
"type": {
"enum": [
"workspaceWrite"
@@ -1028,53 +1028,6 @@
],
"type": "object"
},
"ReadOnlyAccess": {
"oneOf": [
{
"properties": {
"includePlatformDefaults": {
"default": true,
"type": "boolean"
},
"readableRoots": {
"default": [],
"items": {
"$ref": "#/definitions/AbsolutePathBuf"
},
"type": "array"
},
"type": {
"enum": [
"restricted"
],
"title": "RestrictedReadOnlyAccessType",
"type": "string"
}
},
"required": [
"type"
],
"title": "RestrictedReadOnlyAccess",
"type": "object"
},
{
"properties": {
"type": {
"enum": [
"fullAccess"
],
"title": "FullAccessReadOnlyAccessType",
"type": "string"
}
},
"required": [
"type"
],
"title": "FullAccessReadOnlyAccess",
"type": "object"
}
]
},
"ReasoningEffort": {
"description": "See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#get-started-with-reasoning",
"enum": [
@@ -1107,16 +1060,6 @@
},
{
"properties": {
"access": {
"allOf": [
{
"$ref": "#/definitions/ReadOnlyAccess"
}
],
"default": {
"type": "fullAccess"
}
},
"networkAccess": {
"default": false,
"type": "boolean"
@@ -1173,16 +1116,6 @@
"default": false,
"type": "boolean"
},
"readOnlyAccess": {
"allOf": [
{
"$ref": "#/definitions/ReadOnlyAccess"
}
],
"default": {
"type": "fullAccess"
}
},
"type": {
"enum": [
"workspaceWrite"
@@ -1028,53 +1028,6 @@
],
"type": "object"
},
"ReadOnlyAccess": {
"oneOf": [
{
"properties": {
"includePlatformDefaults": {
"default": true,
"type": "boolean"
},
"readableRoots": {
"default": [],
"items": {
"$ref": "#/definitions/AbsolutePathBuf"
},
"type": "array"
},
"type": {
"enum": [
"restricted"
],
"title": "RestrictedReadOnlyAccessType",
"type": "string"
}
},
"required": [
"type"
],
"title": "RestrictedReadOnlyAccess",
"type": "object"
},
{
"properties": {
"type": {
"enum": [
"fullAccess"
],
"title": "FullAccessReadOnlyAccessType",
"type": "string"
}
},
"required": [
"type"
],
"title": "FullAccessReadOnlyAccess",
"type": "object"
}
]
},
"ReasoningEffort": {
"description": "See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#get-started-with-reasoning",
"enum": [
@@ -1107,16 +1060,6 @@
},
{
"properties": {
"access": {
"allOf": [
{
"$ref": "#/definitions/ReadOnlyAccess"
}
],
"default": {
"type": "fullAccess"
}
},
"networkAccess": {
"default": false,
"type": "boolean"
@@ -1173,16 +1116,6 @@
"default": false,
"type": "boolean"
},
"readOnlyAccess": {
"allOf": [
{
"$ref": "#/definitions/ReadOnlyAccess"
}
],
"default": {
"type": "fullAccess"
}
},
"type": {
"enum": [
"workspaceWrite"
@@ -462,53 +462,6 @@
],
"type": "string"
},
"ReadOnlyAccess": {
"oneOf": [
{
"properties": {
"includePlatformDefaults": {
"default": true,
"type": "boolean"
},
"readableRoots": {
"default": [],
"items": {
"$ref": "#/definitions/AbsolutePathBuf"
},
"type": "array"
},
"type": {
"enum": [
"restricted"
],
"title": "RestrictedReadOnlyAccessType",
"type": "string"
}
},
"required": [
"type"
],
"title": "RestrictedReadOnlyAccess",
"type": "object"
},
{
"properties": {
"type": {
"enum": [
"fullAccess"
],
"title": "FullAccessReadOnlyAccessType",
"type": "string"
}
},
"required": [
"type"
],
"title": "FullAccessReadOnlyAccess",
"type": "object"
}
]
},
"ReasoningEffort": {
"description": "See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#get-started-with-reasoning",
"enum": [
@@ -561,16 +514,6 @@
},
{
"properties": {
"access": {
"allOf": [
{
"$ref": "#/definitions/ReadOnlyAccess"
}
],
"default": {
"type": "fullAccess"
}
},
"networkAccess": {
"default": false,
"type": "boolean"
@@ -627,16 +570,6 @@
"default": false,
"type": "boolean"
},
"readOnlyAccess": {
"allOf": [
{
"$ref": "#/definitions/ReadOnlyAccess"
}
],
"default": {
"type": "fullAccess"
}
},
"type": {
"enum": [
"workspaceWrite"
@@ -1,6 +0,0 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { AbsolutePathBuf } from "../AbsolutePathBuf";
export type ReadOnlyAccess = { "type": "restricted", includePlatformDefaults: boolean, readableRoots: Array<AbsolutePathBuf>, } | { "type": "fullAccess" };
@@ -3,6 +3,5 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { AbsolutePathBuf } from "../AbsolutePathBuf";
import type { NetworkAccess } from "./NetworkAccess";
import type { ReadOnlyAccess } from "./ReadOnlyAccess";
export type SandboxPolicy = { "type": "dangerFullAccess" } | { "type": "readOnly", access: ReadOnlyAccess, networkAccess: boolean, } | { "type": "externalSandbox", networkAccess: NetworkAccess, } | { "type": "workspaceWrite", writableRoots: Array<AbsolutePathBuf>, readOnlyAccess: ReadOnlyAccess, networkAccess: boolean, excludeTmpdirEnvVar: boolean, excludeSlashTmp: boolean, };
export type SandboxPolicy = { "type": "dangerFullAccess" } | { "type": "readOnly", networkAccess: boolean, } | { "type": "externalSandbox", networkAccess: NetworkAccess, } | { "type": "workspaceWrite", writableRoots: Array<AbsolutePathBuf>, networkAccess: boolean, excludeTmpdirEnvVar: boolean, excludeSlashTmp: boolean, };
@@ -276,7 +276,6 @@ export type { RateLimitReachedType } from "./RateLimitReachedType";
export type { RateLimitSnapshot } from "./RateLimitSnapshot";
export type { RateLimitWindow } from "./RateLimitWindow";
export type { RawResponseItemCompletedNotification } from "./RawResponseItemCompletedNotification";
export type { ReadOnlyAccess } from "./ReadOnlyAccess";
export type { ReasoningEffortOption } from "./ReasoningEffortOption";
export type { ReasoningSummaryPartAddedNotification } from "./ReasoningSummaryPartAddedNotification";
export type { ReasoningSummaryTextDeltaNotification } from "./ReasoningSummaryTextDeltaNotification";
+149 -114
View File
@@ -83,7 +83,6 @@ use codex_protocol::protocol::PatchApplyStatus as CorePatchApplyStatus;
use codex_protocol::protocol::RateLimitReachedType as CoreRateLimitReachedType;
use codex_protocol::protocol::RateLimitSnapshot as CoreRateLimitSnapshot;
use codex_protocol::protocol::RateLimitWindow as CoreRateLimitWindow;
use codex_protocol::protocol::ReadOnlyAccess as CoreReadOnlyAccess;
use codex_protocol::protocol::RealtimeAudioFrame as CoreRealtimeAudioFrame;
use codex_protocol::protocol::RealtimeConversationVersion;
use codex_protocol::protocol::RealtimeOutputModality;
@@ -808,10 +807,6 @@ const fn default_enabled() -> bool {
true
}
const fn default_include_platform_defaults() -> bool {
true
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS, ExperimentalApi)]
#[serde(rename_all = "snake_case")]
#[ts(export_to = "v2/")]
@@ -1719,54 +1714,7 @@ pub enum NetworkAccess {
Enabled,
}
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(tag = "type", rename_all = "camelCase")]
#[ts(tag = "type")]
#[ts(export_to = "v2/")]
pub enum ReadOnlyAccess {
#[serde(rename_all = "camelCase")]
#[ts(rename_all = "camelCase")]
Restricted {
#[serde(default = "default_include_platform_defaults")]
include_platform_defaults: bool,
#[serde(default)]
readable_roots: Vec<AbsolutePathBuf>,
},
#[default]
FullAccess,
}
impl ReadOnlyAccess {
pub fn to_core(&self) -> CoreReadOnlyAccess {
match self {
ReadOnlyAccess::Restricted {
include_platform_defaults,
readable_roots,
} => CoreReadOnlyAccess::Restricted {
include_platform_defaults: *include_platform_defaults,
readable_roots: readable_roots.clone(),
},
ReadOnlyAccess::FullAccess => CoreReadOnlyAccess::FullAccess,
}
}
}
impl From<CoreReadOnlyAccess> for ReadOnlyAccess {
fn from(value: CoreReadOnlyAccess) -> Self {
match value {
CoreReadOnlyAccess::Restricted {
include_platform_defaults,
readable_roots,
} => ReadOnlyAccess::Restricted {
include_platform_defaults,
readable_roots,
},
CoreReadOnlyAccess::FullAccess => ReadOnlyAccess::FullAccess,
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[derive(Serialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(tag = "type", rename_all = "camelCase")]
#[ts(tag = "type")]
#[ts(export_to = "v2/")]
@@ -1775,8 +1723,6 @@ pub enum SandboxPolicy {
#[serde(rename_all = "camelCase")]
#[ts(rename_all = "camelCase")]
ReadOnly {
#[serde(default)]
access: ReadOnlyAccess,
#[serde(default)]
network_access: bool,
},
@@ -1792,7 +1738,36 @@ pub enum SandboxPolicy {
#[serde(default)]
writable_roots: Vec<AbsolutePathBuf>,
#[serde(default)]
read_only_access: ReadOnlyAccess,
network_access: bool,
#[serde(default)]
exclude_tmpdir_env_var: bool,
#[serde(default)]
exclude_slash_tmp: bool,
},
}
#[derive(Deserialize)]
#[serde(tag = "type", rename_all = "camelCase")]
enum SandboxPolicyDeserialize {
DangerFullAccess,
#[serde(rename_all = "camelCase")]
ReadOnly {
#[serde(default)]
network_access: bool,
#[serde(default)]
access: Option<LegacyReadOnlyAccess>,
},
#[serde(rename_all = "camelCase")]
ExternalSandbox {
#[serde(default)]
network_access: NetworkAccess,
},
#[serde(rename_all = "camelCase")]
WorkspaceWrite {
#[serde(default)]
writable_roots: Vec<AbsolutePathBuf>,
#[serde(default)]
read_only_access: Option<LegacyReadOnlyAccess>,
#[serde(default)]
network_access: bool,
#[serde(default)]
@@ -1802,19 +1777,68 @@ pub enum SandboxPolicy {
},
}
#[derive(Deserialize)]
#[serde(tag = "type", rename_all = "camelCase")]
enum LegacyReadOnlyAccess {
FullAccess,
Restricted,
}
impl<'de> Deserialize<'de> for SandboxPolicy {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
match SandboxPolicyDeserialize::deserialize(deserializer)? {
SandboxPolicyDeserialize::DangerFullAccess => Ok(SandboxPolicy::DangerFullAccess),
SandboxPolicyDeserialize::ReadOnly {
network_access,
access,
} => {
if matches!(access, Some(LegacyReadOnlyAccess::Restricted)) {
return Err(serde::de::Error::custom(
"readOnly.access is no longer supported; use permissionProfile for restricted reads",
));
}
Ok(SandboxPolicy::ReadOnly { network_access })
}
SandboxPolicyDeserialize::ExternalSandbox { network_access } => {
Ok(SandboxPolicy::ExternalSandbox { network_access })
}
SandboxPolicyDeserialize::WorkspaceWrite {
writable_roots,
read_only_access,
network_access,
exclude_tmpdir_env_var,
exclude_slash_tmp,
} => {
if matches!(read_only_access, Some(LegacyReadOnlyAccess::Restricted)) {
return Err(serde::de::Error::custom(
"workspaceWrite.readOnlyAccess is no longer supported; use permissionProfile for restricted reads",
));
}
Ok(SandboxPolicy::WorkspaceWrite {
writable_roots,
network_access,
exclude_tmpdir_env_var,
exclude_slash_tmp,
})
}
}
}
}
impl SandboxPolicy {
pub fn to_core(&self) -> codex_protocol::protocol::SandboxPolicy {
match self {
SandboxPolicy::DangerFullAccess => {
codex_protocol::protocol::SandboxPolicy::DangerFullAccess
}
SandboxPolicy::ReadOnly {
access,
network_access,
} => codex_protocol::protocol::SandboxPolicy::ReadOnly {
access: access.to_core(),
network_access: *network_access,
},
SandboxPolicy::ReadOnly { network_access } => {
codex_protocol::protocol::SandboxPolicy::ReadOnly {
network_access: *network_access,
}
}
SandboxPolicy::ExternalSandbox { network_access } => {
codex_protocol::protocol::SandboxPolicy::ExternalSandbox {
network_access: match network_access {
@@ -1825,13 +1849,11 @@ impl SandboxPolicy {
}
SandboxPolicy::WorkspaceWrite {
writable_roots,
read_only_access,
network_access,
exclude_tmpdir_env_var,
exclude_slash_tmp,
} => codex_protocol::protocol::SandboxPolicy::WorkspaceWrite {
writable_roots: writable_roots.clone(),
read_only_access: read_only_access.to_core(),
network_access: *network_access,
exclude_tmpdir_env_var: *exclude_tmpdir_env_var,
exclude_slash_tmp: *exclude_slash_tmp,
@@ -1846,13 +1868,9 @@ impl From<codex_protocol::protocol::SandboxPolicy> for SandboxPolicy {
codex_protocol::protocol::SandboxPolicy::DangerFullAccess => {
SandboxPolicy::DangerFullAccess
}
codex_protocol::protocol::SandboxPolicy::ReadOnly {
access,
network_access,
} => SandboxPolicy::ReadOnly {
access: ReadOnlyAccess::from(access),
network_access,
},
codex_protocol::protocol::SandboxPolicy::ReadOnly { network_access } => {
SandboxPolicy::ReadOnly { network_access }
}
codex_protocol::protocol::SandboxPolicy::ExternalSandbox { network_access } => {
SandboxPolicy::ExternalSandbox {
network_access: match network_access {
@@ -1863,13 +1881,11 @@ impl From<codex_protocol::protocol::SandboxPolicy> for SandboxPolicy {
}
codex_protocol::protocol::SandboxPolicy::WorkspaceWrite {
writable_roots,
read_only_access,
network_access,
exclude_tmpdir_env_var,
exclude_slash_tmp,
} => SandboxPolicy::WorkspaceWrite {
writable_roots,
read_only_access: ReadOnlyAccess::from(read_only_access),
network_access,
exclude_tmpdir_env_var,
exclude_slash_tmp,
@@ -7578,7 +7594,6 @@ mod tests {
use codex_protocol::items::WebSearchItem;
use codex_protocol::models::WebSearchAction as CoreWebSearchAction;
use codex_protocol::protocol::NetworkAccess as CoreNetworkAccess;
use codex_protocol::protocol::ReadOnlyAccess as CoreReadOnlyAccess;
use codex_protocol::user_input::UserInput as CoreUserInput;
use codex_utils_absolute_path::test_support::PathBufExt;
use codex_utils_absolute_path::test_support::test_path_buf;
@@ -8784,13 +8799,8 @@ mod tests {
}
#[test]
fn sandbox_policy_round_trips_read_only_access() {
let readable_root = test_absolute_path();
fn sandbox_policy_round_trips_read_only_network_access() {
let v2_policy = SandboxPolicy::ReadOnly {
access: ReadOnlyAccess::Restricted {
include_platform_defaults: false,
readable_roots: vec![readable_root.clone()],
},
network_access: true,
};
@@ -8798,10 +8808,6 @@ mod tests {
assert_eq!(
core_policy,
codex_protocol::protocol::SandboxPolicy::ReadOnly {
access: CoreReadOnlyAccess::Restricted {
include_platform_defaults: false,
readable_roots: vec![readable_root],
},
network_access: true,
}
);
@@ -9425,14 +9431,9 @@ mod tests {
}
#[test]
fn sandbox_policy_round_trips_workspace_write_read_only_access() {
let readable_root = test_absolute_path();
fn sandbox_policy_round_trips_workspace_write_access() {
let v2_policy = SandboxPolicy::WorkspaceWrite {
writable_roots: vec![],
read_only_access: ReadOnlyAccess::Restricted {
include_platform_defaults: false,
readable_roots: vec![readable_root.clone()],
},
network_access: true,
exclude_tmpdir_env_var: false,
exclude_slash_tmp: false,
@@ -9443,10 +9444,6 @@ mod tests {
core_policy,
codex_protocol::protocol::SandboxPolicy::WorkspaceWrite {
writable_roots: vec![],
read_only_access: CoreReadOnlyAccess::Restricted {
include_platform_defaults: false,
readable_roots: vec![readable_root],
},
network_access: true,
exclude_tmpdir_env_var: false,
exclude_slash_tmp: false,
@@ -9458,40 +9455,78 @@ mod tests {
}
#[test]
fn sandbox_policy_deserializes_legacy_read_only_without_access_field() {
let policy: SandboxPolicy = serde_json::from_value(json!({
"type": "readOnly"
fn sandbox_policy_deserializes_legacy_read_only_full_access_field() {
let policy = serde_json::from_value::<SandboxPolicy>(json!({
"type": "readOnly",
"access": {
"type": "fullAccess"
},
"networkAccess": true
}))
.expect("read-only policy should deserialize");
.expect("read-only policy should ignore legacy fullAccess field");
assert_eq!(
policy,
SandboxPolicy::ReadOnly {
access: ReadOnlyAccess::FullAccess,
network_access: false,
network_access: true
}
);
}
#[test]
fn sandbox_policy_deserializes_legacy_workspace_write_without_read_only_access_field() {
let policy: SandboxPolicy = serde_json::from_value(json!({
fn sandbox_policy_deserializes_legacy_workspace_write_full_access_field() {
let writable_root = absolute_path("/workspace");
let policy = serde_json::from_value::<SandboxPolicy>(json!({
"type": "workspaceWrite",
"writableRoots": [writable_root],
"readOnlyAccess": {
"type": "fullAccess"
},
"networkAccess": true,
"excludeTmpdirEnvVar": true,
"excludeSlashTmp": true
}))
.expect("workspace-write policy should ignore legacy fullAccess field");
assert_eq!(
policy,
SandboxPolicy::WorkspaceWrite {
writable_roots: vec![absolute_path("/workspace")],
network_access: true,
exclude_tmpdir_env_var: true,
exclude_slash_tmp: true,
}
);
}
#[test]
fn sandbox_policy_rejects_legacy_read_only_restricted_access_field() {
let err = serde_json::from_value::<SandboxPolicy>(json!({
"type": "readOnly",
"access": {
"type": "restricted",
"includePlatformDefaults": false,
"readableRoots": []
}
}))
.expect_err("read-only policy should reject removed restricted access field");
assert!(err.to_string().contains("readOnly.access"));
}
#[test]
fn sandbox_policy_rejects_legacy_workspace_write_restricted_read_access_field() {
let err = serde_json::from_value::<SandboxPolicy>(json!({
"type": "workspaceWrite",
"writableRoots": [],
"readOnlyAccess": {
"type": "restricted",
"includePlatformDefaults": false,
"readableRoots": []
},
"networkAccess": false,
"excludeTmpdirEnvVar": false,
"excludeSlashTmp": false
}))
.expect("workspace-write policy should deserialize");
assert_eq!(
policy,
SandboxPolicy::WorkspaceWrite {
writable_roots: vec![],
read_only_access: ReadOnlyAccess::FullAccess,
network_access: false,
exclude_tmpdir_env_var: false,
exclude_slash_tmp: false,
}
);
.expect_err("workspace-write policy should reject removed restricted readOnlyAccess field");
assert!(err.to_string().contains("workspaceWrite.readOnlyAccess"));
}
#[test]
@@ -48,7 +48,6 @@ use codex_app_server_protocol::JSONRPCResponse;
use codex_app_server_protocol::LoginAccountResponse;
use codex_app_server_protocol::ModelListParams;
use codex_app_server_protocol::ModelListResponse;
use codex_app_server_protocol::ReadOnlyAccess;
use codex_app_server_protocol::RequestId;
use codex_app_server_protocol::SandboxPolicy;
use codex_app_server_protocol::ServerNotification;
@@ -743,7 +742,6 @@ async fn trigger_zsh_fork_multi_cmd_approval(
};
turn_params.approval_policy = Some(AskForApproval::OnRequest);
turn_params.sandbox_policy = Some(SandboxPolicy::ReadOnly {
access: ReadOnlyAccess::FullAccess,
network_access: false,
});
@@ -885,7 +883,6 @@ async fn trigger_cmd_approval(
experimental_api: true,
approval_policy: Some(AskForApproval::OnRequest),
sandbox_policy: Some(SandboxPolicy::ReadOnly {
access: ReadOnlyAccess::FullAccess,
network_access: false,
}),
dynamic_tools,
@@ -912,7 +909,6 @@ async fn trigger_patch_approval(
experimental_api: true,
approval_policy: Some(AskForApproval::OnRequest),
sandbox_policy: Some(SandboxPolicy::ReadOnly {
access: ReadOnlyAccess::FullAccess,
network_access: false,
}),
dynamic_tools,
-3
View File
@@ -712,7 +712,6 @@ mod tests {
use codex_protocol::config_types::WindowsSandboxLevel;
use codex_protocol::permissions::FileSystemSandboxPolicy;
use codex_protocol::permissions::NetworkSandboxPolicy;
use codex_protocol::protocol::ReadOnlyAccess;
use codex_protocol::protocol::SandboxPolicy;
use codex_utils_absolute_path::AbsolutePathBuf;
use pretty_assertions::assert_eq;
@@ -731,7 +730,6 @@ mod tests {
fn windows_sandbox_exec_request() -> ExecRequest {
let sandbox_policy = SandboxPolicy::ReadOnly {
access: ReadOnlyAccess::FullAccess,
network_access: false,
};
ExecRequest::new(
@@ -837,7 +835,6 @@ mod tests {
request_id: codex_app_server_protocol::RequestId::Integer(100),
};
let sandbox_policy = SandboxPolicy::ReadOnly {
access: ReadOnlyAccess::FullAccess,
network_access: false,
};
@@ -1895,7 +1895,6 @@ async fn turn_start_updates_sandbox_and_cwd_between_turns_v2() -> Result<()> {
approvals_reviewer: None,
sandbox_policy: Some(codex_app_server_protocol::SandboxPolicy::WorkspaceWrite {
writable_roots: vec![first_cwd.try_into()?],
read_only_access: codex_app_server_protocol::ReadOnlyAccess::FullAccess,
network_access: false,
exclude_tmpdir_env_var: false,
exclude_slash_tmp: false,
@@ -536,7 +536,6 @@ async fn turn_start_shell_zsh_fork_subcommand_decline_marks_parent_declined_v2()
approval_policy: Some(codex_app_server_protocol::AskForApproval::UnlessTrusted),
sandbox_policy: Some(codex_app_server_protocol::SandboxPolicy::WorkspaceWrite {
writable_roots: vec![workspace.clone().try_into()?],
read_only_access: codex_app_server_protocol::ReadOnlyAccess::FullAccess,
network_access: false,
exclude_tmpdir_env_var: false,
exclude_slash_tmp: false,
@@ -1961,7 +1961,6 @@ allowed_approvals_reviewers = ["user"]
.sandbox_policy
.can_set(&SandboxPolicy::WorkspaceWrite {
writable_roots: vec![AbsolutePathBuf::from_absolute_path(root)?],
read_only_access: Default::default(),
network_access: false,
exclude_tmpdir_env_var: false,
exclude_slash_tmp: false,
@@ -2070,7 +2069,6 @@ allowed_approvals_reviewers = ["user"]
.sandbox_policy
.can_set(&SandboxPolicy::WorkspaceWrite {
writable_roots: vec![AbsolutePathBuf::from_absolute_path(root)?],
read_only_access: Default::default(),
network_access: false,
exclude_tmpdir_env_var: false,
exclude_slash_tmp: false,
-2
View File
@@ -49,7 +49,6 @@ use codex_protocol::config_types::WebSearchToolConfig;
use codex_protocol::config_types::WindowsSandboxLevel;
use codex_protocol::openai_models::ReasoningEffort;
use codex_protocol::protocol::AskForApproval;
use codex_protocol::protocol::ReadOnlyAccess;
use codex_protocol::protocol::SandboxPolicy;
use codex_utils_absolute_path::AbsolutePathBuf;
use codex_utils_path::normalize_for_path_comparison;
@@ -685,7 +684,6 @@ impl ConfigToml {
exclude_slash_tmp,
}) => SandboxPolicy::WorkspaceWrite {
writable_roots: writable_roots.clone(),
read_only_access: ReadOnlyAccess::FullAccess,
network_access: *network_access,
exclude_tmpdir_env_var: *exclude_tmpdir_env_var,
exclude_slash_tmp: *exclude_slash_tmp,
+6 -11
View File
@@ -51,23 +51,18 @@ sandboxed shell commands that would enter the bubblewrap path before invoking
### Windows
Legacy `SandboxPolicy` / `sandbox_mode` configs are still supported on
Windows.
The elevated setup/runner backend supports legacy `ReadOnlyAccess::Restricted`
for `read-only` and `workspace-write` policies. Restricted read access honors
explicit readable roots plus the command `cwd`, and keeps writable roots
readable when `workspace-write` is used.
When `include_platform_defaults = true`, the elevated Windows backend adds
backend-managed system read roots required for basic execution, such as
`C:\Windows`, `C:\Program Files`, `C:\Program Files (x86)`, and
`C:\ProgramData`. When it is `false`, those extra system roots are omitted.
Windows. Legacy `read-only` and `workspace-write` policies imply full
filesystem read access; exact readable roots are represented by split
filesystem policies instead.
The elevated Windows sandbox also supports:
- legacy `ReadOnly` and `WorkspaceWrite` behavior
- split filesystem policies that need exact readable roots, exact writable
roots, or extra read-only carveouts under writable roots
- backend-managed system read roots required for basic execution, such as
`C:\Windows`, `C:\Program Files`, `C:\Program Files (x86)`, and
`C:\ProgramData`, when a split filesystem policy requests platform defaults
The unelevated restricted-token backend still supports the legacy full-read
Windows model for legacy `ReadOnly` and `WorkspaceWrite` behavior. It also
-15
View File
@@ -63,7 +63,6 @@ use codex_protocol::permissions::FileSystemSandboxEntry;
use codex_protocol::permissions::FileSystemSandboxPolicy;
use codex_protocol::permissions::FileSystemSpecialPath;
use codex_protocol::permissions::NetworkSandboxPolicy;
use codex_protocol::protocol::ReadOnlyAccess;
use codex_protocol::protocol::RealtimeVoice;
use codex_protocol::protocol::SandboxPolicy;
use serde::Deserialize;
@@ -789,10 +788,6 @@ async fn default_permissions_profile_populates_runtime_sandbox_policy() -> std::
config.permissions.sandbox_policy.get(),
&SandboxPolicy::WorkspaceWrite {
writable_roots: vec![memories_root],
read_only_access: ReadOnlyAccess::Restricted {
include_platform_defaults: true,
readable_roots: vec![cwd.path().join("docs").abs(),],
},
network_access: false,
exclude_tmpdir_env_var: true,
exclude_slash_tmp: true,
@@ -1150,10 +1145,6 @@ async fn permissions_profiles_allow_unknown_special_paths() -> std::io::Result<(
assert_eq!(
config.permissions.sandbox_policy.get(),
&SandboxPolicy::ReadOnly {
access: ReadOnlyAccess::Restricted {
include_platform_defaults: false,
readable_roots: Vec::new(),
},
network_access: false,
}
);
@@ -1219,10 +1210,6 @@ async fn permissions_profiles_allow_missing_filesystem_with_warning() -> std::io
assert_eq!(
config.permissions.sandbox_policy.get(),
&SandboxPolicy::ReadOnly {
access: ReadOnlyAccess::Restricted {
include_platform_defaults: false,
readable_roots: Vec::new(),
},
network_access: false,
}
);
@@ -1485,7 +1472,6 @@ trust_level = "trusted"
resolution,
SandboxPolicy::WorkspaceWrite {
writable_roots: vec![writable_root.clone()],
read_only_access: ReadOnlyAccess::FullAccess,
network_access: false,
exclude_tmpdir_env_var: true,
exclude_slash_tmp: true,
@@ -1526,7 +1512,6 @@ exclude_slash_tmp = true
resolution,
SandboxPolicy::WorkspaceWrite {
writable_roots: vec![writable_root],
read_only_access: ReadOnlyAccess::FullAccess,
network_access: false,
exclude_tmpdir_env_var: true,
exclude_slash_tmp: true,
-1
View File
@@ -591,7 +591,6 @@ allowed_sandbox_modes = ["read-only"]
.sandbox_policy
.can_set(&SandboxPolicy::WorkspaceWrite {
writable_roots: Vec::new(),
read_only_access: Default::default(),
network_access: false,
exclude_tmpdir_env_var: false,
exclude_slash_tmp: false,
@@ -51,7 +51,6 @@ fn builds_permissions_with_network_access_override() {
fn builds_permissions_from_policy() {
let policy = SandboxPolicy::WorkspaceWrite {
writable_roots: vec![],
read_only_access: Default::default(),
network_access: true,
exclude_tmpdir_env_var: false,
exclude_slash_tmp: false,
+12 -15
View File
@@ -99,11 +99,13 @@ pub struct ExecParams {
/// The unelevated restricted-token backend only consumes extra deny-write
/// carveouts on top of the legacy `WorkspaceWrite` allow set. The elevated
/// backend can also consume explicit read and write roots during setup/refresh.
/// Read-root overrides are layered on top of the baseline helper/platform roots
/// that the elevated setup path needs to launch the sandboxed command.
/// Read-root overrides are layered on top of the baseline helper roots that the
/// elevated setup path needs to launch the sandboxed command. Split policies
/// that opt into platform defaults carry that explicitly with the override.
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct WindowsSandboxFilesystemOverrides {
pub(crate) read_roots_override: Option<Vec<PathBuf>>,
pub(crate) read_roots_include_platform_defaults: bool,
pub(crate) write_roots_override: Option<Vec<PathBuf>>,
pub(crate) additional_deny_write_paths: Vec<AbsolutePathBuf>,
}
@@ -546,6 +548,8 @@ async fn exec_windows_sandbox(
.unwrap_or_default();
let elevated_read_roots_override = windows_sandbox_filesystem_overrides
.and_then(|overrides| overrides.read_roots_override.clone());
let elevated_read_roots_include_platform_defaults = windows_sandbox_filesystem_overrides
.is_some_and(|overrides| overrides.read_roots_include_platform_defaults);
let elevated_write_roots_override = windows_sandbox_filesystem_overrides
.and_then(|overrides| overrides.write_roots_override.clone());
let elevated_deny_write_paths = windows_sandbox_filesystem_overrides
@@ -571,6 +575,8 @@ async fn exec_windows_sandbox(
use_private_desktop: windows_sandbox_private_desktop,
proxy_enforced,
read_roots_override: elevated_read_roots_override.as_deref(),
read_roots_include_platform_defaults:
elevated_read_roots_include_platform_defaults,
write_roots_override: elevated_write_roots_override.as_deref(),
deny_write_paths_override: &elevated_deny_write_paths,
},
@@ -1064,6 +1070,7 @@ pub(crate) fn resolve_windows_restricted_token_filesystem_overrides(
Ok(Some(WindowsSandboxFilesystemOverrides {
read_roots_override: None,
read_roots_include_platform_defaults: false,
write_roots_override: None,
additional_deny_write_paths: additional_deny_write_paths
.into_iter()
@@ -1127,12 +1134,6 @@ pub(crate) fn resolve_windows_elevated_filesystem_overrides(
.needs_direct_runtime_enforcement(network_sandbox_policy, sandbox_policy_cwd);
let normalize_path = |path: PathBuf| dunce::canonicalize(&path).unwrap_or(path);
let legacy_writable_roots = sandbox_policy.get_writable_roots_with_cwd(sandbox_policy_cwd);
let legacy_readable_root_set: BTreeSet<PathBuf> = sandbox_policy
.get_readable_roots_with_cwd(sandbox_policy_cwd)
.into_iter()
.map(codex_utils_absolute_path::AbsolutePathBuf::into_path_buf)
.map(&normalize_path)
.collect();
let legacy_root_paths: BTreeSet<PathBuf> = legacy_writable_roots
.iter()
.map(|root| normalize_path(root.root.to_path_buf()))
@@ -1143,19 +1144,13 @@ pub(crate) fn resolve_windows_elevated_filesystem_overrides(
.map(codex_utils_absolute_path::AbsolutePathBuf::into_path_buf)
.map(&normalize_path)
.collect();
let split_readable_root_set: BTreeSet<PathBuf> = split_readable_roots.iter().cloned().collect();
let split_root_paths: Vec<PathBuf> = split_writable_roots
.iter()
.map(|root| normalize_path(root.root.to_path_buf()))
.collect();
let split_root_path_set: BTreeSet<PathBuf> = split_root_paths.iter().cloned().collect();
let matches_legacy_read_access = file_system_sandbox_policy.has_full_disk_read_access()
== sandbox_policy.has_full_disk_read_access();
let read_roots_override = if matches_legacy_read_access
&& (file_system_sandbox_policy.has_full_disk_read_access()
|| split_readable_root_set == legacy_readable_root_set)
{
let read_roots_override = if file_system_sandbox_policy.has_full_disk_read_access() {
None
} else {
Some(split_readable_roots)
@@ -1209,6 +1204,8 @@ pub(crate) fn resolve_windows_elevated_filesystem_overrides(
}
Ok(Some(WindowsSandboxFilesystemOverrides {
read_roots_include_platform_defaults: read_roots_override.is_some()
&& file_system_sandbox_policy.include_platform_defaults(),
read_roots_override,
write_roots_override,
additional_deny_write_paths,
+10 -15
View File
@@ -470,7 +470,6 @@ fn windows_restricted_token_allows_legacy_restricted_policies() {
fn windows_restricted_token_allows_legacy_workspace_write_policies() {
let policy = SandboxPolicy::WorkspaceWrite {
writable_roots: vec![],
read_only_access: codex_protocol::protocol::ReadOnlyAccess::FullAccess,
network_access: false,
exclude_tmpdir_env_var: true,
exclude_slash_tmp: true,
@@ -492,7 +491,7 @@ fn windows_restricted_token_allows_legacy_workspace_write_policies() {
}
#[test]
fn windows_elevated_allows_legacy_restricted_read_policies() {
fn windows_elevated_allows_split_restricted_read_policies() {
let temp_dir = tempfile::TempDir::new().expect("tempdir");
let docs = codex_utils_absolute_path::AbsolutePathBuf::from_absolute_path(
temp_dir.path().join("docs"),
@@ -500,13 +499,14 @@ fn windows_elevated_allows_legacy_restricted_read_policies() {
.expect("absolute docs");
std::fs::create_dir_all(docs.as_path()).expect("create docs");
let policy = SandboxPolicy::ReadOnly {
access: codex_protocol::protocol::ReadOnlyAccess::Restricted {
readable_roots: vec![docs],
include_platform_defaults: false,
},
network_access: false,
};
let file_system_policy = FileSystemSandboxPolicy::from(&policy);
let file_system_policy = FileSystemSandboxPolicy::restricted(vec![
codex_protocol::permissions::FileSystemSandboxEntry {
path: codex_protocol::permissions::FileSystemPath::Path { path: docs },
access: codex_protocol::permissions::FileSystemAccessMode::Read,
},
]);
assert_eq!(
unsupported_windows_restricted_token_sandbox_reason(
@@ -528,7 +528,6 @@ fn windows_restricted_token_rejects_split_only_filesystem_policies() {
std::fs::create_dir_all(&docs).expect("create docs");
let policy = SandboxPolicy::WorkspaceWrite {
writable_roots: vec![],
read_only_access: codex_protocol::protocol::ReadOnlyAccess::FullAccess,
network_access: false,
exclude_tmpdir_env_var: true,
exclude_slash_tmp: true,
@@ -572,7 +571,6 @@ fn windows_restricted_token_rejects_root_write_read_only_carveouts() {
std::fs::create_dir_all(&docs).expect("create docs");
let policy = SandboxPolicy::WorkspaceWrite {
writable_roots: vec![],
read_only_access: codex_protocol::protocol::ReadOnlyAccess::FullAccess,
network_access: false,
exclude_tmpdir_env_var: true,
exclude_slash_tmp: true,
@@ -619,7 +617,6 @@ fn windows_restricted_token_supports_full_read_split_write_read_carveouts() {
std::fs::create_dir_all(docs.as_path()).expect("create docs");
let policy = SandboxPolicy::WorkspaceWrite {
writable_roots: vec![],
read_only_access: codex_protocol::protocol::ReadOnlyAccess::FullAccess,
network_access: false,
exclude_tmpdir_env_var: true,
exclude_slash_tmp: true,
@@ -658,6 +655,7 @@ fn windows_restricted_token_supports_full_read_split_write_read_carveouts() {
),
Ok(Some(WindowsSandboxFilesystemOverrides {
read_roots_override: None,
read_roots_include_platform_defaults: false,
write_roots_override: None,
additional_deny_write_paths: expected_deny_write_paths,
}))
@@ -671,7 +669,6 @@ fn windows_elevated_supports_split_restricted_read_roots() {
std::fs::create_dir_all(&docs).expect("create docs");
let expected_docs = dunce::canonicalize(&docs).expect("canonical docs");
let policy = SandboxPolicy::ReadOnly {
access: codex_protocol::protocol::ReadOnlyAccess::FullAccess,
network_access: false,
};
let file_system_policy = FileSystemSandboxPolicy::restricted(vec![
@@ -695,6 +692,7 @@ fn windows_elevated_supports_split_restricted_read_roots() {
),
Ok(Some(WindowsSandboxFilesystemOverrides {
read_roots_override: Some(vec![expected_docs]),
read_roots_include_platform_defaults: false,
write_roots_override: None,
additional_deny_write_paths: vec![],
}))
@@ -709,7 +707,6 @@ fn windows_elevated_supports_split_write_read_carveouts() {
let expected_docs = dunce::canonicalize(&docs).expect("canonical docs");
let policy = SandboxPolicy::WorkspaceWrite {
writable_roots: vec![],
read_only_access: codex_protocol::protocol::ReadOnlyAccess::FullAccess,
network_access: false,
exclude_tmpdir_env_var: true,
exclude_slash_tmp: true,
@@ -747,6 +744,7 @@ fn windows_elevated_supports_split_write_read_carveouts() {
),
Ok(Some(WindowsSandboxFilesystemOverrides {
read_roots_override: None,
read_roots_include_platform_defaults: false,
write_roots_override: None,
additional_deny_write_paths: vec![
codex_utils_absolute_path::AbsolutePathBuf::from_absolute_path(expected_docs)
@@ -763,7 +761,6 @@ fn windows_elevated_rejects_unreadable_split_carveouts() {
std::fs::create_dir_all(&blocked).expect("create blocked");
let policy = SandboxPolicy::WorkspaceWrite {
writable_roots: vec![],
read_only_access: codex_protocol::protocol::ReadOnlyAccess::FullAccess,
network_access: false,
exclude_tmpdir_env_var: true,
exclude_slash_tmp: true,
@@ -811,7 +808,6 @@ fn windows_elevated_rejects_unreadable_globs() {
let temp_dir = tempfile::TempDir::new().expect("tempdir");
let policy = SandboxPolicy::WorkspaceWrite {
writable_roots: vec![],
read_only_access: codex_protocol::protocol::ReadOnlyAccess::FullAccess,
network_access: false,
exclude_tmpdir_env_var: true,
exclude_slash_tmp: true,
@@ -861,7 +857,6 @@ fn windows_elevated_rejects_reopened_writable_descendants() {
std::fs::create_dir_all(&nested).expect("create nested");
let policy = SandboxPolicy::WorkspaceWrite {
writable_roots: vec![],
read_only_access: codex_protocol::protocol::ReadOnlyAccess::FullAccess,
network_access: false,
exclude_tmpdir_env_var: true,
exclude_slash_tmp: true,
-1
View File
@@ -323,7 +323,6 @@ mod agent {
// The consolidation agent only needs local memory-root write access and no network.
let consolidation_sandbox_policy = SandboxPolicy::WorkspaceWrite {
writable_roots,
read_only_access: Default::default(),
network_access: false,
exclude_tmpdir_env_var: true,
exclude_slash_tmp: true,
-5
View File
@@ -28,7 +28,6 @@ fn test_writable_roots_constraint() {
// only `cwd` is writable by default.
let policy_workspace_only = SandboxPolicy::WorkspaceWrite {
writable_roots: vec![],
read_only_access: Default::default(),
network_access: false,
exclude_tmpdir_env_var: true,
exclude_slash_tmp: true,
@@ -50,7 +49,6 @@ fn test_writable_roots_constraint() {
// outside write should be permitted.
let policy_with_parent = SandboxPolicy::WorkspaceWrite {
writable_roots: vec![parent],
read_only_access: Default::default(),
network_access: false,
exclude_tmpdir_env_var: true,
exclude_slash_tmp: true,
@@ -98,7 +96,6 @@ fn granular_with_all_flags_true_matches_on_request_for_out_of_root_patch() {
let add_outside = ApplyPatchAction::new_add_for_test(&outside_path, "".to_string());
let policy_workspace_only = SandboxPolicy::WorkspaceWrite {
writable_roots: vec![],
read_only_access: Default::default(),
network_access: false,
exclude_tmpdir_env_var: true,
exclude_slash_tmp: true,
@@ -143,7 +140,6 @@ fn granular_sandbox_approval_false_rejects_out_of_root_patch() {
let add_outside = ApplyPatchAction::new_add_for_test(&outside_path, "".to_string());
let policy_workspace_only = SandboxPolicy::WorkspaceWrite {
writable_roots: vec![],
read_only_access: Default::default(),
network_access: false,
exclude_tmpdir_env_var: true,
exclude_slash_tmp: true,
@@ -294,7 +290,6 @@ fn missing_project_dot_codex_config_requires_approval() {
let action = ApplyPatchAction::new_add_for_test(&config_path, "".to_string());
let sandbox_policy = SandboxPolicy::WorkspaceWrite {
writable_roots: vec![],
read_only_access: Default::default(),
network_access: false,
exclude_tmpdir_env_var: true,
exclude_slash_tmp: true,
-13
View File
@@ -43,7 +43,6 @@ use codex_protocol::permissions::FileSystemSandboxEntry;
use codex_protocol::permissions::FileSystemSandboxPolicy;
use codex_protocol::permissions::FileSystemSpecialPath;
use codex_protocol::protocol::NonSteerableTurnKind;
use codex_protocol::protocol::ReadOnlyAccess;
use codex_protocol::protocol::SandboxPolicy;
use codex_protocol::request_permissions::PermissionGrantScope;
use codex_protocol::request_permissions::RequestPermissionProfile;
@@ -2836,10 +2835,6 @@ async fn session_configuration_apply_preserves_split_file_system_policy_on_cwd_o
session_configuration.sandbox_policy =
codex_config::Constrained::allow_any(SandboxPolicy::WorkspaceWrite {
writable_roots: Vec::new(),
read_only_access: ReadOnlyAccess::Restricted {
include_platform_defaults: true,
readable_roots: vec![docs_dir.clone()],
},
network_access: false,
exclude_tmpdir_env_var: true,
exclude_slash_tmp: true,
@@ -3010,18 +3005,10 @@ async fn session_configuration_apply_rederives_legacy_file_system_policy_on_cwd_
let workspace = tempfile::tempdir().expect("create temp dir");
let project_root = workspace.path().join("project");
let original_cwd = project_root.join("subdir");
let docs_dir = original_cwd.join("docs");
std::fs::create_dir_all(&docs_dir).expect("create docs dir");
let docs_dir = docs_dir.abs();
session_configuration.cwd = original_cwd.abs();
session_configuration.sandbox_policy =
codex_config::Constrained::allow_any(SandboxPolicy::WorkspaceWrite {
writable_roots: Vec::new(),
read_only_access: ReadOnlyAccess::Restricted {
include_platform_defaults: true,
readable_roots: vec![docs_dir],
},
network_access: false,
exclude_tmpdir_env_var: true,
exclude_slash_tmp: true,
@@ -239,7 +239,6 @@ fn write_permissions_for_paths_skip_dirs_already_writable_under_workspace_root()
.expect("nested file path should be absolute");
let sandbox_policy = FileSystemSandboxPolicy::from(&SandboxPolicy::WorkspaceWrite {
writable_roots: vec![],
read_only_access: Default::default(),
network_access: false,
exclude_tmpdir_env_var: true,
exclude_slash_tmp: false,
@@ -262,7 +261,6 @@ fn write_permissions_for_paths_keep_dirs_outside_workspace_root() {
let cwd_abs = cwd.abs();
let sandbox_policy = FileSystemSandboxPolicy::from(&SandboxPolicy::WorkspaceWrite {
writable_roots: vec![],
read_only_access: Default::default(),
network_access: false,
exclude_tmpdir_env_var: true,
exclude_slash_tmp: true,
@@ -28,7 +28,6 @@ use codex_protocol::permissions::NetworkSandboxPolicy;
use codex_protocol::protocol::AskForApproval;
use codex_protocol::protocol::GranularApprovalConfig;
use codex_protocol::protocol::GuardianCommandSource;
use codex_protocol::protocol::ReadOnlyAccess;
use codex_protocol::protocol::SandboxPolicy;
use codex_sandboxing::SandboxType;
use codex_shell_escalation::EscalationExecution;
@@ -269,7 +268,6 @@ fn shell_request_escalation_execution_is_explicit() {
};
let sandbox_policy = SandboxPolicy::WorkspaceWrite {
writable_roots: vec![AbsolutePathBuf::from_absolute_path("/tmp/original/output").unwrap()],
read_only_access: ReadOnlyAccess::FullAccess,
network_access: false,
exclude_tmpdir_env_var: false,
exclude_slash_tmp: false,
-1
View File
@@ -43,7 +43,6 @@ impl ZshForkRuntime {
pub fn restrictive_workspace_write_policy() -> SandboxPolicy {
SandboxPolicy::WorkspaceWrite {
writable_roots: Vec::new(),
read_only_access: Default::default(),
network_access: false,
exclude_tmpdir_env_var: true,
exclude_slash_tmp: true,
@@ -643,7 +643,6 @@ async fn apply_patch_cli_rejects_path_traversal_outside_workspace(
let sandbox_policy = SandboxPolicy::WorkspaceWrite {
writable_roots: vec![],
read_only_access: Default::default(),
network_access: false,
exclude_tmpdir_env_var: true,
exclude_slash_tmp: true,
@@ -699,7 +698,6 @@ async fn apply_patch_cli_rejects_move_path_traversal_outside_workspace(
let sandbox_policy = SandboxPolicy::WorkspaceWrite {
writable_roots: vec![],
read_only_access: Default::default(),
network_access: false,
exclude_tmpdir_env_var: true,
exclude_slash_tmp: true,
-4
View File
@@ -768,7 +768,6 @@ fn scenarios() -> Vec<ScenarioSpec> {
let workspace_write = |network_access| SandboxPolicy::WorkspaceWrite {
writable_roots: vec![],
read_only_access: Default::default(),
network_access,
exclude_tmpdir_env_var: false,
exclude_slash_tmp: false,
@@ -1799,7 +1798,6 @@ async fn approving_apply_patch_for_session_skips_future_prompts_for_same_file()
let approval_policy = AskForApproval::OnRequest;
let sandbox_policy = SandboxPolicy::WorkspaceWrite {
writable_roots: vec![],
read_only_access: Default::default(),
network_access: false,
exclude_tmpdir_env_var: false,
exclude_slash_tmp: false,
@@ -2529,7 +2527,6 @@ allow_local_binding = true
let approval_policy = AskForApproval::OnFailure;
let sandbox_policy = SandboxPolicy::WorkspaceWrite {
writable_roots: vec![],
read_only_access: Default::default(),
network_access: true,
exclude_tmpdir_env_var: false,
exclude_slash_tmp: false,
@@ -2831,7 +2828,6 @@ allow_local_binding = true
let approval_policy = AskForApproval::OnFailure;
let turn_sandbox_policy = SandboxPolicy::WorkspaceWrite {
writable_roots: vec![],
read_only_access: Default::default(),
network_access: true,
exclude_tmpdir_env_var: false,
exclude_slash_tmp: false,
-2
View File
@@ -1417,7 +1417,6 @@ async fn permission_request_hook_allows_apply_patch_with_write_alias() -> Result
AskForApproval::OnRequest,
SandboxPolicy::WorkspaceWrite {
writable_roots: vec![],
read_only_access: Default::default(),
network_access: false,
exclude_tmpdir_env_var: true,
exclude_slash_tmp: true,
@@ -1566,7 +1565,6 @@ allow_local_binding = true
let approval_policy = AskForApproval::OnFailure;
let sandbox_policy = SandboxPolicy::WorkspaceWrite {
writable_roots: vec![],
read_only_access: Default::default(),
network_access: true,
exclude_tmpdir_env_var: false,
exclude_slash_tmp: false,
@@ -542,7 +542,6 @@ async fn permissions_message_includes_writable_roots() -> Result<()> {
let writable_root = AbsolutePathBuf::try_from(writable.path())?;
let sandbox_policy = SandboxPolicy::WorkspaceWrite {
writable_roots: vec![writable_root],
read_only_access: Default::default(),
network_access: false,
exclude_tmpdir_env_var: false,
exclude_slash_tmp: false,
@@ -433,7 +433,6 @@ async fn overrides_turn_context_but_keeps_cached_prefix_and_key_constant() -> an
let writable = TempDir::new().unwrap();
let new_policy = SandboxPolicy::WorkspaceWrite {
writable_roots: vec![writable.path().try_into().unwrap()],
read_only_access: Default::default(),
network_access: true,
exclude_tmpdir_env_var: true,
exclude_slash_tmp: true,
@@ -712,7 +711,6 @@ async fn per_turn_overrides_keep_cached_prefix_and_key_constant() -> anyhow::Res
let writable = TempDir::new().unwrap();
let new_policy = SandboxPolicy::WorkspaceWrite {
writable_roots: vec![writable.abs()],
read_only_access: Default::default(),
network_access: true,
exclude_tmpdir_env_var: true,
exclude_slash_tmp: true,
+22 -27
View File
@@ -4,8 +4,12 @@ use codex_exec_server::CopyOptions;
use codex_exec_server::CreateDirectoryOptions;
use codex_exec_server::FileSystemSandboxContext;
use codex_exec_server::RemoveOptions;
use codex_protocol::protocol::ReadOnlyAccess;
use codex_protocol::protocol::SandboxPolicy;
use codex_protocol::models::PermissionProfile;
use codex_protocol::permissions::FileSystemAccessMode;
use codex_protocol::permissions::FileSystemPath;
use codex_protocol::permissions::FileSystemSandboxEntry;
use codex_protocol::permissions::FileSystemSandboxPolicy;
use codex_protocol::permissions::NetworkSandboxPolicy;
use codex_utils_absolute_path::AbsolutePathBuf;
use core_test_support::PathBufExt;
use core_test_support::get_remote_test_env;
@@ -60,37 +64,28 @@ fn absolute_path(path: PathBuf) -> AbsolutePathBuf {
fn read_only_sandbox(readable_root: PathBuf) -> FileSystemSandboxContext {
let readable_root = absolute_path(readable_root);
// The policy is evaluated in the remote container, so use a container path
// for cwd instead of capturing the local test runner cwd.
FileSystemSandboxContext::from_legacy_sandbox_policy(
SandboxPolicy::ReadOnly {
access: ReadOnlyAccess::Restricted {
include_platform_defaults: false,
readable_roots: vec![readable_root.clone()],
FileSystemSandboxContext::from_permission_profile(PermissionProfile::from_runtime_permissions(
&FileSystemSandboxPolicy::restricted(vec![FileSystemSandboxEntry {
path: FileSystemPath::Path {
path: readable_root,
},
network_access: false,
},
readable_root,
)
access: FileSystemAccessMode::Read,
}]),
NetworkSandboxPolicy::Restricted,
))
}
fn workspace_write_sandbox(writable_root: PathBuf) -> FileSystemSandboxContext {
let writable_root = absolute_path(writable_root);
// The policy is evaluated in the remote container, so use a container path
// for cwd instead of capturing the local test runner cwd.
FileSystemSandboxContext::from_legacy_sandbox_policy(
SandboxPolicy::WorkspaceWrite {
writable_roots: vec![writable_root.clone()],
read_only_access: ReadOnlyAccess::Restricted {
include_platform_defaults: false,
readable_roots: vec![],
FileSystemSandboxContext::from_permission_profile(PermissionProfile::from_runtime_permissions(
&FileSystemSandboxPolicy::restricted(vec![FileSystemSandboxEntry {
path: FileSystemPath::Path {
path: writable_root,
},
network_access: false,
exclude_tmpdir_env_var: true,
exclude_slash_tmp: true,
},
writable_root,
)
access: FileSystemAccessMode::Write,
}]),
NetworkSandboxPolicy::Restricted,
))
}
fn assert_normalized_path_rejected(error: &std::io::Error) {
@@ -286,7 +286,6 @@ async fn expect_request_permissions_event(
fn workspace_write_excluding_tmp() -> SandboxPolicy {
SandboxPolicy::WorkspaceWrite {
writable_roots: vec![],
read_only_access: Default::default(),
network_access: false,
exclude_tmpdir_env_var: true,
exclude_slash_tmp: true,
@@ -73,7 +73,6 @@ fn build_add_file_patch(patch_path: &Path, content: &str) -> String {
fn workspace_write_excluding_tmp() -> SandboxPolicy {
SandboxPolicy::WorkspaceWrite {
writable_roots: vec![],
read_only_access: Default::default(),
network_access: false,
exclude_tmpdir_env_var: true,
exclude_slash_tmp: true,
@@ -138,7 +138,6 @@ async fn submit_turn_with_timeout(test: &TestCodex, prompt: &str) -> Result<()>
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::WorkspaceWrite {
writable_roots: Vec::new(),
read_only_access: Default::default(),
network_access: false,
exclude_tmpdir_env_var: false,
exclude_slash_tmp: false,
-10
View File
@@ -7,7 +7,6 @@ use codex_protocol::permissions::FileSystemSandboxEntry;
use codex_protocol::permissions::FileSystemSandboxPolicy;
use codex_protocol::permissions::FileSystemSpecialPath;
use codex_protocol::permissions::NetworkSandboxPolicy;
use codex_protocol::protocol::ReadOnlyAccess;
use codex_protocol::protocol::SandboxPolicy;
use codex_sandboxing::SandboxCommand;
use codex_sandboxing::SandboxExecRequest;
@@ -194,14 +193,6 @@ fn compatibility_workspace_write_policy(
file_system_policy: &FileSystemSandboxPolicy,
cwd: &std::path::Path,
) -> SandboxPolicy {
let read_only_access = if file_system_policy.has_full_disk_read_access() {
ReadOnlyAccess::FullAccess
} else {
ReadOnlyAccess::Restricted {
include_platform_defaults: file_system_policy.include_platform_defaults(),
readable_roots: file_system_policy.get_readable_roots_with_cwd(cwd),
}
};
let cwd_abs = AbsolutePathBuf::from_absolute_path(cwd).ok();
let writable_roots = file_system_policy
.get_writable_roots_with_cwd(cwd)
@@ -212,7 +203,6 @@ fn compatibility_workspace_write_policy(
SandboxPolicy::WorkspaceWrite {
writable_roots,
read_only_access,
network_access: false,
exclude_tmpdir_env_var: true,
exclude_slash_tmp: true,
@@ -484,7 +484,6 @@ fn summarize_sandbox_policy(sandbox_policy: &SandboxPolicy) -> String {
network_access,
exclude_tmpdir_env_var,
exclude_slash_tmp,
read_only_access: _,
} => {
let mut summary = "workspace-write".to_string();
let mut writable_entries = vec!["workdir".to_string()];
-1
View File
@@ -422,7 +422,6 @@ fn session_configured_from_thread_response_uses_review_policy_from_response() {
approvals_reviewer: codex_app_server_protocol::ApprovalsReviewer::AutoReview,
sandbox: codex_app_server_protocol::SandboxPolicy::WorkspaceWrite {
writable_roots: vec![],
read_only_access: codex_app_server_protocol::ReadOnlyAccess::FullAccess,
network_access: false,
exclude_tmpdir_env_var: false,
exclude_slash_tmp: false,
-3
View File
@@ -182,7 +182,6 @@ async fn python_multiprocessing_lock_works_under_sandbox() {
let policy = SandboxPolicy::WorkspaceWrite {
writable_roots,
read_only_access: Default::default(),
network_access: false,
exclude_tmpdir_env_var: false,
exclude_slash_tmp: false,
@@ -297,7 +296,6 @@ async fn sandbox_distinguishes_command_and_policy_cwds() {
// is under a writable root.
let policy = SandboxPolicy::WorkspaceWrite {
writable_roots: vec![],
read_only_access: Default::default(),
network_access: false,
exclude_tmpdir_env_var: true,
exclude_slash_tmp: true,
@@ -379,7 +377,6 @@ async fn sandbox_blocks_first_time_dot_codex_creation() {
let config_toml = dot_codex.join("config.toml");
let policy = SandboxPolicy::WorkspaceWrite {
writable_roots: vec![],
read_only_access: Default::default(),
network_access: false,
exclude_tmpdir_env_var: true,
exclude_slash_tmp: true,
+18 -34
View File
@@ -32,7 +32,7 @@ use globset::GlobSet;
use globset::GlobSetBuilder;
/// Linux "platform defaults" that keep common system binaries and dynamic
/// libraries readable when `ReadOnlyAccess::Restricted` requests them.
/// libraries readable when a split filesystem policy requests `:minimal`.
///
/// These are intentionally system-level paths only (plus Nix store roots) so
/// `include_platform_defaults` does not silently widen access to user data.
@@ -1002,7 +1002,6 @@ mod tests {
use codex_protocol::protocol::FileSystemSandboxEntry;
use codex_protocol::protocol::FileSystemSandboxPolicy;
use codex_protocol::protocol::FileSystemSpecialPath;
use codex_protocol::protocol::ReadOnlyAccess;
use codex_protocol::protocol::SandboxPolicy;
use codex_utils_absolute_path::AbsolutePathBuf;
use pretty_assertions::assert_eq;
@@ -1371,7 +1370,6 @@ mod tests {
AbsolutePathBuf::try_from(existing_root.as_path()).expect("absolute existing root"),
AbsolutePathBuf::try_from(missing_root.as_path()).expect("absolute missing root"),
],
read_only_access: Default::default(),
network_access: false,
exclude_tmpdir_env_var: true,
exclude_slash_tmp: true,
@@ -1402,7 +1400,6 @@ mod tests {
fn mounts_dev_before_writable_dev_binds() {
let sandbox_policy = SandboxPolicy::WorkspaceWrite {
writable_roots: vec![AbsolutePathBuf::try_from(Path::new("/dev")).expect("/dev path")],
read_only_access: Default::default(),
network_access: false,
exclude_tmpdir_env_var: true,
exclude_slash_tmp: true,
@@ -1449,23 +1446,17 @@ mod tests {
let readable_root = temp_dir.path().join("readable");
std::fs::create_dir(&readable_root).expect("create readable root");
let policy = SandboxPolicy::ReadOnly {
access: ReadOnlyAccess::Restricted {
include_platform_defaults: false,
readable_roots: vec![
AbsolutePathBuf::try_from(readable_root.as_path())
.expect("absolute readable root"),
],
let policy = FileSystemSandboxPolicy::restricted(vec![FileSystemSandboxEntry {
path: FileSystemPath::Path {
path: AbsolutePathBuf::try_from(readable_root.as_path())
.expect("absolute readable root"),
},
network_access: false,
};
access: FileSystemAccessMode::Read,
}]);
let args = create_filesystem_args(
&FileSystemSandboxPolicy::from(&policy),
temp_dir.path(),
NO_UNREADABLE_GLOB_SCAN_MAX_DEPTH,
)
.expect("filesystem args");
let args =
create_filesystem_args(&policy, temp_dir.path(), NO_UNREADABLE_GLOB_SCAN_MAX_DEPTH)
.expect("filesystem args");
assert_eq!(args.args[0..4], ["--tmpfs", "/", "--dev", "/dev"]);
@@ -1483,23 +1474,16 @@ mod tests {
#[test]
fn restricted_read_only_with_platform_defaults_includes_usr_when_present() {
let temp_dir = TempDir::new().expect("temp dir");
let policy = SandboxPolicy::ReadOnly {
access: ReadOnlyAccess::Restricted {
include_platform_defaults: true,
readable_roots: Vec::new(),
let policy = FileSystemSandboxPolicy::restricted(vec![FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::Minimal,
},
network_access: false,
};
access: FileSystemAccessMode::Read,
}]);
// `ReadOnlyAccess::Restricted` always includes `cwd` as a readable
// root. Using `"/"` here would intentionally collapse to broad read
// access, so use a non-root cwd to exercise the restricted path.
let args = create_filesystem_args(
&FileSystemSandboxPolicy::from(&policy),
temp_dir.path(),
NO_UNREADABLE_GLOB_SCAN_MAX_DEPTH,
)
.expect("filesystem args");
let args =
create_filesystem_args(&policy, temp_dir.path(), NO_UNREADABLE_GLOB_SCAN_MAX_DEPTH)
.expect("filesystem args");
assert!(
args.args
@@ -5,8 +5,6 @@ use codex_protocol::protocol::FileSystemSandboxPolicy;
#[cfg(test)]
use codex_protocol::protocol::NetworkSandboxPolicy;
#[cfg(test)]
use codex_protocol::protocol::ReadOnlyAccess;
#[cfg(test)]
use codex_protocol::protocol::SandboxPolicy;
#[cfg(test)]
use codex_utils_absolute_path::AbsolutePathBuf;
@@ -456,7 +454,6 @@ fn resolve_sandbox_policies_accepts_semantically_equivalent_workspace_write_inpu
let workspace = AbsolutePathBuf::from_absolute_path(&workspace).expect("absolute workspace");
let sandbox_policy = SandboxPolicy::WorkspaceWrite {
writable_roots: vec![workspace],
read_only_access: ReadOnlyAccess::FullAccess,
network_access: false,
exclude_tmpdir_env_var: false,
exclude_slash_tmp: false,
@@ -16,7 +16,6 @@ use codex_protocol::permissions::FileSystemSandboxEntry;
use codex_protocol::permissions::FileSystemSandboxPolicy;
use codex_protocol::permissions::FileSystemSpecialPath;
use codex_protocol::permissions::NetworkSandboxPolicy;
use codex_protocol::protocol::ReadOnlyAccess;
use codex_protocol::protocol::SandboxPolicy;
use codex_utils_absolute_path::AbsolutePathBuf;
use pretty_assertions::assert_eq;
@@ -87,7 +86,6 @@ async fn run_cmd_result_with_writable_roots(
.iter()
.map(|p| AbsolutePathBuf::try_from(p.as_path()).unwrap())
.collect(),
read_only_access: Default::default(),
network_access,
// Exclude tmp-related folders from writable roots because we need a
// folder that is writable by tests but that we intentionally disallow
@@ -561,7 +559,6 @@ async fn sandbox_blocks_explicit_split_policy_carveouts_under_bwrap() {
let sandbox_policy = SandboxPolicy::WorkspaceWrite {
writable_roots: vec![AbsolutePathBuf::try_from(tmpdir.path()).expect("absolute tempdir")],
read_only_access: Default::default(),
network_access: true,
exclude_tmpdir_env_var: true,
exclude_slash_tmp: true,
@@ -634,7 +631,6 @@ async fn sandbox_reenables_writable_subpaths_under_unreadable_parents() {
let sandbox_policy = SandboxPolicy::WorkspaceWrite {
writable_roots: vec![AbsolutePathBuf::try_from(tmpdir.path()).expect("absolute tempdir")],
read_only_access: Default::default(),
network_access: true,
exclude_tmpdir_env_var: true,
exclude_slash_tmp: true,
@@ -709,7 +705,6 @@ async fn sandbox_blocks_root_read_carveouts_under_bwrap() {
std::fs::write(&blocked_target, "secret").expect("seed blocked file");
let sandbox_policy = SandboxPolicy::ReadOnly {
access: ReadOnlyAccess::FullAccess,
network_access: true,
};
let file_system_sandbox_policy = FileSystemSandboxPolicy::restricted(vec![
+45 -147
View File
@@ -16,7 +16,6 @@ use tracing::error;
use ts_rs::TS;
use crate::protocol::NetworkAccess;
use crate::protocol::ReadOnlyAccess;
use crate::protocol::SandboxPolicy;
use crate::protocol::WritableRoot;
@@ -834,12 +833,9 @@ impl FileSystemSandboxPolicy {
}
FileSystemSandboxKind::Restricted => {
let cwd_absolute = AbsolutePathBuf::from_absolute_path(cwd).ok();
let mut include_platform_defaults = false;
let has_full_disk_read_access = self.has_full_disk_read_access();
let has_full_disk_write_access = self.has_full_disk_write_access();
let mut workspace_root_writable = false;
let mut writable_roots = Vec::new();
let mut readable_roots = Vec::new();
let mut tmpdir_writable = false;
let mut slash_tmp_writable = false;
let mut unbridgeable_root_write = false;
@@ -854,39 +850,20 @@ impl FileSystemSandboxPolicy {
} else {
writable_roots.push(path.clone());
}
} else if entry.access.can_read() {
readable_roots.push(path.clone());
}
}
FileSystemPath::Special { value } => match value {
FileSystemSpecialPath::Root => match entry.access {
FileSystemAccessMode::None => {}
FileSystemAccessMode::Read => {
if !has_full_disk_read_access
&& let Some(cwd) = cwd_absolute.as_ref()
{
readable_roots.push(absolute_root_path_for_cwd(cwd));
}
}
FileSystemAccessMode::Read => {}
FileSystemAccessMode::Write => {
unbridgeable_root_write = true;
}
},
FileSystemSpecialPath::Minimal => {
if entry.access.can_read() {
include_platform_defaults = true;
}
}
FileSystemSpecialPath::Minimal => {}
FileSystemSpecialPath::CurrentWorkingDirectory => {
if entry.access.can_write() {
workspace_root_writable = true;
} else if entry.access.can_read()
&& let Some(path) = resolve_file_system_special_path(
value,
cwd_absolute.as_ref(),
)
{
readable_roots.push(path);
}
}
FileSystemSpecialPath::ProjectRoots { subpath } => {
@@ -894,36 +871,19 @@ impl FileSystemSandboxPolicy {
workspace_root_writable = true;
} else if let Some(path) =
resolve_file_system_special_path(value, cwd_absolute.as_ref())
&& entry.access.can_write()
{
if entry.access.can_write() {
writable_roots.push(path);
} else if entry.access.can_read() {
readable_roots.push(path);
}
writable_roots.push(path);
}
}
FileSystemSpecialPath::Tmpdir => {
if entry.access.can_write() {
tmpdir_writable = true;
} else if entry.access.can_read()
&& let Some(path) = resolve_file_system_special_path(
value,
cwd_absolute.as_ref(),
)
{
readable_roots.push(path);
}
}
FileSystemSpecialPath::SlashTmp => {
if entry.access.can_write() {
slash_tmp_writable = true;
} else if entry.access.can_read()
&& let Some(path) = resolve_file_system_special_path(
value,
cwd_absolute.as_ref(),
)
{
readable_roots.push(path);
}
}
FileSystemSpecialPath::Unknown { .. } => {}
@@ -941,25 +901,12 @@ impl FileSystemSandboxPolicy {
});
}
let read_only_access = if has_full_disk_read_access {
ReadOnlyAccess::FullAccess
} else {
ReadOnlyAccess::Restricted {
include_platform_defaults,
readable_roots: dedup_absolute_paths(
readable_roots,
/*normalize_effective_paths*/ false,
),
}
};
if workspace_root_writable {
SandboxPolicy::WorkspaceWrite {
writable_roots: dedup_absolute_paths(
writable_roots,
/*normalize_effective_paths*/ false,
),
read_only_access,
network_access: network_policy.is_enabled(),
exclude_tmpdir_env_var: !tmpdir_writable,
exclude_slash_tmp: !slash_tmp_writable,
@@ -975,7 +922,6 @@ impl FileSystemSandboxPolicy {
));
} else {
SandboxPolicy::ReadOnly {
access: read_only_access,
network_access: network_policy.is_enabled(),
}
}
@@ -1026,78 +972,26 @@ impl From<&SandboxPolicy> for FileSystemSandboxPolicy {
match value {
SandboxPolicy::DangerFullAccess => FileSystemSandboxPolicy::unrestricted(),
SandboxPolicy::ExternalSandbox { .. } => FileSystemSandboxPolicy::external_sandbox(),
SandboxPolicy::ReadOnly { access, .. } => {
let mut entries = Vec::new();
match access {
ReadOnlyAccess::FullAccess => entries.push(FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::Root,
},
access: FileSystemAccessMode::Read,
}),
ReadOnlyAccess::Restricted {
include_platform_defaults,
readable_roots,
} => {
entries.push(FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::CurrentWorkingDirectory,
},
access: FileSystemAccessMode::Read,
});
if *include_platform_defaults {
entries.push(FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::Minimal,
},
access: FileSystemAccessMode::Read,
});
}
entries.extend(readable_roots.iter().cloned().map(|path| {
FileSystemSandboxEntry {
path: FileSystemPath::Path { path },
access: FileSystemAccessMode::Read,
}
}));
}
}
FileSystemSandboxPolicy::restricted(entries)
SandboxPolicy::ReadOnly { .. } => {
FileSystemSandboxPolicy::restricted(vec![FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::Root,
},
access: FileSystemAccessMode::Read,
}])
}
SandboxPolicy::WorkspaceWrite {
writable_roots,
read_only_access,
exclude_tmpdir_env_var,
exclude_slash_tmp,
..
} => {
let mut entries = Vec::new();
match read_only_access {
ReadOnlyAccess::FullAccess => entries.push(FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::Root,
},
access: FileSystemAccessMode::Read,
}),
ReadOnlyAccess::Restricted {
include_platform_defaults,
readable_roots,
} => {
if *include_platform_defaults {
entries.push(FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::Minimal,
},
access: FileSystemAccessMode::Read,
});
}
entries.extend(readable_roots.iter().cloned().map(|path| {
FileSystemSandboxEntry {
path: FileSystemPath::Path { path },
access: FileSystemAccessMode::Read,
}
}));
}
}
let mut entries = vec![FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::Root,
},
access: FileSystemAccessMode::Read,
}];
entries.push(FileSystemSandboxEntry {
path: FileSystemPath::Special {
@@ -1592,15 +1486,23 @@ mod tests {
#[test]
fn unknown_special_paths_are_ignored_by_legacy_bridge() -> std::io::Result<()> {
let policy = FileSystemSandboxPolicy::restricted(vec![FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::unknown(
":future_special_path",
/*subpath*/ None,
),
let policy = FileSystemSandboxPolicy::restricted(vec![
FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::Root,
},
access: FileSystemAccessMode::Read,
},
access: FileSystemAccessMode::Write,
}]);
FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::unknown(
":future_special_path",
/*subpath*/ None,
),
},
access: FileSystemAccessMode::Write,
},
]);
let sandbox_policy = policy.to_legacy_sandbox_policy(
NetworkSandboxPolicy::Restricted,
@@ -1610,10 +1512,6 @@ mod tests {
assert_eq!(
sandbox_policy,
SandboxPolicy::ReadOnly {
access: ReadOnlyAccess::Restricted {
include_platform_defaults: false,
readable_roots: Vec::new(),
},
network_access: false,
}
);
@@ -1651,10 +1549,6 @@ mod tests {
fn legacy_workspace_write_projection_preserves_symbolic_cwd() {
let policy = SandboxPolicy::WorkspaceWrite {
writable_roots: Vec::new(),
read_only_access: ReadOnlyAccess::Restricted {
include_platform_defaults: false,
readable_roots: Vec::new(),
},
network_access: false,
exclude_tmpdir_env_var: true,
exclude_slash_tmp: true,
@@ -1663,6 +1557,12 @@ mod tests {
assert_eq!(
FileSystemSandboxPolicy::from_legacy_sandbox_policy(&policy),
FileSystemSandboxPolicy::restricted(vec![
FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::Root,
},
access: FileSystemAccessMode::Read,
},
FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::CurrentWorkingDirectory,
@@ -1741,10 +1641,6 @@ mod tests {
let dot_codex_config = cwd.path().join(".codex").join("config.toml");
let policy = SandboxPolicy::WorkspaceWrite {
writable_roots: vec![],
read_only_access: ReadOnlyAccess::Restricted {
include_platform_defaults: false,
readable_roots: vec![],
},
network_access: false,
exclude_tmpdir_env_var: true,
exclude_slash_tmp: true,
@@ -1768,10 +1664,6 @@ mod tests {
.expect("absolute dot codex");
let policy = SandboxPolicy::WorkspaceWrite {
writable_roots: vec![],
read_only_access: ReadOnlyAccess::Restricted {
include_platform_defaults: false,
readable_roots: vec![],
},
network_access: false,
exclude_tmpdir_env_var: true,
exclude_slash_tmp: true,
@@ -1783,6 +1675,12 @@ mod tests {
assert_eq!(
file_system_policy,
FileSystemSandboxPolicy::restricted(vec![
FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::Root,
},
access: FileSystemAccessMode::Read,
},
FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::CurrentWorkingDirectory,
+3 -244
View File
@@ -1025,76 +1025,6 @@ impl NetworkAccess {
matches!(self, NetworkAccess::Enabled)
}
}
fn default_include_platform_defaults() -> bool {
true
}
/// Determines how read-only file access is granted inside a restricted
/// sandbox.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Display, Default, JsonSchema, TS)]
#[strum(serialize_all = "kebab-case")]
#[serde(tag = "type", rename_all = "kebab-case")]
#[ts(tag = "type")]
pub enum ReadOnlyAccess {
/// Restrict reads to an explicit set of roots.
///
/// When `include_platform_defaults` is `true`, platform defaults required
/// for basic execution are included in addition to `readable_roots`.
Restricted {
/// Include built-in platform read roots required for basic process
/// execution.
#[serde(default = "default_include_platform_defaults")]
include_platform_defaults: bool,
/// Additional absolute roots that should be readable.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
readable_roots: Vec<AbsolutePathBuf>,
},
/// Allow unrestricted file reads.
#[default]
FullAccess,
}
impl ReadOnlyAccess {
pub fn has_full_disk_read_access(&self) -> bool {
matches!(self, ReadOnlyAccess::FullAccess)
}
/// Returns true if platform defaults should be included for restricted read access.
pub fn include_platform_defaults(&self) -> bool {
matches!(
self,
ReadOnlyAccess::Restricted {
include_platform_defaults: true,
..
}
)
}
/// Returns the readable roots for restricted read access.
///
/// For [`ReadOnlyAccess::FullAccess`], returns an empty list because
/// callers should grant blanket read access instead.
pub fn get_readable_roots_with_cwd(&self, cwd: &Path) -> Vec<AbsolutePathBuf> {
let mut roots: Vec<AbsolutePathBuf> = match self {
ReadOnlyAccess::FullAccess => return Vec::new(),
ReadOnlyAccess::Restricted { readable_roots, .. } => {
let mut roots = readable_roots.clone();
match AbsolutePathBuf::from_absolute_path(cwd) {
Ok(cwd_root) => roots.push(cwd_root),
Err(err) => {
error!("Ignoring invalid cwd {cwd:?} for sandbox readable root: {err}");
}
}
roots
}
};
let mut seen = HashSet::new();
roots.retain(|root| seen.insert(root.to_path_buf()));
roots
}
}
/// Determines execution restrictions for model shell commands.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Display, JsonSchema, TS)]
@@ -1108,13 +1038,6 @@ pub enum SandboxPolicy {
/// Read-only access configuration.
#[serde(rename = "read-only")]
ReadOnly {
/// Read access granted while running under this policy.
#[serde(
default,
skip_serializing_if = "ReadOnlyAccess::has_full_disk_read_access"
)]
access: ReadOnlyAccess,
/// When set to `true`, outbound network access is allowed. `false` by
/// default.
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
@@ -1139,13 +1062,6 @@ pub enum SandboxPolicy {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
writable_roots: Vec<AbsolutePathBuf>,
/// Read access granted while running under this policy.
#[serde(
default,
skip_serializing_if = "ReadOnlyAccess::has_full_disk_read_access"
)]
read_only_access: ReadOnlyAccess,
/// When set to `true`, outbound network access is allowed. `false` by
/// default.
#[serde(default)]
@@ -1223,7 +1139,6 @@ impl SandboxPolicy {
/// Returns a policy with read-only disk access and no network.
pub fn new_read_only_policy() -> Self {
SandboxPolicy::ReadOnly {
access: ReadOnlyAccess::FullAccess,
network_access: false,
}
}
@@ -1234,7 +1149,6 @@ impl SandboxPolicy {
pub fn new_workspace_write_policy() -> Self {
SandboxPolicy::WorkspaceWrite {
writable_roots: vec![],
read_only_access: ReadOnlyAccess::FullAccess,
network_access: false,
exclude_tmpdir_env_var: false,
exclude_slash_tmp: false,
@@ -1242,14 +1156,7 @@ impl SandboxPolicy {
}
pub fn has_full_disk_read_access(&self) -> bool {
match self {
SandboxPolicy::DangerFullAccess => true,
SandboxPolicy::ExternalSandbox { .. } => true,
SandboxPolicy::ReadOnly { access, .. } => access.has_full_disk_read_access(),
SandboxPolicy::WorkspaceWrite {
read_only_access, ..
} => read_only_access.has_full_disk_read_access(),
}
true
}
pub fn has_full_disk_write_access(&self) -> bool {
@@ -1270,46 +1177,6 @@ impl SandboxPolicy {
}
}
/// Returns true if platform defaults should be included for restricted read access.
pub fn include_platform_defaults(&self) -> bool {
if self.has_full_disk_read_access() {
return false;
}
match self {
SandboxPolicy::ReadOnly { access, .. } => access.include_platform_defaults(),
SandboxPolicy::WorkspaceWrite {
read_only_access, ..
} => read_only_access.include_platform_defaults(),
SandboxPolicy::DangerFullAccess | SandboxPolicy::ExternalSandbox { .. } => false,
}
}
/// Returns the list of readable roots (tailored to the current working
/// directory) when read access is restricted.
///
/// For policies with full read access, this returns an empty list because
/// callers should grant blanket reads.
pub fn get_readable_roots_with_cwd(&self, cwd: &Path) -> Vec<AbsolutePathBuf> {
let mut roots = match self {
SandboxPolicy::DangerFullAccess | SandboxPolicy::ExternalSandbox { .. } => Vec::new(),
SandboxPolicy::ReadOnly { access, .. } => access.get_readable_roots_with_cwd(cwd),
SandboxPolicy::WorkspaceWrite {
read_only_access, ..
} => {
let mut roots = read_only_access.get_readable_roots_with_cwd(cwd);
roots.extend(
self.get_writable_roots_with_cwd(cwd)
.into_iter()
.map(|root| root.root),
);
roots
}
};
let mut seen = HashSet::new();
roots.retain(|root| seen.insert(root.to_path_buf()));
roots
}
/// Returns the list of writable roots (tailored to the current working
/// directory) together with subpaths that should remain read‑only under
/// each writable root.
@@ -1320,7 +1187,6 @@ impl SandboxPolicy {
SandboxPolicy::ReadOnly { .. } => Vec::new(),
SandboxPolicy::WorkspaceWrite {
writable_roots,
read_only_access: _,
exclude_tmpdir_env_var,
exclude_slash_tmp,
network_access: _,
@@ -4077,19 +3943,8 @@ mod tests {
sorted_roots
}
fn sandbox_policy_allows_read(policy: &SandboxPolicy, path: &Path, cwd: &Path) -> bool {
if policy.has_full_disk_read_access() {
return true;
}
policy
.get_readable_roots_with_cwd(cwd)
.iter()
.any(|root| path.starts_with(root.as_path()))
|| policy
.get_writable_roots_with_cwd(cwd)
.iter()
.any(|root| path.starts_with(root.root.as_path()))
fn sandbox_policy_allows_read(policy: &SandboxPolicy, _path: &Path, _cwd: &Path) -> bool {
policy.has_full_disk_read_access()
}
fn sandbox_policy_allows_write(policy: &SandboxPolicy, path: &Path, cwd: &Path) -> bool {
@@ -4217,12 +4072,6 @@ mod tests {
fn sandbox_policy_probe_paths(policy: &SandboxPolicy, cwd: &Path) -> Vec<PathBuf> {
let mut paths = vec![cwd.to_path_buf()];
paths.extend(
policy
.get_readable_roots_with_cwd(cwd)
.into_iter()
.map(|path| path.to_path_buf()),
);
for root in policy.get_writable_roots_with_cwd(cwd) {
paths.push(root.root.to_path_buf());
paths.extend(
@@ -4253,10 +4102,6 @@ mod tests {
actual.has_full_network_access(),
expected.has_full_network_access()
);
assert_eq!(
actual.include_platform_defaults(),
expected.include_platform_defaults()
);
let mut probe_paths = sandbox_policy_probe_paths(expected, cwd);
probe_paths.extend(sandbox_policy_probe_paths(actual, cwd));
probe_paths.sort();
@@ -4299,7 +4144,6 @@ mod tests {
assert!(!restricted.has_full_network_access());
let enabled = SandboxPolicy::ReadOnly {
access: ReadOnlyAccess::FullAccess,
network_access: true,
};
assert!(enabled.has_full_network_access());
@@ -4398,38 +4242,6 @@ mod tests {
);
}
#[test]
fn workspace_write_restricted_read_access_includes_effective_writable_roots() {
let cwd = if cfg!(windows) {
Path::new(r"C:\workspace")
} else {
Path::new("/tmp/workspace")
};
let policy = SandboxPolicy::WorkspaceWrite {
writable_roots: vec![],
read_only_access: ReadOnlyAccess::Restricted {
include_platform_defaults: false,
readable_roots: vec![],
},
network_access: false,
exclude_tmpdir_env_var: true,
exclude_slash_tmp: false,
};
let readable_roots = policy.get_readable_roots_with_cwd(cwd);
let writable_roots = policy.get_writable_roots_with_cwd(cwd);
for writable_root in writable_roots {
assert!(
readable_roots
.iter()
.any(|root| root.as_path() == writable_root.root.as_path()),
"expected writable root {} to also be readable",
writable_root.root.as_path().display()
);
}
}
#[test]
fn restricted_file_system_policy_reports_full_access_from_root_entries() {
let read_only = FileSystemSandboxPolicy::restricted(vec![FileSystemSandboxEntry {
@@ -4623,34 +4435,6 @@ mod tests {
);
}
#[test]
fn legacy_workspace_write_nested_readable_root_stays_writable() {
let cwd = TempDir::new().expect("tempdir");
let docs = AbsolutePathBuf::resolve_path_against_base("docs", cwd.path());
let canonical_cwd = codex_utils_absolute_path::canonicalize_preserving_symlinks(cwd.path())
.expect("canonicalize cwd");
let expected_dot_codex = AbsolutePathBuf::from_absolute_path(canonical_cwd.join(".codex"))
.expect("canonical .codex");
let policy = SandboxPolicy::WorkspaceWrite {
writable_roots: vec![],
read_only_access: ReadOnlyAccess::Restricted {
include_platform_defaults: true,
readable_roots: vec![docs],
},
network_access: false,
exclude_tmpdir_env_var: true,
exclude_slash_tmp: true,
};
assert_eq!(
sorted_writable_roots(
FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(&policy, cwd.path())
.get_writable_roots_with_cwd(cwd.path())
),
vec![(canonical_cwd, vec![expected_dot_codex.to_path_buf()])]
);
}
#[test]
fn file_system_policy_rejects_legacy_bridge_for_non_workspace_writes() {
let cwd = if cfg!(windows) {
@@ -4684,9 +4468,7 @@ mod tests {
#[test]
fn legacy_sandbox_policy_semantics_survive_split_bridge() {
let cwd = TempDir::new().expect("tempdir");
let readable_root = AbsolutePathBuf::resolve_path_against_base("readable", cwd.path());
let writable_root = AbsolutePathBuf::resolve_path_against_base("writable", cwd.path());
let nested_readable_root = AbsolutePathBuf::resolve_path_against_base("docs", cwd.path());
let policies = [
SandboxPolicy::DangerFullAccess,
SandboxPolicy::ExternalSandbox {
@@ -4696,43 +4478,20 @@ mod tests {
network_access: NetworkAccess::Enabled,
},
SandboxPolicy::ReadOnly {
access: ReadOnlyAccess::FullAccess,
network_access: false,
},
SandboxPolicy::ReadOnly {
access: ReadOnlyAccess::Restricted {
include_platform_defaults: true,
readable_roots: vec![readable_root.clone()],
},
network_access: true,
},
SandboxPolicy::WorkspaceWrite {
writable_roots: vec![],
read_only_access: ReadOnlyAccess::FullAccess,
network_access: false,
exclude_tmpdir_env_var: true,
exclude_slash_tmp: true,
},
SandboxPolicy::WorkspaceWrite {
writable_roots: vec![writable_root],
read_only_access: ReadOnlyAccess::Restricted {
include_platform_defaults: true,
readable_roots: vec![readable_root],
},
network_access: true,
exclude_tmpdir_env_var: false,
exclude_slash_tmp: true,
},
SandboxPolicy::WorkspaceWrite {
writable_roots: vec![],
read_only_access: ReadOnlyAccess::Restricted {
include_platform_defaults: true,
readable_roots: vec![nested_readable_root],
},
network_access: false,
exclude_tmpdir_env_var: true,
exclude_slash_tmp: true,
},
];
for expected in policies {
-2
View File
@@ -15,7 +15,6 @@ use codex_protocol::permissions::FileSystemSandboxPolicy;
use codex_protocol::permissions::FileSystemSpecialPath;
use codex_protocol::permissions::NetworkSandboxPolicy;
use codex_protocol::protocol::NetworkAccess;
use codex_protocol::protocol::ReadOnlyAccess;
use codex_protocol::protocol::SandboxPolicy;
use codex_utils_absolute_path::AbsolutePathBuf;
use dunce::canonicalize;
@@ -191,7 +190,6 @@ fn transform_additional_permissions_preserves_denied_entries() {
}),
},
policy: &SandboxPolicy::ReadOnly {
access: ReadOnlyAccess::FullAccess,
network_access: false,
},
file_system_policy: &FileSystemSandboxPolicy::restricted(vec![
+2 -36
View File
@@ -10,7 +10,6 @@ use codex_protocol::permissions::FileSystemSpecialPath;
use codex_protocol::permissions::NetworkSandboxPolicy;
use codex_protocol::permissions::ReadDenyMatcher;
use codex_protocol::protocol::NetworkAccess;
use codex_protocol::protocol::ReadOnlyAccess;
use codex_protocol::protocol::SandboxPolicy;
use codex_utils_absolute_path::AbsolutePathBuf;
use codex_utils_absolute_path::canonicalize_preserving_symlinks;
@@ -535,26 +534,6 @@ pub fn effective_file_system_sandbox_policy(
}
}
fn merge_read_only_access_with_additional_reads(
read_only_access: &ReadOnlyAccess,
extra_reads: Vec<AbsolutePathBuf>,
) -> ReadOnlyAccess {
match read_only_access {
ReadOnlyAccess::FullAccess => ReadOnlyAccess::FullAccess,
ReadOnlyAccess::Restricted {
include_platform_defaults,
readable_roots,
} => {
let mut merged = readable_roots.clone();
merged.extend(extra_reads);
ReadOnlyAccess::Restricted {
include_platform_defaults: *include_platform_defaults,
readable_roots: dedup_absolute_paths(merged),
}
}
}
}
fn merge_network_access(
base_network_access: bool,
additional_permissions: &AdditionalPermissionProfile,
@@ -590,7 +569,7 @@ fn sandbox_policy_with_additional_permissions(
return sandbox_policy.clone();
}
let (extra_reads, extra_writes) = additional_permission_roots(additional_permissions);
let (_extra_reads, extra_writes) = additional_permission_roots(additional_permissions);
match sandbox_policy {
SandboxPolicy::DangerFullAccess => SandboxPolicy::DangerFullAccess,
@@ -606,7 +585,6 @@ fn sandbox_policy_with_additional_permissions(
},
SandboxPolicy::WorkspaceWrite {
writable_roots,
read_only_access,
network_access,
exclude_tmpdir_env_var,
exclude_slash_tmp,
@@ -615,22 +593,14 @@ fn sandbox_policy_with_additional_permissions(
merged_writes.extend(extra_writes);
SandboxPolicy::WorkspaceWrite {
writable_roots: dedup_absolute_paths(merged_writes),
read_only_access: merge_read_only_access_with_additional_reads(
read_only_access,
extra_reads,
),
network_access: merge_network_access(*network_access, additional_permissions),
exclude_tmpdir_env_var: *exclude_tmpdir_env_var,
exclude_slash_tmp: *exclude_slash_tmp,
}
}
SandboxPolicy::ReadOnly {
access,
network_access,
} => {
SandboxPolicy::ReadOnly { network_access } => {
if extra_writes.is_empty() {
SandboxPolicy::ReadOnly {
access: merge_read_only_access_with_additional_reads(access, extra_reads),
network_access: merge_network_access(*network_access, additional_permissions),
}
} else {
@@ -639,10 +609,6 @@ fn sandbox_policy_with_additional_permissions(
// UnderDevelopment, it's a useful approximation of the desired behavior.
SandboxPolicy::WorkspaceWrite {
writable_roots: dedup_absolute_paths(extra_writes),
read_only_access: merge_read_only_access_with_additional_reads(
access,
extra_reads,
),
network_access: merge_network_access(*network_access, additional_permissions),
exclude_tmpdir_env_var: false,
exclude_slash_tmp: false,
@@ -14,7 +14,6 @@ use codex_protocol::permissions::FileSystemSandboxPolicy;
use codex_protocol::permissions::FileSystemSpecialPath;
use codex_protocol::permissions::NetworkSandboxPolicy;
use codex_protocol::protocol::NetworkAccess;
use codex_protocol::protocol::ReadOnlyAccess;
use codex_protocol::protocol::SandboxPolicy;
use codex_utils_absolute_path::AbsolutePathBuf;
use dunce::canonicalize;
@@ -767,10 +766,6 @@ fn read_only_additional_permissions_can_enable_network_without_writes() {
.expect("absolute temp dir");
let policy = sandbox_policy_with_additional_permissions(
&SandboxPolicy::ReadOnly {
access: ReadOnlyAccess::Restricted {
include_platform_defaults: true,
readable_roots: vec![path.clone()],
},
network_access: false,
},
&PermissionProfile {
@@ -778,7 +773,7 @@ fn read_only_additional_permissions_can_enable_network_without_writes() {
enabled: Some(true),
}),
file_system: Some(FileSystemPermissions::from_read_write_roots(
Some(vec![path.clone()]),
Some(vec![path]),
Some(Vec::new()),
)),
},
@@ -787,10 +782,6 @@ fn read_only_additional_permissions_can_enable_network_without_writes() {
assert_eq!(
policy,
SandboxPolicy::ReadOnly {
access: ReadOnlyAccess::Restricted {
include_platform_defaults: true,
readable_roots: vec![path],
},
network_access: true,
}
);
@@ -1,4 +1,4 @@
; macOS platform defaults included via `ReadOnlyAccess::Restricted::include_platform_defaults`
; macOS platform defaults included when a split filesystem policy requests `:minimal`.
; Read access to standard system paths
(allow file-read* file-test-existence
-46
View File
@@ -26,7 +26,6 @@ use codex_protocol::permissions::FileSystemSandboxEntry;
use codex_protocol::permissions::FileSystemSandboxPolicy;
use codex_protocol::permissions::FileSystemSpecialPath;
use codex_protocol::permissions::NetworkSandboxPolicy;
use codex_protocol::protocol::ReadOnlyAccess;
use codex_protocol::protocol::SandboxPolicy;
use codex_utils_absolute_path::AbsolutePathBuf;
use pretty_assertions::assert_eq;
@@ -350,43 +349,6 @@ fn seatbelt_args_without_extension_profile_keep_legacy_preferences_read_access()
assert!(!policy.contains("(allow user-preference-write)"));
}
#[test]
fn seatbelt_legacy_workspace_write_nested_readable_root_stays_writable() {
let tmp = TempDir::new().expect("tempdir");
let cwd = tmp.path().join("workspace");
fs::create_dir_all(cwd.join("docs")).expect("create docs");
let docs = AbsolutePathBuf::from_absolute_path(cwd.join("docs")).expect("absolute docs");
let args = create_seatbelt_command_args_for_legacy_policy(
vec!["/bin/true".to_string()],
&SandboxPolicy::WorkspaceWrite {
writable_roots: vec![],
read_only_access: ReadOnlyAccess::Restricted {
include_platform_defaults: true,
readable_roots: vec![docs.clone()],
},
network_access: false,
exclude_tmpdir_env_var: true,
exclude_slash_tmp: true,
},
cwd.as_path(),
/*enforce_managed_network*/ false,
/*network*/ None,
);
assert!(
!args
.iter()
.any(|arg| arg.ends_with(&format!("={}", docs.as_path().display()))),
"legacy workspace-write readable roots under cwd should not become seatbelt carveouts:\n{args:#?}",
);
assert!(
args.iter()
.any(|arg| arg.starts_with("-DWRITABLE_ROOT_0_EXCLUDED_")
&& arg.ends_with("/workspace/.codex")),
"expected proactive .codex carveout for cwd root: {args:#?}",
);
}
#[test]
fn create_seatbelt_args_allows_local_binding_when_explicitly_enabled() {
let policy = dynamic_network_policy(
@@ -427,7 +389,6 @@ fn dynamic_network_policy_preserves_restricted_policy_when_proxy_config_without_
let policy = dynamic_network_policy(
&SandboxPolicy::WorkspaceWrite {
writable_roots: vec![],
read_only_access: Default::default(),
network_access: true,
exclude_tmpdir_env_var: false,
exclude_slash_tmp: false,
@@ -464,7 +425,6 @@ fn dynamic_network_policy_blocks_dns_when_local_binding_has_no_proxy_ports() {
let policy = dynamic_network_policy(
&SandboxPolicy::WorkspaceWrite {
writable_roots: vec![],
read_only_access: Default::default(),
network_access: true,
exclude_tmpdir_env_var: false,
exclude_slash_tmp: false,
@@ -493,7 +453,6 @@ fn dynamic_network_policy_preserves_restricted_policy_for_managed_network_withou
let policy = dynamic_network_policy(
&SandboxPolicy::WorkspaceWrite {
writable_roots: vec![],
read_only_access: Default::default(),
network_access: true,
exclude_tmpdir_env_var: false,
exclude_slash_tmp: false,
@@ -784,7 +743,6 @@ fn create_seatbelt_args_full_network_with_proxy_is_still_proxy_only() {
let policy = dynamic_network_policy(
&SandboxPolicy::WorkspaceWrite {
writable_roots: vec![],
read_only_access: Default::default(),
network_access: true,
exclude_tmpdir_env_var: false,
exclude_slash_tmp: false,
@@ -835,7 +793,6 @@ fn create_seatbelt_args_with_read_only_git_and_codex_subpaths() {
.into_iter()
.map(|p| p.try_into().unwrap())
.collect(),
read_only_access: Default::default(),
network_access: false,
exclude_tmpdir_env_var: true,
exclude_slash_tmp: true,
@@ -1054,7 +1011,6 @@ fn create_seatbelt_args_block_first_time_dot_codex_creation_with_exact_and_desce
let config_toml = dot_codex.join("config.toml");
let policy = SandboxPolicy::WorkspaceWrite {
writable_roots: vec![repo_root.as_path().try_into().expect("absolute repo root")],
read_only_access: Default::default(),
network_access: false,
exclude_tmpdir_env_var: true,
exclude_slash_tmp: true,
@@ -1110,7 +1066,6 @@ fn create_seatbelt_args_with_read_only_git_pointer_file() {
let policy = SandboxPolicy::WorkspaceWrite {
writable_roots: vec![worktree_root.try_into().expect("worktree_root is absolute")],
read_only_access: Default::default(),
network_access: false,
exclude_tmpdir_env_var: true,
exclude_slash_tmp: true,
@@ -1206,7 +1161,6 @@ fn create_seatbelt_args_for_cwd_as_git_repo() {
// `.codex` checks are done properly for cwd.
let policy = SandboxPolicy::WorkspaceWrite {
writable_roots: vec![],
read_only_access: Default::default(),
network_access: false,
exclude_tmpdir_env_var: false,
exclude_slash_tmp: false,
-1
View File
@@ -182,7 +182,6 @@ pub(super) use codex_protocol::protocol::PatchApplyStatus as CorePatchApplyStatu
pub(super) use codex_protocol::protocol::RateLimitReachedType;
pub(super) use codex_protocol::protocol::RateLimitSnapshot;
pub(super) use codex_protocol::protocol::RateLimitWindow;
pub(super) use codex_protocol::protocol::ReadOnlyAccess;
pub(super) use codex_protocol::protocol::RealtimeConversationClosedEvent;
pub(super) use codex_protocol::protocol::RealtimeConversationRealtimeEvent;
pub(super) use codex_protocol::protocol::RealtimeEvent;
@@ -54,7 +54,6 @@ async fn preset_matching_accepts_workspace_write_with_extra_roots() {
let extra_root = test_path_buf("/tmp/extra").abs();
let current_sandbox = SandboxPolicy::WorkspaceWrite {
writable_roots: vec![extra_root],
read_only_access: Default::default(),
network_access: false,
exclude_tmpdir_env_var: false,
exclude_slash_tmp: false,
@@ -537,7 +536,6 @@ async fn permissions_selection_marks_auto_review_current_with_custom_workspace_w
approvals_reviewer: ApprovalsReviewer::AutoReview,
sandbox_policy: SandboxPolicy::WorkspaceWrite {
writable_roots: vec![extra_root],
read_only_access: ReadOnlyAccess::FullAccess,
network_access: false,
exclude_tmpdir_env_var: false,
exclude_slash_tmp: false,
-2
View File
@@ -102,7 +102,6 @@ async fn status_snapshot_includes_reasoning_details() {
.sandbox_policy
.set(SandboxPolicy::WorkspaceWrite {
writable_roots: Vec::new(),
read_only_access: Default::default(),
network_access: false,
exclude_tmpdir_env_var: false,
exclude_slash_tmp: false,
@@ -188,7 +187,6 @@ async fn status_permissions_non_default_workspace_write_is_custom() {
.sandbox_policy
.set(SandboxPolicy::WorkspaceWrite {
writable_roots: Vec::new(),
read_only_access: Default::default(),
network_access: true,
exclude_tmpdir_env_var: false,
exclude_slash_tmp: false,
@@ -23,7 +23,6 @@ pub fn summarize_sandbox_policy(sandbox_policy: &SandboxPolicy) -> String {
network_access,
exclude_tmpdir_env_var,
exclude_slash_tmp,
read_only_access: _,
} => {
let mut summary = "workspace-write".to_string();
@@ -75,7 +74,6 @@ mod tests {
#[test]
fn summarizes_read_only_with_enabled_network() {
let summary = summarize_sandbox_policy(&SandboxPolicy::ReadOnly {
access: Default::default(),
network_access: true,
});
assert_eq!(summary, "read-only (network access enabled)");
@@ -87,7 +85,6 @@ mod tests {
let writable_root = AbsolutePathBuf::try_from(root).unwrap();
let summary = summarize_sandbox_policy(&SandboxPolicy::WorkspaceWrite {
writable_roots: vec![writable_root.clone()],
read_only_access: Default::default(),
network_access: true,
exclude_tmpdir_env_var: true,
exclude_slash_tmp: true,
-6
View File
@@ -110,7 +110,6 @@ mod tests {
let policy = SandboxPolicy::WorkspaceWrite {
writable_roots: vec![AbsolutePathBuf::try_from(extra_root.as_path()).unwrap()],
read_only_access: Default::default(),
network_access: false,
exclude_tmpdir_env_var: false,
exclude_slash_tmp: false,
@@ -137,7 +136,6 @@ mod tests {
let policy = SandboxPolicy::WorkspaceWrite {
writable_roots: vec![],
read_only_access: Default::default(),
network_access: false,
exclude_tmpdir_env_var: true,
exclude_slash_tmp: false,
@@ -165,7 +163,6 @@ mod tests {
let policy = SandboxPolicy::WorkspaceWrite {
writable_roots: vec![],
read_only_access: Default::default(),
network_access: false,
exclude_tmpdir_env_var: true,
exclude_slash_tmp: false,
@@ -193,7 +190,6 @@ mod tests {
let policy = SandboxPolicy::WorkspaceWrite {
writable_roots: vec![],
read_only_access: Default::default(),
network_access: false,
exclude_tmpdir_env_var: true,
exclude_slash_tmp: false,
@@ -222,7 +218,6 @@ mod tests {
let policy = SandboxPolicy::WorkspaceWrite {
writable_roots: vec![],
read_only_access: Default::default(),
network_access: false,
exclude_tmpdir_env_var: true,
exclude_slash_tmp: false,
@@ -251,7 +246,6 @@ mod tests {
let policy = SandboxPolicy::WorkspaceWrite {
writable_roots: vec![],
read_only_access: Default::default(),
network_access: false,
exclude_tmpdir_env_var: true,
exclude_slash_tmp: false,
@@ -13,6 +13,7 @@ pub struct ElevatedSandboxCaptureRequest<'a> {
pub use_private_desktop: bool,
pub proxy_enforced: bool,
pub read_roots_override: Option<&'a [PathBuf]>,
pub read_roots_include_platform_defaults: bool,
pub write_roots_override: Option<&'a [PathBuf]>,
pub deny_write_paths_override: &'a [PathBuf],
}
@@ -121,6 +122,7 @@ mod windows_impl {
use_private_desktop,
proxy_enforced,
read_roots_override,
read_roots_include_platform_defaults,
write_roots_override,
deny_write_paths_override,
} = request;
@@ -142,6 +144,7 @@ mod windows_impl {
&env_map,
codex_home,
read_roots_override,
read_roots_include_platform_defaults,
write_roots_override,
deny_write_paths_override,
proxy_enforced,
@@ -250,7 +253,6 @@ mod windows_impl {
fn workspace_policy(network_access: bool) -> SandboxPolicy {
SandboxPolicy::WorkspaceWrite {
writable_roots: Vec::new(),
read_only_access: Default::default(),
network_access,
exclude_tmpdir_env_var: false,
exclude_slash_tmp: false,
@@ -137,6 +137,7 @@ pub fn require_logon_sandbox_creds(
env_map: &HashMap<String, String>,
codex_home: &Path,
read_roots_override: Option<&[PathBuf]>,
read_roots_include_platform_defaults: bool,
write_roots_override: Option<&[PathBuf]>,
deny_write_paths_override: &[PathBuf],
proxy_enforced: bool,
@@ -198,6 +199,7 @@ pub fn require_logon_sandbox_creds(
},
crate::setup::SetupRootOverrides {
read_roots: Some(needed_read.clone()),
read_roots_include_platform_defaults,
write_roots: Some(needed_write.clone()),
deny_write_paths: Some(deny_write_paths_override.to_vec()),
},
@@ -216,6 +218,7 @@ pub fn require_logon_sandbox_creds(
},
crate::setup::SetupRootOverrides {
read_roots: Some(needed_read),
read_roots_include_platform_defaults,
write_roots: Some(needed_write),
deny_write_paths: Some(deny_write_paths_override.to_vec()),
},
-1
View File
@@ -642,7 +642,6 @@ mod windows_impl {
fn workspace_policy(network_access: bool) -> SandboxPolicy {
SandboxPolicy::WorkspaceWrite {
writable_roots: Vec::new(),
read_only_access: Default::default(),
network_access,
exclude_tmpdir_env_var: false,
exclude_slash_tmp: false,
@@ -94,6 +94,7 @@ pub struct SandboxSetupRequest<'a> {
#[derive(Default)]
pub struct SetupRootOverrides {
pub read_roots: Option<Vec<PathBuf>>,
pub read_roots_include_platform_defaults: bool,
pub write_roots: Option<Vec<PathBuf>>,
pub deny_write_paths: Option<Vec<PathBuf>>,
}
@@ -148,6 +149,7 @@ pub fn run_setup_refresh_with_extra_read_roots(
},
SetupRootOverrides {
read_roots: Some(read_roots),
read_roots_include_platform_defaults: false,
write_roots: Some(Vec::new()),
deny_write_paths: None,
},
@@ -373,38 +375,12 @@ fn gather_legacy_full_read_roots(
canonical_existing(&roots)
}
fn gather_restricted_read_roots(
command_cwd: &Path,
policy: &SandboxPolicy,
codex_home: &Path,
) -> Vec<PathBuf> {
let mut roots = gather_helper_read_roots(codex_home);
if policy.include_platform_defaults() {
roots.extend(
WINDOWS_PLATFORM_DEFAULT_READ_ROOTS
.iter()
.map(PathBuf::from),
);
}
roots.extend(
policy
.get_readable_roots_with_cwd(command_cwd)
.into_iter()
.map(|path| path.to_path_buf()),
);
canonical_existing(&roots)
}
pub(crate) fn gather_read_roots(
command_cwd: &Path,
policy: &SandboxPolicy,
codex_home: &Path,
) -> Vec<PathBuf> {
if policy.has_full_disk_read_access() {
gather_legacy_full_read_roots(command_cwd, policy, codex_home)
} else {
gather_restricted_read_roots(command_cwd, policy, codex_home)
}
gather_legacy_full_read_roots(command_cwd, policy, codex_home)
}
pub(crate) fn gather_write_roots(
@@ -792,7 +768,7 @@ fn build_payload_roots(
// An explicit override is the split policy's complete readable set. Keep only the
// helper/platform roots the elevated setup needs; do not re-add legacy cwd/full-read roots.
let mut read_roots = gather_helper_read_roots(request.codex_home);
if request.policy.include_platform_defaults() {
if overrides.read_roots_include_platform_defaults {
read_roots.extend(
WINDOWS_PLATFORM_DEFAULT_READ_ROOTS
.iter()
@@ -973,7 +949,6 @@ mod tests {
use super::proxy_ports_from_env;
use crate::helper_materialization::helper_bin_dir;
use crate::policy::SandboxPolicy;
use codex_protocol::protocol::ReadOnlyAccess;
use codex_utils_absolute_path::AbsolutePathBuf;
use pretty_assertions::assert_eq;
use std::collections::HashMap;
@@ -1298,66 +1273,7 @@ mod tests {
}
#[test]
fn restricted_read_roots_skip_platform_defaults_when_disabled() {
let tmp = TempDir::new().expect("tempdir");
let codex_home = tmp.path().join("codex-home");
let command_cwd = tmp.path().join("workspace");
let readable_root = tmp.path().join("docs");
fs::create_dir_all(&command_cwd).expect("create workspace");
fs::create_dir_all(&readable_root).expect("create readable root");
let policy = SandboxPolicy::ReadOnly {
access: ReadOnlyAccess::Restricted {
include_platform_defaults: false,
readable_roots: vec![
AbsolutePathBuf::from_absolute_path(&readable_root)
.expect("absolute readable root"),
],
},
network_access: false,
};
let roots = gather_read_roots(&command_cwd, &policy, &codex_home);
let expected_helper =
dunce::canonicalize(helper_bin_dir(&codex_home)).expect("canonical helper dir");
let expected_cwd = dunce::canonicalize(&command_cwd).expect("canonical workspace");
let expected_readable =
dunce::canonicalize(&readable_root).expect("canonical readable root");
assert!(roots.contains(&expected_helper));
assert!(roots.contains(&expected_cwd));
assert!(roots.contains(&expected_readable));
assert!(
canonical_windows_platform_default_roots()
.into_iter()
.all(|path| !roots.contains(&path))
);
}
#[test]
fn restricted_read_roots_include_platform_defaults_when_enabled() {
let tmp = TempDir::new().expect("tempdir");
let codex_home = tmp.path().join("codex-home");
let command_cwd = tmp.path().join("workspace");
fs::create_dir_all(&command_cwd).expect("create workspace");
let policy = SandboxPolicy::ReadOnly {
access: ReadOnlyAccess::Restricted {
include_platform_defaults: true,
readable_roots: Vec::new(),
},
network_access: false,
};
let roots = gather_read_roots(&command_cwd, &policy, &codex_home);
assert!(
canonical_windows_platform_default_roots()
.into_iter()
.all(|path| roots.contains(&path))
);
}
#[test]
fn restricted_workspace_write_roots_remain_readable() {
fn workspace_write_roots_remain_readable() {
let tmp = TempDir::new().expect("tempdir");
let codex_home = tmp.path().join("codex-home");
let command_cwd = tmp.path().join("workspace");
@@ -1369,10 +1285,6 @@ mod tests {
AbsolutePathBuf::from_absolute_path(&writable_root)
.expect("absolute writable root"),
],
read_only_access: ReadOnlyAccess::Restricted {
include_platform_defaults: false,
readable_roots: Vec::new(),
},
network_access: false,
exclude_tmpdir_env_var: true,
exclude_slash_tmp: true,
@@ -1385,55 +1297,6 @@ mod tests {
assert!(roots.contains(&expected_writable));
}
#[test]
fn build_payload_roots_preserves_restricted_read_policy_when_no_override_is_needed() {
let tmp = TempDir::new().expect("tempdir");
let codex_home = tmp.path().join("codex-home");
let policy_cwd = tmp.path().join("policy-cwd");
let command_cwd = tmp.path().join("workspace");
let readable_root = tmp.path().join("docs");
fs::create_dir_all(&policy_cwd).expect("create policy cwd");
fs::create_dir_all(&command_cwd).expect("create workspace");
fs::create_dir_all(&readable_root).expect("create readable root");
let policy = SandboxPolicy::ReadOnly {
access: ReadOnlyAccess::Restricted {
include_platform_defaults: false,
readable_roots: vec![
AbsolutePathBuf::from_absolute_path(&readable_root)
.expect("absolute readable root"),
],
},
network_access: false,
};
let (read_roots, write_roots) = build_payload_roots(
&super::SandboxSetupRequest {
policy: &policy,
policy_cwd: &policy_cwd,
command_cwd: &command_cwd,
env_map: &HashMap::new(),
codex_home: &codex_home,
proxy_enforced: false,
},
&super::SetupRootOverrides::default(),
);
let expected_helper =
dunce::canonicalize(helper_bin_dir(&codex_home)).expect("canonical helper dir");
let expected_cwd = dunce::canonicalize(&command_cwd).expect("canonical workspace");
let expected_readable =
dunce::canonicalize(&readable_root).expect("canonical readable root");
assert_eq!(write_roots, Vec::<PathBuf>::new());
assert!(read_roots.contains(&expected_helper));
assert!(read_roots.contains(&expected_cwd));
assert!(read_roots.contains(&expected_readable));
assert!(
canonical_windows_platform_default_roots()
.into_iter()
.all(|path| !read_roots.contains(&path))
);
}
#[test]
fn build_payload_roots_preserves_helper_roots_when_read_override_is_provided() {
let tmp = TempDir::new().expect("tempdir");
@@ -1445,10 +1308,6 @@ mod tests {
fs::create_dir_all(&command_cwd).expect("create workspace");
fs::create_dir_all(&readable_root).expect("create readable root");
let policy = SandboxPolicy::ReadOnly {
access: ReadOnlyAccess::Restricted {
include_platform_defaults: true,
readable_roots: Vec::new(),
},
network_access: false,
};
@@ -1463,6 +1322,7 @@ mod tests {
},
&super::SetupRootOverrides {
read_roots: Some(vec![readable_root.clone()]),
read_roots_include_platform_defaults: true,
write_roots: None,
deny_write_paths: None,
},
@@ -1495,7 +1355,6 @@ mod tests {
fs::create_dir_all(&command_cwd).expect("create workspace");
fs::create_dir_all(&readable_root).expect("create readable root");
let policy = SandboxPolicy::ReadOnly {
access: ReadOnlyAccess::FullAccess,
network_access: false,
};
@@ -1510,6 +1369,7 @@ mod tests {
},
&super::SetupRootOverrides {
read_roots: Some(vec![readable_root.clone()]),
read_roots_include_platform_defaults: false,
write_roots: None,
deny_write_paths: None,
},
@@ -1547,10 +1407,6 @@ mod tests {
AbsolutePathBuf::from_absolute_path(&extra_write_root)
.expect("absolute writable root"),
],
read_only_access: ReadOnlyAccess::Restricted {
include_platform_defaults: false,
readable_roots: Vec::new(),
},
network_access: false,
exclude_tmpdir_env_var: true,
exclude_slash_tmp: true,
@@ -294,6 +294,7 @@ pub(crate) fn prepare_elevated_spawn_context(
env_map,
codex_home,
/*read_roots_override*/ None,
/*read_roots_include_platform_defaults*/ false,
write_roots_override,
&deny_write_paths,
/*proxy_enforced*/ false,
@@ -349,7 +350,6 @@ mod tests {
assert!(!should_apply_network_block(
&SandboxPolicy::WorkspaceWrite {
writable_roots: Vec::new(),
read_only_access: Default::default(),
network_access: true,
exclude_tmpdir_env_var: false,
exclude_slash_tmp: false,