How OpenHuman Implements Agent Security Using the action_dir/workspace_dir Path Model

OpenHuman isolates every agent tool execution in a sandboxed filesystem view that separates untrusted operations (action_dir) from internal core state (workspace_dir), enforcing strict path validation at the policy layer to prevent directory traversal attacks.

The tinyhumansai/openhuman repository implements a defense-in-depth security architecture that prevents agents from accessing sensitive internal data. By segregating the filesystem into a tool-visible sandbox and a core-private workspace, the framework ensures that shell commands, Python scripts, and npm operations can only modify files within designated boundaries. This design creates an immutable security perimeter that is cloned and specialized for each tool invocation.

The Dual-Directory Security Architecture

OpenHuman maintains two distinct filesystem roots that serve fundamentally different purposes. The separation is absolute: one directory is exposed to agent tools, while the other remains entirely internal to the core runtime.

  • action_dir – The read/write sandbox for all agent tools. This path serves as the current working directory (cwd) for ShellTool, NodeExec, PythonExec, and similar executors. All relative paths are resolved against this root, and all file operations are validated to ensure they remain within this boundary.

  • workspace_dir – The internal state directory used exclusively by the core for persistence layers such as tinyhuman::memory, web3::x402, and web3::wallet. This path is never transmitted to any tool or agent.

This model ensures that agents can read and write temporary files, build artifacts, and execution outputs without risking corruption of the core's memory stores, ledger files, or configuration snapshots.

SecurityPolicy Construction and Immutable Configuration

Security boundaries are established at initialization through the SecurityPolicy struct defined in src/openhuman/security/policy/types.rs (lines 232‑256). The policy is constructed via SecurityPolicy::from_config(), which reads user-provided settings from Config (located in src/openhuman/config/schema/types.rs).

use openhuman::security::policy::SecurityPolicy;
use openhuman::config::Config;

// Load configuration containing action_dir and workspace_dir paths
let cfg = Config::load_or_init()?;

// Build immutable security policy
let policy = SecurityPolicy::from_config(&cfg)?;

The resulting policy object is immutable and contains both PathBuf fields. If the user does not specify an explicit action_dir, the core automatically generates a temporary directory under the OS temp area, ensuring the sandbox always exists.

Runtime Path Validation and Enforcement

Before any filesystem operation executes, the policy validates the target path against the allowed sandbox. The enforcement logic resides in src/openhuman/security/policy/path_checks.rs, which implements functions such as is_path_allowed() and is_path_write_allowed().

These functions use prefix checks to ensure that target.starts_with(policy.action_dir) returns true for every operation. When a tool attempts to write outside its sandbox—such as to /etc/passwd—the check returns an error:

let outside = Path::new("/etc/passwd");
if !policy.is_path_write_allowed(outside) {
    return Err(SecurityError::WriteRejected {
        path: outside.to_path_buf(),
        action_dir: policy.action_dir.clone(),
    });
}

The specific error message "write rejected: forbidden path outside action_dir" is defined in src/openhuman/tools/status/ops.rs at line 368, providing auditable rejection details that include both the offending path and the sandbox root.

Tool Integration and Per-Profile Isolation

Each tool execution receives a cloned SecurityPolicy with a potentially overridden action_dir, enabling per-profile isolation. The cloning logic in src/openhuman/security/policy/enforcement.rs (lines 197‑207) comments explicitly that this mechanism "reports the action sandbox path, never workspace_dir".

When the agent runtime invokes a tool, the core calls security_for_tool_context(), which clones the base policy and sets a fresh sandbox directory specific to that profile:

// Clone base policy and override with profile-specific sandbox
let tool_policy = base_policy
    .clone()
    .with_action_dir(tempdir.path().join("sandbox"));

Tools receive this cloned policy and use policy.action_dir as their working directory. For example, in src/openhuman/tools/impl/system/shell.rs (lines 864‑879), the ShellTool::run_with_security method extracts the sandbox path:

// Inside ShellTool::run_with_security
let cwd = self.security.action_dir.clone(); // sandbox root
let mut cmd = self.runtime.build_shell_command(command, &cwd);

Similar resolution logic appears in npm_exec.rs and python_exec.rs, where all paths are resolved against the cloned action_dir via resolve_cwd(&policy.action_dir, …).

Security Guarantees

Guarantee Implementation Mechanism
No direct access to internal state workspace_dir is never passed to any tool; only core modules use it for persistence.
All tool file operations are sandboxed Path checks enforce that every target path starts with policy.action_dir.
Per-profile isolation Each agent profile receives a cloned policy with a dedicated sandbox directory.
Fail-closed defaults If a tool descriptor lacks a cwd specification, the runtime defaults to action_dir; explicit cwd values are still validated against the sandbox.
Auditable errors Rejection messages include the attempted path and the sandbox root, enabling precise debugging.

Practical Implementation Examples

Creating a SecurityPolicy from Configuration

use openhuman::security::policy::SecurityPolicy;
use openhuman::config::Config;

// Initialize from user config
let cfg = Config::load_or_init()?;
let policy = SecurityPolicy::from_config(&cfg)?;

// policy now contains action_dir and workspace_dir

Cloning for Tool-Specific Sandboxing

use openhuman::security::policy::SecurityPolicy;
use std::path::PathBuf;

let base_policy = SecurityPolicy::from_config(&cfg)?;
let profile_sandbox = PathBuf::from("/tmp/agent_123");

// Create isolated policy for this specific execution
let tool_policy = base_policy
    .clone()
    .with_action_dir(profile_sandbox);

Shell Tool Execution Within Sandbox

// As implemented in src/openhuman/tools/impl/system/shell.rs
fn run_with_security(&self, command: &str) -> Result<Output> {
    // Extract the sandbox root—never the workspace_dir
    let cwd = self.security.action_dir.clone();
    let mut cmd = self.runtime.build_shell_command(command, &cwd);
    cmd.output()
}

Summary

Frequently Asked Questions

What happens if a tool tries to write outside its action_dir?

The policy's path validation rejects the operation with a SecurityError::WriteRejected error containing the message "write rejected: forbidden path outside action_dir". This error includes both the attempted path and the sandbox root for debugging, as implemented in src/openhuman/tools/status/ops.rs.

Can agents access the workspace_dir through path traversal attacks?

No. The workspace_dir is never passed to any tool or included in the SecurityPolicy objects provided to tool runtimes. Even if an agent attempts a traversal sequence like ../../../, the is_path_allowed check in src/openhuman/security/policy/path_checks.rs resolves the canonical path and verifies it starts with action_dir, preventing escape.

How does OpenHuman handle multiple concurrent agent profiles?

Each profile receives its own cloned SecurityPolicy with a unique action_dir subdirectory. When security_for_tool_context() is called, it overrides the base policy's action_dir with a profile-specific path (lines 197‑207 in src/openhuman/security/policy/enforcement.rs), ensuring filesystem isolation between concurrent agents.

Is the action_dir persistent between sessions?

It depends on configuration. If the user explicitly defines action_dir in Config (src/openhuman/config/schema/types.rs), that path persists. If omitted, the core creates a temporary directory that is cleaned up after the session terminates, ensuring that sensitive build artifacts do not persist unintentionally.

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 →