mur-common 2.20.7

Shared types and traits for the MUR ecosystem
Documentation
use super::types::TrustLevel;
use serde::{Deserialize, Serialize};
use std::str::FromStr;

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Capability {
    FsReadAgentHome,
    FsWriteAgentHome,
    FsReadHost,
    FsWriteHost,
    NetworkOutbound,
    NetworkOutboundAllowlisted,
    SpawnAllowlisted,
    Spawn,
    Mcp,
    SkillReadOthers,
}

impl FromStr for Capability {
    type Err = ();
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let v: serde_yaml_ng::Value = serde_yaml_ng::Value::String(s.to_string());
        serde_yaml_ng::from_value(v).map_err(|_| ())
    }
}

pub fn allowed_for(level: TrustLevel) -> &'static [Capability] {
    use Capability::*;
    match level {
        TrustLevel::Sandboxed => &[FsReadAgentHome, Mcp],
        TrustLevel::Verified => &[
            FsReadAgentHome,
            FsWriteAgentHome,
            NetworkOutboundAllowlisted,
            SpawnAllowlisted,
            Mcp,
        ],
        TrustLevel::Trusted => &[
            FsReadAgentHome,
            FsWriteAgentHome,
            FsReadHost,
            FsWriteHost,
            NetworkOutbound,
            NetworkOutboundAllowlisted,
            Spawn,
            SpawnAllowlisted,
            Mcp,
            SkillReadOthers,
        ],
    }
}

#[derive(Debug, PartialEq, Eq)]
pub struct CapabilityViolation {
    pub capability: Capability,
    pub trust_level: TrustLevel,
}

pub fn check_capabilities(
    declared: &[String],
    level: TrustLevel,
) -> Result<(), CapabilityViolation> {
    let allowed = allowed_for(level);
    for s in declared {
        let Ok(cap) = Capability::from_str(s) else {
            return Err(CapabilityViolation {
                capability: Capability::Mcp,
                trust_level: level,
            });
        };
        if !allowed.contains(&cap) {
            return Err(CapabilityViolation {
                capability: cap,
                trust_level: level,
            });
        }
    }
    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn sandboxed_blocks_network() {
        let r = check_capabilities(&["network_outbound".into()], TrustLevel::Sandboxed);
        assert!(matches!(
            r,
            Err(CapabilityViolation {
                capability: Capability::NetworkOutbound,
                ..
            })
        ));
    }

    #[test]
    fn verified_allows_allowlisted_net() {
        let r = check_capabilities(
            &[
                "network_outbound_allowlisted".into(),
                "fs_write_agent_home".into(),
            ],
            TrustLevel::Verified,
        );
        assert!(r.is_ok());
    }

    #[test]
    fn trusted_allows_everything_declared() {
        let r = check_capabilities(
            &["spawn".into(), "fs_write_host".into()],
            TrustLevel::Trusted,
        );
        assert!(r.is_ok());
    }

    #[test]
    fn unknown_capability_rejected() {
        let r = check_capabilities(&["nuke_from_orbit".into()], TrustLevel::Trusted);
        assert!(r.is_err());
    }

    #[test]
    fn empty_declarations_always_ok() {
        assert!(check_capabilities(&[], TrustLevel::Sandboxed).is_ok());
    }
}