open_kioku_sandbox/
lib.rs1use 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}