# OpenHuman action_dir and workspace_dir Explained: How the Sandbox Enforces Path Boundaries

> Understand OpenHuman's action_dir and workspace_dir for agent sandboxing. Learn how path boundaries are enforced to secure operations and protect system files.

- Repository: [Tiny Humans/openhuman](https://github.com/tinyhumansai/openhuman)
- Tags: internals
- Published: 2026-09-01

---

**OpenHuman uses `action_dir` as the agent's isolated filesystem root for all read/write operations, while `workspace_dir` stores internal core state; the sandbox enforces boundaries by validating that all resolved paths start with `action_dir` and checking against a hard-coded denylist of system locations.**

OpenHuman separates agent-accessible storage from internal application data using two distinct directory configurations. Understanding how `action_dir` and `workspace_dir` function is critical for securing AI agent operations and preventing filesystem escapes. This guide examines the source code implementation in the `tinyhumansai/openhuman` repository to explain how the sandbox validates every filesystem access.

## What Are action_dir and workspace_dir in OpenHuman?

OpenHuman defines two filesystem roots that serve entirely different security purposes. The separation ensures that AI agents operate within a controlled sandbox while the core system maintains private state storage.

### action_dir: The Agent's Read/Write Sandbox

The **`action_dir`** represents the root directory where agents can freely read and write files. By default, this location is set to `~/OpenHuman/projects`, though it can be overridden using the `OPENHUMAN_ACTION_DIR` environment variable.

According to the source in [[`src/openhuman/config/schema/types_part_01.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/config/schema/types_part_01.rs)](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/config/schema/types_part_01.rs#L83-L92), the effective value is determined by `Config::action_dir()`, which first checks for an override stored in `Config.action_dir_override` (set via the Settings UI) before falling back to the default path. All tool executions resolve their current working directory (CWD) against this path, ensuring agents cannot access files outside this boundary.

### workspace_dir: Isolated Internal State

The **`workspace_dir`** serves as the internal state directory for core system operations. Located at `~/.openhuman/users/<id>/workspace` by default, this directory stores sensitive resources such as SQLite databases, authentication tokens, and internal logs.

As implemented in [[`src/openhuman/web_chat/session.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/web_chat/session.rs)](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/web_chat/session.rs#L165), this path is injected into the core at startup and is **never** exposed to agents or tools. The sandbox architecture explicitly prevents any tool from reading or writing to this location, maintaining strict isolation between agent operations and system internals.

## How the OpenHuman Sandbox Enforces Path Boundaries

The sandbox implements a multi-layered validation system that checks every filesystem access before execution. This ensures agents remain confined to their designated `action_dir` while protecting critical system locations.

### Path Resolution via ToolRunContext

All built-in tools interact with the filesystem through `ToolRunContext`, which carries a `PathPolicy` containing the authorized `action_dir`. When a tool needs to resolve a filesystem path, it calls `effective_action_dir_for_context()`, implemented in [[`src/openhuman/tools/impl/system/shell.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/tools/impl/system/shell.rs)](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/tools/impl/system/shell.rs#L154-L166):

```rust
fn effective_action_dir_for_context(&self, context: Option<&dyn ToolRunContext>) -> PathBuf {
    // Directly use the security-policy-provided action_dir.
    self.security.action_dir.clone()
}

```

This method returns a clone of the security policy's `action_dir`, which serves as the root for all subsequent path resolution operations.

### The Boundary Validation Check

Before executing any file operation, the sandbox validates that the resolved absolute path remains within the `action_dir` boundary:

```rust
let resolved = workspace_dir.join(&rel_path);
if !resolved.starts_with(workspace_dir) {
    return Err(ToolError::ForbiddenPath);
}

```

If the resolved path fails the `starts_with` validation against `action_dir`, the tool immediately returns `ToolError::ForbiddenPath`. The core translates this error into an approval-gate denial, preventing the operation from executing.

### The Security Denylist Layer

Beyond the `action_dir` boundary check, the sandbox applies a hard-coded denylist through `security::is_always_forbidden()`. This function blocks access to known-dangerous locations such as `/etc`, the user's home directory, and the core's internal `workspace_dir`.

The denylist is applied **after** the `action_dir` validation, ensuring that even if a path technically resolves within `action_dir`, it will still be rejected if it matches a forbidden pattern. This defense-in-depth strategy prevents agents from exploiting symbolic links or edge-case path traversals to access sensitive system locations.

## Implementation Examples

The following examples demonstrate how tools interact with the sandbox boundaries in practice. As shown in [[`src/openhuman/tools/ops.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/tools/ops.rs)](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/tools/ops.rs), tools are registered with the sandbox by passing `Config.action_dir` to ensure proper isolation:

```rust
// Example: creating a tool that writes a file under the agent's sandbox.
let cfg = Config::load_or_init();               // loads action_dir & workspace_dir
let tool = FileWriteTool::new(cfg.action_dir.clone());

tool.run("notes.txt", b"Hello world!")?;       // succeeds – path resolves inside action_dir

// Attempt to write outside the sandbox (will error):
let illegal_path = cfg.action_dir.parent().unwrap().join("outside.txt");
tool.run(illegal_path, b"Oops!")?;             // → ToolError::ForbiddenPath

```

When implementing custom tools, developers must resolve paths through the provided context to maintain sandbox guarantees:

```rust
// Inspect the resolved CWD inside a tool implementation:
fn run(&self, cmd: &str, ctx: Option<&dyn ToolRunContext>) -> Result<(), ToolError> {
    let cwd = self.effective_action_dir_for_context(ctx);
    // cwd is guaranteed to be a sub-directory of `action_dir`.
    self.runtime.build_shell_command(cmd, &cwd)?;
    // ...
}

```

## Summary

- **Dual-directory architecture**: OpenHuman separates `action_dir` (agent workspace) from `workspace_dir` (internal core state) to prevent data leakage and unauthorized access.
- **Configurable agent root**: The `action_dir` defaults to `~/OpenHuman/projects` but can be overridden via environment variables or the Settings UI through `Config::action_dir()`.
- **Strict path validation**: Every filesystem operation validates that resolved paths `start_with` the `action_dir`, returning `ToolError::ForbiddenPath` for violations.
- **Defense in depth**: A secondary denylist (`security::is_always_forbidden`) blocks access to system-critical paths even if they somehow pass the primary boundary check.
- **Source verification**: The test suite in [[`shell_tests_part_01_tests.rs`](https://github.com/tinyhumansai/openhuman/blob/main/shell_tests_part_01_tests.rs)](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/tools/impl/system/shell_tests_part_01_tests.rs) confirms that tools route CWD through `action_dir` and never fall back to `workspace_dir`.

## Frequently Asked Questions

### What is the default location for action_dir in OpenHuman?

By default, OpenHuman sets `action_dir` to `~/OpenHuman/projects`. This location can be changed by setting the `OPENHUMAN_ACTION_DIR` environment variable before startup, or by using the Settings UI which writes to `Config.action_dir_override` and takes precedence over the environment default.

### How does OpenHuman prevent agents from accessing workspace_dir?

The sandbox enforces this separation through compile-time architecture and runtime validation. The `workspace_dir` is never passed to tool contexts or `PathPolicy` structures, and the `effective_action_dir_for_context()` method exclusively returns the `action_dir`. Additionally, the `security::is_always_forbidden` denylist explicitly blocks the `workspace_dir` path, ensuring any attempted access results in a `ToolError::ForbiddenPath` error.

### Can the action_dir location be customized after installation?

Yes. Users can override the `action_dir` location through the Settings UI, which persists the path to `Config.action_dir_override`. When the core loads configuration from [[`src/openhuman/config/schema/types_part_01.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/config/schema/types_part_01.rs)](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/config/schema/types_part_01.rs#L83-L92), the `Config::action_dir()` method checks for this override before falling back to the default or environment variable setting.

### What happens when an agent tries to write a file outside action_dir?

When a tool attempts to write to a path that does not start with the `action_dir` base path, the sandbox's boundary validation logic detects the violation and returns `ToolError::ForbiddenPath`. This error propagates through the execution pipeline and typically results in an approval-gate denial presented to the user, preventing the unauthorized filesystem access without crashing the agent session.