use std::sync::Arc;
use async_trait::async_trait;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ShellApprovalRequest {
pub command: String,
pub reason: String,
pub full_access: bool,
}
#[async_trait]
pub trait ShellApprovalGate: Send + Sync {
async fn approve(&self, request: ShellApprovalRequest) -> bool;
}
pub struct HostShellApproval(Arc<dyn ShellApprovalGate>);
impl HostShellApproval {
pub fn new(gate: Arc<dyn ShellApprovalGate>) -> Self {
Self(gate)
}
pub fn gate(&self) -> &Arc<dyn ShellApprovalGate> {
&self.0
}
}
pub(crate) struct DenyAll;
#[async_trait]
impl ShellApprovalGate for DenyAll {
async fn approve(&self, _request: ShellApprovalRequest) -> bool {
false
}
}
#[cfg(test)]
mod tests {
use super::*;
struct AllowAll;
#[async_trait]
impl ShellApprovalGate for AllowAll {
async fn approve(&self, _request: ShellApprovalRequest) -> bool {
true
}
}
#[tokio::test]
async fn the_default_gate_refuses() {
let request = ShellApprovalRequest {
command: "rm -rf /".to_string(),
reason: "outside the trusted set".to_string(),
full_access: false,
};
assert!(!DenyAll.approve(request).await);
}
#[tokio::test]
async fn an_installed_gate_is_reachable_through_the_extension() {
let extension = HostShellApproval::new(Arc::new(AllowAll));
let request = ShellApprovalRequest {
command: "cargo build".to_string(),
reason: "escalation requested".to_string(),
full_access: true,
};
assert!(extension.gate().approve(request).await);
}
}