openmoat 0.8.0

Policy checks for AI coding agents: decides allow, defer, ask or deny for every tool call from Claude Code, Codex and Cursor, configures Claude Code's and Codex's own OS sandboxes from the same policy, and keeps a local audit log.
use openmoat_core::{DEFAULT_POLICY, EvalContext, Policy};

use super::*;

fn generated(yaml: &str) -> Generated {
    let policy = Policy::parse(yaml).expect("test policy lints");
    let ctx = EvalContext {
        home: "/home/me".into(),
        project: Some("/home/me/proj".into()),
        real_home: None,
        real_project: None,
        moved_dirs: Vec::new(),
        cwd: "/home/me/proj".into(),
        case_insensitive_paths: false,
    };
    let grants = Grants {
        proxy_port: Some(18080),
        tmpdir: Some("/tmp".into()),
        program: Some("/home/me/.local/bin/agent".into()),
        writes: vec!["/home/me/.agent".into()],
    };
    let ir = openmoat_core::ir::lower(&policy, &ctx).expect("lowers");
    generate(&ir, &grants).expect("generates")
}

fn allowance<'a>(out: &'a Generated, kind: Kind, rule: &str) -> Option<&'a [String]> {
    let found = out.report.allowances.iter();
    let mut found = found.filter(|a| a.kind == kind && a.rule == rule);
    found.next().map(|a| a.patterns.as_slice())
}

#[test]
fn the_default_policy_grants_the_project_roots_and_session_paths_only() {
    let out = generated(DEFAULT_POLICY);
    let rules = &out.rules;
    for read in [
        "/home/me/proj",
        "/usr",
        "/home/me/.cargo",
        "/dev/null",
        "/home/me/.local/bin/agent",
    ] {
        assert!(
            rules.read.contains(&read.to_owned()),
            "{read}: {:?}",
            rules.read
        );
    }
    for write in ["/home/me/proj", "/tmp", "/home/me/.agent", "/dev/null"] {
        assert!(
            rules.write.contains(&write.to_owned()),
            "{write}: {:?}",
            rules.write
        );
    }
    for never in ["/", "/home/me", "/home/me/.ssh"] {
        assert!(!rules.read.contains(&never.to_owned()), "{never}");
        assert!(!rules.write.contains(&never.to_owned()), "{never}");
    }
    assert!(
        !rules.write.contains(&"/usr".to_owned()),
        "read roots stay read-only"
    );
    assert_eq!(rules.connect_port, Some(18080));
}

#[test]
fn denies_inside_grants_are_listed_as_allowances() {
    let out = generated(DEFAULT_POLICY);
    let read = allowance(&out, Kind::FsRead, "landlock.inside-grants").unwrap_or_default();
    for open in ["**/.env", "/home/me/.cargo/credentials.toml"] {
        assert!(read.contains(&open.to_owned()), "{open}: {read:?}");
    }
    assert!(
        !read.contains(&"/home/me/.ssh/**".to_owned()),
        "outside every grant: enforced"
    );
    let write = allowance(&out, Kind::FsWrite, "landlock.inside-grants").unwrap_or_default();
    for open in ["/home/me/proj/.git/**", "**/.claude/settings.json"] {
        assert!(write.contains(&open.to_owned()), "{open}: {write:?}");
    }
    assert!(
        !write.contains(&"/home/me/.zshrc".to_owned()),
        "outside every grant: enforced"
    );
    assert!(allowance(&out, Kind::Net, "landlock.tcp-port").is_some());
    assert!(
        allowance(&out, Kind::Net, "landlock.sockets").is_none(),
        "the seccomp filter closes UDP and Unix sockets"
    );
    assert!(out.report.losses.iter().any(|l| l.rule == "registries"));
}

#[test]
fn a_grant_inside_a_denied_path_is_not_made_and_globs_stay_denied() {
    let out = generated(
        "version: 1\ndeny:\n  - id: s\n    fs.read: ['~/.ssh/**']\n\
         allow:\n  - id: g\n    fs.read: ['/srv/*.log']\n\
         sandbox:\n  read_roots: ['~/.ssh/pub', '/usr']\n",
    );
    assert!(!out.rules.read.contains(&"/home/me/.ssh/pub".to_owned()));
    assert!(out.rules.read.contains(&"/usr".to_owned()));
    let losses: Vec<String> = out.report.losses.iter().map(ToString::to_string).collect();
    assert!(
        losses.iter().any(|l| l.contains("/home/me/.ssh/pub")),
        "{losses:?}"
    );
    assert!(
        losses.iter().any(|l| l.contains("/srv/*.log")),
        "{losses:?}"
    );
}

#[test]
fn an_allow_default_grants_the_root() {
    let out = generated("version: 1\ndefaults:\n  fs.read: allow\n");
    assert!(out.rules.read.contains(&"/".to_owned()));
    assert!(!out.rules.write.contains(&"/".to_owned()));
}