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

> Discover how OpenHuman secures agent execution using the action dir workspace dir path model. Learn about sandboxed filesystems and strict path validation to prevent attacks.

- Repository: [Tiny Humans/openhuman](https://github.com/tinyhumansai/openhuman)
- Tags: security-best-practices
- Published: 2026-08-27

---

**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`](https://github.com/tinyhumansai/openhuman/blob/main/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`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/config/schema/types.rs)).

```rust
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`](https://github.com/tinyhumansai/openhuman/blob/main/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:

```rust
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`](https://github.com/tinyhumansai/openhuman/blob/main/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`](https://github.com/tinyhumansai/openhuman/blob/main/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:

```rust
// 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`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/tools/impl/system/shell.rs) (lines 864‑879), the `ShellTool::run_with_security` method extracts the sandbox path:

```rust
// 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`](https://github.com/tinyhumansai/openhuman/blob/main/npm_exec.rs) and [`python_exec.rs`](https://github.com/tinyhumansai/openhuman/blob/main/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

```rust
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

```rust
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

```rust
// 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

- **Dual-directory model**: `action_dir` provides the sandbox for tools, while `workspace_dir` remains strictly internal to the core.
- **Immutable policies**: `SecurityPolicy::from_config()` in [`src/openhuman/security/policy/types.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/security/policy/types.rs) establishes the security perimeter at startup.
- **Strict path validation**: Functions in [`src/openhuman/security/policy/path_checks.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/security/policy/path_checks.rs) enforce that all file operations remain within `action_dir`.
- **Per-execution isolation**: The cloning mechanism in [`src/openhuman/security/policy/enforcement.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/security/policy/enforcement.rs) ensures each tool receives a sandboxed view without access to `workspace_dir`.
- **Auditable failures**: Attempts to escape the sandbox trigger specific errors from [`src/openhuman/tools/status/ops.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/tools/status/ops.rs) that identify the policy violation.

## 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`](https://github.com/tinyhumansai/openhuman/blob/main/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`](https://github.com/tinyhumansai/openhuman/blob/main/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`](https://github.com/tinyhumansai/openhuman/blob/main/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`](https://github.com/tinyhumansai/openhuman/blob/main/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.