mirror of
https://github.com/openai/codex.git
synced 2026-05-18 02:02:30 +00:00
## Why Permission approval responses must not be able to grant more access than the tool requested. Moving this flow to `PermissionProfile` means the comparison must be profile-shaped instead of `SandboxPolicy`-shaped, and cwd-relative special paths such as `:cwd` and `:project_roots` must stay anchored to the turn that produced the request. ## What changed This implements semantic `PermissionProfile` intersection in `codex-sandboxing` for file-system and network permissions. The intersection accepts narrower path grants, rejects broader grants, preserves deny-read carve-outs and glob scan depth, and materializes cwd-dependent special-path grants to absolute paths before they can be recorded for reuse. The request-permissions response paths now use that intersection consistently. App-server captures the request turn cwd before waiting for the client response, includes that cwd in the v2 approval params, and core stores the requested profile plus cwd for direct TUI/client responses and Guardian decisions before recording turn- or session-scoped grants. The TUI app-server bridge now preserves the app-server request cwd when converting permission approval params into core events. ## Verification - `cargo test -p codex-sandboxing intersect_permission_profiles -- --nocapture` - `cargo test -p codex-app-server request_permissions_response -- --nocapture` - `cargo test -p codex-core request_permissions_response_materializes_session_cwd_grants_before_recording -- --nocapture` - `cargo check -p codex-tui --tests` - `cargo check --tests` - `cargo test -p codex-tui app_server_request_permissions_preserves_file_system_permissions`
78 lines
2.4 KiB
Rust
78 lines
2.4 KiB
Rust
use crate::models::FileSystemPermissions;
|
|
use crate::models::NetworkPermissions;
|
|
use crate::models::PermissionProfile;
|
|
use codex_utils_absolute_path::AbsolutePathBuf;
|
|
use schemars::JsonSchema;
|
|
use serde::Deserialize;
|
|
use serde::Serialize;
|
|
use ts_rs::TS;
|
|
|
|
#[derive(Debug, Clone, Copy, Default, Deserialize, Serialize, PartialEq, Eq, JsonSchema, TS)]
|
|
#[serde(rename_all = "snake_case")]
|
|
pub enum PermissionGrantScope {
|
|
#[default]
|
|
Turn,
|
|
Session,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Default, Deserialize, Serialize, PartialEq, Eq, JsonSchema, TS)]
|
|
#[serde(deny_unknown_fields)]
|
|
pub struct RequestPermissionProfile {
|
|
pub network: Option<NetworkPermissions>,
|
|
pub file_system: Option<FileSystemPermissions>,
|
|
}
|
|
|
|
impl RequestPermissionProfile {
|
|
pub fn is_empty(&self) -> bool {
|
|
self.network.is_none() && self.file_system.is_none()
|
|
}
|
|
}
|
|
|
|
impl From<RequestPermissionProfile> for PermissionProfile {
|
|
fn from(value: RequestPermissionProfile) -> Self {
|
|
Self {
|
|
network: value.network,
|
|
file_system: value.file_system,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<PermissionProfile> for RequestPermissionProfile {
|
|
fn from(value: PermissionProfile) -> Self {
|
|
Self {
|
|
network: value.network,
|
|
file_system: value.file_system,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, JsonSchema, TS)]
|
|
pub struct RequestPermissionsArgs {
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub reason: Option<String>,
|
|
pub permissions: RequestPermissionProfile,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, JsonSchema, TS)]
|
|
pub struct RequestPermissionsResponse {
|
|
pub permissions: RequestPermissionProfile,
|
|
#[serde(default)]
|
|
pub scope: PermissionGrantScope,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, JsonSchema, TS)]
|
|
pub struct RequestPermissionsEvent {
|
|
/// Responses API call id for the associated tool call, if available.
|
|
pub call_id: String,
|
|
/// Turn ID that this request belongs to.
|
|
/// Uses `#[serde(default)]` for backwards compatibility.
|
|
#[serde(default)]
|
|
pub turn_id: String,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub reason: Option<String>,
|
|
pub permissions: RequestPermissionProfile,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
#[ts(optional)]
|
|
pub cwd: Option<AbsolutePathBuf>,
|
|
}
|