Skip to content

The sandboxing model

What neoc strips from Lua, why, and what replaces it.

Why sandbox

Luau ships modules exposing unrestricted OS access — io.open, os.execute, package.loadlib, debug library. Embedding without stripping inherits whatever scripts choose to do.

neoc takes the opposite default. Unsafe standard library stripped at engine construction. Every capability reintroduced through explicit modules under std:*, lib:*, vnd:*. Scripts cannot reach unregistered capabilities.

What is stripped

SurfaceReason
io.*Replaced by std:fs and std:io
os.*Replaced by std:thread, std:net, worker model
package.*require replaced; see module system
debug.*Reflection unsafe in multi-tenant runtime
loadfile, dofileReplaced by argument-driven model

Available unchanged: string, table, math, coroutine, bit32, utf8, buffer, vector, print family.

What replaces it

std:* : Rust standard library mirrors. Filesystem, networking, threading, collections, I/O. See std:*.

lib:* : Gap-fillers and project-native modules. JSON handles, base64, test runner, VCR fixtures. See lib:*.

vnd:* : Vendored Rust crates. Module name matches upstream crate name. See vnd:*.

Note: Scripts cannot invent access to stripped capabilities. No FFI, no shared library loading, no subprocess execution. Only the host extends the capability surface by registering modules at construction.

The worker model

Scripts execute in independent Luau VMs called workers. Each worker: self-contained interpreter with private globals, locals, upvalues, userdata.

Cross-worker state follows Sendable protocol via shared table in thread.spawn. Sendable types: Map, Counter from std:collections, TCP listener from std:net. Non-Sendable types (json handles, json.null) rejected at spawn time. Plain Lua values pass via args field.

See also