whitelist command prefix integration in core and tui (#7033)

this PR enables TUI to approve commands and add their prefixes to an
allowlist:
<img width="708" height="605" alt="Screenshot 2025-11-21 at 4 18 07 PM"
src="https://github.com/user-attachments/assets/56a19893-4553-4770-a881-becf79eeda32"
/>

note: we only show the option to whitelist the command when 
1) command is not multi-part (e.g `git add -A && git commit -m 'hello
world'`)
2) command is not already matched by an existing rule
This commit is contained in:
zhao-oai
2025-12-03 23:17:02 -08:00
committed by GitHub
parent ccdeb9d9c4
commit e925a380dc
27 changed files with 733 additions and 155 deletions
@@ -127,6 +127,7 @@ impl Approvable<ApplyPatchRequest> for ApplyPatchRuntime {
cwd,
Some(reason),
risk,
None,
)
.await
} else if user_explicitly_approved {
+15 -7
View File
@@ -9,7 +9,7 @@ use crate::sandboxing::execute_env;
use crate::tools::runtimes::build_command_spec;
use crate::tools::sandboxing::Approvable;
use crate::tools::sandboxing::ApprovalCtx;
use crate::tools::sandboxing::ApprovalRequirement;
use crate::tools::sandboxing::ExecApprovalRequirement;
use crate::tools::sandboxing::ProvidesSandboxRetryData;
use crate::tools::sandboxing::SandboxAttempt;
use crate::tools::sandboxing::SandboxOverride;
@@ -32,7 +32,7 @@ pub struct ShellRequest {
pub env: std::collections::HashMap<String, String>,
pub with_escalated_permissions: Option<bool>,
pub justification: Option<String>,
pub approval_requirement: ApprovalRequirement,
pub exec_approval_requirement: ExecApprovalRequirement,
}
impl ProvidesSandboxRetryData for ShellRequest {
@@ -107,22 +107,30 @@ impl Approvable<ShellRequest> for ShellRuntime {
Box::pin(async move {
with_cached_approval(&session.services, key, move || async move {
session
.request_command_approval(turn, call_id, command, cwd, reason, risk)
.request_command_approval(
turn,
call_id,
command,
cwd,
reason,
risk,
req.exec_approval_requirement.allow_prefix().cloned(),
)
.await
})
.await
})
}
fn approval_requirement(&self, req: &ShellRequest) -> Option<ApprovalRequirement> {
Some(req.approval_requirement.clone())
fn exec_approval_requirement(&self, req: &ShellRequest) -> Option<ExecApprovalRequirement> {
Some(req.exec_approval_requirement.clone())
}
fn sandbox_mode_for_first_attempt(&self, req: &ShellRequest) -> SandboxOverride {
if req.with_escalated_permissions.unwrap_or(false)
|| matches!(
req.approval_requirement,
ApprovalRequirement::Skip {
req.exec_approval_requirement,
ExecApprovalRequirement::Skip {
bypass_sandbox: true
}
)
@@ -10,7 +10,7 @@ use crate::exec::ExecExpiration;
use crate::tools::runtimes::build_command_spec;
use crate::tools::sandboxing::Approvable;
use crate::tools::sandboxing::ApprovalCtx;
use crate::tools::sandboxing::ApprovalRequirement;
use crate::tools::sandboxing::ExecApprovalRequirement;
use crate::tools::sandboxing::ProvidesSandboxRetryData;
use crate::tools::sandboxing::SandboxAttempt;
use crate::tools::sandboxing::SandboxOverride;
@@ -36,7 +36,7 @@ pub struct UnifiedExecRequest {
pub env: HashMap<String, String>,
pub with_escalated_permissions: Option<bool>,
pub justification: Option<String>,
pub approval_requirement: ApprovalRequirement,
pub exec_approval_requirement: ExecApprovalRequirement,
}
impl ProvidesSandboxRetryData for UnifiedExecRequest {
@@ -66,7 +66,7 @@ impl UnifiedExecRequest {
env: HashMap<String, String>,
with_escalated_permissions: Option<bool>,
justification: Option<String>,
approval_requirement: ApprovalRequirement,
exec_approval_requirement: ExecApprovalRequirement,
) -> Self {
Self {
command,
@@ -74,7 +74,7 @@ impl UnifiedExecRequest {
env,
with_escalated_permissions,
justification,
approval_requirement,
exec_approval_requirement,
}
}
}
@@ -125,22 +125,33 @@ impl Approvable<UnifiedExecRequest> for UnifiedExecRuntime<'_> {
Box::pin(async move {
with_cached_approval(&session.services, key, || async move {
session
.request_command_approval(turn, call_id, command, cwd, reason, risk)
.request_command_approval(
turn,
call_id,
command,
cwd,
reason,
risk,
req.exec_approval_requirement.allow_prefix().cloned(),
)
.await
})
.await
})
}
fn approval_requirement(&self, req: &UnifiedExecRequest) -> Option<ApprovalRequirement> {
Some(req.approval_requirement.clone())
fn exec_approval_requirement(
&self,
req: &UnifiedExecRequest,
) -> Option<ExecApprovalRequirement> {
Some(req.exec_approval_requirement.clone())
}
fn sandbox_mode_for_first_attempt(&self, req: &UnifiedExecRequest) -> SandboxOverride {
if req.with_escalated_permissions.unwrap_or(false)
|| matches!(
req.approval_requirement,
ApprovalRequirement::Skip {
req.exec_approval_requirement,
ExecApprovalRequirement::Skip {
bypass_sandbox: true
}
)