Handle pre-approved permissions in zsh fork (#14431)

This commit is contained in:
Jack Mousseau 2026-03-12 00:27:11 -07:00 committed by GitHub
parent e99e8e4a6b
commit 19d0949aab
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 93 additions and 1 deletions

View file

@ -422,6 +422,9 @@ impl ShellHandler {
network: exec_params.network.clone(),
sandbox_permissions: effective_additional_permissions.sandbox_permissions,
additional_permissions: normalized_additional_permissions,
#[cfg(unix)]
additional_permissions_preapproved: effective_additional_permissions
.permissions_preapproved,
justification: exec_params.justification.clone(),
exec_approval_requirement,
};

View file

@ -51,6 +51,8 @@ pub struct ShellRequest {
pub network: Option<NetworkProxy>,
pub sandbox_permissions: SandboxPermissions,
pub additional_permissions: Option<PermissionProfile>,
#[cfg(unix)]
pub additional_permissions_preapproved: bool,
pub justification: Option<String>,
pub exec_approval_requirement: ExecApprovalRequirement,
}

View file

@ -71,6 +71,22 @@ const REJECT_RULES_APPROVAL_REASON: &str =
const REJECT_SKILL_APPROVAL_REASON: &str =
"approval required by skill, but AskForApproval::Reject.skill_approval is set";
fn approval_sandbox_permissions(
sandbox_permissions: SandboxPermissions,
additional_permissions_preapproved: bool,
) -> SandboxPermissions {
if additional_permissions_preapproved
&& matches!(
sandbox_permissions,
SandboxPermissions::WithAdditionalPermissions
)
{
SandboxPermissions::UseDefault
} else {
sandbox_permissions
}
}
pub(super) async fn try_run_zsh_fork(
req: &ShellRequest,
attempt: &SandboxAttempt<'_>,
@ -170,6 +186,10 @@ pub(super) async fn try_run_zsh_fork(
// escalation server.
let stopwatch = Stopwatch::new(effective_timeout);
let cancel_token = stopwatch.cancellation_token();
let approval_sandbox_permissions = approval_sandbox_permissions(
req.sandbox_permissions,
req.additional_permissions_preapproved,
);
let escalation_policy = CoreShellActionProvider {
policy: Arc::clone(&exec_policy),
session: Arc::clone(&ctx.session),
@ -181,6 +201,7 @@ pub(super) async fn try_run_zsh_fork(
file_system_sandbox_policy: command_executor.file_system_sandbox_policy.clone(),
network_sandbox_policy: command_executor.network_sandbox_policy,
sandbox_permissions: req.sandbox_permissions,
approval_sandbox_permissions,
prompt_permissions: req.additional_permissions.clone(),
stopwatch: stopwatch.clone(),
};
@ -281,6 +302,10 @@ pub(crate) async fn prepare_unified_exec_zsh_fork(
file_system_sandbox_policy: exec_request.file_system_sandbox_policy.clone(),
network_sandbox_policy: exec_request.network_sandbox_policy,
sandbox_permissions: req.sandbox_permissions,
approval_sandbox_permissions: approval_sandbox_permissions(
req.sandbox_permissions,
req.additional_permissions_preapproved,
),
prompt_permissions: req.additional_permissions.clone(),
stopwatch: Stopwatch::unlimited(),
};
@ -312,6 +337,7 @@ struct CoreShellActionProvider {
file_system_sandbox_policy: FileSystemSandboxPolicy,
network_sandbox_policy: NetworkSandboxPolicy,
sandbox_permissions: SandboxPermissions,
approval_sandbox_permissions: SandboxPermissions,
prompt_permissions: Option<PermissionProfile>,
stopwatch: Stopwatch,
}
@ -696,7 +722,7 @@ impl EscalationPolicy for CoreShellActionProvider {
approval_policy: self.approval_policy,
sandbox_policy: &self.sandbox_policy,
file_system_sandbox_policy: &self.file_system_sandbox_policy,
sandbox_permissions: self.sandbox_permissions,
sandbox_permissions: self.approval_sandbox_permissions,
enable_shell_wrapper_parsing:
ENABLE_INTERCEPTED_EXEC_POLICY_SHELL_WRAPPER_PARSING,
},

View file

@ -165,6 +165,22 @@ fn execve_prompt_rejection_keeps_unmatched_commands_on_sandbox_flag() {
);
}
#[test]
fn approval_sandbox_permissions_only_downgrades_preapproved_additional_permissions() {
assert_eq!(
super::approval_sandbox_permissions(SandboxPermissions::WithAdditionalPermissions, true),
SandboxPermissions::UseDefault,
);
assert_eq!(
super::approval_sandbox_permissions(SandboxPermissions::WithAdditionalPermissions, false),
SandboxPermissions::WithAdditionalPermissions,
);
assert_eq!(
super::approval_sandbox_permissions(SandboxPermissions::RequireEscalated, true),
SandboxPermissions::RequireEscalated,
);
}
#[test]
fn extract_shell_script_preserves_login_flag() {
assert_eq!(
@ -548,6 +564,47 @@ host_executable(name = "git", paths = ["{git_path_literal}"])
));
}
#[test]
fn intercepted_exec_policy_treats_preapproved_additional_permissions_as_default() {
let policy = PolicyParser::new().build();
let program = AbsolutePathBuf::try_from(host_absolute_path(&["usr", "bin", "printf"])).unwrap();
let argv = ["printf".to_string(), "hello".to_string()];
let approval_policy = AskForApproval::OnRequest;
let sandbox_policy = SandboxPolicy::new_workspace_write_policy();
let file_system_sandbox_policy = read_only_file_system_sandbox_policy();
let preapproved = evaluate_intercepted_exec_policy(
&policy,
&program,
&argv,
InterceptedExecPolicyContext {
approval_policy,
sandbox_policy: &sandbox_policy,
file_system_sandbox_policy: &file_system_sandbox_policy,
sandbox_permissions: super::approval_sandbox_permissions(
SandboxPermissions::WithAdditionalPermissions,
true,
),
enable_shell_wrapper_parsing: false,
},
);
let fresh_request = evaluate_intercepted_exec_policy(
&policy,
&program,
&argv,
InterceptedExecPolicyContext {
approval_policy,
sandbox_policy: &sandbox_policy,
file_system_sandbox_policy: &file_system_sandbox_policy,
sandbox_permissions: SandboxPermissions::WithAdditionalPermissions,
enable_shell_wrapper_parsing: false,
},
);
assert_eq!(preapproved.decision, Decision::Allow);
assert_eq!(fresh_request.decision, Decision::Prompt);
}
#[test]
fn intercepted_exec_policy_rejects_disallowed_host_executable_mapping() {
let allowed_git = host_absolute_path(&["usr", "bin", "git"]);

View file

@ -54,6 +54,8 @@ pub struct UnifiedExecRequest {
pub tty: bool,
pub sandbox_permissions: SandboxPermissions,
pub additional_permissions: Option<PermissionProfile>,
#[cfg(unix)]
pub additional_permissions_preapproved: bool,
pub justification: Option<String>,
pub exec_approval_requirement: ExecApprovalRequirement,
}

View file

@ -603,6 +603,8 @@ impl UnifiedExecProcessManager {
tty: request.tty,
sandbox_permissions: request.sandbox_permissions,
additional_permissions: request.additional_permissions.clone(),
#[cfg(unix)]
additional_permissions_preapproved: request.additional_permissions_preapproved,
justification: request.justification.clone(),
exec_approval_requirement,
};