Make sandbox read access configurable with ReadOnlyAccess

## What

This change introduces a new `ReadOnlyAccess` model and threads it through
sandbox policy consumers so read access is explicit instead of implicit.

- Added `ReadOnlyAccess` to protocol:
  - `Restricted { include_platform_defaults, readable_roots }`
  - `FullAccess`
- Changed `SandboxPolicy` shape:
  - `ReadOnly` is now `ReadOnly { access: ReadOnlyAccess }`
  - `WorkspaceWrite` now carries `read_only_access: ReadOnlyAccess`
- Kept existing behavior for now by defaulting to `ReadOnlyAccess::FullAccess`
  in constructors and current config/app-server mappings.
- Added helper methods to compute effective readable roots (including optional
  platform defaults + cwd) and to detect full read access.
- Updated seatbelt policy generation to honor restricted read roots by emitting
  scoped `(allow file-read* ...)` entries when full read access is not granted.
- Updated Linux backends (`bwrap`, legacy landlock path) to fail closed with an
  explicit `UnsupportedOperation` when restricted read access is requested but
  not yet implemented there.
- Updated Windows sandbox backends (standard, elevated, and runner paths) to
  fail closed in the same way for restricted read access.
- Updated all call sites/tests/pattern matches for the new structured variants
  and regenerated app-server protocol schema/types.

## Why

The previous `SandboxPolicy::ReadOnly` implied full-disk read access and left
no way to express a narrower read surface.

This refactor establishes the policy model needed to support user-configurable
read restrictions in a follow-up without changing current runtime behavior.

It also ensures we do not silently ignore future restricted-read policies on
platform backends that do not support them yet. Failing closed keeps sandbox
semantics predictable and avoids accidental over-permission.

## Compatibility and rollout notes

- Existing behavior is preserved by default (`FullAccess`).
- Existing config/app-server flows continue to serialize/deserialize cleanly.
- New schema artifacts are included to keep generated protocol outputs in sync.

## Validation

- `just fmt`
- `just fix -p codex-protocol -p codex-core -p codex-linux-sandbox -p codex-windows-sandbox -p codex-app-server-protocol`
- `cargo check -p codex-windows-sandbox`
- Targeted crate/test runs were executed during development for protocol/core/
  sandbox-related crates.
This commit is contained in:
Michael Bolin
2026-02-11 10:30:37 -08:00
parent 1170ffeeae
commit dc092a644d
82 changed files with 1820 additions and 302 deletions

View File

@@ -4582,6 +4582,57 @@
],
"type": "object"
},
"ReadOnlyAccess": {
"description": "Determines how read-only file access is granted inside a restricted sandbox.",
"oneOf": [
{
"description": "Restrict reads to an explicit set of roots.\n\nWhen `include_platform_defaults` is `true`, platform defaults required for basic execution are included in addition to `readable_roots`.",
"properties": {
"include_platform_defaults": {
"default": true,
"description": "Include built-in platform read roots required for basic process execution.",
"type": "boolean"
},
"readable_roots": {
"description": "Additional absolute roots that should be readable.",
"items": {
"$ref": "#/definitions/AbsolutePathBuf"
},
"type": "array"
},
"type": {
"enum": [
"restricted"
],
"title": "RestrictedReadOnlyAccessType",
"type": "string"
}
},
"required": [
"type"
],
"title": "RestrictedReadOnlyAccess",
"type": "object"
},
{
"description": "Allow unrestricted file reads.",
"properties": {
"type": {
"enum": [
"full-access"
],
"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": [
@@ -5499,8 +5550,16 @@
"type": "object"
},
{
"description": "Read-only access to the entire file-system.",
"description": "Read-only access configuration.",
"properties": {
"access": {
"allOf": [
{
"$ref": "#/definitions/ReadOnlyAccess"
}
],
"description": "Read access granted while running under this policy."
},
"type": {
"enum": [
"read-only"
@@ -5559,6 +5618,14 @@
"description": "When set to `true`, outbound network access is allowed. `false` by default.",
"type": "boolean"
},
"read_only_access": {
"allOf": [
{
"$ref": "#/definitions/ReadOnlyAccess"
}
],
"description": "Read access granted while running under this policy."
},
"type": {
"enum": [
"workspace-write"