Skip to content

LuaU Sandbox

codegen embeds the LuaU VM (Roblox’s typed Lua variant with a JIT compiler) as the rule execution environment. Each rule invocation gets a fresh VM instance with no shared state between calls.

APIDescription
json.encode(value)Serialize a Lua value to a JSON string
json.decode(string)Parse a JSON string to a Lua value
Standard LuaU librarytable, string, math, ipairs, pairs, tostring, error, pcall, etc.

That is the complete default environment.

CapabilityStatus
File system (io.open, os.execute)Blocked, no io or os library
NetworkBlocked by default, requires explicit HTTP permission in config
require / module loadingBlocked, no module system in the sandbox
Global state between invocationsImpossible, fresh VM per call
Inter-rule communicationImpossible, VMs are isolated
Process spawningBlocked
  • Typed: LuaU adds gradual typing, allowing rule authors to annotate their scripts for better IDE support.
  • Fast: The JIT compiler makes tight loops over large entity lists performant.
  • Sandboxable: The LuaU VM is designed to be embedded with a restricted environment, the default global table can be replaced entirely.
  • Auditable: LuaU is a small language. A non-expert can read and audit a rule script in minutes.

A rule that calls error() aborts processing for that entity only. The engine logs the error and continues with remaining entities. Use pcall for recoverable error paths:

return function(input)
local ok, node = pcall(json.decode, input)
if not ok or not node then
error("failed to decode input: " .. tostring(node))
end
-- ...
end

Validate defensively at the top of every rule. The AST schema can evolve, and future engine versions may pass additional fields or change optional field presence.

If your rule legitimately needs to fetch data from a remote service, add the allowlist to config:

permissions:
http:
allowlist:
- "schema-registry.internal.example.com"

The engine then injects http.get(url) -> string into the sandbox. Any URL not on the allowlist raises a runtime error.

Never add a domain to the HTTP allowlist in a rule you did not write and audit yourself. An allowlisted domain gives that rule read access to your AST data.

Key Takeaways
  • Each rule invocation gets a fresh LuaU VM, no shared state, no inter-rule communication.
  • Default environment: json.encode, json.decode, and standard Lua libraries only.
  • No io, os, require, or network access by default.
  • HTTP access requires an explicit domain allowlist in config.yaml.
  • error() in a rule aborts that entity only, the engine continues.