use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::time::Duration;
use kovra_core::{
ConfirmOutcome, ConfirmRequest, Confirmer, Coordinate, EnvRefs, FileAuditSink, KEY_LEN,
MockKeyring, MockProvider, Origin, Registry, SecretRecord, SecretValue, Sensitivity,
SystemClock, SystemEnvSource, seal, store,
};
use kovra_wrapper::{Allowlist, SystemRunner, Wrapper, WrapperError};
const MASTER: [u8; KEY_LEN] = [0x5a; KEY_LEN];
struct AutoDenyConfirmer;
impl Confirmer for AutoDenyConfirmer {
fn confirm(&self, req: &ConfirmRequest, _timeout: Duration) -> ConfirmOutcome {
println!("\n ┌─ kovra confirmation required (high/prod injection) ────────");
println!(
" │ command : {}",
req.resolved_command.as_deref().unwrap_or("-")
);
println!(" │ secret : {}", req.coordinate);
println!(" │ sens/env: {:?} / {}", req.sensitivity, req.environment);
println!(" └─ (harness auto-denies to avoid hanging)");
ConfirmOutcome::Denied
}
}
fn seed(reg: &Registry, env: &str, comp: &str, key: &str, value: &str, s: Sensitivity) {
let rec = SecretRecord::Literal {
value: SecretValue::from(value),
sensitivity: s,
revealable: false,
environment: env.into(),
component: comp.into(),
key: key.into(),
description: None,
created: "2026-05-31T00:00:00Z".into(),
updated: "2026-05-31T00:00:00Z".into(),
};
let coord = Coordinate::from_str(&format!("secret:{env}/{comp}/{key}")).unwrap();
store::write_record(®.global_dir(), &coord, &seal(&rec, &MASTER).unwrap()).unwrap();
}
fn docker_bin() -> PathBuf {
for cand in ["/usr/local/bin/docker", "/opt/homebrew/bin/docker"] {
if Path::new(cand).exists() {
return PathBuf::from(cand);
}
}
PathBuf::from("docker") }
fn main() {
let root = Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
.and_then(Path::parent)
.expect("workspace root")
.to_path_buf();
let project = root.join("sandbox/docker-demo");
let vault_dir = root.join("sandbox/.kovra-compose-vault");
let compose = project.join("docker-compose.yml");
let refs_path = project.join(".env.refs");
let env = std::env::args().nth(1).unwrap_or_else(|| "dev".to_string());
let reg = Registry::open(&vault_dir).expect("open vault");
seed(
®,
"dev",
"db",
"password",
"dev-db-pw",
Sensitivity::Medium,
);
seed(
®,
"dev",
"app",
"api-key",
"dev-api-key-0001",
Sensitivity::Medium,
);
seed(
®,
"prod",
"db",
"password",
"prod-db-pw-REDACTED",
Sensitivity::High,
);
seed(
®,
"prod",
"app",
"api-key",
"prod-api-key-REDACTED",
Sensitivity::High,
);
let refs_src = std::fs::read_to_string(&refs_path).expect("read .env.refs");
let refs = EnvRefs::parse(&refs_src).expect("parse .env.refs");
let docker = docker_bin();
let args = vec![
"compose".to_string(),
"-f".to_string(),
compose.display().to_string(),
"config".to_string(),
];
let keyring = MockKeyring::with_key(MASTER);
let env_source = SystemEnvSource; let provider = MockProvider::new();
let audit = FileAuditSink::under_root(&vault_dir);
let clock = SystemClock;
let confirmer = AutoDenyConfirmer;
let runner = SystemRunner;
let allowlist = Allowlist::from_paths([&docker]);
let w = Wrapper {
registry: ®,
keyring: &keyring,
env_source: &env_source,
provider: &provider,
confirmer: &confirmer,
audit: &audit,
clock: &clock,
allowlist: &allowlist,
runner: &runner,
confirm_timeout: Duration::from_secs(120),
sanitize_output: true,
stdio_passthrough: false,
requesting_process: kovra_wrapper::observe_parent(),
};
println!(
"kovra run --env {env} -- {} compose -f {} config",
docker.display(),
compose.display()
);
match w.run(&refs, &env, None, &docker, &args, Origin::Human) {
Ok(out) => {
print!("\n{}", String::from_utf8_lossy(&out.stdout));
if !out.stderr.is_empty() {
eprint!("{}", String::from_utf8_lossy(&out.stderr));
}
println!(
"(exit {:?}; vault-backed values masked above — §5.1)",
out.status
);
}
Err(WrapperError::ConfirmationDenied) => {
println!("\n→ denied: docker compose never launched (no secret reached it).")
}
Err(WrapperError::NotAllowlisted { program }) => {
println!("\n→ refused (I15): `{program}` is not a reviewed executor.")
}
Err(e) => println!("\n→ error: {e}"),
}
println!("\naudit log: {}", vault_dir.join("audit.log").display());
}