mirror of
https://github.com/openai/codex.git
synced 2026-10-19 16:17:49 +00:00
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:
@@ -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"
|
||||
|
||||
-67
@@ -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"
|
||||
|
||||
-67
@@ -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";
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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()];
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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![
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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![
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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()),
|
||||
},
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user