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.fsAPIs instead of native Node modules. - Network mediation through the whitelisted
openhuman.httpmodule 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_buswithSend + 'staticconstraints 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →