Add request permissions tool (#13092)

Adds a built-in `request_permissions` tool and wires it through the
Codex core, protocol, and app-server layers so a running turn can ask
the client for additional permissions instead of relying on a static
session policy.

The new flow emits a `RequestPermissions` event from core, tracks the
pending request by call ID, forwards it through app-server v2 as an
`item/permissions/requestApproval` request, and resumes the tool call
once the client returns an approved subset of the requested permission
profile.
This commit is contained in:
Jack Mousseau
2026-03-08 20:23:06 -07:00
committed by GitHub
parent 4ad3b59de3
commit e6b93841c5
48 changed files with 3332 additions and 130 deletions

View File

@@ -10,6 +10,7 @@ mod mcp_resource;
pub(crate) mod multi_agents;
mod plan;
mod read_file;
mod request_permissions;
mod request_user_input;
mod search_tool_bm25;
mod shell;
@@ -24,8 +25,10 @@ use serde_json::Value;
use std::path::Path;
use std::path::PathBuf;
use crate::codex::Session;
use crate::function_tool::FunctionCallError;
use crate::sandboxing::SandboxPermissions;
use crate::sandboxing::merge_permission_profiles;
use crate::sandboxing::normalize_additional_permissions;
pub use apply_patch::ApplyPatchHandler;
pub use artifacts::ArtifactsHandler;
@@ -41,6 +44,8 @@ pub use mcp_resource::McpResourceHandler;
pub use multi_agents::MultiAgentHandler;
pub use plan::PlanHandler;
pub use read_file::ReadFileHandler;
pub use request_permissions::RequestPermissionsHandler;
pub(crate) use request_permissions::request_permissions_tool_description;
pub use request_user_input::RequestUserInputHandler;
pub(crate) use request_user_input::request_user_input_tool_description;
pub(crate) use search_tool_bm25::DEFAULT_LIMIT as SEARCH_TOOL_BM25_DEFAULT_LIMIT;
@@ -95,6 +100,7 @@ pub(super) fn normalize_and_validate_additional_permissions(
approval_policy: AskForApproval,
sandbox_permissions: SandboxPermissions,
additional_permissions: Option<PermissionProfile>,
permissions_preapproved: bool,
_cwd: &Path,
) -> Result<Option<PermissionProfile>, String> {
let uses_additional_permissions = matches!(
@@ -112,7 +118,7 @@ pub(super) fn normalize_and_validate_additional_permissions(
}
if uses_additional_permissions {
if !matches!(approval_policy, AskForApproval::OnRequest) {
if !permissions_preapproved && !matches!(approval_policy, AskForApproval::OnRequest) {
return Err(format!(
"approval policy is {approval_policy:?}; reject command — you cannot request additional permissions unless the approval policy is OnRequest"
));
@@ -146,3 +152,51 @@ pub(super) fn normalize_and_validate_additional_permissions(
Ok(None)
}
}
pub(super) struct EffectiveAdditionalPermissions {
pub sandbox_permissions: SandboxPermissions,
pub additional_permissions: Option<PermissionProfile>,
pub permissions_preapproved: bool,
}
pub(super) async fn apply_granted_turn_permissions(
session: &Session,
sandbox_permissions: SandboxPermissions,
additional_permissions: Option<PermissionProfile>,
) -> EffectiveAdditionalPermissions {
if matches!(sandbox_permissions, SandboxPermissions::RequireEscalated) {
return EffectiveAdditionalPermissions {
sandbox_permissions,
additional_permissions,
permissions_preapproved: false,
};
}
let granted_permissions = session.granted_turn_permissions().await;
let effective_permissions = merge_permission_profiles(
additional_permissions.as_ref(),
granted_permissions.as_ref(),
);
let permissions_preapproved = match (effective_permissions.as_ref(), granted_permissions) {
(Some(effective_permissions), Some(granted_permissions)) => {
crate::sandboxing::intersect_permission_profiles(
effective_permissions.clone(),
granted_permissions,
) == *effective_permissions
}
_ => false,
};
let sandbox_permissions =
if effective_permissions.is_some() && !sandbox_permissions.uses_additional_permissions() {
SandboxPermissions::WithAdditionalPermissions
} else {
sandbox_permissions
};
EffectiveAdditionalPermissions {
sandbox_permissions,
additional_permissions: effective_permissions,
permissions_preapproved,
}
}