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.
What rules have access to
Section titled “What rules have access to”| API | Description |
|---|---|
json.encode(value) | Serialize a Lua value to a JSON string |
json.decode(string) | Parse a JSON string to a Lua value |
| Standard LuaU library | table, string, math, ipairs, pairs, tostring, error, pcall, etc. |
That is the complete default environment.
What rules cannot access
Section titled “What rules cannot access”| Capability | Status |
|---|---|
File system (io.open, os.execute) | Blocked, no io or os library |
| Network | Blocked by default, requires explicit HTTP permission in config |
require / module loading | Blocked, no module system in the sandbox |
| Global state between invocations | Impossible, fresh VM per call |
| Inter-rule communication | Impossible, VMs are isolated |
| Process spawning | Blocked |
Why LuaU specifically
Section titled “Why LuaU specifically”- 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.
Writing safe rules
Section titled “Writing safe rules”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 -- ...endValidate 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.
HTTP permission
Section titled “HTTP permission”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.
- 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.