QuickJS Skills Sandbox Security Boundaries in OpenHuman

OpenHuman enforces strict security boundaries in its QuickJS skills sandbox through memory limits, CPU step counters, filesystem restrictions, and mediated network APIs, ensuring user JavaScript cannot exhaust host resources or access unauthorized system functions.

The OpenHuman project implements hardened QuickJS skills sandbox security boundaries using the Rust rquickjs crate to create isolated execution environments for user-contributed JavaScript. This architecture strictly controls resource consumption and API exposure, ensuring untrusted code cannot compromise the host system or access unauthorized resources.

Memory and Execution Limits

Heap Size Constraints

Each skill operates within a dedicated QuickJS runtime instance configured with strict memory boundaries. In src/openhuman/skills/ops_create.rs at lines 42-49, the system initializes the runtime with a 32 MiB heap limit using set_memory_limit(32 * 1024 * 1024), causing immediate termination if the JavaScript heap exceeds this allocation.

Instruction Step Limits

To prevent infinite loops and CPU exhaustion, OpenHuman implements a per-instruction step counter enforced by the QuickJS engine. The code at src/openhuman/skills/ops_create.rs lines 51-56 configures set_step_limit(10_000_000), which throws an ExecutionError and terminates the script once the JavaScript execution exceeds ten million steps.

// src/openhuman/skills/ops_create.rs
let runtime = rquickjs::Runtime::new();
runtime.set_memory_limit(32 * 1024 * 1024);          // 32 MiB
runtime.set_step_limit(10_000_000);                // 10M JS steps
let ctx = rquickjs::Context::full(&runtime)?;

Filesystem and Network Sanitization

Restricted Filesystem Access

The sandbox deliberately excludes Node-style filesystem APIs, eliminating direct disk access from user code. Instead, skills interact with files exclusively through the openhuman.fs host module defined in src/openhuman/skills/bus.rs at lines 12-20, which enforces workspace isolation and read-only or write-only modes based on the skill's permissions.

Mediated HTTP Requests

Native fetch and XMLHttpRequest objects are unavailable in the sandbox environment. Network access is routed through the openhuman.http API implemented in src/openhuman/skills/ops.rs at lines 78-85, which validates URLs against a whitelist, caps request sizes, and enforces timeouts before executing any outbound connection.

// Skill script executing inside the sandbox
log("Skill started");

// URL is validated against whitelist by host
const resp = await openhuman.http.get("https://api.example.com/data");

// Filesystem access restricted to skill's private workspace
await openhuman.fs.writeFile("output.json", JSON.stringify(resp));

Runtime Environment Lockdown

Controlled Host API Surface

OpenHuman exposes only explicitly registered functions to the JavaScript global scope. The register_host function in src/openhuman/skills/mod.rs at lines 18-30 injects only vetted utilities like log, http, fs, and env, while stripping all other globals before script execution begins.

// src/openhuman/skills/mod.rs
fn register_host(ctx: &rquickjs::Context) -> Result<()> {
    ctx.globals().set("log", rquickjs::function(|msg: String| {
        tracing::debug!("[skill] {}", msg);
    }))?;
    // http and fs are similarly registered with validation
    Ok(())
}

No Native Extension Loading

QuickJS does not support loading native shared libraries or external binaries. This architectural constraint inherent to the rquickjs implementation prevents skills from invoking unsafe OS-level code or loading arbitrary dynamic libraries, effectively blocking escape attempts through native extensions.

Fresh Runtime Per Execution

Each skill invocation starts from a deterministic, fresh runtime with no persistent state from previous executions. As implemented in src/openhuman/skills/ops_parse.rs at lines 23-31, state persistence is only possible through the sandboxed store API, preventing data leakage across skill boundaries.

Safe Inter-Process Communication

Typed Request Bus Architecture

Communication between the sandbox and OpenHuman's core uses the typed native request bus (event_bus::request_native_global) defined in src/core/event_bus/native_request.rs at lines 38-46. This bus transmits only Send + 'static data types, eliminating risks of pointer sharing or reference leakage between the JavaScript runtime and the host Rust process.

Summary

OpenHuman's QuickJS skills sandbox implements eight critical security boundaries:

  • Memory isolation via 32 MiB heap limits per runtime configured in ops_create.rs.
  • CPU throttling through 10-million-step execution caps enforced by the QuickJS engine.
  • Filesystem restrictions requiring use of vetted openhuman.fs APIs instead of native Node modules.
  • Network mediation through the whitelisted openhuman.http module with URL validation.
  • API minimization exposing only functions registered in openhuman::skills::host.
  • Native code blocking by QuickJS's inability to load shared libraries or external binaries.
  • Runtime freshness ensuring no state persistence between runs except via the sandboxed store.
  • Type-safe IPC using the event_bus with Send + 'static constraints to prevent pointer leakage.

Frequently Asked Questions

How does OpenHuman prevent memory exhaustion from malicious JavaScript skills?

OpenHuman configures each QuickJS runtime with a fixed heap size limit of 32 MiB in src/openhuman/skills/ops_create.rs. If a skill attempts to allocate memory beyond this boundary, the QuickJS engine immediately aborts execution, preventing the script from consuming excessive host resources.

Can skills access the filesystem or network directly?

No. The sandbox removes native Node.js filesystem and network APIs. Skills must use the host-provided openhuman.fs and openhuman.http modules, which enforce workspace isolation and URL whitelisting respectively, as implemented in src/openhuman/skills/bus.rs and src/openhuman/skills/ops.rs.

What happens if a skill enters an infinite loop?

The sandbox counts JavaScript execution steps and enforces a hard limit of 10 million steps per skill invocation. When exceeded, QuickJS throws an ExecutionError and terminates the script, effectively neutralizing infinite loops or excessive CPU consumption attempts.

Is it possible for skills to load native code or binary extensions?

No. QuickJS does not support loading native shared libraries or external binaries. This design constraint, inherent to the rquickjs implementation, prevents skills from escaping the sandbox through native code execution or invoking unsafe system calls.

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 →