mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
Decouple request permissions feature and tool (#14426)
This commit is contained in:
committed by
GitHub
Unverified
parent
bc48b9289a
commit
a314c7d3ae
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ use crate::tools::events::ToolEmitter;
|
||||
use crate::tools::events::ToolEventCtx;
|
||||
use crate::tools::handlers::apply_granted_turn_permissions;
|
||||
use crate::tools::handlers::apply_patch::intercept_apply_patch;
|
||||
use crate::tools::handlers::implicit_granted_permissions;
|
||||
use crate::tools::handlers::normalize_and_validate_additional_permissions;
|
||||
use crate::tools::handlers::parse_arguments_with_base_path;
|
||||
use crate::tools::handlers::resolve_workdir_base_path;
|
||||
@@ -336,19 +337,33 @@ impl ShellHandler {
|
||||
}
|
||||
|
||||
let request_permission_enabled = session.features().enabled(Feature::RequestPermissions);
|
||||
let requested_additional_permissions = additional_permissions.clone();
|
||||
let effective_additional_permissions = apply_granted_turn_permissions(
|
||||
session.as_ref(),
|
||||
exec_params.sandbox_permissions,
|
||||
additional_permissions,
|
||||
)
|
||||
.await;
|
||||
let normalized_additional_permissions = normalize_and_validate_additional_permissions(
|
||||
request_permission_enabled,
|
||||
turn.approval_policy.value(),
|
||||
effective_additional_permissions.sandbox_permissions,
|
||||
effective_additional_permissions.additional_permissions,
|
||||
effective_additional_permissions.permissions_preapproved,
|
||||
&exec_params.cwd,
|
||||
let additional_permissions_allowed = request_permission_enabled
|
||||
|| (session.features().enabled(Feature::RequestPermissionsTool)
|
||||
&& effective_additional_permissions.permissions_preapproved);
|
||||
let normalized_additional_permissions = implicit_granted_permissions(
|
||||
exec_params.sandbox_permissions,
|
||||
requested_additional_permissions.as_ref(),
|
||||
&effective_additional_permissions,
|
||||
)
|
||||
.map_or_else(
|
||||
|| {
|
||||
normalize_and_validate_additional_permissions(
|
||||
additional_permissions_allowed,
|
||||
turn.approval_policy.value(),
|
||||
effective_additional_permissions.sandbox_permissions,
|
||||
effective_additional_permissions.additional_permissions,
|
||||
effective_additional_permissions.permissions_preapproved,
|
||||
&exec_params.cwd,
|
||||
)
|
||||
},
|
||||
|permissions| Ok(Some(permissions)),
|
||||
)
|
||||
.map_err(FunctionCallError::RespondToModel)?;
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ use crate::tools::context::ToolInvocation;
|
||||
use crate::tools::context::ToolPayload;
|
||||
use crate::tools::handlers::apply_granted_turn_permissions;
|
||||
use crate::tools::handlers::apply_patch::intercept_apply_patch;
|
||||
use crate::tools::handlers::implicit_granted_permissions;
|
||||
use crate::tools::handlers::normalize_and_validate_additional_permissions;
|
||||
use crate::tools::handlers::parse_arguments;
|
||||
use crate::tools::handlers::parse_arguments_with_base_path;
|
||||
@@ -171,12 +172,16 @@ impl ToolHandler for UnifiedExecHandler {
|
||||
|
||||
let request_permission_enabled =
|
||||
session.features().enabled(Feature::RequestPermissions);
|
||||
let requested_additional_permissions = additional_permissions.clone();
|
||||
let effective_additional_permissions = apply_granted_turn_permissions(
|
||||
context.session.as_ref(),
|
||||
sandbox_permissions,
|
||||
additional_permissions,
|
||||
)
|
||||
.await;
|
||||
let additional_permissions_allowed = request_permission_enabled
|
||||
|| (session.features().enabled(Feature::RequestPermissionsTool)
|
||||
&& effective_additional_permissions.permissions_preapproved);
|
||||
|
||||
// Sticky turn permissions have already been approved, so they should
|
||||
// continue through the normal exec approval flow for the command.
|
||||
@@ -200,21 +205,30 @@ impl ToolHandler for UnifiedExecHandler {
|
||||
|
||||
let workdir = workdir.map(|dir| context.turn.resolve_path(Some(dir)));
|
||||
let cwd = workdir.clone().unwrap_or(cwd);
|
||||
let normalized_additional_permissions =
|
||||
match normalize_and_validate_additional_permissions(
|
||||
request_permission_enabled,
|
||||
context.turn.approval_policy.value(),
|
||||
effective_additional_permissions.sandbox_permissions,
|
||||
effective_additional_permissions.additional_permissions,
|
||||
effective_additional_permissions.permissions_preapproved,
|
||||
&cwd,
|
||||
) {
|
||||
Ok(normalized) => normalized,
|
||||
Err(err) => {
|
||||
manager.release_process_id(process_id).await;
|
||||
return Err(FunctionCallError::RespondToModel(err));
|
||||
}
|
||||
};
|
||||
let normalized_additional_permissions = match implicit_granted_permissions(
|
||||
sandbox_permissions,
|
||||
requested_additional_permissions.as_ref(),
|
||||
&effective_additional_permissions,
|
||||
)
|
||||
.map_or_else(
|
||||
|| {
|
||||
normalize_and_validate_additional_permissions(
|
||||
additional_permissions_allowed,
|
||||
context.turn.approval_policy.value(),
|
||||
effective_additional_permissions.sandbox_permissions,
|
||||
effective_additional_permissions.additional_permissions,
|
||||
effective_additional_permissions.permissions_preapproved,
|
||||
&cwd,
|
||||
)
|
||||
},
|
||||
|permissions| Ok(Some(permissions)),
|
||||
) {
|
||||
Ok(normalized) => normalized,
|
||||
Err(err) => {
|
||||
manager.release_process_id(process_id).await;
|
||||
return Err(FunctionCallError::RespondToModel(err));
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(output) = intercept_apply_patch(
|
||||
&command,
|
||||
|
||||
Reference in New Issue
Block a user