How OpenHuman Classifies Commands Using gate_decision in SecurityPolicy
OpenHuman determines whether a tool command should be allowed, prompted, or blocked through a deterministic two-step process implemented in the SecurityPolicy type, where classify_command assigns risk classes and gate_decision maps them to final execution decisions based on autonomy tiers.
OpenHuman is an open-source framework designed to safely execute AI-generated shell commands by enforcing strict security boundaries. The SecurityPolicy type provides the core classification engine that evaluates every command before execution. This article examines how the gate_decision method classifies commands by combining static analysis with configurable autonomy levels.
The Two-Step Classification Process
The command classification system operates as a fail-closed pipeline that first analyzes command structure, then applies policy rules to determine the final gate decision.
Step 1: Structural Classification with classify_command
The SecurityPolicy::classify_command method performs static analysis on raw command strings to determine their inherent risk level. Located in src/openhuman/security/policy/command_checks.rs, this function parses the input by splitting on unquoted separators (;, |, &&, ||, newlines) and stripping leading environment-variable assignments.
Each command segment is evaluated against five risk classes ordered by severity:
- Read – File read operations, directory listings
- Write – File modifications, output redirections
- Network – HTTP requests, socket operations
- Install – Package managers, dependency installation
- Destructive – Deletions, system modifications,
rm -rfpatterns
The system adopts the highest-risk class observed across all segments. Additionally, file redirections (> or >>) or the presence of tee automatically upgrade the classification to at least Write, as these operations modify the filesystem regardless of the base command's nature. This redirection uplift logic appears at lines 40-46 of the same file.
Step 2: Policy Decision with gate_decision
Once the CommandClass is determined, SecurityPolicy::gate_decision maps it to a concrete GateDecision (Allow, Prompt, or Block) based on the current autonomy tier:
| Autonomy Tier | Read | Write | Network/Install/Destructive |
|---|---|---|---|
| ReadOnly | Allow | Block | Block |
| Supervised | Allow | Prompt | Prompt |
| Full | Allow | Allow | Prompt |
The decision matrix is implemented as a match expression in gate_decision, ensuring deterministic evaluation. Under ReadOnly mode, only Read commands execute directly; any write operation or network call is immediately blocked. The Supervised tier allows reads while requiring user confirmation for higher-risk operations. Full autonomy permits read and write operations without prompting, but still requires approval for network, install, and destructive commands.
Code Implementation Examples
The following Rust code demonstrates the end-to-end flow from command string to final decision:
use openhuman::security::policy::{SecurityPolicy, AutonomyLevel, GateDecision};
// Configure a supervised policy tier
let policy = SecurityPolicy::default()
.with_autonomy(AutonomyLevel::Supervised);
// Classify a compound command with redirection
let cmd = "git commit -m 'fix' && echo done > out.txt";
let class = policy.classify_command(cmd);
// class == CommandClass::Write (forced by redirection)
// Determine execution path
match policy.gate_decision(class) {
GateDecision::Allow => println!("Execute immediately"),
GateDecision::Prompt => println!("Request user approval"),
GateDecision::Block => println!("Reject execution"),
}
Read-only tier behavior:
let read_only = SecurityPolicy::default()
.with_autonomy(AutonomyLevel::ReadOnly);
// Safe read operation
let class = read_only.classify_command("ls -l");
assert_eq!(read_only.gate_decision(class), GateDecision::Allow);
// Destructive command blocked
let class = read_only.classify_command("rm -rf /");
assert_eq!(read_only.gate_decision(class), GateDecision::Block);
Full autonomy tier behavior:
let full = SecurityPolicy::default()
.with_autonomy(AutonomyLevel::Full);
// Local write allowed without prompt
let class = full.classify_command("npm install");
assert_eq!(full.gate_decision(class), GateDecision::Allow);
// Network still requires confirmation
let class = full.classify_command("curl http://example.com");
assert_eq!(full.gate_decision(class), GateDecision::Prompt);
Security Guarantees and Fail-Closed Design
The classification system provides a deterministic floor that guarantees runtime safety. The gate_decision output represents the minimum restriction level for a command; the LLM-declared intent can only raise the classification higher, never lower it. This architecture ensures that even if the AI model misclassifies a command's intent, the static analysis in classify_command prevents unauthorized execution.
The implementation references several key source locations:
- Classification logic:
src/openhuman/security/policy/command_checks.rs(lines 26-48) - Redirection handling:
src/openhuman/security/policy/command_checks.rs(lines 40-46) - Decision matrix:
src/openhuman/security/policy/command_checks.rs(lines 58-73) - Type definitions:
src/openhuman/security/policy/types.rs
Summary
- Two-step pipeline:
classify_commandextracts risk classes from shell syntax, thengate_decisionapplies tier-based policy rules. - Automatic write detection: Output redirections (
>,>>) andteecommands forceCommandClass::Writeregardless of base executable. - Three autonomy tiers:
ReadOnlyblocks all non-reads,Supervisedprompts for elevated operations, andFullallows writes but prompts for network/destructive actions. - Fail-closed guarantee: The static analysis sets a security floor that the LLM cannot override, preventing permission escalation.
- Deterministic evaluation: All classification logic resides in
src/openhuman/security/policy/command_checks.rswith no external dependencies for command evaluation.
Frequently Asked Questions
What autonomy tiers does OpenHuman support?
OpenHuman implements three autonomy levels defined in src/openhuman/security/policy/types.rs: ReadOnly, which permits only read operations; Supervised, which allows reads automatically but prompts for writes, network, install, and destructive commands; and Full, which permits reads and writes without user interaction but maintains prompts for network, installation, and destructive operations to prevent accidental system damage.
How does file redirection affect command classification?
The classify_command method detects output redirection operators (> and >>) and the tee utility during parsing. When present in any command segment, the classification automatically upgrades to at least CommandClass::Write because these operations modify the filesystem. This redirection uplift occurs at lines 40-46 of src/openhuman/security/policy/command_checks.rs, ensuring that commands like echo "data" > file.txt are treated with write-level permissions even though echo is inherently read-only.
Can the LLM override the gate_decision classification?
No. The gate_decision system operates as a deterministic floor that the LLM can only make more restrictive, never less. If the static analysis in classify_command determines a command is Destructive, the AI cannot downgrade it to Read or force an Allow decision. The LLM may provide additional context that raises the classification (for example, flagging a write operation as potentially destructive), but the final GateDecision always respects the maximum of the static analysis and the LLM's declared intent.
Where is the gate_decision logic implemented?
The core decision matrix is implemented in the gate_decision method within src/openhuman/security/policy/command_checks.rs at lines 58-73. This method takes a CommandClass enum and returns a GateDecision based on the policy's current AutonomyLevel setting. The method uses a pattern match to map each risk class to its appropriate decision for the configured tier, providing the final authorization check before command execution.
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 →