OpenHuman Command Permission Model: Classification, Gate, and Forbidden Checks

OpenHuman enforces a fine-grained two-step permission model where commands are first classified by risk level using SecurityPolicy::classify_command, then evaluated against tier-based policies via SecurityPolicy::gate_decision to determine if execution is allowed, prompted, or blocked.

The tinyhumansai/openhuman repository implements a deterministic security framework that prevents unauthorized system modifications by analyzing shell commands before execution. This architecture ensures that AI agents operate within strictly defined boundaries, with every command string undergoing semantic analysis to detect filesystem modifications, network activity, or destructive operations.

Command Classification with classify_command

Every raw command string entering the system is parsed by SecurityPolicy::classify_command to determine its potential impact on the host environment. This function, implemented in src/openhuman/security/policy/command_checks.rs at line 126, examines shell features including pipes, redirects, variable expansions, and subshells to assign a CommandClass value.

The Five CommandClass Categories

The classifier assigns one of five security classifications based on detected behavior:

  • Read – Commands that only read files without modification (cat, less, head, grep without redirects)
  • Write – Any operation that modifies the filesystem, including redirections (>, >>), file moves (mv), or deletions (rm)
  • Network – Commands performing network I/O (curl, wget, ssh, nc)
  • Install – Software installation commands (cargo add, npm install, pip install)
  • Destructive – Host-level destructive operations (reboot, shutdown, poweroff, halt)

Fail-Closed Security Design

The classification system operates on a fail-closed principle: any command containing unrecognized shell constructs or complex syntax that cannot be safely parsed is automatically upgraded to Write classification. This prevents adversarial commands from bypassing security through obfuscation. The comprehensive test suite in src/openhuman/security/policy/policy_tests.rs (lines 438-553) validates this behavior across hundreds of edge cases.

Gate Decision Logic and Tier Enforcement

After classification, the system applies policy rules through SecurityPolicy::gate_decision, defined in src/openhuman/security/policy/policy_command.rs at line 214. This function maps the command's CommandClass against the agent's assigned tier (e.g., readonly, supervised, full) to produce a GateDecision: Allow, Prompt, or Block.

Tier-Based Access Control

Each tier defines maximum permissible command classes:

  • Readonly tiers typically allow only Read commands
  • Supervised tiers may permit Write operations after user confirmation
  • Full tiers can execute Network and Install commands with appropriate checks

The Raise-Only Rule

The gate logic enforces a strict "raise-only" policy: a tier may escalate the security classification of a command (treating a Read as Write for safety), but can never downgrade or lower the classification. This guarantees that once the policy identifies a potential risk, no subsequent logic can override the decision to block or prompt. Property-based tests in src/openhuman/security/policy/proptest_tests.rs (lines 17-28) verify this invariant.

Real-World Implementation

The permission model integrates throughout the core codebase. The shell tool invokes these checks at src/openhuman/tools/impl/system/shell.rs (lines 251 and 1068) before spawning subprocesses. Similarly, the Node runtime applies gating logic in src/openhuman/runtime/node/ops.rs at line 38 to sandbox JavaScript execution.

Typical execution flow follows this pattern:

// 1. Classification
let class = security_policy.classify_command(raw_cmd);

// 2. Optional raise by the caller (e.g., a tool declares it needs Write)
let class = class.max(declared_class);

// 3. Gate decision based on the agent's tier
match security_policy.gate_decision(class) {
    GateDecision::Allow   => { /* execute */ }
    GateDecision::Prompt  => { /* ask the user */ }
    GateDecision::Block   => { /* reject */ }
}

Practical Integration Examples

Manually Checking Commands Before Execution

use openhuman::security::policy::{SecurityPolicy, Tier};

let policy = SecurityPolicy::new(Tier::Supervised);
let raw = "cat /etc/passwd > /tmp/passwd_copy";

// Classify the command (will be upgraded to Write because of the redirect)
let class = policy.classify_command(raw);

// Apply the policy gate
match policy.gate_decision(class) {
    GateDecision::Allow => {
        // Safe to run the command
        std::process::Command::new("sh")
            .arg("-c")
            .arg(raw)
            .status()
            .expect("failed to execute");
    }
    GateDecision::Prompt => {
        // Prompt the user before proceeding
        println!("The command '{raw}' requires permission. Allow? (y/n)");
        // …handle response…
    }
    GateDecision::Block => {
        eprintln!("Command blocked by security policy");
    }
}

Embedding Checks in Custom Tools

use openhuman::tools::Tool;
use openhuman::security::policy::{SecurityPolicy, GateDecision};

pub struct MyTool {
    policy: SecurityPolicy,
}

impl Tool for MyTool {
    fn run(&self, cmd: &str) -> ToolResult {
        let class = self.policy.classify_command(cmd);
        match self.policy.gate_decision(class) {
            GateDecision::Allow => {
                // …run the command safely…
                ToolResult::Success
            }
            GateDecision::Prompt => {
                // …return a prompt to the model…
                ToolResult::UserPrompt
            }
            GateDecision::Block => ToolResult::PermissionDenied,
        }
    }
}

Summary

  • Command classification in command_checks.rs parses shell syntax to assign Read, Write, Network, Install, or Destructive labels, defaulting to Write for unrecognized commands.
  • Gate decisions in policy_command.rs apply tier-based rules (Allow, Prompt, Block) following a "raise-only" policy that prevents security downgrades.
  • Integration points include the shell tool (shell.rs) and Node runtime (ops.rs), ensuring all subprocess execution undergoes permission checks.
  • Test coverage spans unit tests (policy_tests.rs lines 438-553) and property-based tests (proptest_tests.rs lines 17-28) that verify the fail-closed behavior.

Frequently Asked Questions

How does OpenHuman classify complex shell commands with pipes and redirects?

SecurityPolicy::classify_command parses the abstract syntax tree of the command string to detect shell metacharacters. Any presence of output redirection operators (>, >>), pipes (|), command substitution, or variable expansion immediately upgrades the classification to Write, even if the base command (like cat or echo) would normally be Read-only. This ensures that cat /etc/passwd > /tmp/leak is treated as a write operation requiring appropriate tier permissions.

What is the "raise-only" rule in OpenHuman's permission gate?

The raise-only rule states that once gate_decision determines a command's minimum security classification based on its syntax, higher-tier policies may only increase (raise) the required permission level or maintain it. A supervised tier cannot override a Write classification down to Read to bypass user prompts. This prevents privilege escalation attacks where malicious code attempts to downgrade dangerous commands to execute silently.

Which CommandClass is assigned to unknown or unrecognized commands?

The classifier implements a fail-closed strategy: any command that cannot be parsed or contains unrecognized shell constructs is automatically classified as Write. This conservative default ensures that obfuscated commands, novel syntax, or edge-case shell features cannot slip past security controls under the assumption they are safe.

Where is the permission model implemented in the OpenHuman codebase?

The core logic resides in two primary locations: src/openhuman/security/policy/command_checks.rs (line 126) contains the classify_command implementation and CommandClass definitions, while src/openhuman/security/policy/policy_command.rs (line 214) houses the gate_decision function and tier-based enforcement logic. Usage examples appear in src/openhuman/tools/impl/system/shell.rs and src/openhuman/runtime/node/ops.rs, with comprehensive test coverage in src/openhuman/security/policy/policy_tests.rs and src/openhuman/security/policy/proptest_tests.rs.

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 →