feat: make sandbox read access configurable with ReadOnlyAccess (#11387)
`SandboxPolicy::ReadOnly` previously implied broad read access and could
not express a narrower read surface.
This change introduces an explicit read-access model so we can support
user-configurable read restrictions in follow-up work, while preserving
current behavior today.
It also ensures unsupported backends fail closed for restricted-read
policies instead of silently granting broader access than intended.
## What
- Added `ReadOnlyAccess` in protocol with:
- `Restricted { include_platform_defaults, readable_roots }`
- `FullAccess`
- Updated `SandboxPolicy` to carry read-access configuration:
- `ReadOnly { access: ReadOnlyAccess }`
- `WorkspaceWrite { ..., read_only_access: ReadOnlyAccess }`
- Preserved existing behavior by defaulting current construction paths
to `ReadOnlyAccess::FullAccess`.
- Threaded the new fields through sandbox policy consumers and call
sites across `core`, `tui`, `linux-sandbox`, `windows-sandbox`, and
related tests.
- Updated Seatbelt policy generation to honor restricted read roots by
emitting scoped read rules when full read access is not granted.
- Added fail-closed behavior on Linux and Windows backends when
restricted read access is requested but not yet implemented there
(`UnsupportedOperation`).
- Regenerated app-server protocol schema and TypeScript artifacts,
including `ReadOnlyAccess`.
## Compatibility / rollout
- Runtime behavior remains unchanged by default (`FullAccess`).
- API/schema changes are in place so future config wiring can enable
restricted read access without another policy-shape migration.
This commit is contained in:
parent
572ab66496
commit
abbd74e2be
79 changed files with 1797 additions and 188 deletions
|
|
@ -1243,6 +1243,104 @@
|
|||
],
|
||||
"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"
|
||||
}
|
||||
]
|
||||
},
|
||||
"ReadOnlyAccess2": {
|
||||
"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": "RestrictedReadOnlyAccess2Type",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type"
|
||||
],
|
||||
"title": "RestrictedReadOnlyAccess2",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"description": "Allow unrestricted file reads.",
|
||||
"properties": {
|
||||
"type": {
|
||||
"enum": [
|
||||
"full-access"
|
||||
],
|
||||
"title": "FullAccessReadOnlyAccess2Type",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type"
|
||||
],
|
||||
"title": "FullAccessReadOnlyAccess2",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
"ReasoningEffort": {
|
||||
"description": "See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#get-started-with-reasoning",
|
||||
"enum": [
|
||||
|
|
@ -1922,6 +2020,16 @@
|
|||
},
|
||||
{
|
||||
"properties": {
|
||||
"access": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ReadOnlyAccess"
|
||||
}
|
||||
],
|
||||
"default": {
|
||||
"type": "fullAccess"
|
||||
}
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"readOnly"
|
||||
|
|
@ -1974,6 +2082,16 @@
|
|||
"default": false,
|
||||
"type": "boolean"
|
||||
},
|
||||
"readOnlyAccess": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ReadOnlyAccess"
|
||||
}
|
||||
],
|
||||
"default": {
|
||||
"type": "fullAccess"
|
||||
}
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"workspaceWrite"
|
||||
|
|
@ -2018,8 +2136,16 @@
|
|||
"type": "object"
|
||||
},
|
||||
{
|
||||
"description": "Read-only access to the entire file-system.",
|
||||
"description": "Read-only access configuration.",
|
||||
"properties": {
|
||||
"access": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ReadOnlyAccess2"
|
||||
}
|
||||
],
|
||||
"description": "Read access granted while running under this policy."
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"read-only"
|
||||
|
|
@ -2078,6 +2204,14 @@
|
|||
"description": "When set to `true`, outbound network access is allowed. `false` by default.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"read_only_access": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ReadOnlyAccess2"
|
||||
}
|
||||
],
|
||||
"description": "Read access granted while running under this policy."
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"workspace-write"
|
||||
|
|
|
|||
|
|
@ -3516,6 +3516,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": [
|
||||
|
|
@ -4353,8 +4404,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"
|
||||
|
|
@ -4413,6 +4472,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"
|
||||
|
|
|
|||
|
|
@ -4526,6 +4526,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": [
|
||||
|
|
@ -5443,8 +5494,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"
|
||||
|
|
@ -5503,6 +5562,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"
|
||||
|
|
|
|||
|
|
@ -6601,6 +6601,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": [
|
||||
|
|
@ -7614,8 +7665,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"
|
||||
|
|
@ -7674,6 +7733,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"
|
||||
|
|
@ -12915,6 +12982,53 @@
|
|||
"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"
|
||||
}
|
||||
]
|
||||
},
|
||||
"ReasoningEffort": {
|
||||
"description": "See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#get-started-with-reasoning",
|
||||
"enum": [
|
||||
|
|
@ -13755,6 +13869,16 @@
|
|||
},
|
||||
{
|
||||
"properties": {
|
||||
"access": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/v2/ReadOnlyAccess"
|
||||
}
|
||||
],
|
||||
"default": {
|
||||
"type": "fullAccess"
|
||||
}
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"readOnly"
|
||||
|
|
@ -13807,6 +13931,16 @@
|
|||
"default": false,
|
||||
"type": "boolean"
|
||||
},
|
||||
"readOnlyAccess": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/v2/ReadOnlyAccess"
|
||||
}
|
||||
],
|
||||
"default": {
|
||||
"type": "fullAccess"
|
||||
}
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"workspaceWrite"
|
||||
|
|
|
|||
|
|
@ -13,6 +13,57 @@
|
|||
],
|
||||
"type": "string"
|
||||
},
|
||||
"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"
|
||||
}
|
||||
]
|
||||
},
|
||||
"SandboxPolicy": {
|
||||
"description": "Determines execution restrictions for model shell commands.",
|
||||
"oneOf": [
|
||||
|
|
@ -34,8 +85,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"
|
||||
|
|
@ -94,6 +153,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"
|
||||
|
|
|
|||
|
|
@ -3516,6 +3516,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": [
|
||||
|
|
@ -4353,8 +4404,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"
|
||||
|
|
@ -4413,6 +4472,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"
|
||||
|
|
|
|||
|
|
@ -3516,6 +3516,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": [
|
||||
|
|
@ -4353,8 +4404,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"
|
||||
|
|
@ -4413,6 +4472,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"
|
||||
|
|
|
|||
|
|
@ -142,6 +142,57 @@
|
|||
],
|
||||
"type": "string"
|
||||
},
|
||||
"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": [
|
||||
|
|
@ -195,8 +246,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"
|
||||
|
|
@ -255,6 +314,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"
|
||||
|
|
|
|||
|
|
@ -3516,6 +3516,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": [
|
||||
|
|
@ -4353,8 +4404,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"
|
||||
|
|
@ -4413,6 +4472,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"
|
||||
|
|
|
|||
|
|
@ -12,6 +12,53 @@
|
|||
],
|
||||
"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"
|
||||
}
|
||||
]
|
||||
},
|
||||
"SandboxPolicy": {
|
||||
"oneOf": [
|
||||
{
|
||||
|
|
@ -32,6 +79,16 @@
|
|||
},
|
||||
{
|
||||
"properties": {
|
||||
"access": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ReadOnlyAccess"
|
||||
}
|
||||
],
|
||||
"default": {
|
||||
"type": "fullAccess"
|
||||
}
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"readOnly"
|
||||
|
|
@ -84,6 +141,16 @@
|
|||
"default": false,
|
||||
"type": "boolean"
|
||||
},
|
||||
"readOnlyAccess": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ReadOnlyAccess"
|
||||
}
|
||||
],
|
||||
"default": {
|
||||
"type": "fullAccess"
|
||||
}
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"workspaceWrite"
|
||||
|
|
|
|||
|
|
@ -460,6 +460,53 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"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": [
|
||||
|
|
@ -492,6 +539,16 @@
|
|||
},
|
||||
{
|
||||
"properties": {
|
||||
"access": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ReadOnlyAccess"
|
||||
}
|
||||
],
|
||||
"default": {
|
||||
"type": "fullAccess"
|
||||
}
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"readOnly"
|
||||
|
|
@ -544,6 +601,16 @@
|
|||
"default": false,
|
||||
"type": "boolean"
|
||||
},
|
||||
"readOnlyAccess": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ReadOnlyAccess"
|
||||
}
|
||||
],
|
||||
"default": {
|
||||
"type": "fullAccess"
|
||||
}
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"workspaceWrite"
|
||||
|
|
|
|||
|
|
@ -460,6 +460,53 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"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": [
|
||||
|
|
@ -492,6 +539,16 @@
|
|||
},
|
||||
{
|
||||
"properties": {
|
||||
"access": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ReadOnlyAccess"
|
||||
}
|
||||
],
|
||||
"default": {
|
||||
"type": "fullAccess"
|
||||
}
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"readOnly"
|
||||
|
|
@ -544,6 +601,16 @@
|
|||
"default": false,
|
||||
"type": "boolean"
|
||||
},
|
||||
"readOnlyAccess": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ReadOnlyAccess"
|
||||
}
|
||||
],
|
||||
"default": {
|
||||
"type": "fullAccess"
|
||||
}
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"workspaceWrite"
|
||||
|
|
|
|||
|
|
@ -460,6 +460,53 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"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": [
|
||||
|
|
@ -492,6 +539,16 @@
|
|||
},
|
||||
{
|
||||
"properties": {
|
||||
"access": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ReadOnlyAccess"
|
||||
}
|
||||
],
|
||||
"default": {
|
||||
"type": "fullAccess"
|
||||
}
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"readOnly"
|
||||
|
|
@ -544,6 +601,16 @@
|
|||
"default": false,
|
||||
"type": "boolean"
|
||||
},
|
||||
"readOnlyAccess": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ReadOnlyAccess"
|
||||
}
|
||||
],
|
||||
"default": {
|
||||
"type": "fullAccess"
|
||||
}
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"workspaceWrite"
|
||||
|
|
|
|||
|
|
@ -72,6 +72,53 @@
|
|||
],
|
||||
"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": [
|
||||
|
|
@ -124,6 +171,16 @@
|
|||
},
|
||||
{
|
||||
"properties": {
|
||||
"access": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ReadOnlyAccess"
|
||||
}
|
||||
],
|
||||
"default": {
|
||||
"type": "fullAccess"
|
||||
}
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"readOnly"
|
||||
|
|
@ -176,6 +233,16 @@
|
|||
"default": false,
|
||||
"type": "boolean"
|
||||
},
|
||||
"readOnlyAccess": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ReadOnlyAccess"
|
||||
}
|
||||
],
|
||||
"default": {
|
||||
"type": "fullAccess"
|
||||
}
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"workspaceWrite"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
// 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";
|
||||
|
||||
/**
|
||||
* Determines how read-only file access is granted inside a restricted
|
||||
* sandbox.
|
||||
*/
|
||||
export type ReadOnlyAccess = { "type": "restricted",
|
||||
/**
|
||||
* Include built-in platform read roots required for basic process
|
||||
* execution.
|
||||
*/
|
||||
include_platform_defaults: boolean,
|
||||
/**
|
||||
* Additional absolute roots that should be readable.
|
||||
*/
|
||||
readable_roots?: Array<AbsolutePathBuf>, } | { "type": "full-access" };
|
||||
|
|
@ -3,11 +3,16 @@
|
|||
// 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";
|
||||
|
||||
/**
|
||||
* Determines execution restrictions for model shell commands.
|
||||
*/
|
||||
export type SandboxPolicy = { "type": "danger-full-access" } | { "type": "read-only" } | { "type": "external-sandbox",
|
||||
export type SandboxPolicy = { "type": "danger-full-access" } | { "type": "read-only",
|
||||
/**
|
||||
* Read access granted while running under this policy.
|
||||
*/
|
||||
access?: ReadOnlyAccess, } | { "type": "external-sandbox",
|
||||
/**
|
||||
* Whether the external sandbox permits outbound network traffic.
|
||||
*/
|
||||
|
|
@ -17,6 +22,10 @@ network_access: NetworkAccess, } | { "type": "workspace-write",
|
|||
* writable from within the sandbox.
|
||||
*/
|
||||
writable_roots?: Array<AbsolutePathBuf>,
|
||||
/**
|
||||
* Read access granted while running under this policy.
|
||||
*/
|
||||
read_only_access?: ReadOnlyAccess,
|
||||
/**
|
||||
* When set to `true`, outbound network access is allowed. `false` by
|
||||
* default.
|
||||
|
|
|
|||
|
|
@ -137,6 +137,7 @@ export type { Profile } from "./Profile";
|
|||
export type { RateLimitSnapshot } from "./RateLimitSnapshot";
|
||||
export type { RateLimitWindow } from "./RateLimitWindow";
|
||||
export type { RawResponseItemEvent } from "./RawResponseItemEvent";
|
||||
export type { ReadOnlyAccess } from "./ReadOnlyAccess";
|
||||
export type { ReasoningContentDeltaEvent } from "./ReasoningContentDeltaEvent";
|
||||
export type { ReasoningEffort } from "./ReasoningEffort";
|
||||
export type { ReasoningItem } from "./ReasoningItem";
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
// 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,5 +3,6 @@
|
|||
// 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" } | { "type": "externalSandbox", networkAccess: NetworkAccess, } | { "type": "workspaceWrite", writableRoots: Array<AbsolutePathBuf>, networkAccess: boolean, excludeTmpdirEnvVar: boolean, excludeSlashTmp: boolean, };
|
||||
export type SandboxPolicy = { "type": "dangerFullAccess" } | { "type": "readOnly", access: ReadOnlyAccess, } | { "type": "externalSandbox", networkAccess: NetworkAccess, } | { "type": "workspaceWrite", writableRoots: Array<AbsolutePathBuf>, readOnlyAccess: ReadOnlyAccess, networkAccess: boolean, excludeTmpdirEnvVar: boolean, excludeSlashTmp: boolean, };
|
||||
|
|
|
|||
|
|
@ -101,6 +101,7 @@ export type { ProfileV2 } from "./ProfileV2";
|
|||
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";
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ use codex_protocol::protocol::CreditsSnapshot as CoreCreditsSnapshot;
|
|||
use codex_protocol::protocol::NetworkAccess as CoreNetworkAccess;
|
||||
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::SessionSource as CoreSessionSource;
|
||||
use codex_protocol::protocol::SkillDependencies as CoreSkillDependencies;
|
||||
use codex_protocol::protocol::SkillErrorInfo as CoreSkillErrorInfo;
|
||||
|
|
@ -395,6 +396,10 @@ 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/")]
|
||||
|
|
@ -638,13 +643,65 @@ 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)]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
#[ts(tag = "type")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum SandboxPolicy {
|
||||
DangerFullAccess,
|
||||
ReadOnly,
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(rename_all = "camelCase")]
|
||||
ReadOnly {
|
||||
#[serde(default)]
|
||||
access: ReadOnlyAccess,
|
||||
},
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(rename_all = "camelCase")]
|
||||
ExternalSandbox {
|
||||
|
|
@ -657,6 +714,8 @@ pub enum SandboxPolicy {
|
|||
#[serde(default)]
|
||||
writable_roots: Vec<AbsolutePathBuf>,
|
||||
#[serde(default)]
|
||||
read_only_access: ReadOnlyAccess,
|
||||
#[serde(default)]
|
||||
network_access: bool,
|
||||
#[serde(default)]
|
||||
exclude_tmpdir_env_var: bool,
|
||||
|
|
@ -671,7 +730,11 @@ impl SandboxPolicy {
|
|||
SandboxPolicy::DangerFullAccess => {
|
||||
codex_protocol::protocol::SandboxPolicy::DangerFullAccess
|
||||
}
|
||||
SandboxPolicy::ReadOnly => codex_protocol::protocol::SandboxPolicy::ReadOnly,
|
||||
SandboxPolicy::ReadOnly { access } => {
|
||||
codex_protocol::protocol::SandboxPolicy::ReadOnly {
|
||||
access: access.to_core(),
|
||||
}
|
||||
}
|
||||
SandboxPolicy::ExternalSandbox { network_access } => {
|
||||
codex_protocol::protocol::SandboxPolicy::ExternalSandbox {
|
||||
network_access: match network_access {
|
||||
|
|
@ -682,11 +745,13 @@ 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,
|
||||
|
|
@ -701,7 +766,11 @@ impl From<codex_protocol::protocol::SandboxPolicy> for SandboxPolicy {
|
|||
codex_protocol::protocol::SandboxPolicy::DangerFullAccess => {
|
||||
SandboxPolicy::DangerFullAccess
|
||||
}
|
||||
codex_protocol::protocol::SandboxPolicy::ReadOnly => SandboxPolicy::ReadOnly,
|
||||
codex_protocol::protocol::SandboxPolicy::ReadOnly { access } => {
|
||||
SandboxPolicy::ReadOnly {
|
||||
access: ReadOnlyAccess::from(access),
|
||||
}
|
||||
}
|
||||
codex_protocol::protocol::SandboxPolicy::ExternalSandbox { network_access } => {
|
||||
SandboxPolicy::ExternalSandbox {
|
||||
network_access: match network_access {
|
||||
|
|
@ -712,11 +781,13 @@ 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,
|
||||
|
|
@ -3226,11 +3297,21 @@ 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 pretty_assertions::assert_eq;
|
||||
use serde_json::json;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn test_absolute_path() -> AbsolutePathBuf {
|
||||
let path = if cfg!(windows) {
|
||||
r"C:\readable"
|
||||
} else {
|
||||
"/readable"
|
||||
};
|
||||
AbsolutePathBuf::from_absolute_path(path).expect("path must be absolute")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sandbox_policy_round_trips_external_sandbox_network_access() {
|
||||
let v2_policy = SandboxPolicy::ExternalSandbox {
|
||||
|
|
@ -3249,6 +3330,100 @@ mod tests {
|
|||
assert_eq!(back_to_v2, v2_policy);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sandbox_policy_round_trips_read_only_access() {
|
||||
let readable_root = test_absolute_path();
|
||||
let v2_policy = SandboxPolicy::ReadOnly {
|
||||
access: ReadOnlyAccess::Restricted {
|
||||
include_platform_defaults: false,
|
||||
readable_roots: vec![readable_root.clone()],
|
||||
},
|
||||
};
|
||||
|
||||
let core_policy = v2_policy.to_core();
|
||||
assert_eq!(
|
||||
core_policy,
|
||||
codex_protocol::protocol::SandboxPolicy::ReadOnly {
|
||||
access: CoreReadOnlyAccess::Restricted {
|
||||
include_platform_defaults: false,
|
||||
readable_roots: vec![readable_root],
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
let back_to_v2 = SandboxPolicy::from(core_policy);
|
||||
assert_eq!(back_to_v2, v2_policy);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sandbox_policy_round_trips_workspace_write_read_only_access() {
|
||||
let readable_root = test_absolute_path();
|
||||
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,
|
||||
};
|
||||
|
||||
let core_policy = v2_policy.to_core();
|
||||
assert_eq!(
|
||||
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,
|
||||
}
|
||||
);
|
||||
|
||||
let back_to_v2 = SandboxPolicy::from(core_policy);
|
||||
assert_eq!(back_to_v2, v2_policy);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sandbox_policy_deserializes_legacy_read_only_without_access_field() {
|
||||
let policy: SandboxPolicy = serde_json::from_value(json!({
|
||||
"type": "readOnly"
|
||||
}))
|
||||
.expect("read-only policy should deserialize");
|
||||
assert_eq!(
|
||||
policy,
|
||||
SandboxPolicy::ReadOnly {
|
||||
access: ReadOnlyAccess::FullAccess,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sandbox_policy_deserializes_legacy_workspace_write_without_read_only_access_field() {
|
||||
let policy: SandboxPolicy = serde_json::from_value(json!({
|
||||
"type": "workspaceWrite",
|
||||
"writableRoots": [],
|
||||
"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,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn core_turn_item_into_thread_item_converts_supported_variants() {
|
||||
let user_item = TurnItem::UserMessage(UserMessageItem {
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ use codex_app_server_protocol::ModelListParams;
|
|||
use codex_app_server_protocol::ModelListResponse;
|
||||
use codex_app_server_protocol::NewConversationParams;
|
||||
use codex_app_server_protocol::NewConversationResponse;
|
||||
use codex_app_server_protocol::ReadOnlyAccess;
|
||||
use codex_app_server_protocol::RequestId;
|
||||
use codex_app_server_protocol::SandboxPolicy;
|
||||
use codex_app_server_protocol::SendUserMessageParams;
|
||||
|
|
@ -301,7 +302,9 @@ fn trigger_cmd_approval(
|
|||
config_overrides,
|
||||
message,
|
||||
Some(AskForApproval::OnRequest),
|
||||
Some(SandboxPolicy::ReadOnly),
|
||||
Some(SandboxPolicy::ReadOnly {
|
||||
access: ReadOnlyAccess::FullAccess,
|
||||
}),
|
||||
dynamic_tools,
|
||||
)
|
||||
}
|
||||
|
|
@ -320,7 +323,9 @@ fn trigger_patch_approval(
|
|||
config_overrides,
|
||||
message,
|
||||
Some(AskForApproval::OnRequest),
|
||||
Some(SandboxPolicy::ReadOnly),
|
||||
Some(SandboxPolicy::ReadOnly {
|
||||
access: ReadOnlyAccess::FullAccess,
|
||||
}),
|
||||
dynamic_tools,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -444,6 +444,7 @@ async fn test_send_user_turn_updates_sandbox_and_cwd_between_turns() -> Result<(
|
|||
approval_policy: AskForApproval::Never,
|
||||
sandbox_policy: SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots: vec![first_cwd.try_into()?],
|
||||
read_only_access: Default::default(),
|
||||
network_access: false,
|
||||
exclude_tmpdir_env_var: false,
|
||||
exclude_slash_tmp: false,
|
||||
|
|
|
|||
|
|
@ -1104,6 +1104,7 @@ async fn turn_start_updates_sandbox_and_cwd_between_turns_v2() -> Result<()> {
|
|||
approval_policy: Some(codex_app_server_protocol::AskForApproval::Never),
|
||||
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,
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ impl Default for ConfigRequirements {
|
|||
None,
|
||||
),
|
||||
sandbox_policy: ConstrainedWithSource::new(
|
||||
Constrained::allow_any(SandboxPolicy::ReadOnly),
|
||||
Constrained::allow_any(SandboxPolicy::new_read_only_policy()),
|
||||
None,
|
||||
),
|
||||
web_search_mode: ConstrainedWithSource::new(
|
||||
|
|
@ -421,7 +421,7 @@ impl TryFrom<ConfigRequirementsWithSources> for ConfigRequirements {
|
|||
// the other variants (WorkspaceWrite, ExternalSandbox) require
|
||||
// additional parameters. Ultimately, we should expand the config
|
||||
// format to allow specifying those parameters.
|
||||
let default_sandbox_policy = SandboxPolicy::ReadOnly;
|
||||
let default_sandbox_policy = SandboxPolicy::new_read_only_policy();
|
||||
let sandbox_policy = match allowed_sandbox_modes {
|
||||
Some(Sourced {
|
||||
value: modes,
|
||||
|
|
@ -439,7 +439,7 @@ impl TryFrom<ConfigRequirementsWithSources> for ConfigRequirements {
|
|||
let requirement_source_for_error = requirement_source.clone();
|
||||
let constrained = Constrained::new(default_sandbox_policy, move |candidate| {
|
||||
let mode = match candidate {
|
||||
SandboxPolicy::ReadOnly => SandboxModeRequirement::ReadOnly,
|
||||
SandboxPolicy::ReadOnly { .. } => SandboxModeRequirement::ReadOnly,
|
||||
SandboxPolicy::WorkspaceWrite { .. } => {
|
||||
SandboxModeRequirement::WorkspaceWrite
|
||||
}
|
||||
|
|
@ -878,7 +878,7 @@ mod tests {
|
|||
assert!(
|
||||
requirements
|
||||
.sandbox_policy
|
||||
.can_set(&SandboxPolicy::ReadOnly)
|
||||
.can_set(&SandboxPolicy::new_read_only_policy())
|
||||
.is_ok()
|
||||
);
|
||||
|
||||
|
|
@ -897,7 +897,7 @@ mod tests {
|
|||
assert!(
|
||||
requirements
|
||||
.sandbox_policy
|
||||
.can_set(&SandboxPolicy::ReadOnly)
|
||||
.can_set(&SandboxPolicy::new_read_only_policy())
|
||||
.is_ok()
|
||||
);
|
||||
assert!(
|
||||
|
|
@ -905,6 +905,7 @@ mod tests {
|
|||
.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,
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ use crate::model_provider_info::built_in_model_providers;
|
|||
use crate::project_doc::DEFAULT_PROJECT_DOC_FILENAME;
|
||||
use crate::project_doc::LOCAL_PROJECT_DOC_FILENAME;
|
||||
use crate::protocol::AskForApproval;
|
||||
use crate::protocol::ReadOnlyAccess;
|
||||
use crate::protocol::SandboxPolicy;
|
||||
use crate::windows_sandbox::WindowsSandboxLevelExt;
|
||||
use crate::windows_sandbox::resolve_windows_sandbox_mode;
|
||||
|
|
@ -1198,6 +1199,7 @@ 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,
|
||||
|
|
@ -2122,7 +2124,7 @@ network_access = true # This should be ignored.
|
|||
&PathBuf::from("/tmp/test"),
|
||||
None,
|
||||
);
|
||||
assert_eq!(resolution, SandboxPolicy::ReadOnly);
|
||||
assert_eq!(resolution, SandboxPolicy::new_read_only_policy());
|
||||
|
||||
let writable_root = test_absolute_path("/my/workspace");
|
||||
let sandbox_workspace_write = format!(
|
||||
|
|
@ -2150,12 +2152,13 @@ exclude_slash_tmp = true
|
|||
None,
|
||||
);
|
||||
if cfg!(target_os = "windows") {
|
||||
assert_eq!(resolution, SandboxPolicy::ReadOnly);
|
||||
assert_eq!(resolution, SandboxPolicy::new_read_only_policy());
|
||||
} else {
|
||||
assert_eq!(
|
||||
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,
|
||||
|
|
@ -2191,12 +2194,13 @@ trust_level = "trusted"
|
|||
None,
|
||||
);
|
||||
if cfg!(target_os = "windows") {
|
||||
assert_eq!(resolution, SandboxPolicy::ReadOnly);
|
||||
assert_eq!(resolution, SandboxPolicy::new_read_only_policy());
|
||||
} else {
|
||||
assert_eq!(
|
||||
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,
|
||||
|
|
@ -2363,7 +2367,7 @@ trust_level = "trusted"
|
|||
let expected_backend = AbsolutePathBuf::try_from(backend).unwrap();
|
||||
if cfg!(target_os = "windows") {
|
||||
match config.sandbox_policy.get() {
|
||||
&SandboxPolicy::ReadOnly => {}
|
||||
SandboxPolicy::ReadOnly { .. } => {}
|
||||
other => panic!("expected read-only policy on Windows, got {other:?}"),
|
||||
}
|
||||
} else {
|
||||
|
|
@ -2509,7 +2513,10 @@ trust_level = "trusted"
|
|||
#[test]
|
||||
fn web_search_mode_for_turn_uses_preference_for_read_only() {
|
||||
let web_search_mode = Constrained::allow_any(WebSearchMode::Cached);
|
||||
let mode = resolve_web_search_mode_for_turn(&web_search_mode, &SandboxPolicy::ReadOnly);
|
||||
let mode = resolve_web_search_mode_for_turn(
|
||||
&web_search_mode,
|
||||
&SandboxPolicy::new_read_only_policy(),
|
||||
);
|
||||
|
||||
assert_eq!(mode, WebSearchMode::Cached);
|
||||
}
|
||||
|
|
@ -2692,7 +2699,7 @@ profile = "project"
|
|||
if cfg!(target_os = "windows") {
|
||||
assert!(matches!(
|
||||
config.sandbox_policy.get(),
|
||||
SandboxPolicy::ReadOnly
|
||||
SandboxPolicy::ReadOnly { .. }
|
||||
));
|
||||
} else {
|
||||
assert!(matches!(
|
||||
|
|
@ -4666,7 +4673,7 @@ trust_level = "untrusted"
|
|||
// Verify that untrusted projects get WorkspaceWrite (or ReadOnly on Windows due to downgrade)
|
||||
if cfg!(target_os = "windows") {
|
||||
assert!(
|
||||
matches!(resolution, SandboxPolicy::ReadOnly),
|
||||
matches!(resolution, SandboxPolicy::ReadOnly { .. }),
|
||||
"Expected ReadOnly on Windows, got {resolution:?}"
|
||||
);
|
||||
} else {
|
||||
|
|
@ -4757,7 +4764,7 @@ trust_level = "untrusted"
|
|||
);
|
||||
|
||||
if cfg!(target_os = "windows") {
|
||||
assert_eq!(resolution, SandboxPolicy::ReadOnly);
|
||||
assert_eq!(resolution, SandboxPolicy::new_read_only_policy());
|
||||
} else {
|
||||
assert_eq!(resolution, SandboxPolicy::new_workspace_write_policy());
|
||||
}
|
||||
|
|
@ -4904,7 +4911,7 @@ mcp_oauth_callback_port = 5678
|
|||
// Verify that untrusted projects still get WorkspaceWrite sandbox (or ReadOnly on Windows)
|
||||
if cfg!(target_os = "windows") {
|
||||
assert!(
|
||||
matches!(config.sandbox_policy.get(), SandboxPolicy::ReadOnly),
|
||||
matches!(config.sandbox_policy.get(), SandboxPolicy::ReadOnly { .. }),
|
||||
"Expected ReadOnly on Windows"
|
||||
);
|
||||
} else {
|
||||
|
|
@ -4938,7 +4945,10 @@ mcp_oauth_callback_port = 5678
|
|||
.build()
|
||||
.await?;
|
||||
|
||||
assert_eq!(*config.sandbox_policy.get(), SandboxPolicy::ReadOnly);
|
||||
assert_eq!(
|
||||
*config.sandbox_policy.get(),
|
||||
SandboxPolicy::new_read_only_policy()
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
@ -4972,7 +4982,10 @@ mcp_oauth_callback_port = 5678
|
|||
))
|
||||
.build()
|
||||
.await?;
|
||||
assert_eq!(*config.sandbox_policy.get(), SandboxPolicy::ReadOnly);
|
||||
assert_eq!(
|
||||
*config.sandbox_policy.get(),
|
||||
SandboxPolicy::new_read_only_policy()
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -410,7 +410,7 @@ allowed_sandbox_modes = ["read-only"]
|
|||
);
|
||||
assert_eq!(
|
||||
*state.requirements().sandbox_policy.get(),
|
||||
SandboxPolicy::ReadOnly
|
||||
SandboxPolicy::new_read_only_policy()
|
||||
);
|
||||
assert!(
|
||||
state
|
||||
|
|
@ -425,6 +425,7 @@ 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,
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ pub async fn list_accessible_connectors_from_mcp_tools_with_options(
|
|||
let cancel_token = CancellationToken::new();
|
||||
|
||||
let sandbox_state = SandboxState {
|
||||
sandbox_policy: SandboxPolicy::ReadOnly,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
codex_linux_sandbox_exe: config.codex_linux_sandbox_exe.clone(),
|
||||
sandbox_cwd: env::current_dir().unwrap_or_else(|_| PathBuf::from("/")),
|
||||
use_linux_sandbox_bwrap: config.features.enabled(Feature::UseLinuxSandboxBwrap),
|
||||
|
|
|
|||
|
|
@ -1094,7 +1094,13 @@ mod tests {
|
|||
arg0: None,
|
||||
};
|
||||
|
||||
let output = exec(params, SandboxType::None, &SandboxPolicy::ReadOnly, None).await?;
|
||||
let output = exec(
|
||||
params,
|
||||
SandboxType::None,
|
||||
&SandboxPolicy::new_read_only_policy(),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
assert!(output.timed_out);
|
||||
|
||||
let stdout = output.stdout.from_utf8_lossy().text;
|
||||
|
|
|
|||
|
|
@ -290,7 +290,7 @@ pub fn render_decision_for_unmatched_command(
|
|||
// On Windows, ReadOnly sandbox is not a real sandbox, so special-case it
|
||||
// here.
|
||||
let runtime_sandbox_provides_safety =
|
||||
cfg!(windows) && matches!(sandbox_policy, SandboxPolicy::ReadOnly);
|
||||
cfg!(windows) && matches!(sandbox_policy, SandboxPolicy::ReadOnly { .. });
|
||||
|
||||
// If the command is flagged as dangerous or we have no sandbox protection,
|
||||
// we should never allow it to run without user approval.
|
||||
|
|
@ -325,7 +325,7 @@ pub fn render_decision_for_unmatched_command(
|
|||
// command has not been flagged as dangerous.
|
||||
Decision::Allow
|
||||
}
|
||||
SandboxPolicy::ReadOnly | SandboxPolicy::WorkspaceWrite { .. } => {
|
||||
SandboxPolicy::ReadOnly { .. } | SandboxPolicy::WorkspaceWrite { .. } => {
|
||||
// In restricted sandboxes (ReadOnly/WorkspaceWrite), do not prompt for
|
||||
// non‑escalated, non‑dangerous commands — let the sandbox enforce
|
||||
// restrictions (e.g., block network/write) without a user prompt.
|
||||
|
|
@ -852,7 +852,7 @@ prefix_rule(pattern=["rm"], decision="forbidden")
|
|||
.create_exec_approval_requirement_for_command(ExecApprovalRequest {
|
||||
command: &command,
|
||||
approval_policy: AskForApproval::OnRequest,
|
||||
sandbox_policy: &SandboxPolicy::ReadOnly,
|
||||
sandbox_policy: &SandboxPolicy::new_read_only_policy(),
|
||||
sandbox_permissions: SandboxPermissions::UseDefault,
|
||||
prefix_rule: None,
|
||||
})
|
||||
|
|
@ -879,7 +879,7 @@ prefix_rule(pattern=["rm"], decision="forbidden")
|
|||
.create_exec_approval_requirement_for_command(ExecApprovalRequest {
|
||||
command: &command,
|
||||
approval_policy: AskForApproval::UnlessTrusted,
|
||||
sandbox_policy: &SandboxPolicy::ReadOnly,
|
||||
sandbox_policy: &SandboxPolicy::new_read_only_policy(),
|
||||
sandbox_permissions: SandboxPermissions::UseDefault,
|
||||
prefix_rule: None,
|
||||
})
|
||||
|
|
@ -907,7 +907,7 @@ prefix_rule(pattern=["rm"], decision="forbidden")
|
|||
.create_exec_approval_requirement_for_command(ExecApprovalRequest {
|
||||
command: &command,
|
||||
approval_policy: AskForApproval::UnlessTrusted,
|
||||
sandbox_policy: &SandboxPolicy::ReadOnly,
|
||||
sandbox_policy: &SandboxPolicy::new_read_only_policy(),
|
||||
sandbox_permissions: SandboxPermissions::UseDefault,
|
||||
prefix_rule: Some(requested_prefix.clone()),
|
||||
})
|
||||
|
|
@ -1028,7 +1028,7 @@ prefix_rule(
|
|||
.create_exec_approval_requirement_for_command(ExecApprovalRequest {
|
||||
command: &command,
|
||||
approval_policy: AskForApproval::UnlessTrusted,
|
||||
sandbox_policy: &SandboxPolicy::ReadOnly,
|
||||
sandbox_policy: &SandboxPolicy::new_read_only_policy(),
|
||||
sandbox_permissions: SandboxPermissions::UseDefault,
|
||||
prefix_rule: None,
|
||||
})
|
||||
|
|
@ -1052,7 +1052,7 @@ prefix_rule(
|
|||
.create_exec_approval_requirement_for_command(ExecApprovalRequest {
|
||||
command: &command,
|
||||
approval_policy: AskForApproval::UnlessTrusted,
|
||||
sandbox_policy: &SandboxPolicy::ReadOnly,
|
||||
sandbox_policy: &SandboxPolicy::new_read_only_policy(),
|
||||
sandbox_permissions: SandboxPermissions::UseDefault,
|
||||
prefix_rule: None,
|
||||
})
|
||||
|
|
@ -1080,7 +1080,7 @@ prefix_rule(
|
|||
.create_exec_approval_requirement_for_command(ExecApprovalRequest {
|
||||
command: &command,
|
||||
approval_policy: AskForApproval::UnlessTrusted,
|
||||
sandbox_policy: &SandboxPolicy::ReadOnly,
|
||||
sandbox_policy: &SandboxPolicy::new_read_only_policy(),
|
||||
sandbox_permissions: SandboxPermissions::UseDefault,
|
||||
prefix_rule: None,
|
||||
})
|
||||
|
|
@ -1110,7 +1110,7 @@ prefix_rule(
|
|||
.create_exec_approval_requirement_for_command(ExecApprovalRequest {
|
||||
command: &command,
|
||||
approval_policy: AskForApproval::OnRequest,
|
||||
sandbox_policy: &SandboxPolicy::ReadOnly,
|
||||
sandbox_policy: &SandboxPolicy::new_read_only_policy(),
|
||||
sandbox_permissions: SandboxPermissions::RequireEscalated,
|
||||
prefix_rule: Some(vec!["cargo".to_string(), "install".to_string()]),
|
||||
})
|
||||
|
|
@ -1221,7 +1221,7 @@ prefix_rule(
|
|||
.create_exec_approval_requirement_for_command(ExecApprovalRequest {
|
||||
command: &command,
|
||||
approval_policy: AskForApproval::UnlessTrusted,
|
||||
sandbox_policy: &SandboxPolicy::ReadOnly,
|
||||
sandbox_policy: &SandboxPolicy::new_read_only_policy(),
|
||||
sandbox_permissions: SandboxPermissions::UseDefault,
|
||||
prefix_rule: None,
|
||||
})
|
||||
|
|
@ -1278,7 +1278,7 @@ prefix_rule(
|
|||
.create_exec_approval_requirement_for_command(ExecApprovalRequest {
|
||||
command: &command,
|
||||
approval_policy: AskForApproval::UnlessTrusted,
|
||||
sandbox_policy: &SandboxPolicy::ReadOnly,
|
||||
sandbox_policy: &SandboxPolicy::new_read_only_policy(),
|
||||
sandbox_permissions: SandboxPermissions::UseDefault,
|
||||
prefix_rule: None,
|
||||
})
|
||||
|
|
@ -1316,7 +1316,7 @@ prefix_rule(
|
|||
.create_exec_approval_requirement_for_command(ExecApprovalRequest {
|
||||
command: &command,
|
||||
approval_policy: AskForApproval::UnlessTrusted,
|
||||
sandbox_policy: &SandboxPolicy::ReadOnly,
|
||||
sandbox_policy: &SandboxPolicy::new_read_only_policy(),
|
||||
sandbox_permissions: SandboxPermissions::UseDefault,
|
||||
prefix_rule: None,
|
||||
})
|
||||
|
|
@ -1339,7 +1339,7 @@ prefix_rule(
|
|||
.create_exec_approval_requirement_for_command(ExecApprovalRequest {
|
||||
command: &command,
|
||||
approval_policy: AskForApproval::OnRequest,
|
||||
sandbox_policy: &SandboxPolicy::ReadOnly,
|
||||
sandbox_policy: &SandboxPolicy::new_read_only_policy(),
|
||||
sandbox_permissions: SandboxPermissions::UseDefault,
|
||||
prefix_rule: None,
|
||||
})
|
||||
|
|
@ -1369,7 +1369,7 @@ prefix_rule(
|
|||
.create_exec_approval_requirement_for_command(ExecApprovalRequest {
|
||||
command: &command,
|
||||
approval_policy: AskForApproval::OnRequest,
|
||||
sandbox_policy: &SandboxPolicy::ReadOnly,
|
||||
sandbox_policy: &SandboxPolicy::new_read_only_policy(),
|
||||
sandbox_permissions: SandboxPermissions::UseDefault,
|
||||
prefix_rule: None,
|
||||
})
|
||||
|
|
@ -1458,7 +1458,7 @@ prefix_rule(
|
|||
.create_exec_approval_requirement_for_command(ExecApprovalRequest {
|
||||
command: &sneaky_command,
|
||||
approval_policy: AskForApproval::OnRequest,
|
||||
sandbox_policy: &SandboxPolicy::ReadOnly,
|
||||
sandbox_policy: &SandboxPolicy::new_read_only_policy(),
|
||||
sandbox_permissions: permissions,
|
||||
prefix_rule: None,
|
||||
})
|
||||
|
|
@ -1481,7 +1481,7 @@ prefix_rule(
|
|||
.create_exec_approval_requirement_for_command(ExecApprovalRequest {
|
||||
command: &dangerous_command,
|
||||
approval_policy: AskForApproval::OnRequest,
|
||||
sandbox_policy: &SandboxPolicy::ReadOnly,
|
||||
sandbox_policy: &SandboxPolicy::new_read_only_policy(),
|
||||
sandbox_permissions: permissions,
|
||||
prefix_rule: None,
|
||||
})
|
||||
|
|
@ -1500,7 +1500,7 @@ prefix_rule(
|
|||
.create_exec_approval_requirement_for_command(ExecApprovalRequest {
|
||||
command: &dangerous_command,
|
||||
approval_policy: AskForApproval::Never,
|
||||
sandbox_policy: &SandboxPolicy::ReadOnly,
|
||||
sandbox_policy: &SandboxPolicy::new_read_only_policy(),
|
||||
sandbox_permissions: permissions,
|
||||
prefix_rule: None,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ mod tests {
|
|||
fn bwrap_flags_are_feature_gated() {
|
||||
let command = vec!["/bin/true".to_string()];
|
||||
let cwd = Path::new("/tmp");
|
||||
let policy = SandboxPolicy::ReadOnly;
|
||||
let policy = SandboxPolicy::new_read_only_policy();
|
||||
|
||||
let with_bwrap =
|
||||
create_linux_sandbox_command_args(command.clone(), &policy, cwd, true, false);
|
||||
|
|
@ -132,7 +132,7 @@ mod tests {
|
|||
fn proxy_flag_is_included_when_requested() {
|
||||
let command = vec!["/bin/true".to_string()];
|
||||
let cwd = Path::new("/tmp");
|
||||
let policy = SandboxPolicy::ReadOnly;
|
||||
let policy = SandboxPolicy::new_read_only_policy();
|
||||
|
||||
let args = create_linux_sandbox_command_args(command, &policy, cwd, true, true);
|
||||
assert_eq!(
|
||||
|
|
|
|||
|
|
@ -165,7 +165,7 @@ pub async fn collect_mcp_snapshot(config: &Config) -> McpListToolsResponseEvent
|
|||
|
||||
// Use ReadOnly sandbox policy for MCP snapshot collection (safest default)
|
||||
let sandbox_state = SandboxState {
|
||||
sandbox_policy: SandboxPolicy::ReadOnly,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
codex_linux_sandbox_exe: config.codex_linux_sandbox_exe.clone(),
|
||||
sandbox_cwd: env::current_dir().unwrap_or_else(|_| PathBuf::from("/")),
|
||||
use_linux_sandbox_bwrap: config.features.enabled(Feature::UseLinuxSandboxBwrap),
|
||||
|
|
|
|||
|
|
@ -83,6 +83,7 @@ pub(super) async fn run_global_memory_consolidation(
|
|||
}
|
||||
let consolidation_sandbox_policy = SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots,
|
||||
read_only_access: Default::default(),
|
||||
network_access: false,
|
||||
exclude_tmpdir_env_var: false,
|
||||
exclude_slash_tmp: false,
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ pub(crate) fn builder_from_session_meta(
|
|||
builder.model_provider = session_meta.meta.model_provider.clone();
|
||||
builder.cwd = session_meta.meta.cwd.clone();
|
||||
builder.cli_version = Some(session_meta.meta.cli_version.clone());
|
||||
builder.sandbox_policy = SandboxPolicy::ReadOnly;
|
||||
builder.sandbox_policy = SandboxPolicy::new_read_only_policy();
|
||||
builder.approval_mode = AskForApproval::OnRequest;
|
||||
if let Some(git) = session_meta.git.as_ref() {
|
||||
builder.git_sha = git.commit_hash.clone();
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ fn is_write_patch_constrained_to_writable_paths(
|
|||
) -> bool {
|
||||
// Early‑exit if there are no declared writable roots.
|
||||
let writable_roots = match sandbox_policy {
|
||||
SandboxPolicy::ReadOnly => {
|
||||
SandboxPolicy::ReadOnly { .. } => {
|
||||
return false;
|
||||
}
|
||||
SandboxPolicy::DangerFullAccess | SandboxPolicy::ExternalSandbox { .. } => {
|
||||
|
|
@ -195,6 +195,7 @@ mod tests {
|
|||
// 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,
|
||||
|
|
@ -216,6 +217,7 @@ mod tests {
|
|||
// outside write should be permitted.
|
||||
let policy_with_parent = SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots: vec![AbsolutePathBuf::try_from(parent).unwrap()],
|
||||
read_only_access: Default::default(),
|
||||
network_access: false,
|
||||
exclude_tmpdir_env_var: true,
|
||||
exclude_slash_tmp: true,
|
||||
|
|
|
|||
|
|
@ -237,10 +237,40 @@ pub(crate) fn create_seatbelt_command_args(
|
|||
}
|
||||
};
|
||||
|
||||
let file_read_policy = if sandbox_policy.has_full_disk_read_access() {
|
||||
"; allow read-only file operations\n(allow file-read*)"
|
||||
let (file_read_policy, file_read_dir_params) = if sandbox_policy.has_full_disk_read_access() {
|
||||
(
|
||||
"; allow read-only file operations\n(allow file-read*)".to_string(),
|
||||
Vec::new(),
|
||||
)
|
||||
} else {
|
||||
""
|
||||
let mut readable_roots_policies: Vec<String> = Vec::new();
|
||||
let mut file_read_params = Vec::new();
|
||||
for (index, root) in sandbox_policy
|
||||
.get_readable_roots_with_cwd(sandbox_policy_cwd)
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
{
|
||||
// Canonicalize to avoid mismatches like /var vs /private/var on macOS.
|
||||
let canonical_root = root
|
||||
.as_path()
|
||||
.canonicalize()
|
||||
.unwrap_or_else(|_| root.to_path_buf());
|
||||
let root_param = format!("READABLE_ROOT_{index}");
|
||||
file_read_params.push((root_param.clone(), canonical_root));
|
||||
readable_roots_policies.push(format!("(subpath (param \"{root_param}\"))"));
|
||||
}
|
||||
|
||||
if readable_roots_policies.is_empty() {
|
||||
("".to_string(), Vec::new())
|
||||
} else {
|
||||
(
|
||||
format!(
|
||||
"; allow read-only file operations\n(allow file-read*\n{}\n)",
|
||||
readable_roots_policies.join(" ")
|
||||
),
|
||||
file_read_params,
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
let proxy = proxy_policy_inputs(network);
|
||||
|
|
@ -250,7 +280,12 @@ pub(crate) fn create_seatbelt_command_args(
|
|||
"{MACOS_SEATBELT_BASE_POLICY}\n{file_read_policy}\n{file_write_policy}\n{network_policy}"
|
||||
);
|
||||
|
||||
let dir_params = [file_write_dir_params, macos_dir_params()].concat();
|
||||
let dir_params = [
|
||||
file_read_dir_params,
|
||||
file_write_dir_params,
|
||||
macos_dir_params(),
|
||||
]
|
||||
.concat();
|
||||
|
||||
let mut seatbelt_args: Vec<String> = vec!["-p".to_string(), full_policy];
|
||||
let definition_args = dir_params
|
||||
|
|
@ -329,7 +364,7 @@ mod tests {
|
|||
#[test]
|
||||
fn create_seatbelt_args_routes_network_through_proxy_ports() {
|
||||
let policy = dynamic_network_policy(
|
||||
&SandboxPolicy::ReadOnly,
|
||||
&SandboxPolicy::new_read_only_policy(),
|
||||
false,
|
||||
&ProxyPolicyInputs {
|
||||
ports: vec![43128, 48081],
|
||||
|
|
@ -363,7 +398,7 @@ mod tests {
|
|||
#[test]
|
||||
fn create_seatbelt_args_allows_local_binding_when_explicitly_enabled() {
|
||||
let policy = dynamic_network_policy(
|
||||
&SandboxPolicy::ReadOnly,
|
||||
&SandboxPolicy::new_read_only_policy(),
|
||||
false,
|
||||
&ProxyPolicyInputs {
|
||||
ports: vec![43128],
|
||||
|
|
@ -395,6 +430,7 @@ mod tests {
|
|||
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,
|
||||
|
|
@ -422,6 +458,7 @@ mod tests {
|
|||
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,
|
||||
|
|
@ -442,6 +479,7 @@ mod tests {
|
|||
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,
|
||||
|
|
@ -491,6 +529,7 @@ mod tests {
|
|||
.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,
|
||||
|
|
@ -676,6 +715,7 @@ mod tests {
|
|||
|
||||
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,
|
||||
|
|
@ -760,6 +800,7 @@ mod tests {
|
|||
// `.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,
|
||||
|
|
|
|||
|
|
@ -275,7 +275,7 @@ fn unsupported_tool_call_message(payload: &ToolPayload, tool_name: &str) -> Stri
|
|||
|
||||
fn sandbox_policy_tag(policy: &SandboxPolicy) -> &'static str {
|
||||
match policy {
|
||||
SandboxPolicy::ReadOnly => "read-only",
|
||||
SandboxPolicy::ReadOnly { .. } => "read-only",
|
||||
SandboxPolicy::WorkspaceWrite { .. } => "workspace-write",
|
||||
SandboxPolicy::DangerFullAccess => "danger-full-access",
|
||||
SandboxPolicy::ExternalSandbox { .. } => "external-sandbox",
|
||||
|
|
|
|||
|
|
@ -326,7 +326,10 @@ mod tests {
|
|||
#[test]
|
||||
fn restricted_sandbox_requires_exec_approval_on_request() {
|
||||
assert_eq!(
|
||||
default_exec_approval_requirement(AskForApproval::OnRequest, &SandboxPolicy::ReadOnly),
|
||||
default_exec_approval_requirement(
|
||||
AskForApproval::OnRequest,
|
||||
&SandboxPolicy::new_read_only_policy()
|
||||
),
|
||||
ExecApprovalRequirement::NeedsApproval {
|
||||
reason: None,
|
||||
proposed_execpolicy_amendment: None,
|
||||
|
|
|
|||
|
|
@ -578,6 +578,7 @@ 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,
|
||||
|
|
@ -634,6 +635,7 @@ 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,
|
||||
|
|
|
|||
|
|
@ -628,6 +628,7 @@ 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,
|
||||
|
|
@ -841,7 +842,7 @@ fn scenarios() -> Vec<ScenarioSpec> {
|
|||
ScenarioSpec {
|
||||
name: "read_only_on_request_requires_approval",
|
||||
approval_policy: OnRequest,
|
||||
sandbox_policy: SandboxPolicy::ReadOnly,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
action: ActionKind::WriteFile {
|
||||
target: TargetPath::Workspace("ro_on_request.txt"),
|
||||
content: "read-only-approval",
|
||||
|
|
@ -861,7 +862,7 @@ fn scenarios() -> Vec<ScenarioSpec> {
|
|||
ScenarioSpec {
|
||||
name: "read_only_on_request_requires_approval_gpt_5_1_no_exit",
|
||||
approval_policy: OnRequest,
|
||||
sandbox_policy: SandboxPolicy::ReadOnly,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
action: ActionKind::WriteFile {
|
||||
target: TargetPath::Workspace("ro_on_request_5_1.txt"),
|
||||
content: "read-only-approval",
|
||||
|
|
@ -881,7 +882,7 @@ fn scenarios() -> Vec<ScenarioSpec> {
|
|||
ScenarioSpec {
|
||||
name: "trusted_command_on_request_read_only_runs_without_prompt",
|
||||
approval_policy: OnRequest,
|
||||
sandbox_policy: SandboxPolicy::ReadOnly,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
action: ActionKind::RunCommand {
|
||||
command: "echo trusted-read-only",
|
||||
},
|
||||
|
|
@ -896,7 +897,7 @@ fn scenarios() -> Vec<ScenarioSpec> {
|
|||
ScenarioSpec {
|
||||
name: "trusted_command_on_request_read_only_runs_without_prompt_gpt_5_1_no_exit",
|
||||
approval_policy: OnRequest,
|
||||
sandbox_policy: SandboxPolicy::ReadOnly,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
action: ActionKind::RunCommand {
|
||||
command: "echo trusted-read-only",
|
||||
},
|
||||
|
|
@ -911,7 +912,7 @@ fn scenarios() -> Vec<ScenarioSpec> {
|
|||
ScenarioSpec {
|
||||
name: "read_only_on_request_blocks_network",
|
||||
approval_policy: OnRequest,
|
||||
sandbox_policy: SandboxPolicy::ReadOnly,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
action: ActionKind::FetchUrl {
|
||||
endpoint: "/ro/network-blocked",
|
||||
response_body: "should-not-see",
|
||||
|
|
@ -925,7 +926,7 @@ fn scenarios() -> Vec<ScenarioSpec> {
|
|||
ScenarioSpec {
|
||||
name: "read_only_on_request_denied_blocks_execution",
|
||||
approval_policy: OnRequest,
|
||||
sandbox_policy: SandboxPolicy::ReadOnly,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
action: ActionKind::WriteFile {
|
||||
target: TargetPath::Workspace("ro_on_request_denied.txt"),
|
||||
content: "should-not-write",
|
||||
|
|
@ -946,7 +947,7 @@ fn scenarios() -> Vec<ScenarioSpec> {
|
|||
ScenarioSpec {
|
||||
name: "read_only_on_failure_escalates_after_sandbox_error",
|
||||
approval_policy: OnFailure,
|
||||
sandbox_policy: SandboxPolicy::ReadOnly,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
action: ActionKind::WriteFile {
|
||||
target: TargetPath::Workspace("ro_on_failure.txt"),
|
||||
content: "read-only-on-failure",
|
||||
|
|
@ -967,7 +968,7 @@ fn scenarios() -> Vec<ScenarioSpec> {
|
|||
ScenarioSpec {
|
||||
name: "read_only_on_failure_escalates_after_sandbox_error_gpt_5_1_no_exit",
|
||||
approval_policy: OnFailure,
|
||||
sandbox_policy: SandboxPolicy::ReadOnly,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
action: ActionKind::WriteFile {
|
||||
target: TargetPath::Workspace("ro_on_failure_5_1.txt"),
|
||||
content: "read-only-on-failure",
|
||||
|
|
@ -987,7 +988,7 @@ fn scenarios() -> Vec<ScenarioSpec> {
|
|||
ScenarioSpec {
|
||||
name: "read_only_on_request_network_escalates_when_approved",
|
||||
approval_policy: OnRequest,
|
||||
sandbox_policy: SandboxPolicy::ReadOnly,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
action: ActionKind::FetchUrl {
|
||||
endpoint: "/ro/network-approved",
|
||||
response_body: "read-only-network-ok",
|
||||
|
|
@ -1006,7 +1007,7 @@ fn scenarios() -> Vec<ScenarioSpec> {
|
|||
ScenarioSpec {
|
||||
name: "read_only_on_request_network_escalates_when_approved_gpt_5_1_no_exit",
|
||||
approval_policy: OnRequest,
|
||||
sandbox_policy: SandboxPolicy::ReadOnly,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
action: ActionKind::FetchUrl {
|
||||
endpoint: "/ro/network-approved",
|
||||
response_body: "read-only-network-ok",
|
||||
|
|
@ -1178,7 +1179,7 @@ fn scenarios() -> Vec<ScenarioSpec> {
|
|||
ScenarioSpec {
|
||||
name: "read_only_unless_trusted_requires_approval",
|
||||
approval_policy: UnlessTrusted,
|
||||
sandbox_policy: SandboxPolicy::ReadOnly,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
action: ActionKind::WriteFile {
|
||||
target: TargetPath::Workspace("ro_unless_trusted.txt"),
|
||||
content: "read-only-unless-trusted",
|
||||
|
|
@ -1198,7 +1199,7 @@ fn scenarios() -> Vec<ScenarioSpec> {
|
|||
ScenarioSpec {
|
||||
name: "read_only_unless_trusted_requires_approval_gpt_5_1_no_exit",
|
||||
approval_policy: UnlessTrusted,
|
||||
sandbox_policy: SandboxPolicy::ReadOnly,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
action: ActionKind::WriteFile {
|
||||
target: TargetPath::Workspace("ro_unless_trusted_5_1.txt"),
|
||||
content: "read-only-unless-trusted",
|
||||
|
|
@ -1218,7 +1219,7 @@ fn scenarios() -> Vec<ScenarioSpec> {
|
|||
ScenarioSpec {
|
||||
name: "read_only_never_reports_sandbox_failure",
|
||||
approval_policy: Never,
|
||||
sandbox_policy: SandboxPolicy::ReadOnly,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
action: ActionKind::WriteFile {
|
||||
target: TargetPath::Workspace("ro_never.txt"),
|
||||
content: "read-only-never",
|
||||
|
|
@ -1242,7 +1243,7 @@ fn scenarios() -> Vec<ScenarioSpec> {
|
|||
ScenarioSpec {
|
||||
name: "trusted_command_never_runs_without_prompt",
|
||||
approval_policy: Never,
|
||||
sandbox_policy: SandboxPolicy::ReadOnly,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
action: ActionKind::RunCommand {
|
||||
command: "echo trusted-never",
|
||||
},
|
||||
|
|
@ -1407,7 +1408,7 @@ fn scenarios() -> Vec<ScenarioSpec> {
|
|||
ScenarioSpec {
|
||||
name: "unified exec on request escalated requires approval",
|
||||
approval_policy: OnRequest,
|
||||
sandbox_policy: SandboxPolicy::ReadOnly,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
action: ActionKind::RunUnifiedExecCommand {
|
||||
command: "python3 -c 'print('\"'\"'escalated unified exec'\"'\"')'",
|
||||
justification: Some(DEFAULT_UNIFIED_EXEC_JUSTIFICATION),
|
||||
|
|
@ -1574,6 +1575,7 @@ 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,
|
||||
|
|
@ -1687,7 +1689,7 @@ async fn approving_apply_patch_for_session_skips_future_prompts_for_same_file()
|
|||
async fn approving_execpolicy_amendment_persists_policy_and_skips_future_prompts() -> Result<()> {
|
||||
let server = start_mock_server().await;
|
||||
let approval_policy = AskForApproval::UnlessTrusted;
|
||||
let sandbox_policy = SandboxPolicy::ReadOnly;
|
||||
let sandbox_policy = SandboxPolicy::new_read_only_policy();
|
||||
let sandbox_policy_for_config = sandbox_policy.clone();
|
||||
let mut builder = test_codex().with_config(move |config| {
|
||||
config.approval_policy = Constrained::allow_any(approval_policy);
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ async fn codex_delegate_forwards_exec_approval_and_proceeds_on_approval() {
|
|||
// routes ExecApprovalRequest via the parent.
|
||||
let mut builder = test_codex().with_model("gpt-5.1").with_config(|config| {
|
||||
config.approval_policy = Constrained::allow_any(AskForApproval::OnRequest);
|
||||
config.sandbox_policy = Constrained::allow_any(SandboxPolicy::ReadOnly);
|
||||
config.sandbox_policy = Constrained::allow_any(SandboxPolicy::new_read_only_policy());
|
||||
});
|
||||
let test = builder.build(&server).await.expect("build test codex");
|
||||
|
||||
|
|
@ -146,7 +146,7 @@ async fn codex_delegate_forwards_patch_approval_and_proceeds_on_decision() {
|
|||
let mut builder = test_codex().with_model("gpt-5.1").with_config(|config| {
|
||||
config.approval_policy = Constrained::allow_any(AskForApproval::OnRequest);
|
||||
// Use a restricted sandbox so patch approval is required
|
||||
config.sandbox_policy = Constrained::allow_any(SandboxPolicy::ReadOnly);
|
||||
config.sandbox_policy = Constrained::allow_any(SandboxPolicy::new_read_only_policy());
|
||||
config.include_apply_patch_tool = true;
|
||||
});
|
||||
let test = builder.build(&server).await.expect("build test codex");
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ async fn model_change_appends_model_instructions_developer_message() -> Result<(
|
|||
final_output_json_schema: None,
|
||||
cwd: test.cwd_path().to_path_buf(),
|
||||
approval_policy: AskForApproval::Never,
|
||||
sandbox_policy: SandboxPolicy::ReadOnly,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
model: test.session_configured.model.clone(),
|
||||
effort: test.config.model_reasoning_effort,
|
||||
summary: ReasoningSummary::Auto,
|
||||
|
|
@ -89,7 +89,7 @@ async fn model_change_appends_model_instructions_developer_message() -> Result<(
|
|||
final_output_json_schema: None,
|
||||
cwd: test.cwd_path().to_path_buf(),
|
||||
approval_policy: AskForApproval::Never,
|
||||
sandbox_policy: SandboxPolicy::ReadOnly,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
model: next_model.to_string(),
|
||||
effort: test.config.model_reasoning_effort,
|
||||
summary: ReasoningSummary::Auto,
|
||||
|
|
@ -144,7 +144,7 @@ async fn model_and_personality_change_only_appends_model_instructions() -> Resul
|
|||
final_output_json_schema: None,
|
||||
cwd: test.cwd_path().to_path_buf(),
|
||||
approval_policy: AskForApproval::Never,
|
||||
sandbox_policy: SandboxPolicy::ReadOnly,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
model: test.session_configured.model.clone(),
|
||||
effort: test.config.model_reasoning_effort,
|
||||
summary: ReasoningSummary::Auto,
|
||||
|
|
@ -177,7 +177,7 @@ async fn model_and_personality_change_only_appends_model_instructions() -> Resul
|
|||
final_output_json_schema: None,
|
||||
cwd: test.cwd_path().to_path_buf(),
|
||||
approval_policy: AskForApproval::Never,
|
||||
sandbox_policy: SandboxPolicy::ReadOnly,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
model: next_model.to_string(),
|
||||
effort: test.config.model_reasoning_effort,
|
||||
summary: ReasoningSummary::Auto,
|
||||
|
|
@ -291,7 +291,7 @@ async fn model_change_from_image_to_text_strips_prior_image_content() -> Result<
|
|||
final_output_json_schema: None,
|
||||
cwd: test.cwd_path().to_path_buf(),
|
||||
approval_policy: AskForApproval::Never,
|
||||
sandbox_policy: SandboxPolicy::ReadOnly,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
model: image_model_slug.to_string(),
|
||||
effort: test.config.model_reasoning_effort,
|
||||
summary: ReasoningSummary::Auto,
|
||||
|
|
@ -310,7 +310,7 @@ async fn model_change_from_image_to_text_strips_prior_image_content() -> Result<
|
|||
final_output_json_schema: None,
|
||||
cwd: test.cwd_path().to_path_buf(),
|
||||
approval_policy: AskForApproval::Never,
|
||||
sandbox_policy: SandboxPolicy::ReadOnly,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
model: text_model_slug.to_string(),
|
||||
effort: test.config.model_reasoning_effort,
|
||||
summary: ReasoningSummary::Auto,
|
||||
|
|
|
|||
|
|
@ -457,6 +457,7 @@ 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,
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ async fn user_turn_personality_none_does_not_add_update_message() -> anyhow::Res
|
|||
final_output_json_schema: None,
|
||||
cwd: test.cwd_path().to_path_buf(),
|
||||
approval_policy: test.config.approval_policy.value(),
|
||||
sandbox_policy: SandboxPolicy::ReadOnly,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
model: test.session_configured.model.clone(),
|
||||
effort: test.config.model_reasoning_effort,
|
||||
summary: ReasoningSummary::Auto,
|
||||
|
|
@ -142,7 +142,7 @@ async fn config_personality_some_sets_instructions_template() -> anyhow::Result<
|
|||
final_output_json_schema: None,
|
||||
cwd: test.cwd_path().to_path_buf(),
|
||||
approval_policy: test.config.approval_policy.value(),
|
||||
sandbox_policy: SandboxPolicy::ReadOnly,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
model: test.session_configured.model.clone(),
|
||||
effort: test.config.model_reasoning_effort,
|
||||
summary: ReasoningSummary::Auto,
|
||||
|
|
@ -196,7 +196,7 @@ async fn config_personality_none_sends_no_personality() -> anyhow::Result<()> {
|
|||
final_output_json_schema: None,
|
||||
cwd: test.cwd_path().to_path_buf(),
|
||||
approval_policy: test.config.approval_policy.value(),
|
||||
sandbox_policy: SandboxPolicy::ReadOnly,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
model: test.session_configured.model.clone(),
|
||||
effort: test.config.model_reasoning_effort,
|
||||
summary: ReasoningSummary::Auto,
|
||||
|
|
@ -256,7 +256,7 @@ async fn default_personality_is_pragmatic_without_config_toml() -> anyhow::Resul
|
|||
final_output_json_schema: None,
|
||||
cwd: test.cwd_path().to_path_buf(),
|
||||
approval_policy: test.config.approval_policy.value(),
|
||||
sandbox_policy: SandboxPolicy::ReadOnly,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
model: test.session_configured.model.clone(),
|
||||
effort: test.config.model_reasoning_effort,
|
||||
summary: ReasoningSummary::Auto,
|
||||
|
|
@ -304,7 +304,7 @@ async fn user_turn_personality_some_adds_update_message() -> anyhow::Result<()>
|
|||
final_output_json_schema: None,
|
||||
cwd: test.cwd_path().to_path_buf(),
|
||||
approval_policy: test.config.approval_policy.value(),
|
||||
sandbox_policy: SandboxPolicy::ReadOnly,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
model: test.session_configured.model.clone(),
|
||||
effort: test.config.model_reasoning_effort,
|
||||
summary: ReasoningSummary::Auto,
|
||||
|
|
@ -338,7 +338,7 @@ async fn user_turn_personality_some_adds_update_message() -> anyhow::Result<()>
|
|||
final_output_json_schema: None,
|
||||
cwd: test.cwd_path().to_path_buf(),
|
||||
approval_policy: test.config.approval_policy.value(),
|
||||
sandbox_policy: SandboxPolicy::ReadOnly,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
model: test.session_configured.model.clone(),
|
||||
effort: test.config.model_reasoning_effort,
|
||||
summary: ReasoningSummary::Auto,
|
||||
|
|
@ -401,7 +401,7 @@ async fn user_turn_personality_same_value_does_not_add_update_message() -> anyho
|
|||
final_output_json_schema: None,
|
||||
cwd: test.cwd_path().to_path_buf(),
|
||||
approval_policy: test.config.approval_policy.value(),
|
||||
sandbox_policy: SandboxPolicy::ReadOnly,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
model: test.session_configured.model.clone(),
|
||||
effort: test.config.model_reasoning_effort,
|
||||
summary: ReasoningSummary::Auto,
|
||||
|
|
@ -435,7 +435,7 @@ async fn user_turn_personality_same_value_does_not_add_update_message() -> anyho
|
|||
final_output_json_schema: None,
|
||||
cwd: test.cwd_path().to_path_buf(),
|
||||
approval_policy: test.config.approval_policy.value(),
|
||||
sandbox_policy: SandboxPolicy::ReadOnly,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
model: test.session_configured.model.clone(),
|
||||
effort: test.config.model_reasoning_effort,
|
||||
summary: ReasoningSummary::Auto,
|
||||
|
|
@ -508,7 +508,7 @@ async fn user_turn_personality_skips_if_feature_disabled() -> anyhow::Result<()>
|
|||
final_output_json_schema: None,
|
||||
cwd: test.cwd_path().to_path_buf(),
|
||||
approval_policy: test.config.approval_policy.value(),
|
||||
sandbox_policy: SandboxPolicy::ReadOnly,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
model: test.session_configured.model.clone(),
|
||||
effort: test.config.model_reasoning_effort,
|
||||
summary: ReasoningSummary::Auto,
|
||||
|
|
@ -542,7 +542,7 @@ async fn user_turn_personality_skips_if_feature_disabled() -> anyhow::Result<()>
|
|||
final_output_json_schema: None,
|
||||
cwd: test.cwd_path().to_path_buf(),
|
||||
approval_policy: test.config.approval_policy.value(),
|
||||
sandbox_policy: SandboxPolicy::ReadOnly,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
model: test.session_configured.model.clone(),
|
||||
effort: test.config.model_reasoning_effort,
|
||||
summary: ReasoningSummary::Auto,
|
||||
|
|
@ -654,7 +654,7 @@ async fn ignores_remote_personality_if_remote_models_disabled() -> anyhow::Resul
|
|||
final_output_json_schema: None,
|
||||
cwd: test.cwd_path().to_path_buf(),
|
||||
approval_policy: AskForApproval::Never,
|
||||
sandbox_policy: SandboxPolicy::ReadOnly,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
model: remote_slug.to_string(),
|
||||
effort: test.config.model_reasoning_effort,
|
||||
summary: ReasoningSummary::Auto,
|
||||
|
|
@ -771,7 +771,7 @@ async fn remote_model_friendly_personality_instructions_with_feature() -> anyhow
|
|||
final_output_json_schema: None,
|
||||
cwd: test.cwd_path().to_path_buf(),
|
||||
approval_policy: AskForApproval::Never,
|
||||
sandbox_policy: SandboxPolicy::ReadOnly,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
model: remote_slug.to_string(),
|
||||
effort: test.config.model_reasoning_effort,
|
||||
summary: ReasoningSummary::Auto,
|
||||
|
|
@ -886,7 +886,7 @@ async fn user_turn_personality_remote_model_template_includes_update_message() -
|
|||
final_output_json_schema: None,
|
||||
cwd: test.cwd_path().to_path_buf(),
|
||||
approval_policy: AskForApproval::Never,
|
||||
sandbox_policy: SandboxPolicy::ReadOnly,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
model: remote_slug.to_string(),
|
||||
effort: test.config.model_reasoning_effort,
|
||||
summary: ReasoningSummary::Auto,
|
||||
|
|
@ -920,7 +920,7 @@ async fn user_turn_personality_remote_model_template_includes_update_message() -
|
|||
final_output_json_schema: None,
|
||||
cwd: test.cwd_path().to_path_buf(),
|
||||
approval_policy: AskForApproval::Never,
|
||||
sandbox_policy: SandboxPolicy::ReadOnly,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
model: remote_slug.to_string(),
|
||||
effort: test.config.model_reasoning_effort,
|
||||
summary: ReasoningSummary::Auto,
|
||||
|
|
|
|||
|
|
@ -375,6 +375,7 @@ 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,
|
||||
|
|
@ -618,6 +619,7 @@ 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![AbsolutePathBuf::try_from(writable.path()).unwrap()],
|
||||
read_only_access: Default::default(),
|
||||
network_access: true,
|
||||
exclude_tmpdir_env_var: true,
|
||||
exclude_slash_tmp: true,
|
||||
|
|
|
|||
|
|
@ -126,7 +126,7 @@ async fn stdio_server_round_trip() -> anyhow::Result<()> {
|
|||
final_output_json_schema: None,
|
||||
cwd: fixture.cwd.path().to_path_buf(),
|
||||
approval_policy: AskForApproval::Never,
|
||||
sandbox_policy: SandboxPolicy::ReadOnly,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
model: session_model,
|
||||
effort: None,
|
||||
summary: ReasoningSummary::Auto,
|
||||
|
|
@ -293,7 +293,7 @@ async fn stdio_image_responses_round_trip() -> anyhow::Result<()> {
|
|||
final_output_json_schema: None,
|
||||
cwd: fixture.cwd.path().to_path_buf(),
|
||||
approval_policy: AskForApproval::Never,
|
||||
sandbox_policy: SandboxPolicy::ReadOnly,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
model: session_model,
|
||||
effort: None,
|
||||
summary: ReasoningSummary::Auto,
|
||||
|
|
@ -491,7 +491,7 @@ async fn stdio_image_responses_are_sanitized_for_text_only_model() -> anyhow::Re
|
|||
final_output_json_schema: None,
|
||||
cwd: fixture.cwd.path().to_path_buf(),
|
||||
approval_policy: AskForApproval::Never,
|
||||
sandbox_policy: SandboxPolicy::ReadOnly,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
model: text_only_model_slug.to_string(),
|
||||
effort: None,
|
||||
summary: ReasoningSummary::Auto,
|
||||
|
|
@ -603,7 +603,7 @@ async fn stdio_server_propagates_whitelisted_env_vars() -> anyhow::Result<()> {
|
|||
final_output_json_schema: None,
|
||||
cwd: fixture.cwd.path().to_path_buf(),
|
||||
approval_policy: AskForApproval::Never,
|
||||
sandbox_policy: SandboxPolicy::ReadOnly,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
model: session_model,
|
||||
effort: None,
|
||||
summary: ReasoningSummary::Auto,
|
||||
|
|
@ -762,7 +762,7 @@ async fn streamable_http_tool_call_round_trip() -> anyhow::Result<()> {
|
|||
final_output_json_schema: None,
|
||||
cwd: fixture.cwd.path().to_path_buf(),
|
||||
approval_policy: AskForApproval::Never,
|
||||
sandbox_policy: SandboxPolicy::ReadOnly,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
model: session_model,
|
||||
effort: None,
|
||||
summary: ReasoningSummary::Auto,
|
||||
|
|
@ -953,7 +953,7 @@ async fn streamable_http_with_oauth_round_trip() -> anyhow::Result<()> {
|
|||
final_output_json_schema: None,
|
||||
cwd: fixture.cwd.path().to_path_buf(),
|
||||
approval_policy: AskForApproval::Never,
|
||||
sandbox_policy: SandboxPolicy::ReadOnly,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
model: session_model,
|
||||
effort: None,
|
||||
summary: ReasoningSummary::Auto,
|
||||
|
|
|
|||
|
|
@ -77,6 +77,7 @@ async fn if_parent_of_repo_is_writable_then_dot_git_folder_is_writable() {
|
|||
let test_scenario = create_test_scenario(&tmp);
|
||||
let policy = SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots: vec![test_scenario.repo_parent.as_path().try_into().unwrap()],
|
||||
read_only_access: Default::default(),
|
||||
network_access: false,
|
||||
exclude_tmpdir_env_var: true,
|
||||
exclude_slash_tmp: true,
|
||||
|
|
@ -103,6 +104,7 @@ async fn if_git_repo_is_writable_root_then_dot_git_folder_is_read_only() {
|
|||
let test_scenario = create_test_scenario(&tmp);
|
||||
let policy = SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots: vec![test_scenario.repo_root.as_path().try_into().unwrap()],
|
||||
read_only_access: Default::default(),
|
||||
network_access: false,
|
||||
exclude_tmpdir_env_var: true,
|
||||
exclude_slash_tmp: true,
|
||||
|
|
@ -145,7 +147,7 @@ async fn danger_full_access_allows_all_writes() {
|
|||
async fn read_only_forbids_all_writes() {
|
||||
let tmp = TempDir::new().expect("should be able to create temp dir");
|
||||
let test_scenario = create_test_scenario(&tmp);
|
||||
let policy = SandboxPolicy::ReadOnly;
|
||||
let policy = SandboxPolicy::new_read_only_policy();
|
||||
|
||||
test_scenario
|
||||
.run_test(
|
||||
|
|
@ -171,7 +173,7 @@ async fn openpty_works_under_seatbelt() {
|
|||
return;
|
||||
}
|
||||
|
||||
let policy = SandboxPolicy::ReadOnly;
|
||||
let policy = SandboxPolicy::new_read_only_policy();
|
||||
let command_cwd = std::env::current_dir().expect("getcwd");
|
||||
let sandbox_cwd = command_cwd.clone();
|
||||
|
||||
|
|
@ -229,7 +231,7 @@ async fn java_home_finds_runtime_under_seatbelt() {
|
|||
return;
|
||||
}
|
||||
|
||||
let policy = SandboxPolicy::ReadOnly;
|
||||
let policy = SandboxPolicy::new_read_only_policy();
|
||||
let command_cwd = std::env::current_dir().expect("getcwd");
|
||||
let sandbox_cwd = command_cwd.clone();
|
||||
|
||||
|
|
|
|||
|
|
@ -230,7 +230,7 @@ async fn sandbox_denied_shell_returns_original_output() -> Result<()> {
|
|||
fixture
|
||||
.submit_turn_with_policy(
|
||||
"run a command that should be denied by the read-only sandbox",
|
||||
SandboxPolicy::ReadOnly,
|
||||
SandboxPolicy::new_read_only_policy(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
|
|
|
|||
|
|
@ -384,7 +384,7 @@ async fn mcp_tool_call_output_exceeds_limit_truncated_for_model() -> Result<()>
|
|||
fixture
|
||||
.submit_turn_with_policy(
|
||||
"call the rmcp echo tool with a very large message",
|
||||
SandboxPolicy::ReadOnly,
|
||||
SandboxPolicy::new_read_only_policy(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
|
|
@ -485,7 +485,7 @@ async fn mcp_image_output_preserves_image_and_no_text_summary() -> Result<()> {
|
|||
final_output_json_schema: None,
|
||||
cwd: fixture.cwd.path().to_path_buf(),
|
||||
approval_policy: AskForApproval::Never,
|
||||
sandbox_policy: SandboxPolicy::ReadOnly,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
model: session_model,
|
||||
effort: None,
|
||||
summary: ReasoningSummary::Auto,
|
||||
|
|
@ -742,7 +742,7 @@ async fn mcp_tool_call_output_not_truncated_with_custom_limit() -> Result<()> {
|
|||
fixture
|
||||
.submit_turn_with_policy(
|
||||
"call the rmcp echo tool with a very large message",
|
||||
SandboxPolicy::ReadOnly,
|
||||
SandboxPolicy::new_read_only_policy(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
|
|
|
|||
|
|
@ -2540,7 +2540,7 @@ async fn unified_exec_runs_under_sandbox() -> Result<()> {
|
|||
cwd: cwd.path().to_path_buf(),
|
||||
approval_policy: AskForApproval::Never,
|
||||
// Important!
|
||||
sandbox_policy: SandboxPolicy::ReadOnly,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
model: session_model,
|
||||
effort: None,
|
||||
summary: ReasoningSummary::Auto,
|
||||
|
|
@ -2644,7 +2644,7 @@ async fn unified_exec_python_prompt_under_seatbelt() -> Result<()> {
|
|||
final_output_json_schema: None,
|
||||
cwd: cwd.path().to_path_buf(),
|
||||
approval_policy: AskForApproval::Never,
|
||||
sandbox_policy: SandboxPolicy::ReadOnly,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
model: session_model,
|
||||
effort: None,
|
||||
summary: ReasoningSummary::Auto,
|
||||
|
|
|
|||
|
|
@ -44,9 +44,12 @@ async fn web_search_mode_cached_sets_external_web_access_false() {
|
|||
.await
|
||||
.expect("create test Codex conversation");
|
||||
|
||||
test.submit_turn_with_policy("hello cached web search", SandboxPolicy::ReadOnly)
|
||||
.await
|
||||
.expect("submit turn");
|
||||
test.submit_turn_with_policy(
|
||||
"hello cached web search",
|
||||
SandboxPolicy::new_read_only_policy(),
|
||||
)
|
||||
.await
|
||||
.expect("submit turn");
|
||||
|
||||
let body = resp_mock.single_request().body_json();
|
||||
let tool = find_web_search_tool(&body);
|
||||
|
|
@ -82,9 +85,12 @@ async fn web_search_mode_takes_precedence_over_legacy_flags() {
|
|||
.await
|
||||
.expect("create test Codex conversation");
|
||||
|
||||
test.submit_turn_with_policy("hello cached+live flags", SandboxPolicy::ReadOnly)
|
||||
.await
|
||||
.expect("submit turn");
|
||||
test.submit_turn_with_policy(
|
||||
"hello cached+live flags",
|
||||
SandboxPolicy::new_read_only_policy(),
|
||||
)
|
||||
.await
|
||||
.expect("submit turn");
|
||||
|
||||
let body = resp_mock.single_request().body_json();
|
||||
let tool = find_web_search_tool(&body);
|
||||
|
|
@ -121,9 +127,12 @@ async fn web_search_mode_defaults_to_cached_when_features_disabled() {
|
|||
.await
|
||||
.expect("create test Codex conversation");
|
||||
|
||||
test.submit_turn_with_policy("hello default cached web search", SandboxPolicy::ReadOnly)
|
||||
.await
|
||||
.expect("submit turn");
|
||||
test.submit_turn_with_policy(
|
||||
"hello default cached web search",
|
||||
SandboxPolicy::new_read_only_policy(),
|
||||
)
|
||||
.await
|
||||
.expect("submit turn");
|
||||
|
||||
let body = resp_mock.single_request().body_json();
|
||||
let tool = find_web_search_tool(&body);
|
||||
|
|
@ -169,7 +178,7 @@ async fn web_search_mode_updates_between_turns_with_sandbox_policy() {
|
|||
.await
|
||||
.expect("create test Codex conversation");
|
||||
|
||||
test.submit_turn_with_policy("hello cached", SandboxPolicy::ReadOnly)
|
||||
test.submit_turn_with_policy("hello cached", SandboxPolicy::new_read_only_policy())
|
||||
.await
|
||||
.expect("submit first turn");
|
||||
test.submit_turn_with_policy("hello live", SandboxPolicy::DangerFullAccess)
|
||||
|
|
|
|||
|
|
@ -123,7 +123,7 @@ impl ExecTool {
|
|||
.await
|
||||
.clone()
|
||||
.unwrap_or_else(|| SandboxState {
|
||||
sandbox_policy: SandboxPolicy::ReadOnly,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
codex_linux_sandbox_exe: None,
|
||||
sandbox_cwd: PathBuf::from(¶ms.workdir),
|
||||
use_linux_sandbox_bwrap: false,
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ where
|
|||
S: Service<RoleClient> + ClientHandler,
|
||||
{
|
||||
let sandbox_state = SandboxState {
|
||||
sandbox_policy: SandboxPolicy::ReadOnly,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
codex_linux_sandbox_exe,
|
||||
sandbox_cwd: sandbox_cwd.as_ref().to_path_buf(),
|
||||
use_linux_sandbox_bwrap: false,
|
||||
|
|
@ -110,6 +110,7 @@ where
|
|||
// Note that sandbox_cwd will already be included as a writable root
|
||||
// when the sandbox policy is expanded.
|
||||
writable_roots: vec![],
|
||||
read_only_access: Default::default(),
|
||||
network_access: false,
|
||||
// Disable writes to temp dir because this is a test, so
|
||||
// writable_folder is likely also under /tmp and we want to be
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ fn session_configured_produces_thread_started_event() {
|
|||
model: "codex-mini-latest".to_string(),
|
||||
model_provider_id: "test-provider".to_string(),
|
||||
approval_policy: AskForApproval::Never,
|
||||
sandbox_policy: SandboxPolicy::ReadOnly,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
cwd: PathBuf::from("/home/user/project"),
|
||||
reasoning_effort: None,
|
||||
history_log_id: 0,
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ async fn spawn_command_under_sandbox(
|
|||
async fn linux_sandbox_test_env() -> Option<HashMap<String, String>> {
|
||||
let command_cwd = std::env::current_dir().ok()?;
|
||||
let sandbox_cwd = command_cwd.clone();
|
||||
let policy = SandboxPolicy::ReadOnly;
|
||||
let policy = SandboxPolicy::new_read_only_policy();
|
||||
|
||||
if can_apply_linux_sandbox_policy(&policy, &command_cwd, sandbox_cwd.as_path(), HashMap::new())
|
||||
.await
|
||||
|
|
@ -134,6 +134,7 @@ 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,
|
||||
|
|
@ -194,7 +195,7 @@ async fn python_getpwuid_works_under_sandbox() {
|
|||
return;
|
||||
}
|
||||
|
||||
let policy = SandboxPolicy::ReadOnly;
|
||||
let policy = SandboxPolicy::new_read_only_policy();
|
||||
let command_cwd = std::env::current_dir().expect("should be able to get current dir");
|
||||
let sandbox_cwd = command_cwd.clone();
|
||||
|
||||
|
|
@ -247,6 +248,7 @@ 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,
|
||||
|
|
@ -387,7 +389,7 @@ fn unix_sock_body() {
|
|||
async fn allow_unix_socketpair_recvfrom() {
|
||||
run_code_under_sandbox(
|
||||
"allow_unix_socketpair_recvfrom",
|
||||
&SandboxPolicy::ReadOnly,
|
||||
&SandboxPolicy::new_read_only_policy(),
|
||||
|| async { unix_sock_body() },
|
||||
)
|
||||
.await
|
||||
|
|
|
|||
|
|
@ -141,6 +141,13 @@ fn create_bwrap_flags(
|
|||
/// 4. `--dev-bind /dev/null /dev/null` preserves the common sink even under a
|
||||
/// read-only root.
|
||||
fn create_filesystem_args(sandbox_policy: &SandboxPolicy, cwd: &Path) -> Result<Vec<String>> {
|
||||
if !sandbox_policy.has_full_disk_read_access() {
|
||||
return Err(CodexErr::UnsupportedOperation(
|
||||
"Restricted read-only access is not yet supported by the Linux bubblewrap backend."
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let writable_roots = sandbox_policy.get_writable_roots_with_cwd(cwd);
|
||||
ensure_mount_targets_exist(&writable_roots)?;
|
||||
|
||||
|
|
|
|||
|
|
@ -62,6 +62,13 @@ pub(crate) fn apply_sandbox_policy_to_current_thread(
|
|||
}
|
||||
|
||||
if apply_landlock_fs && !sandbox_policy.has_full_disk_write_access() {
|
||||
if !sandbox_policy.has_full_disk_read_access() {
|
||||
return Err(CodexErr::UnsupportedOperation(
|
||||
"Restricted read-only access is not supported by the legacy Linux Landlock filesystem backend."
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let writable_roots = sandbox_policy
|
||||
.get_writable_roots_with_cwd(cwd)
|
||||
.into_iter()
|
||||
|
|
@ -70,9 +77,6 @@ pub(crate) fn apply_sandbox_policy_to_current_thread(
|
|||
install_filesystem_landlock_rules_on_current_thread(writable_roots)?;
|
||||
}
|
||||
|
||||
// TODO(ragona): Add appropriate restrictions if
|
||||
// `sandbox_policy.has_full_disk_read_access()` is `false`.
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
@ -222,11 +226,11 @@ mod tests {
|
|||
#[test]
|
||||
fn restricted_network_policy_always_installs_seccomp() {
|
||||
assert!(should_install_network_seccomp(
|
||||
&SandboxPolicy::ReadOnly,
|
||||
&SandboxPolicy::new_read_only_policy(),
|
||||
false
|
||||
));
|
||||
assert!(should_install_network_seccomp(
|
||||
&SandboxPolicy::ReadOnly,
|
||||
&SandboxPolicy::new_read_only_policy(),
|
||||
true
|
||||
));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -391,7 +391,7 @@ mod tests {
|
|||
fn inserts_bwrap_argv0_before_command_separator() {
|
||||
let argv = build_bwrap_argv(
|
||||
vec!["/bin/true".to_string()],
|
||||
&SandboxPolicy::ReadOnly,
|
||||
&SandboxPolicy::new_read_only_policy(),
|
||||
Path::new("/"),
|
||||
BwrapOptions {
|
||||
mount_proc: true,
|
||||
|
|
@ -425,7 +425,7 @@ mod tests {
|
|||
fn inserts_unshare_net_when_network_isolation_requested() {
|
||||
let argv = build_bwrap_argv(
|
||||
vec!["/bin/true".to_string()],
|
||||
&SandboxPolicy::ReadOnly,
|
||||
&SandboxPolicy::new_read_only_policy(),
|
||||
Path::new("/"),
|
||||
BwrapOptions {
|
||||
mount_proc: true,
|
||||
|
|
@ -439,7 +439,7 @@ mod tests {
|
|||
fn inserts_unshare_net_when_proxy_only_network_mode_requested() {
|
||||
let argv = build_bwrap_argv(
|
||||
vec!["/bin/true".to_string()],
|
||||
&SandboxPolicy::ReadOnly,
|
||||
&SandboxPolicy::new_read_only_policy(),
|
||||
Path::new("/"),
|
||||
BwrapOptions {
|
||||
mount_proc: true,
|
||||
|
|
|
|||
|
|
@ -87,6 +87,7 @@ 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: false,
|
||||
// Exclude tmp-related folders from writable roots because we need a
|
||||
// folder that is writable by tests but that we intentionally disallow
|
||||
|
|
|
|||
|
|
@ -301,7 +301,7 @@ mod tests {
|
|||
model: "gpt-4o".to_string(),
|
||||
model_provider_id: "test-provider".to_string(),
|
||||
approval_policy: AskForApproval::Never,
|
||||
sandbox_policy: SandboxPolicy::ReadOnly,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
cwd: PathBuf::from("/home/user/project"),
|
||||
reasoning_effort: Some(ReasoningEffort::default()),
|
||||
history_log_id: 1,
|
||||
|
|
@ -343,7 +343,7 @@ mod tests {
|
|||
model: "gpt-4o".to_string(),
|
||||
model_provider_id: "test-provider".to_string(),
|
||||
approval_policy: AskForApproval::Never,
|
||||
sandbox_policy: SandboxPolicy::ReadOnly,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
cwd: PathBuf::from("/home/user/project"),
|
||||
reasoning_effort: Some(ReasoningEffort::default()),
|
||||
history_log_id: 1,
|
||||
|
|
@ -409,7 +409,7 @@ mod tests {
|
|||
model: "gpt-4o".to_string(),
|
||||
model_provider_id: "test-provider".to_string(),
|
||||
approval_policy: AskForApproval::Never,
|
||||
sandbox_policy: SandboxPolicy::ReadOnly,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
cwd: PathBuf::from("/home/user/project"),
|
||||
reasoning_effort: Some(ReasoningEffort::default()),
|
||||
history_log_id: 1,
|
||||
|
|
|
|||
|
|
@ -312,7 +312,7 @@ impl DeveloperInstructions {
|
|||
|
||||
let (sandbox_mode, writable_roots) = match sandbox_policy {
|
||||
SandboxPolicy::DangerFullAccess => (SandboxMode::DangerFullAccess, None),
|
||||
SandboxPolicy::ReadOnly => (SandboxMode::ReadOnly, None),
|
||||
SandboxPolicy::ReadOnly { .. } => (SandboxMode::ReadOnly, None),
|
||||
SandboxPolicy::ExternalSandbox { .. } => (SandboxMode::DangerFullAccess, None),
|
||||
SandboxPolicy::WorkspaceWrite { .. } => {
|
||||
let roots = sandbox_policy.get_writable_roots_with_cwd(cwd);
|
||||
|
|
@ -1223,6 +1223,7 @@ mod tests {
|
|||
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,
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
//! between user and agent.
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::collections::HashSet;
|
||||
use std::ffi::OsStr;
|
||||
use std::fmt;
|
||||
use std::path::Path;
|
||||
|
|
@ -382,6 +383,107 @@ impl NetworkAccess {
|
|||
}
|
||||
}
|
||||
|
||||
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 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 {
|
||||
include_platform_defaults,
|
||||
readable_roots,
|
||||
} => {
|
||||
let mut roots = readable_roots.clone();
|
||||
if *include_platform_defaults {
|
||||
#[cfg(target_os = "macos")]
|
||||
for platform_path in [
|
||||
"/bin", "/dev", "/etc", "/Library", "/private", "/sbin", "/System", "/tmp",
|
||||
"/usr",
|
||||
] {
|
||||
#[allow(clippy::expect_used)]
|
||||
roots.push(
|
||||
AbsolutePathBuf::from_absolute_path(platform_path)
|
||||
.expect("platform defaults should be absolute"),
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
for platform_path in ["/bin", "/dev", "/etc", "/lib", "/lib64", "/tmp", "/usr"]
|
||||
{
|
||||
#[allow(clippy::expect_used)]
|
||||
roots.push(
|
||||
AbsolutePathBuf::from_absolute_path(platform_path)
|
||||
.expect("platform defaults should be absolute"),
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
for platform_path in [
|
||||
r"C:\Windows",
|
||||
r"C:\Program Files",
|
||||
r"C:\Program Files (x86)",
|
||||
r"C:\ProgramData",
|
||||
] {
|
||||
#[allow(clippy::expect_used)]
|
||||
roots.push(
|
||||
AbsolutePathBuf::from_absolute_path(platform_path)
|
||||
.expect("platform defaults should be absolute"),
|
||||
);
|
||||
}
|
||||
|
||||
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)]
|
||||
#[strum(serialize_all = "kebab-case")]
|
||||
|
|
@ -391,9 +493,16 @@ pub enum SandboxPolicy {
|
|||
#[serde(rename = "danger-full-access")]
|
||||
DangerFullAccess,
|
||||
|
||||
/// Read-only access to the entire file-system.
|
||||
/// Read-only access configuration.
|
||||
#[serde(rename = "read-only")]
|
||||
ReadOnly,
|
||||
ReadOnly {
|
||||
/// Read access granted while running under this policy.
|
||||
#[serde(
|
||||
default,
|
||||
skip_serializing_if = "ReadOnlyAccess::has_full_disk_read_access"
|
||||
)]
|
||||
access: ReadOnlyAccess,
|
||||
},
|
||||
|
||||
/// Indicates the process is already in an external sandbox. Allows full
|
||||
/// disk access while honoring the provided network setting.
|
||||
|
|
@ -413,6 +522,13 @@ 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)]
|
||||
|
|
@ -473,7 +589,9 @@ impl FromStr for SandboxPolicy {
|
|||
impl SandboxPolicy {
|
||||
/// Returns a policy with read-only disk access and no network.
|
||||
pub fn new_read_only_policy() -> Self {
|
||||
SandboxPolicy::ReadOnly
|
||||
SandboxPolicy::ReadOnly {
|
||||
access: ReadOnlyAccess::FullAccess,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a policy that can read the entire disk, but can only write to
|
||||
|
|
@ -482,22 +600,29 @@ 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,
|
||||
}
|
||||
}
|
||||
|
||||
/// Always returns `true`; restricting read access is not supported.
|
||||
pub fn has_full_disk_read_access(&self) -> bool {
|
||||
true
|
||||
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(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn has_full_disk_write_access(&self) -> bool {
|
||||
match self {
|
||||
SandboxPolicy::DangerFullAccess => true,
|
||||
SandboxPolicy::ExternalSandbox { .. } => true,
|
||||
SandboxPolicy::ReadOnly => false,
|
||||
SandboxPolicy::ReadOnly { .. } => false,
|
||||
SandboxPolicy::WorkspaceWrite { .. } => false,
|
||||
}
|
||||
}
|
||||
|
|
@ -506,11 +631,37 @@ impl SandboxPolicy {
|
|||
match self {
|
||||
SandboxPolicy::DangerFullAccess => true,
|
||||
SandboxPolicy::ExternalSandbox { network_access } => network_access.is_enabled(),
|
||||
SandboxPolicy::ReadOnly => false,
|
||||
SandboxPolicy::ReadOnly { .. } => false,
|
||||
SandboxPolicy::WorkspaceWrite { network_access, .. } => *network_access,
|
||||
}
|
||||
}
|
||||
|
||||
/// 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.
|
||||
|
|
@ -518,9 +669,10 @@ impl SandboxPolicy {
|
|||
match self {
|
||||
SandboxPolicy::DangerFullAccess => Vec::new(),
|
||||
SandboxPolicy::ExternalSandbox { .. } => Vec::new(),
|
||||
SandboxPolicy::ReadOnly => Vec::new(),
|
||||
SandboxPolicy::ReadOnly { .. } => Vec::new(),
|
||||
SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots,
|
||||
read_only_access: _,
|
||||
exclude_tmpdir_env_var,
|
||||
exclude_slash_tmp,
|
||||
network_access: _,
|
||||
|
|
@ -2565,6 +2717,38 @@ mod tests {
|
|||
assert!(enabled.has_full_network_access());
|
||||
}
|
||||
|
||||
#[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 item_started_event_from_web_search_emits_begin_event() {
|
||||
let event = ItemStartedEvent {
|
||||
|
|
@ -2730,7 +2914,7 @@ mod tests {
|
|||
model: "codex-mini-latest".to_string(),
|
||||
model_provider_id: "openai".to_string(),
|
||||
approval_policy: AskForApproval::Never,
|
||||
sandbox_policy: SandboxPolicy::ReadOnly,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
cwd: PathBuf::from("/home/user/project"),
|
||||
reasoning_effort: Some(ReasoningEffortConfig::default()),
|
||||
history_log_id: 0,
|
||||
|
|
|
|||
|
|
@ -138,7 +138,7 @@ impl ThreadMetadataBuilder {
|
|||
model_provider: None,
|
||||
cwd: PathBuf::new(),
|
||||
cli_version: None,
|
||||
sandbox_policy: SandboxPolicy::ReadOnly,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
approval_mode: AskForApproval::OnRequest,
|
||||
archived_at: None,
|
||||
git_sha: None,
|
||||
|
|
|
|||
|
|
@ -2141,7 +2141,7 @@ VALUES (?, ?, ?, ?, ?)
|
|||
cwd,
|
||||
cli_version: "0.0.0".to_string(),
|
||||
title: String::new(),
|
||||
sandbox_policy: crate::extract::enum_to_string(&SandboxPolicy::ReadOnly),
|
||||
sandbox_policy: crate::extract::enum_to_string(&SandboxPolicy::new_read_only_policy()),
|
||||
approval_mode: crate::extract::enum_to_string(&AskForApproval::OnRequest),
|
||||
tokens_used: 0,
|
||||
first_user_message: Some("hello".to_string()),
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ pub fn add_dir_warning_message(
|
|||
SandboxPolicy::WorkspaceWrite { .. }
|
||||
| SandboxPolicy::DangerFullAccess
|
||||
| SandboxPolicy::ExternalSandbox { .. } => None,
|
||||
SandboxPolicy::ReadOnly => Some(format_warning(additional_dirs)),
|
||||
SandboxPolicy::ReadOnly { .. } => Some(format_warning(additional_dirs)),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -64,7 +64,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn warns_for_read_only() {
|
||||
let sandbox = SandboxPolicy::ReadOnly;
|
||||
let sandbox = SandboxPolicy::new_read_only_policy();
|
||||
let dirs = vec![PathBuf::from("relative"), PathBuf::from("/abs")];
|
||||
let message = add_dir_warning_message(&dirs, &sandbox)
|
||||
.expect("expected warning for read-only sandbox");
|
||||
|
|
@ -76,7 +76,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn returns_none_when_no_additional_dirs() {
|
||||
let sandbox = SandboxPolicy::ReadOnly;
|
||||
let sandbox = SandboxPolicy::new_read_only_policy();
|
||||
let dirs: Vec<PathBuf> = Vec::new();
|
||||
assert_eq!(add_dir_warning_message(&dirs, &sandbox), None);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1158,7 +1158,7 @@ impl App {
|
|||
&& matches!(
|
||||
app.config.sandbox_policy.get(),
|
||||
codex_core::protocol::SandboxPolicy::WorkspaceWrite { .. }
|
||||
| codex_core::protocol::SandboxPolicy::ReadOnly
|
||||
| codex_core::protocol::SandboxPolicy::ReadOnly { .. }
|
||||
)
|
||||
&& !app
|
||||
.config
|
||||
|
|
@ -2016,9 +2016,8 @@ impl App {
|
|||
let policy_is_workspace_write_or_ro = matches!(
|
||||
&policy,
|
||||
codex_core::protocol::SandboxPolicy::WorkspaceWrite { .. }
|
||||
| codex_core::protocol::SandboxPolicy::ReadOnly
|
||||
| codex_core::protocol::SandboxPolicy::ReadOnly { .. }
|
||||
);
|
||||
#[cfg(target_os = "windows")]
|
||||
let policy_for_chat = policy.clone();
|
||||
|
||||
if let Err(err) = self.config.sandbox_policy.set(policy) {
|
||||
|
|
@ -2027,7 +2026,6 @@ impl App {
|
|||
.add_error_message(format!("Failed to set sandbox policy: {err}"));
|
||||
return Ok(AppRunControl::Continue);
|
||||
}
|
||||
#[cfg(target_os = "windows")]
|
||||
if let Err(err) = self.chat_widget.set_sandbox_policy(policy_for_chat) {
|
||||
tracing::warn!(%err, "failed to set sandbox policy on chat config");
|
||||
self.chat_widget
|
||||
|
|
@ -3081,7 +3079,7 @@ mod tests {
|
|||
model: "gpt-test".to_string(),
|
||||
model_provider_id: "test-provider".to_string(),
|
||||
approval_policy: AskForApproval::Never,
|
||||
sandbox_policy: SandboxPolicy::ReadOnly,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
cwd: PathBuf::from("/home/user/project"),
|
||||
reasoning_effort: None,
|
||||
history_log_id: 0,
|
||||
|
|
@ -3136,7 +3134,7 @@ mod tests {
|
|||
model: "gpt-test".to_string(),
|
||||
model_provider_id: "test-provider".to_string(),
|
||||
approval_policy: AskForApproval::Never,
|
||||
sandbox_policy: SandboxPolicy::ReadOnly,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
cwd: PathBuf::from("/home/user/project"),
|
||||
reasoning_effort: None,
|
||||
history_log_id: 0,
|
||||
|
|
@ -3185,7 +3183,7 @@ mod tests {
|
|||
model: "gpt-test".to_string(),
|
||||
model_provider_id: "test-provider".to_string(),
|
||||
approval_policy: AskForApproval::Never,
|
||||
sandbox_policy: SandboxPolicy::ReadOnly,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
cwd: PathBuf::from("/home/user/project"),
|
||||
reasoning_effort: None,
|
||||
history_log_id: 0,
|
||||
|
|
@ -3264,7 +3262,7 @@ mod tests {
|
|||
model: "gpt-test".to_string(),
|
||||
model_provider_id: "test-provider".to_string(),
|
||||
approval_policy: AskForApproval::Never,
|
||||
sandbox_policy: SandboxPolicy::ReadOnly,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
cwd: PathBuf::from("/home/user/project"),
|
||||
reasoning_effort: None,
|
||||
history_log_id: 0,
|
||||
|
|
@ -3388,7 +3386,7 @@ mod tests {
|
|||
model: "gpt-test".to_string(),
|
||||
model_provider_id: "test-provider".to_string(),
|
||||
approval_policy: AskForApproval::Never,
|
||||
sandbox_policy: SandboxPolicy::ReadOnly,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
cwd: PathBuf::from("/home/user/project"),
|
||||
reasoning_effort: None,
|
||||
history_log_id: 0,
|
||||
|
|
|
|||
|
|
@ -5540,7 +5540,7 @@ impl ChatWidget {
|
|||
let mut header_children: Vec<Box<dyn Renderable>> = Vec::new();
|
||||
let describe_policy = |policy: &SandboxPolicy| match policy {
|
||||
SandboxPolicy::WorkspaceWrite { .. } => "Agent mode",
|
||||
SandboxPolicy::ReadOnly => "Read-Only mode",
|
||||
SandboxPolicy::ReadOnly { .. } => "Read-Only mode",
|
||||
_ => "Agent mode",
|
||||
};
|
||||
let mode_label = preset
|
||||
|
|
|
|||
|
|
@ -151,7 +151,7 @@ async fn resumed_initial_messages_render_history() {
|
|||
model: "test-model".to_string(),
|
||||
model_provider_id: "test-provider".to_string(),
|
||||
approval_policy: AskForApproval::Never,
|
||||
sandbox_policy: SandboxPolicy::ReadOnly,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
cwd: PathBuf::from("/home/user/project"),
|
||||
reasoning_effort: Some(ReasoningEffortConfig::default()),
|
||||
history_log_id: 0,
|
||||
|
|
@ -219,7 +219,7 @@ async fn replayed_user_message_preserves_text_elements_and_local_images() {
|
|||
model: "test-model".to_string(),
|
||||
model_provider_id: "test-provider".to_string(),
|
||||
approval_policy: AskForApproval::Never,
|
||||
sandbox_policy: SandboxPolicy::ReadOnly,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
cwd: PathBuf::from("/home/user/project"),
|
||||
reasoning_effort: Some(ReasoningEffortConfig::default()),
|
||||
history_log_id: 0,
|
||||
|
|
@ -337,7 +337,7 @@ async fn submission_preserves_text_elements_and_local_images() {
|
|||
model: "test-model".to_string(),
|
||||
model_provider_id: "test-provider".to_string(),
|
||||
approval_policy: AskForApproval::Never,
|
||||
sandbox_policy: SandboxPolicy::ReadOnly,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
cwd: PathBuf::from("/home/user/project"),
|
||||
reasoning_effort: Some(ReasoningEffortConfig::default()),
|
||||
history_log_id: 0,
|
||||
|
|
@ -417,7 +417,7 @@ async fn submission_prefers_selected_duplicate_skill_path() {
|
|||
model: "test-model".to_string(),
|
||||
model_provider_id: "test-provider".to_string(),
|
||||
approval_policy: AskForApproval::Never,
|
||||
sandbox_policy: SandboxPolicy::ReadOnly,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
cwd: PathBuf::from("/home/user/project"),
|
||||
reasoning_effort: Some(ReasoningEffortConfig::default()),
|
||||
history_log_id: 0,
|
||||
|
|
@ -3105,7 +3105,7 @@ async fn plan_slash_command_with_args_submits_prompt_in_plan_mode() {
|
|||
model: "test-model".to_string(),
|
||||
model_provider_id: "test-provider".to_string(),
|
||||
approval_policy: AskForApproval::Never,
|
||||
sandbox_policy: SandboxPolicy::ReadOnly,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
cwd: PathBuf::from("/home/user/project"),
|
||||
reasoning_effort: Some(ReasoningEffortConfig::default()),
|
||||
history_log_id: 0,
|
||||
|
|
@ -4180,6 +4180,7 @@ async fn preset_matching_requires_exact_workspace_write_settings() {
|
|||
.expect("auto preset exists");
|
||||
let current_sandbox = SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots: vec![AbsolutePathBuf::try_from("C:\\extra").unwrap()],
|
||||
read_only_access: Default::default(),
|
||||
network_access: false,
|
||||
exclude_tmpdir_env_var: false,
|
||||
exclude_slash_tmp: false,
|
||||
|
|
|
|||
|
|
@ -474,13 +474,14 @@ mod tests {
|
|||
} else {
|
||||
absolute_path("/etc/codex/requirements.toml")
|
||||
};
|
||||
|
||||
let requirements = ConfigRequirements {
|
||||
approval_policy: ConstrainedWithSource::new(
|
||||
Constrained::allow_any(AskForApproval::OnRequest),
|
||||
Some(RequirementSource::CloudRequirements),
|
||||
),
|
||||
sandbox_policy: ConstrainedWithSource::new(
|
||||
Constrained::allow_any(SandboxPolicy::ReadOnly),
|
||||
Constrained::allow_any(SandboxPolicy::new_read_only_policy()),
|
||||
Some(RequirementSource::SystemRequirementsToml {
|
||||
file: requirements_file.clone(),
|
||||
}),
|
||||
|
|
@ -572,7 +573,6 @@ mod tests {
|
|||
));
|
||||
assert!(!rendered.contains(" - rules:"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn debug_config_output_lists_session_flag_key_value_pairs() {
|
||||
let session_flags = toml::from_str::<TomlValue>(
|
||||
|
|
|
|||
|
|
@ -193,7 +193,7 @@ impl StatusHistoryCell {
|
|||
.unwrap_or_else(|| "<unknown>".to_string());
|
||||
let sandbox = match config.sandbox_policy.get() {
|
||||
SandboxPolicy::DangerFullAccess => "danger-full-access".to_string(),
|
||||
SandboxPolicy::ReadOnly => "read-only".to_string(),
|
||||
SandboxPolicy::ReadOnly { .. } => "read-only".to_string(),
|
||||
SandboxPolicy::WorkspaceWrite {
|
||||
network_access: true,
|
||||
..
|
||||
|
|
|
|||
|
|
@ -101,6 +101,7 @@ 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,
|
||||
|
|
@ -183,6 +184,7 @@ 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,
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ pub fn builtin_approval_presets() -> Vec<ApprovalPreset> {
|
|||
label: "Read Only",
|
||||
description: "Codex can read files in the current workspace. Approval is required to edit files or access the internet.",
|
||||
approval: AskForApproval::OnRequest,
|
||||
sandbox: SandboxPolicy::ReadOnly,
|
||||
sandbox: SandboxPolicy::new_read_only_policy(),
|
||||
},
|
||||
ApprovalPreset {
|
||||
id: "auto",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use codex_core::protocol::SandboxPolicy;
|
|||
pub fn summarize_sandbox_policy(sandbox_policy: &SandboxPolicy) -> String {
|
||||
match sandbox_policy {
|
||||
SandboxPolicy::DangerFullAccess => "danger-full-access".to_string(),
|
||||
SandboxPolicy::ReadOnly => "read-only".to_string(),
|
||||
SandboxPolicy::ReadOnly { .. } => "read-only".to_string(),
|
||||
SandboxPolicy::ExternalSandbox { network_access } => {
|
||||
let mut summary = "external-sandbox".to_string();
|
||||
if matches!(network_access, NetworkAccess::Enabled) {
|
||||
|
|
@ -17,6 +17,7 @@ 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();
|
||||
|
||||
|
|
@ -71,6 +72,7 @@ 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,
|
||||
|
|
|
|||
|
|
@ -108,6 +108,7 @@ 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,
|
||||
|
|
@ -134,6 +135,7 @@ 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,
|
||||
|
|
@ -161,6 +163,7 @@ 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,
|
||||
|
|
@ -188,6 +191,7 @@ 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,
|
||||
|
|
@ -213,6 +217,7 @@ 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,
|
||||
|
|
|
|||
|
|
@ -262,7 +262,7 @@ pub fn apply_capability_denies_for_world_writable(
|
|||
}
|
||||
(sid, roots)
|
||||
}
|
||||
SandboxPolicy::ReadOnly => (
|
||||
SandboxPolicy::ReadOnly { .. } => (
|
||||
unsafe { convert_string_sid_to_sid(&caps.readonly) }.ok_or_else(|| {
|
||||
anyhow!("ConvertStringSidToSidW failed for readonly capability")
|
||||
})?,
|
||||
|
|
|
|||
|
|
@ -114,6 +114,11 @@ pub fn main() -> Result<()> {
|
|||
);
|
||||
|
||||
let policy = parse_policy(&req.policy_json_or_preset).context("parse policy_json_or_preset")?;
|
||||
if !policy.has_full_disk_read_access() {
|
||||
anyhow::bail!(
|
||||
"Restricted read-only access is not yet supported by the Windows sandbox backend"
|
||||
);
|
||||
}
|
||||
let mut cap_psids: Vec<*mut c_void> = Vec::new();
|
||||
for sid in &req.cap_sids {
|
||||
let Some(psid) = (unsafe { convert_string_sid_to_sid(sid) }) else {
|
||||
|
|
@ -129,7 +134,9 @@ pub fn main() -> Result<()> {
|
|||
let base = unsafe { get_current_token_for_restriction()? };
|
||||
let token_res: Result<HANDLE> = unsafe {
|
||||
match &policy {
|
||||
SandboxPolicy::ReadOnly => create_readonly_token_with_caps_from(base, &cap_psids),
|
||||
SandboxPolicy::ReadOnly { .. } => {
|
||||
create_readonly_token_with_caps_from(base, &cap_psids)
|
||||
}
|
||||
SandboxPolicy::WorkspaceWrite { .. } => {
|
||||
create_workspace_write_token_with_caps_from(base, &cap_psids)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -238,9 +238,14 @@ mod windows_impl {
|
|||
) {
|
||||
anyhow::bail!("DangerFullAccess and ExternalSandbox are not supported for sandboxing")
|
||||
}
|
||||
if !policy.has_full_disk_read_access() {
|
||||
anyhow::bail!(
|
||||
"Restricted read-only access is not yet supported by the Windows sandbox backend"
|
||||
);
|
||||
}
|
||||
let caps = load_or_create_cap_sids(codex_home)?;
|
||||
let (psid_to_use, cap_sids) = match &policy {
|
||||
SandboxPolicy::ReadOnly => (
|
||||
SandboxPolicy::ReadOnly { .. } => (
|
||||
unsafe { convert_string_sid_to_sid(&caps.readonly).unwrap() },
|
||||
vec![caps.readonly.clone()],
|
||||
),
|
||||
|
|
@ -469,6 +474,7 @@ 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,
|
||||
|
|
@ -487,7 +493,7 @@ mod windows_impl {
|
|||
|
||||
#[test]
|
||||
fn applies_network_block_for_read_only() {
|
||||
assert!(!SandboxPolicy::ReadOnly.has_full_network_access());
|
||||
assert!(!SandboxPolicy::new_read_only_policy().has_full_network_access());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -262,10 +262,15 @@ mod windows_impl {
|
|||
) {
|
||||
anyhow::bail!("DangerFullAccess and ExternalSandbox are not supported for sandboxing")
|
||||
}
|
||||
if !policy.has_full_disk_read_access() {
|
||||
anyhow::bail!(
|
||||
"Restricted read-only access is not yet supported by the Windows sandbox backend"
|
||||
);
|
||||
}
|
||||
let caps = load_or_create_cap_sids(codex_home)?;
|
||||
let (h_token, psid_generic, psid_workspace): (HANDLE, *mut c_void, Option<*mut c_void>) = unsafe {
|
||||
match &policy {
|
||||
SandboxPolicy::ReadOnly => {
|
||||
SandboxPolicy::ReadOnly { .. } => {
|
||||
let psid = convert_string_sid_to_sid(&caps.readonly).unwrap();
|
||||
let (h, _) = super::token::create_readonly_token_with_cap(psid)?;
|
||||
(h, psid, None)
|
||||
|
|
@ -558,6 +563,7 @@ 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,
|
||||
|
|
@ -576,7 +582,9 @@ mod windows_impl {
|
|||
|
||||
#[test]
|
||||
fn applies_network_block_for_read_only() {
|
||||
assert!(should_apply_network_block(&SandboxPolicy::ReadOnly));
|
||||
assert!(should_apply_network_block(
|
||||
&SandboxPolicy::new_read_only_policy()
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ pub use codex_protocol::protocol::SandboxPolicy;
|
|||
|
||||
pub fn parse_policy(value: &str) -> Result<SandboxPolicy> {
|
||||
match value {
|
||||
"read-only" => Ok(SandboxPolicy::ReadOnly),
|
||||
"read-only" => Ok(SandboxPolicy::new_read_only_policy()),
|
||||
"workspace-write" => Ok(SandboxPolicy::new_workspace_write_policy()),
|
||||
"danger-full-access" | "external-sandbox" => anyhow::bail!(
|
||||
"DangerFullAccess and ExternalSandbox are not supported for sandboxing"
|
||||
|
|
@ -52,6 +52,9 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn parses_read_only_policy() {
|
||||
assert_eq!(parse_policy("read-only").unwrap(), SandboxPolicy::ReadOnly);
|
||||
assert_eq!(
|
||||
parse_policy("read-only").unwrap(),
|
||||
SandboxPolicy::new_read_only_policy()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue