permissions: make profiles represent enforcement (#19231)

## Why

`PermissionProfile` is becoming the canonical permissions abstraction,
but the old shape only carried optional filesystem and network fields.
It could describe allowed access, but not who is responsible for
enforcing it. That made `DangerFullAccess` and `ExternalSandbox` lossy
when profiles were exported, cached, or round-tripped through app-server
APIs.

The important model change is that active permissions are now a disjoint
union over the enforcement mode. Conceptually:

```rust
pub enum PermissionProfile {
    Managed {
        file_system: FileSystemSandboxPolicy,
        network: NetworkSandboxPolicy,
    },
    Disabled,
    External {
        network: NetworkSandboxPolicy,
    },
}
```

This distinction matters because `Disabled` means Codex should apply no
outer sandbox at all, while `External` means filesystem isolation is
owned by an outside caller. Those are not equivalent to a broad managed
sandbox. For example, macOS cannot nest Seatbelt inside Seatbelt, so an
inner sandbox may require the outer Codex layer to use no sandbox rather
than a permissive one.

## How Existing Modeling Maps

Legacy `SandboxPolicy` remains a boundary projection, but it now maps
into the higher-fidelity profile model:

- `ReadOnly` and `WorkspaceWrite` map to `PermissionProfile::Managed`
with restricted filesystem entries plus the corresponding network
policy.
- `DangerFullAccess` maps to `PermissionProfile::Disabled`, preserving
the “no outer sandbox” intent instead of treating it as a lax managed
sandbox.
- `ExternalSandbox { network_access }` maps to
`PermissionProfile::External { network }`, preserving external
filesystem enforcement while still carrying the active network policy.
- Split runtime policies that legacy `SandboxPolicy` cannot faithfully
express, such as managed unrestricted filesystem plus restricted
network, stay `Managed` instead of being collapsed into
`ExternalSandbox`.
- Per-command/session/turn grants remain partial overlays via
`AdditionalPermissionProfile`; full `PermissionProfile` is reserved for
complete active runtime permissions.

## What Changed

- Change active `PermissionProfile` into a tagged union: `managed`,
`disabled`, and `external`.
- Keep partial permission grants separate with
`AdditionalPermissionProfile` for command/session/turn overlays.
- Represent managed filesystem permissions as either `restricted`
entries or `unrestricted`; `glob_scan_max_depth` is non-zero when
present.
- Preserve old rollout compatibility by accepting the pre-tagged `{
network, file_system }` profile shape during deserialization.
- Preserve fidelity for important edge cases: `DangerFullAccess`
round-trips as `disabled`, `ExternalSandbox` round-trips as `external`,
and managed unrestricted filesystem + restricted network stays managed
instead of being mistaken for external enforcement.
- Preserve configured deny-read entries and bounded glob scan depth when
full profiles are projected back into runtime policies, including
unrestricted replacements that now become `:root = write` plus deny
entries.
- Regenerate the experimental app-server v2 JSON/TypeScript schema and
update the `command/exec` README example for the tagged
`permissionProfile` shape.

## Compatibility

Legacy `SandboxPolicy` remains available at config/API boundaries as the
compatibility projection. Existing rollout lines with the old
`PermissionProfile` shape continue to load. The app-server
`permissionProfile` field is experimental, so its v2 wire shape is
intentionally updated to match the higher-fidelity model.

## Verification

- `just write-app-server-schema`
- `cargo check --tests`
- `cargo test -p codex-protocol permission_profile`
- `cargo test -p codex-protocol
preserving_deny_entries_keeps_unrestricted_policy_enforceable`
- `cargo test -p codex-app-server-protocol
permission_profile_file_system_permissions`
- `cargo test -p codex-app-server-protocol serialize_client_response`
- `cargo test -p codex-core
session_configured_reports_permission_profile_for_external_sandbox`
- `just fix`
- `just fix -p codex-protocol`
- `just fix -p codex-app-server-protocol`
- `just fix -p codex-core`
- `just fix -p codex-app-server`
This commit is contained in:
Michael Bolin
2026-04-23 23:02:18 -07:00
committed by GitHub
parent 33cc135cc3
commit 4816b89204
73 changed files with 2090 additions and 889 deletions
+4 -3
View File
@@ -1,4 +1,5 @@
use crate::mcp::RequestId;
use crate::models::AdditionalPermissionProfile;
use crate::models::PermissionProfile;
use crate::parse_command::ParsedCommand;
use crate::protocol::FileChange;
@@ -28,7 +29,7 @@ pub struct ResolvedPermissionProfile {
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EscalationPermissions {
/// Permissions to merge with the active turn permissions.
AdditionalPermissionProfile(PermissionProfile),
AdditionalPermissionProfile(AdditionalPermissionProfile),
/// Fully resolved permissions that should replace the active turn permissions.
ResolvedPermissionProfile(ResolvedPermissionProfile),
}
@@ -249,7 +250,7 @@ pub struct ExecApprovalRequestEvent {
/// Optional additional filesystem permissions requested for this command.
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub additional_permissions: Option<PermissionProfile>,
pub additional_permissions: Option<AdditionalPermissionProfile>,
/// Ordered list of decisions the client may present for this prompt.
///
/// When absent, clients should derive the legacy default set from the
@@ -285,7 +286,7 @@ impl ExecApprovalRequestEvent {
network_approval_context: Option<&NetworkApprovalContext>,
proposed_execpolicy_amendment: Option<&ExecPolicyAmendment>,
proposed_network_policy_amendments: Option<&[NetworkPolicyAmendment]>,
additional_permissions: Option<&PermissionProfile>,
additional_permissions: Option<&AdditionalPermissionProfile>,
) -> Vec<ReviewDecision> {
if network_approval_context.is_some() {
let mut decisions = vec![ReviewDecision::Approved, ReviewDecision::ApprovedForSession];
+406 -27
View File
@@ -266,43 +266,274 @@ impl NetworkPermissions {
}
}
/// Partial permission overlay used for per-command requests and approved
/// session/turn grants.
#[derive(Debug, Clone, Default, Eq, Hash, PartialEq, Serialize, Deserialize, JsonSchema, TS)]
pub struct PermissionProfile {
pub struct AdditionalPermissionProfile {
pub network: Option<NetworkPermissions>,
pub file_system: Option<FileSystemPermissions>,
}
impl PermissionProfile {
impl AdditionalPermissionProfile {
pub fn is_empty(&self) -> bool {
self.network.is_none() && self.file_system.is_none()
}
}
#[derive(
Debug, Clone, Copy, Default, Eq, Hash, PartialEq, Serialize, Deserialize, JsonSchema, TS,
)]
#[serde(rename_all = "snake_case")]
pub enum SandboxEnforcement {
/// Codex owns sandbox construction for this profile.
#[default]
Managed,
/// No outer filesystem sandbox should be applied.
Disabled,
/// Filesystem isolation is enforced by an external caller.
External,
}
impl SandboxEnforcement {
pub fn from_legacy_sandbox_policy(sandbox_policy: &SandboxPolicy) -> Self {
match sandbox_policy {
SandboxPolicy::DangerFullAccess => Self::Disabled,
SandboxPolicy::ExternalSandbox { .. } => Self::External,
SandboxPolicy::ReadOnly { .. } | SandboxPolicy::WorkspaceWrite { .. } => Self::Managed,
}
}
}
/// Filesystem permissions for profiles where Codex owns sandbox construction.
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, JsonSchema, TS)]
#[serde(tag = "type", rename_all = "snake_case")]
#[ts(tag = "type")]
pub enum ManagedFileSystemPermissions {
/// Apply a managed filesystem sandbox from the listed entries.
#[serde(rename_all = "snake_case")]
#[ts(rename_all = "snake_case")]
Restricted {
entries: Vec<FileSystemSandboxEntry>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
glob_scan_max_depth: Option<NonZeroUsize>,
},
/// Apply a managed sandbox that allows all filesystem access.
Unrestricted,
}
impl ManagedFileSystemPermissions {
fn from_sandbox_policy(file_system_sandbox_policy: &FileSystemSandboxPolicy) -> Self {
match file_system_sandbox_policy.kind {
FileSystemSandboxKind::Restricted => Self::Restricted {
entries: file_system_sandbox_policy.entries.clone(),
glob_scan_max_depth: file_system_sandbox_policy
.glob_scan_max_depth
.and_then(NonZeroUsize::new),
},
FileSystemSandboxKind::Unrestricted => Self::Unrestricted,
FileSystemSandboxKind::ExternalSandbox => unreachable!(
"external filesystem policies are represented by PermissionProfile::External"
),
}
}
pub fn to_sandbox_policy(&self) -> FileSystemSandboxPolicy {
match self {
Self::Restricted {
entries,
glob_scan_max_depth,
} => FileSystemSandboxPolicy {
kind: FileSystemSandboxKind::Restricted,
glob_scan_max_depth: glob_scan_max_depth.map(usize::from),
entries: entries.clone(),
},
Self::Unrestricted => FileSystemSandboxPolicy::unrestricted(),
}
}
}
/// Canonical active runtime permissions for a conversation, turn, or command.
#[derive(Debug, Clone, Eq, PartialEq, Serialize, JsonSchema, TS)]
#[serde(tag = "type", rename_all = "snake_case")]
#[ts(tag = "type")]
pub enum PermissionProfile {
/// Codex owns sandbox construction for this profile.
#[serde(rename_all = "snake_case")]
#[ts(rename_all = "snake_case")]
Managed {
file_system: ManagedFileSystemPermissions,
network: NetworkSandboxPolicy,
},
/// Do not apply an outer sandbox.
Disabled,
/// Filesystem isolation is enforced by an external caller.
#[serde(rename_all = "snake_case")]
#[ts(rename_all = "snake_case")]
External { network: NetworkSandboxPolicy },
}
impl Default for PermissionProfile {
fn default() -> Self {
Self::Managed {
file_system: ManagedFileSystemPermissions::Restricted {
entries: Vec::new(),
glob_scan_max_depth: None,
},
network: NetworkSandboxPolicy::Restricted,
}
}
}
impl PermissionProfile {
pub fn from_runtime_permissions(
file_system_sandbox_policy: &FileSystemSandboxPolicy,
network_sandbox_policy: NetworkSandboxPolicy,
) -> Self {
Self {
network: Some(network_sandbox_policy.into()),
file_system: Some(file_system_sandbox_policy.into()),
let enforcement = match file_system_sandbox_policy.kind {
FileSystemSandboxKind::Restricted | FileSystemSandboxKind::Unrestricted => {
SandboxEnforcement::Managed
}
FileSystemSandboxKind::ExternalSandbox => SandboxEnforcement::External,
};
Self::from_runtime_permissions_with_enforcement(
enforcement,
file_system_sandbox_policy,
network_sandbox_policy,
)
}
pub fn from_runtime_permissions_with_enforcement(
enforcement: SandboxEnforcement,
file_system_sandbox_policy: &FileSystemSandboxPolicy,
network_sandbox_policy: NetworkSandboxPolicy,
) -> Self {
match file_system_sandbox_policy.kind {
FileSystemSandboxKind::ExternalSandbox => Self::External {
network: network_sandbox_policy,
},
FileSystemSandboxKind::Unrestricted
if enforcement == SandboxEnforcement::Disabled
&& network_sandbox_policy.is_enabled() =>
{
Self::Disabled
}
FileSystemSandboxKind::Restricted | FileSystemSandboxKind::Unrestricted => {
Self::Managed {
file_system: ManagedFileSystemPermissions::from_sandbox_policy(
file_system_sandbox_policy,
),
network: network_sandbox_policy,
}
}
}
}
pub fn from_legacy_sandbox_policy(sandbox_policy: &SandboxPolicy, cwd: &Path) -> Self {
Self::from_runtime_permissions(
Self::from_runtime_permissions_with_enforcement(
SandboxEnforcement::from_legacy_sandbox_policy(sandbox_policy),
&FileSystemSandboxPolicy::from_legacy_sandbox_policy(sandbox_policy, cwd),
NetworkSandboxPolicy::from(sandbox_policy),
)
}
pub fn enforcement(&self) -> SandboxEnforcement {
match self {
Self::Managed { .. } => SandboxEnforcement::Managed,
Self::Disabled => SandboxEnforcement::Disabled,
Self::External { .. } => SandboxEnforcement::External,
}
}
pub fn file_system_sandbox_policy(&self) -> FileSystemSandboxPolicy {
self.file_system.as_ref().map_or_else(
|| FileSystemSandboxPolicy::restricted(Vec::new()),
FileSystemSandboxPolicy::from,
)
match self {
Self::Managed { file_system, .. } => file_system.to_sandbox_policy(),
Self::Disabled => FileSystemSandboxPolicy::unrestricted(),
Self::External { .. } => FileSystemSandboxPolicy::external_sandbox(),
}
}
pub fn network_sandbox_policy(&self) -> NetworkSandboxPolicy {
if self
match self {
Self::Managed { network, .. } | Self::External { network } => *network,
Self::Disabled => NetworkSandboxPolicy::Enabled,
}
}
pub fn to_legacy_sandbox_policy(&self, cwd: &Path) -> io::Result<SandboxPolicy> {
match self {
Self::Managed {
file_system,
network,
} => file_system
.to_sandbox_policy()
.to_legacy_sandbox_policy(*network, cwd),
Self::Disabled => Ok(SandboxPolicy::DangerFullAccess),
Self::External { network } => Ok(SandboxPolicy::ExternalSandbox {
network_access: if network.is_enabled() {
crate::protocol::NetworkAccess::Enabled
} else {
crate::protocol::NetworkAccess::Restricted
},
}),
}
}
pub fn to_runtime_permissions(&self) -> (FileSystemSandboxPolicy, NetworkSandboxPolicy) {
(
self.file_system_sandbox_policy(),
self.network_sandbox_policy(),
)
}
}
#[derive(Debug, Clone, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
enum TaggedPermissionProfile {
#[serde(rename_all = "snake_case")]
Managed {
file_system: ManagedFileSystemPermissions,
network: NetworkSandboxPolicy,
},
Disabled,
#[serde(rename_all = "snake_case")]
External {
network: NetworkSandboxPolicy,
},
}
impl From<TaggedPermissionProfile> for PermissionProfile {
fn from(value: TaggedPermissionProfile) -> Self {
match value {
TaggedPermissionProfile::Managed {
file_system,
network,
} => Self::Managed {
file_system,
network,
},
TaggedPermissionProfile::Disabled => Self::Disabled,
TaggedPermissionProfile::External { network } => Self::External { network },
}
}
}
/// Pre-tagged shape written to rollout files before `PermissionProfile`
/// represented enforcement explicitly.
#[derive(Debug, Clone, Default, Deserialize)]
#[serde(deny_unknown_fields)]
struct LegacyPermissionProfile {
network: Option<NetworkPermissions>,
file_system: Option<FileSystemPermissions>,
}
impl From<LegacyPermissionProfile> for PermissionProfile {
fn from(value: LegacyPermissionProfile) -> Self {
let file_system_sandbox_policy = value.file_system.as_ref().map_or_else(
|| FileSystemSandboxPolicy::restricted(Vec::new()),
FileSystemSandboxPolicy::from,
);
let network_sandbox_policy = if value
.network
.as_ref()
.and_then(|network| network.enabled)
@@ -311,19 +542,27 @@ impl PermissionProfile {
NetworkSandboxPolicy::Enabled
} else {
NetworkSandboxPolicy::Restricted
}
};
Self::from_runtime_permissions(&file_system_sandbox_policy, network_sandbox_policy)
}
}
pub fn to_legacy_sandbox_policy(&self, cwd: &Path) -> io::Result<SandboxPolicy> {
self.file_system_sandbox_policy()
.to_legacy_sandbox_policy(self.network_sandbox_policy(), cwd)
}
#[derive(Debug, Clone, Deserialize)]
#[serde(untagged)]
enum PermissionProfileDe {
Tagged(TaggedPermissionProfile),
Legacy(LegacyPermissionProfile),
}
pub fn to_runtime_permissions(&self) -> (FileSystemSandboxPolicy, NetworkSandboxPolicy) {
(
self.file_system_sandbox_policy(),
self.network_sandbox_policy(),
)
impl<'de> Deserialize<'de> for PermissionProfile {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
Ok(match PermissionProfileDe::deserialize(deserializer)? {
PermissionProfileDe::Tagged(tagged) => tagged.into(),
PermissionProfileDe::Legacy(legacy) => legacy.into(),
})
}
}
@@ -977,7 +1216,7 @@ pub struct ShellToolCallParams {
pub prefix_rule: Option<Vec<String>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub additional_permissions: Option<PermissionProfile>,
pub additional_permissions: Option<AdditionalPermissionProfile>,
#[serde(skip_serializing_if = "Option::is_none")]
pub justification: Option<String>,
}
@@ -1003,7 +1242,7 @@ pub struct ShellCommandToolCallParams {
pub prefix_rule: Option<Vec<String>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub additional_permissions: Option<PermissionProfile>,
pub additional_permissions: Option<AdditionalPermissionProfile>,
#[serde(skip_serializing_if = "Option::is_none")]
pub justification: Option<String>,
}
@@ -1448,13 +1687,13 @@ mod tests {
}
#[test]
fn permission_profile_is_empty_when_all_fields_are_none() {
assert_eq!(PermissionProfile::default().is_empty(), true);
fn additional_permission_profile_is_empty_when_all_fields_are_none() {
assert_eq!(AdditionalPermissionProfile::default().is_empty(), true);
}
#[test]
fn permission_profile_is_not_empty_when_field_is_present_but_nested_empty() {
let permission_profile = PermissionProfile {
fn additional_permission_profile_is_not_empty_when_field_is_present_but_nested_empty() {
let permission_profile = AdditionalPermissionProfile {
network: Some(NetworkPermissions { enabled: None }),
file_system: None,
};
@@ -1483,6 +1722,146 @@ mod tests {
);
}
#[test]
fn permission_profile_deserializes_legacy_rollout_shape() -> Result<()> {
let legacy = serde_json::json!({
"network": {
"enabled": true,
},
"file_system": {
"entries": [{
"path": {
"type": "special",
"value": {
"kind": "root",
},
},
"access": "write",
}],
"glob_scan_max_depth": 2,
},
});
let permission_profile: PermissionProfile = serde_json::from_value(legacy)?;
assert_eq!(
permission_profile,
PermissionProfile::Managed {
file_system: ManagedFileSystemPermissions::Restricted {
entries: vec![FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::Root,
},
access: FileSystemAccessMode::Write,
}],
glob_scan_max_depth: NonZeroUsize::new(2),
},
network: NetworkSandboxPolicy::Enabled,
}
);
Ok(())
}
#[test]
fn permission_profile_round_trip_preserves_disabled_sandbox() -> Result<()> {
let cwd = tempdir()?;
let permission_profile = PermissionProfile::from_legacy_sandbox_policy(
&SandboxPolicy::DangerFullAccess,
cwd.path(),
);
assert_eq!(permission_profile, PermissionProfile::Disabled);
assert_eq!(
permission_profile.to_legacy_sandbox_policy(cwd.path())?,
SandboxPolicy::DangerFullAccess
);
assert_eq!(
permission_profile.to_runtime_permissions(),
(
FileSystemSandboxPolicy::unrestricted(),
NetworkSandboxPolicy::Enabled
)
);
Ok(())
}
#[test]
fn permission_profile_from_runtime_permissions_preserves_external_sandbox() {
let permission_profile = PermissionProfile::from_runtime_permissions(
&FileSystemSandboxPolicy::external_sandbox(),
NetworkSandboxPolicy::Restricted,
);
assert_eq!(
permission_profile,
PermissionProfile::External {
network: NetworkSandboxPolicy::Restricted,
}
);
assert_eq!(
PermissionProfile::from_runtime_permissions_with_enforcement(
SandboxEnforcement::Managed,
&FileSystemSandboxPolicy::external_sandbox(),
NetworkSandboxPolicy::Restricted,
),
permission_profile,
);
}
#[test]
fn permission_profile_from_runtime_permissions_preserves_unrestricted_managed_network() {
let permission_profile = PermissionProfile::from_runtime_permissions_with_enforcement(
SandboxEnforcement::External,
&FileSystemSandboxPolicy::unrestricted(),
NetworkSandboxPolicy::Restricted,
);
assert_eq!(
permission_profile,
PermissionProfile::Managed {
file_system: ManagedFileSystemPermissions::Unrestricted,
network: NetworkSandboxPolicy::Restricted,
},
"the legacy ExternalSandbox projection must not hide a split unrestricted filesystem policy"
);
assert_eq!(
permission_profile.to_runtime_permissions(),
(
FileSystemSandboxPolicy::unrestricted(),
NetworkSandboxPolicy::Restricted,
)
);
}
#[test]
fn permission_profile_round_trip_preserves_external_sandbox() -> Result<()> {
let cwd = tempdir()?;
let sandbox_policy = SandboxPolicy::ExternalSandbox {
network_access: crate::protocol::NetworkAccess::Restricted,
};
let permission_profile =
PermissionProfile::from_legacy_sandbox_policy(&sandbox_policy, cwd.path());
assert_eq!(
permission_profile,
PermissionProfile::External {
network: NetworkSandboxPolicy::Restricted,
}
);
assert_eq!(
permission_profile.to_legacy_sandbox_policy(cwd.path())?,
sandbox_policy
);
assert_eq!(
permission_profile.to_runtime_permissions(),
(
FileSystemSandboxPolicy::external_sandbox(),
NetworkSandboxPolicy::Restricted
)
);
Ok(())
}
#[test]
fn file_system_permissions_with_glob_scan_depth_uses_canonical_json() -> Result<()> {
let path = AbsolutePathBuf::try_from(PathBuf::from(if cfg!(windows) {
+57
View File
@@ -340,6 +340,41 @@ impl FileSystemSandboxPolicy {
rebuilt
}
/// Preserve explicit read-deny rules from `existing` when a caller
/// replaces the allow side of a policy.
pub fn preserve_deny_read_restrictions_from(&mut self, existing: &Self) {
let has_deny_read_entries = existing
.entries
.iter()
.any(|entry| entry.access == FileSystemAccessMode::None);
if matches!(self.kind, FileSystemSandboxKind::Unrestricted) && has_deny_read_entries {
*self = Self::restricted(vec![FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::Root,
},
access: FileSystemAccessMode::Write,
}]);
}
if !matches!(self.kind, FileSystemSandboxKind::Restricted) {
return;
}
if self.glob_scan_max_depth.is_none() {
self.glob_scan_max_depth = existing.glob_scan_max_depth;
}
for deny_entry in existing
.entries
.iter()
.filter(|entry| entry.access == FileSystemAccessMode::None)
{
if !self.entries.iter().any(|entry| entry == deny_entry) {
self.entries.push(deny_entry.clone());
}
}
}
/// Returns true when a restricted policy contains any entry that really
/// reduces a broader `:root = write` grant.
///
@@ -2297,6 +2332,28 @@ mod tests {
);
}
#[test]
fn preserving_deny_entries_keeps_unrestricted_policy_enforceable() {
let deny_entry = unreadable_glob_entry("/tmp/project/**/*.env".to_string());
let mut existing = FileSystemSandboxPolicy::restricted(vec![deny_entry.clone()]);
existing.glob_scan_max_depth = Some(2);
let mut replacement = FileSystemSandboxPolicy::unrestricted();
replacement.preserve_deny_read_restrictions_from(&existing);
let mut expected = FileSystemSandboxPolicy::restricted(vec![
FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::Root,
},
access: FileSystemAccessMode::Write,
},
deny_entry,
]);
expected.glob_scan_max_depth = Some(2);
assert_eq!(replacement, expected);
}
fn deny_policy(path: &Path) -> FileSystemSandboxPolicy {
FileSystemSandboxPolicy::restricted(vec![FileSystemSandboxEntry {
path: FileSystemPath::Path {
+3 -1
View File
@@ -41,6 +41,7 @@ use crate::models::MessagePhase;
use crate::models::PermissionProfile;
use crate::models::ResponseInputItem;
use crate::models::ResponseItem;
use crate::models::SandboxEnforcement;
use crate::models::WebSearchAction;
use crate::num_format::format_with_separators;
use crate::openai_models::ReasoningEffort as ReasoningEffortConfig;
@@ -3062,7 +3063,8 @@ impl TurnContextItem {
&self.cwd,
)
});
PermissionProfile::from_runtime_permissions(
PermissionProfile::from_runtime_permissions_with_enforcement(
SandboxEnforcement::from_legacy_sandbox_policy(&self.sandbox_policy),
&file_system_sandbox_policy,
NetworkSandboxPolicy::from(&self.sandbox_policy),
)
+4 -4
View File
@@ -1,6 +1,6 @@
use crate::models::AdditionalPermissionProfile;
use crate::models::FileSystemPermissions;
use crate::models::NetworkPermissions;
use crate::models::PermissionProfile;
use codex_utils_absolute_path::AbsolutePathBuf;
use schemars::JsonSchema;
use serde::Deserialize;
@@ -28,7 +28,7 @@ impl RequestPermissionProfile {
}
}
impl From<RequestPermissionProfile> for PermissionProfile {
impl From<RequestPermissionProfile> for AdditionalPermissionProfile {
fn from(value: RequestPermissionProfile) -> Self {
Self {
network: value.network,
@@ -37,8 +37,8 @@ impl From<RequestPermissionProfile> for PermissionProfile {
}
}
impl From<PermissionProfile> for RequestPermissionProfile {
fn from(value: PermissionProfile) -> Self {
impl From<AdditionalPermissionProfile> for RequestPermissionProfile {
fn from(value: AdditionalPermissionProfile) -> Self {
Self {
network: value.network,
file_system: value.file_system,