use kavach::scanning::types::{ExternalizationPolicy, ScanVerdict, Severity};
use kavach::{Backend, ExecResult, Sandbox, SandboxConfig, StrengthScore};
use serde::{Deserialize, Serialize};
use tracing::{debug, info, warn};
use super::policy::{IsolationLevel, SandboxPolicy};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct KavachToolResult {
pub output: String,
pub stderr: String,
pub exit_code: i32,
pub timed_out: bool,
pub duration_ms: u64,
pub strength: StrengthScore,
pub scan_verdict: Option<ScanVerdict>,
}
#[must_use]
pub fn map_backend(level: IsolationLevel) -> Backend {
match level {
IsolationLevel::None => Backend::Noop,
IsolationLevel::Wasm => Backend::Wasm,
IsolationLevel::Process => Backend::Process,
IsolationLevel::Oci => Backend::Oci,
}
}
#[must_use]
pub fn build_config(policy: &SandboxPolicy, agent_id: Option<&str>) -> SandboxConfig {
let backend = map_backend(policy.effective_isolation());
let mut builder = SandboxConfig::builder()
.backend(backend)
.network(policy.needs_network)
.timeout_ms(policy.max_duration_secs * 1000);
if let Some(id) = agent_id {
builder = builder.agent_id(id);
}
builder = builder.externalization(ExternalizationPolicy::default());
if policy.effective_isolation() >= IsolationLevel::Process {
builder = builder.policy_seccomp("basic");
}
builder.build()
}
#[must_use]
pub fn strength_for_policy(policy: &SandboxPolicy) -> StrengthScore {
let backend = map_backend(policy.effective_isolation());
let kavach_policy = to_kavach_policy(policy);
kavach::score_backend(backend, &kavach_policy)
}
fn to_kavach_policy(policy: &SandboxPolicy) -> kavach::SandboxPolicy {
let mut kp = kavach::SandboxPolicy::basic();
kp.network.enabled = policy.needs_network;
if policy.effective_isolation() >= IsolationLevel::Process {
kp.seccomp_enabled = true;
kp.seccomp_profile = Some("basic".into());
}
kp
}
pub async fn execute(
command: &str,
policy: &SandboxPolicy,
agent_id: Option<&str>,
) -> crate::core::Result<KavachToolResult> {
let config = build_config(policy, agent_id);
let backend = config.backend;
let strength = strength_for_policy(policy);
debug!(
backend = %backend,
strength = strength.value(),
timeout_ms = config.timeout_ms,
"creating kavach sandbox"
);
let mut sandbox = Sandbox::create(config)
.await
.map_err(|e| crate::core::AgnosaiError::Sandbox(format!("kavach create failed: {e}")))?;
sandbox
.transition(kavach::SandboxState::Running)
.map_err(|e| crate::core::AgnosaiError::Sandbox(format!("kavach start failed: {e}")))?;
let result = sandbox.exec(command).await;
if let Err(e) = sandbox.transition(kavach::SandboxState::Stopped) {
warn!(error = %e, "kavach stop failed (continuing with destroy)");
}
if let Err(e) = sandbox.transition(kavach::SandboxState::Destroyed) {
warn!(error = %e, "kavach destroy failed");
}
let exec_result = result
.map_err(|e| crate::core::AgnosaiError::Sandbox(format!("kavach exec failed: {e}")))?;
info!(
exit_code = exec_result.exit_code,
duration_ms = exec_result.duration_ms,
timed_out = exec_result.timed_out,
strength = strength.value(),
"kavach tool execution completed"
);
Ok(KavachToolResult {
output: exec_result.stdout,
stderr: exec_result.stderr,
exit_code: exec_result.exit_code,
timed_out: exec_result.timed_out,
duration_ms: exec_result.duration_ms,
strength,
scan_verdict: None, })
}
#[must_use]
pub fn scan_output(output: &str) -> ScanVerdict {
let gate = kavach::ExternalizationGate::new();
let result = ExecResult {
exit_code: 0,
stdout: output.to_string(),
stderr: String::new(),
duration_ms: 0,
timed_out: false,
};
let policy = ExternalizationPolicy::default();
match gate.apply(result, &policy) {
Ok(_) => ScanVerdict::Pass,
Err(e) => {
warn!(error = %e, "externalization gate blocked output");
ScanVerdict::Block
}
}
}
#[must_use]
pub fn policy_for_trust(trust: &str) -> ExternalizationPolicy {
match trust {
"minimal" => ExternalizationPolicy {
enabled: true,
max_artifact_size_bytes: 1024 * 1024, block_threshold: Severity::Medium,
quarantine_threshold: Severity::Low,
redact_secrets: true,
},
"strict" => ExternalizationPolicy {
enabled: true,
max_artifact_size_bytes: 10 * 1024 * 1024, block_threshold: Severity::High,
quarantine_threshold: Severity::Medium,
redact_secrets: true,
},
_ => ExternalizationPolicy::default(), }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn map_backend_none_to_noop() {
assert_eq!(map_backend(IsolationLevel::None), Backend::Noop);
}
#[test]
fn map_backend_wasm() {
assert_eq!(map_backend(IsolationLevel::Wasm), Backend::Wasm);
}
#[test]
fn map_backend_process() {
assert_eq!(map_backend(IsolationLevel::Process), Backend::Process);
}
#[test]
fn map_backend_oci() {
assert_eq!(map_backend(IsolationLevel::Oci), Backend::Oci);
}
#[test]
fn strength_for_native_policy() {
let p = SandboxPolicy::native();
let score = strength_for_policy(&p);
assert!(
score.value() <= 20,
"native should be low, got {}",
score.value()
);
}
#[test]
fn strength_for_process_policy() {
let p = SandboxPolicy::process();
let score = strength_for_policy(&p);
assert!(
score.value() >= 50,
"process should be ≥50, got {}",
score.value()
);
}
#[test]
fn strength_for_wasm_policy() {
let p = SandboxPolicy::wasm();
let score = strength_for_policy(&p);
assert!(
score.value() >= 60,
"wasm should be ≥60, got {}",
score.value()
);
}
#[test]
fn strength_ordering_matches_isolation() {
let native = strength_for_policy(&SandboxPolicy::native());
let wasm = strength_for_policy(&SandboxPolicy::wasm());
let process = strength_for_policy(&SandboxPolicy::process());
assert!(native.value() < wasm.value());
assert!(native.value() < process.value());
}
#[test]
fn build_config_sets_backend() {
let p = SandboxPolicy::process();
let config = build_config(&p, Some("agent-1"));
assert_eq!(config.backend, Backend::Process);
assert_eq!(config.agent_id.as_deref(), Some("agent-1"));
}
#[test]
fn build_config_enables_externalization() {
let p = SandboxPolicy::wasm();
let config = build_config(&p, None);
assert!(config.externalization.is_some());
}
#[test]
fn policy_for_trust_minimal() {
let p = policy_for_trust("minimal");
assert_eq!(p.block_threshold, Severity::Medium);
}
#[test]
fn policy_for_trust_strict() {
let p = policy_for_trust("strict");
assert_eq!(p.block_threshold, Severity::High);
}
#[test]
fn policy_for_trust_default() {
let p = policy_for_trust("basic");
assert_eq!(p.block_threshold, Severity::Critical);
}
#[test]
fn scan_clean_output_passes() {
let verdict = scan_output("hello world, task completed successfully");
assert_eq!(verdict, ScanVerdict::Pass);
}
#[test]
fn scan_secret_output_blocks() {
let verdict = scan_output("result: AKIAIOSFODNN7EXAMPLE");
assert_eq!(verdict, ScanVerdict::Block);
}
#[test]
fn strength_label_ranges() {
assert_eq!(StrengthScore(0).label(), "minimal");
assert_eq!(StrengthScore(50).label(), "standard");
assert_eq!(StrengthScore(90).label(), "fortress");
}
}