Compare commits

...

1 Commits

Author SHA1 Message Date
David Wiesen
3064937bce Require explicit reuse of granted permissions 2026-03-24 09:27:05 -07:00
4 changed files with 34 additions and 19 deletions

View File

@@ -1008,9 +1008,9 @@ The client responds with `result.permissions`, which should be the granted subse
Only the granted subset matters on the wire. Any permissions omitted from `result.permissions` are treated as denied, including omitted nested keys inside `result.permissions.macos`, so a sparse response like `{ "permissions": { "macos": { "accessibility": true } } }` grants only accessibility. Any permissions not present in the original request are ignored by the server.
Within the same turn, granted permissions are sticky: later shell-like tool calls can automatically reuse the granted subset without reissuing a separate permission request.
Within the same turn, granted permissions remain available for explicit follow-up commands. Later shell-like tool calls must still opt into `with_additional_permissions`, but previously granted subsets can satisfy that request without a new permission prompt.
If the session approval policy uses `Granular` with `request_permissions: false`, standalone `request_permissions` tool calls are auto-denied and no `item/permissions/requestApproval` prompt is sent. Inline `with_additional_permissions` command requests remain controlled by `sandbox_approval`, and any previously granted permissions remain sticky for later shell-like calls in the same turn.
If the session approval policy uses `Granular` with `request_permissions: false`, standalone `request_permissions` tool calls are auto-denied and no `item/permissions/requestApproval` prompt is sent. Inline `with_additional_permissions` command requests remain controlled by `sandbox_approval`, and any previously granted permissions can only be reused by later shell-like calls that explicitly request them again.
### Dynamic tool calls (experimental)

View File

@@ -169,16 +169,12 @@ pub(super) fn implicit_granted_permissions(
additional_permissions: Option<&PermissionProfile>,
effective_additional_permissions: &EffectiveAdditionalPermissions,
) -> Option<PermissionProfile> {
if !sandbox_permissions.uses_additional_permissions()
&& !matches!(sandbox_permissions, SandboxPermissions::RequireEscalated)
&& additional_permissions.is_none()
{
effective_additional_permissions
.additional_permissions
.clone()
} else {
None
}
let _ = (
sandbox_permissions,
additional_permissions,
effective_additional_permissions,
);
None
}
pub(super) async fn apply_granted_turn_permissions(
@@ -309,7 +305,7 @@ mod tests {
}
#[test]
fn implicit_sticky_grants_bypass_inline_permission_validation() {
fn implicit_sticky_grants_do_not_auto_apply_to_plain_commands() {
let cwd = tempdir().expect("tempdir");
let granted_permissions = file_system_permissions(cwd.path());
let implicit_permissions = implicit_granted_permissions(
@@ -322,7 +318,7 @@ mod tests {
},
);
assert_eq!(implicit_permissions, Some(granted_permissions));
assert_eq!(implicit_permissions, None);
}
#[test]

View File

@@ -11,7 +11,7 @@ use crate::tools::registry::ToolHandler;
use crate::tools::registry::ToolKind;
pub(crate) fn request_permissions_tool_description() -> String {
"Request additional filesystem or network permissions from the user and wait for the client to grant a subset of the requested permission profile. Granted permissions apply automatically to later shell-like commands in the current turn, or for the rest of the session if the client approves them at session scope."
"Request additional filesystem or network permissions from the user and wait for the client to grant a subset of the requested permission profile. Later shell-like commands must explicitly request those granted permissions again, and the client may approve them for the rest of the session."
.to_string()
}

View File

@@ -4,6 +4,7 @@
use anyhow::Result;
use codex_core::config::Constrained;
use codex_core::features::Feature;
use codex_core::sandboxing::SandboxPermissions;
use codex_protocol::models::FileSystemPermissions;
use codex_protocol::protocol::AskForApproval;
use codex_protocol::protocol::EventMsg;
@@ -61,6 +62,21 @@ fn exec_command_event(call_id: &str, command: &str) -> Result<Value> {
Ok(ev_function_call(call_id, "exec_command", &args_str))
}
fn exec_command_event_with_request_permissions(
call_id: &str,
command: &str,
additional_permissions: &RequestPermissionProfile,
) -> Result<Value> {
let args = json!({
"cmd": command,
"yield_time_ms": 1_000_u64,
"sandbox_permissions": SandboxPermissions::WithAdditionalPermissions,
"additional_permissions": additional_permissions,
});
let args_str = serde_json::to_string(&args)?;
Ok(ev_function_call(call_id, "exec_command", &args_str))
}
fn build_add_file_patch(patch_path: &Path, content: &str) -> String {
format!(
"*** Begin Patch\n*** Add File: {}\n+{}\n*** End Patch\n",
@@ -181,8 +197,7 @@ async fn expect_request_permissions_event(
#[tokio::test(flavor = "current_thread")]
#[cfg(target_os = "macos")]
async fn approved_folder_write_request_permissions_unblocks_later_exec_without_sandbox_args()
-> Result<()> {
async fn approved_folder_write_request_permissions_unblocks_explicit_followup_exec() -> Result<()> {
skip_if_no_network!(Ok(()));
skip_if_sandbox!(Ok(()));
@@ -229,7 +244,11 @@ async fn approved_folder_write_request_permissions_unblocks_later_exec_without_s
]),
sse(vec![
ev_response_created("resp-request-permissions-2"),
exec_command_event("exec-call", &command)?,
exec_command_event_with_request_permissions(
"exec-call",
&command,
&requested_permissions,
)?,
ev_completed("resp-request-permissions-2"),
]),
sse(vec![
@@ -303,7 +322,7 @@ async fn approved_folder_write_request_permissions_unblocks_later_exec_without_s
#[tokio::test(flavor = "current_thread")]
#[cfg(target_os = "macos")]
async fn approved_folder_write_request_permissions_unblocks_later_apply_patch_without_prompt()
async fn approved_folder_write_request_permissions_unblocks_explicit_followup_apply_patch()
-> Result<()> {
skip_if_no_network!(Ok(()));
skip_if_sandbox!(Ok(()));