# How the OpenHuman Skills Runtime Sandbox Executes QuickJS Skill Code

> Discover how OpenHuman's QuickJS runtime sandbox securely executes skill code using process isolation and custom host bindings. Learn about the rquickjs Rust crate.

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

---

**OpenHuman uses an isolated QuickJS sandbox built on the `rquickjs` Rust crate to compile, load, and execute user-contributed skill code with process-level isolation and curated host bindings.**

The `tinyhumansai/openhuman` repository implements a secure skills runtime that leverages QuickJS to run user-contributed TypeScript bundles in a controlled environment. This system creates distinct execution contexts for each skill, ensuring that untrusted code cannot access host memory or interfere with other running skills while still providing controlled access to logging and RPC capabilities.

## Skills Architecture Overview

OpenHuman separates skill packages from the core application by storing them in the **`tinyhumansai/openhuman-skills`** registry repository. The core application pulls compiled TypeScript bundles from this external registry at runtime rather than embedding them directly in the binary.

According to the **Skills registry** section in [[`AGENTS.md`](https://github.com/tinyhumansai/openhuman/blob/main/AGENTS.md)](https://github.com/tinyhumansai/openhuman/blob/main/AGENTS.md), this separation allows skills to be developed, versioned, and distributed independently from the runtime engine. The core application discovers available skills through the registry and fetches the compiled JavaScript bundles on demand.

## QuickJS Sandbox Implementation

The sandbox implementation resides in `src/openhuman/skills/` and utilizes the **`rquickjs`** crate to create secure JavaScript execution environments.

### Creating the Isolated Runtime

The primary logic for initializing the sandbox lives in **[`src/openhuman/skills/ops.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/skills/ops.rs)**. When a skill is loaded, the runtime calls `SkillRuntime::new()`, which performs the following:

1. Creates a new **`rquickjs::Runtime`** instance dedicated to that specific skill
2. Spins up a **`Context`** within that runtime for code evaluation
3. Compiles the JavaScript bundle source into QuickJS bytecode

Each skill receives its own `Runtime` + `Context` pair, guaranteeing memory isolation. A skill cannot access global state from the host process or other skills, as the QuickJS engine runs within a sandboxed heap distinct from the host Rust application.

### Host Bindings and Security Model

The sandbox exposes only a curated subset of host functions to the JavaScript environment. According to the implementation in [`src/openhuman/skills/ops.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/skills/ops.rs), the runtime registers specific bindings for operations like logging and RPC access while explicitly excluding Node.js, Electron, or browser APIs.

This restricted binding model ensures that skill code operates within a minimal capability set. The JavaScript entry point ([`main.js`](https://github.com/tinyhumansai/openhuman/blob/main/main.js)) executes without access to the file system, network (unless explicitly proxied through the RPC interface), or other sensitive host resources.

## Execution Flow

The lifecycle of a skill spans discovery, initialization, execution, and cleanup across multiple source files.

### Skill Discovery and Loading

In **[`src/openhuman/skills/ops_discover.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/skills/ops_discover.rs)**, the core implements the discovery logic:

1. Queries the skills registry for available bundles
2. Downloads the compiled TypeScript/JavaScript bundle to local storage
3. Invokes `SkillRuntime::new()` to instantiate the QuickJS context
4. Evaluates the skill's [`main.js`](https://github.com/tinyhumansai/openhuman/blob/main/main.js) entry point within the sandbox

Once initialized, the skill registers callback handlers such as `onMessage` or `onSchedule` with the runtime. The core later invokes these registered functions via the same QuickJS context, marshaling arguments from Rust into JavaScript values and returning results back to the host.

### Cleanup and Lifecycle Management

The runtime ensures safe resource destruction through the skill cleanup subscriber implemented in **[`src/openhuman/skills/bus.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/skills/bus.rs)**. While currently a no-op placeholder (see lines 1-17), this subscriber preserves the contract that cleanup is side-effect-free and will eventually handle the explicit dropping of the `Runtime` when a skill is unloaded.

When the `SkillRuntime` object goes out of scope in Rust, the underlying QuickJS `Runtime` and its associated `Context` are automatically dropped, freeing all memory associated with the skill's execution environment.

## Code Example: Loading and Running a Skill Bundle

The following Rust code demonstrates how to instantiate the skills runtime sandbox and execute skill code:

```rust
use openhuman::skills::{SkillRuntime, SkillBundle};
use std::path::Path;

fn load_and_run_skill(bundle_path: &Path) -> anyhow::Result<()> {
    // 1. Read the bundled JS source (generated by the external skills repo)
    let bundle = SkillBundle::from_path(bundle_path)?;

    // 2. Create an isolated QuickJS runtime for this skill
    let mut runtime = SkillRuntime::new(&bundle)?;

    // 3. Initialise the skill (executes its main.js)
    runtime.execute_entrypoint()?;

    // 4. Call a skill-exposed function, e.g. `runTask`
    let result: String = runtime.call("runTask", ("hello",))?;
    println!("Skill returned: {result}");

    // Runtime is automatically cleaned up when it goes out of scope.
    Ok(())
}

```

This pattern ensures that each skill operates within its own isolated QuickJS environment, with the `SkillBundle` providing the code and `SkillRuntime` managing the sandbox lifecycle.

## Summary

- **Process isolation**: Each skill runs in a dedicated `rquickjs::Runtime` with its own `Context`, preventing cross-skill memory access
- **Curated bindings**: The sandbox in [`src/openhuman/skills/ops.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/skills/ops.rs) exposes only whitelisted host functions (logging, RPC) while blocking system access
- **Registry separation**: Skill code lives in `tinyhumansai/openhuman-skills` and is fetched at runtime by the discovery logic in [`ops_discover.rs`](https://github.com/tinyhumansai/openhuman/blob/main/ops_discover.rs)
- **Safe cleanup**: The [`bus.rs`](https://github.com/tinyhumansai/openhuman/blob/main/bus.rs) subscriber ensures that QuickJS runtimes are properly dropped when skills unload, with placeholder infrastructure for future event-bus integration

## Frequently Asked Questions

### How does OpenHuman prevent malicious skill code from accessing the file system?

The **QuickJS sandbox** explicitly does not expose Node.js or standard library APIs to the JavaScript environment. The runtime in [`src/openhuman/skills/ops.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/skills/ops.rs) only registers specific host bindings for logging and RPC communication, meaning JavaScript code has no native mechanism to read files or open network sockets unless the host application explicitly provides such capabilities through the restricted binding interface.

### Can skills communicate with each other directly within the QuickJS sandbox?

No. Each skill receives its own isolated `rquickjs::Runtime` instance created by `SkillRuntime::new()` in [`ops.rs`](https://github.com/tinyhumansai/openhuman/blob/main/ops.rs). These runtimes cannot access each other's memory or global objects. If skills need to communicate, they must do so through the host application's mediated channels, such as the RPC bindings registered during sandbox initialization.

### What happens if a skill enters an infinite loop or hangs during execution?

While the current implementation creates isolated QuickJS contexts, specific execution timeouts or CPU throttling mechanisms would need to be implemented at the host level. The `rquickjs` crate supports interrupt handlers that the OpenHuman runtime could leverage to terminate runaway scripts, though the specific timeout policy would be configured in the `SkillRuntime` initialization logic within [`src/openhuman/skills/ops.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/skills/ops.rs).

### Where is the actual skill code stored if not in the main repository?

Skill packages reside in the separate **`tinyhumansai/openhuman-skills`** repository. As documented in [`AGENTS.md`](https://github.com/tinyhumansai/openhuman/blob/main/AGENTS.md), the core application treats this as an external registry, downloading compiled TypeScript bundles at runtime when the discovery process in [`src/openhuman/skills/ops_discover.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/skills/ops_discover.rs) identifies a skill that needs to be loaded.