Decouple request permissions feature and tool (#14426)

This commit is contained in:
Jack Mousseau
2026-03-12 14:47:08 -07:00
committed by GitHub
parent bc48b9289a
commit a314c7d3ae
9 changed files with 482 additions and 35 deletions

View File

@@ -101,7 +101,7 @@ fn resolve_workdir_base_path(
/// Validates feature/policy constraints for `with_additional_permissions` and
/// normalizes any path-based permissions. Errors if the request is invalid.
pub(crate) fn normalize_and_validate_additional_permissions(
request_permission_enabled: bool,
additional_permissions_allowed: bool,
approval_policy: AskForApproval,
sandbox_permissions: SandboxPermissions,
additional_permissions: Option<PermissionProfile>,
@@ -113,11 +113,12 @@ pub(crate) fn normalize_and_validate_additional_permissions(
SandboxPermissions::WithAdditionalPermissions
);
if !request_permission_enabled
if !permissions_preapproved
&& !additional_permissions_allowed
&& (uses_additional_permissions || additional_permissions.is_some())
{
return Err(
"additional permissions are disabled; enable `features.request_permission` before using `with_additional_permissions`"
"additional permissions are disabled; enable `features.request_permissions` before using `with_additional_permissions`"
.to_string(),
);
}
@@ -164,6 +165,23 @@ pub(super) struct EffectiveAdditionalPermissions {
pub permissions_preapproved: bool,
}
pub(super) fn implicit_granted_permissions(
sandbox_permissions: SandboxPermissions,
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
}
}
pub(super) async fn apply_granted_turn_permissions(
session: &Session,
sandbox_permissions: SandboxPermissions,
@@ -210,3 +228,118 @@ pub(super) async fn apply_granted_turn_permissions(
permissions_preapproved,
}
}
#[cfg(test)]
mod tests {
use super::EffectiveAdditionalPermissions;
use super::implicit_granted_permissions;
use super::normalize_and_validate_additional_permissions;
use crate::sandboxing::SandboxPermissions;
use codex_protocol::models::FileSystemPermissions;
use codex_protocol::models::NetworkPermissions;
use codex_protocol::models::PermissionProfile;
use codex_protocol::protocol::AskForApproval;
use codex_protocol::protocol::RejectConfig;
use codex_utils_absolute_path::AbsolutePathBuf;
use pretty_assertions::assert_eq;
use tempfile::tempdir;
fn network_permissions() -> PermissionProfile {
PermissionProfile {
network: Some(NetworkPermissions {
enabled: Some(true),
}),
..Default::default()
}
}
fn file_system_permissions(path: &std::path::Path) -> PermissionProfile {
PermissionProfile {
file_system: Some(FileSystemPermissions {
read: None,
write: Some(vec![
AbsolutePathBuf::from_absolute_path(path).expect("absolute path"),
]),
}),
..Default::default()
}
}
#[test]
fn preapproved_permissions_work_when_request_permissions_tool_is_enabled_without_inline_feature()
{
let cwd = tempdir().expect("tempdir");
let normalized = normalize_and_validate_additional_permissions(
false,
AskForApproval::Reject(RejectConfig {
sandbox_approval: false,
rules: false,
skill_approval: false,
request_permissions: true,
mcp_elicitations: false,
}),
SandboxPermissions::WithAdditionalPermissions,
Some(network_permissions()),
true,
cwd.path(),
)
.expect("preapproved permissions should be allowed");
assert_eq!(normalized, Some(network_permissions()));
}
#[test]
fn fresh_additional_permissions_still_require_request_permissions_feature() {
let cwd = tempdir().expect("tempdir");
let err = normalize_and_validate_additional_permissions(
false,
AskForApproval::OnRequest,
SandboxPermissions::WithAdditionalPermissions,
Some(network_permissions()),
false,
cwd.path(),
)
.expect_err("fresh inline permission requests should remain disabled");
assert_eq!(
err,
"additional permissions are disabled; enable `features.request_permissions` before using `with_additional_permissions`"
);
}
#[test]
fn implicit_sticky_grants_bypass_inline_permission_validation() {
let cwd = tempdir().expect("tempdir");
let granted_permissions = file_system_permissions(cwd.path());
let implicit_permissions = implicit_granted_permissions(
SandboxPermissions::UseDefault,
None,
&EffectiveAdditionalPermissions {
sandbox_permissions: SandboxPermissions::WithAdditionalPermissions,
additional_permissions: Some(granted_permissions.clone()),
permissions_preapproved: false,
},
);
assert_eq!(implicit_permissions, Some(granted_permissions));
}
#[test]
fn explicit_inline_permissions_do_not_use_implicit_sticky_grant_path() {
let cwd = tempdir().expect("tempdir");
let requested_permissions = file_system_permissions(cwd.path());
let implicit_permissions = implicit_granted_permissions(
SandboxPermissions::WithAdditionalPermissions,
Some(&requested_permissions),
&EffectiveAdditionalPermissions {
sandbox_permissions: SandboxPermissions::WithAdditionalPermissions,
additional_permissions: Some(requested_permissions.clone()),
permissions_preapproved: false,
},
);
assert_eq!(implicit_permissions, None);
}
}