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,grepwithout 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
Readcommands - Supervised tiers may permit
Writeoperations after user confirmation - Full tiers can execute
NetworkandInstallcommands 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.rsparses shell syntax to assignRead,Write,Network,Install, orDestructivelabels, defaulting toWritefor unrecognized commands. - Gate decisions in
policy_command.rsapply 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.rslines 438-553) and property-based tests (proptest_tests.rslines 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →