Skip to main content

open_kioku_sandbox/
lib.rs

1use open_kioku_actions::PolicyGate;
2use open_kioku_config::OkConfig;
3use open_kioku_errors::{OkError, Result};
4use serde::{Deserialize, Serialize};
5use std::process::Stdio;
6use std::time::Duration;
7use tokio::process::Command;
8use tokio::time::timeout;
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct CommandOutput {
12    pub status: Option<i32>,
13    pub stdout: String,
14    pub stderr: String,
15    pub timed_out: bool,
16}
17
18pub async fn run_allowlisted(
19    config: &OkConfig,
20    command: &str,
21    timeout_secs: u64,
22) -> Result<CommandOutput> {
23    PolicyGate::new(config).ensure_command_allowed(command)?;
24    let mut parts = command.split_whitespace();
25    let program = parts
26        .next()
27        .ok_or_else(|| OkError::PolicyDenied("empty command".into()))?;
28    let args = parts.collect::<Vec<_>>();
29    let child = Command::new(program)
30        .args(args)
31        .stdin(Stdio::null())
32        .stdout(Stdio::piped())
33        .stderr(Stdio::piped())
34        .spawn()?;
35    match timeout(Duration::from_secs(timeout_secs), child.wait_with_output()).await {
36        Ok(output) => {
37            let output = output?;
38            Ok(CommandOutput {
39                status: output.status.code(),
40                stdout: String::from_utf8_lossy(&output.stdout).to_string(),
41                stderr: String::from_utf8_lossy(&output.stderr).to_string(),
42                timed_out: false,
43            })
44        }
45        Err(_) => Ok(CommandOutput {
46            status: None,
47            stdout: String::new(),
48            stderr: "command timed out".into(),
49            timed_out: true,
50        }),
51    }
52}