OpenHuman Command Permission Tiers: How classify_command Gates Security Decisions

OpenHuman enforces a tiered security model where the AutonomyLevel setting (Read-only, Supervised, or Full) determines whether shell commands classified as Read, Write, Network, Install, or Destructive are Allowed, Prompted, or Blocked.

The tinyhumansai/openhuman repository implements a fine-grained security policy that governs how AI agents execute shell commands. At its core, the system uses the classify_command method to categorize command risk levels and the gate_decision method to enforce session-specific OpenHuman command permission tiers.

Understanding the Three Permission Tiers (AutonomyLevel)

OpenHuman defines three distinct autonomy tiers in src/openhuman/security/policy/types.rs#L45 that control how permissive a session is. These tiers act as the primary gate for all command execution.

Read-only Tier

In Read-only mode, the policy allows only commands classified as Read. Any command that modifies the filesystem, accesses the network, or performs destructive operations receives a Block decision. This tier is ideal for safe exploration and data inspection where no system changes are permitted.

Supervised Tier

Supervised mode enables write-capable operations but requires user approval. Commands classified as Write, Network, or Install trigger a Prompt decision, presenting an approval card to the user before execution. Destructive commands may also be blocked or prompted depending on policy configuration.

Full Tier

Full autonomy removes all restrictions, mapping every CommandClass to Allow. This tier should only be used in trusted environments where the AI requires unrestricted system access.

The Command Classification System (CommandClass)

Before gating occurs, every command is classified according to its potential impact. The CommandClass enum, defined in src/openhuman/security/policy/types.rs#L82, contains five distinct variants:

  • Read – Commands that only observe data (e.g., cat, ls, grep)
  • Write – Commands that modify the filesystem (e.g., touch, mv, echo > file)
  • Network – Commands performing network I/O (e.g., curl, wget, ping)
  • Install – Package manager actions (e.g., npm install, pip install)
  • Destructive – Commands that delete or irrevocably alter data (e.g., rm -rf, dd, mkfs)

The classification logic handles complex command strings by splitting them into segments (respecting pipelines, &&, and || operators) and computing the maximum risk class across all segments. If any segment is Destructive, the entire command inherits that classification.

How classify_command and gate_decision Work Together

The security flow involves two critical methods implemented in src/openhuman/security/policy/policy_command.rs and src/openhuman/security/policy/policy.rs.

The Classification Process

When a tool invokes SecurityPolicy::classify_command(&self, cmd: &str) -> CommandClass, the method parses the command string and checks each segment against whitelists of safe verbs. It returns the highest CommandClass found among all segments, ensuring that a seemingly safe command appended with a destructive operation is correctly flagged.

The Gate Decision Mapping

After classification, SecurityPolicy::gate_decision(class: CommandClass) -> GateDecision maps the class to an action based on the current AutonomyLevel:

CommandClass Read-only Tier Supervised Tier Full Tier
Read Allow Allow Allow
Write Block Prompt Allow
Network Block Prompt Allow
Install Block Prompt Allow
Destructive Block Block/Prompt Allow

The GateDecision enum (defined in src/openhuman/security/policy/types.rs) has three variants: Allow, Prompt, and Block.

Enforcement in Tools

Tools that invoke shell commands check the policy before execution. In src/openhuman/tools/impl/system/shell.rs, the implementation consults the security policy:

match self.security.gate_decision(self.security.classify_command(&shell_cmd)) {
    GateDecision::Allow => { /* Execute command */ },
    GateDecision::Prompt => { /* Request user approval */ },
    GateDecision::Block => { /* Abort with security error */ },
}

This ensures a fail-closed model where unknown commands default to the most restrictive action appropriate for the current tier.

Practical Implementation Examples

Manual Policy Usage

You can instantiate and query the policy directly in Rust applications:

use openhuman::security::{SecurityPolicy, AutonomyLevel, CommandClass, GateDecision};

fn main() {
    // Initialize policy for supervised session
    let policy = SecurityPolicy::new(AutonomyLevel::Supervised);
    
    let cmd = r#"git clone https://example.com/repo.git && rm -rf repo"#;
    
    // Classify the compound command
    let class = policy.classify_command(cmd);
    
    // Query the gate decision
    let decision = policy.gate_decision(class);
    
    println!("Class: {:?}, Decision: {:?}", class, decision);
    // Output: Class: Destructive, Decision: Prompt
}

Tool Integration Pattern

Tools that wrap shell execution implement the security check as follows:

fn execute_shell_command(&self, args: &[String]) -> Result<(), String> {
    let shell_cmd = args.join(" ");
    
    // Security gate check
    match self.security.gate_decision(
        self.security.classify_command(&shell_cmd)
    ) {
        GateDecision::Allow => {
            std::process::Command::new("sh")
                .arg("-c")
                .arg(&shell_cmd)
                .status()
                .map_err(|e| e.to_string())?;
            Ok(())
        },
        GateDecision::Prompt => Err("User approval required".into()),
        GateDecision::Block => Err("Command blocked by security policy".into()),
    }
}

Key Source Files

The security policy implementation spans several critical files in the tinyhumansai/openhuman repository:

Summary

  • OpenHuman uses three permission tiers (Read-only, Supervised, Full) defined in src/openhuman/security/policy/types.rs to control command execution scope.
  • Commands are classified into five risk categories (Read, Write, Network, Install, Destructive) by the classify_command method in src/openhuman/security/policy/policy_command.rs.
  • Gate decisions map classifications to Allow, Prompt, or Block actions based on the current AutonomyLevel.
  • Multi-segment commands are evaluated by their highest-risk component, ensuring pipelines containing destructive operations are properly flagged.
  • Tools enforce the policy by checking gate_decision before executing shell commands, creating a centralized, auditable security boundary.

Frequently Asked Questions

What are the three AutonomyLevel tiers in OpenHuman?

The three tiers are Read-only, Supervised, and Full. Read-only permits only Read class commands and blocks everything else. Supervised allows Read commands silently but prompts for approval on Write, Network, Install, and Destructive operations. Full permits all command classes without user intervention.

How does classify_command determine the CommandClass?

The classify_command method parses the command string into segments, handling operators like pipes and && chains. It checks each segment against internal whitelists to assign a class, then returns the maximum risk level found using the CommandClass::max implementation. This ensures compound commands are classified by their most dangerous component.

What happens when a command is classified as Destructive?

In Read-only tier, Destructive commands are always Blocked. In Supervised tier, they typically trigger a Prompt for user approval or may be blocked depending on policy configuration. In Full tier, they are Allowed to execute immediately. The exact mapping is determined by gate_decision in src/openhuman/security/policy/policy.rs.

Where is the security policy enforced in the codebase?

Policy enforcement occurs in tool implementations, specifically in src/openhuman/tools/impl/system/shell.rs, where the tool calls self.security.gate_decision() with the classified command class before executing any shell command. This centralizes security checks across all tooling that may invoke system commands.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →