Reading — step 1 of 4
Learn
~1 min readSandboxing and Embedding
Lua's small surface makes it easy to embed AND sandbox. Both Roblox and Redis run untrusted Lua in restricted environments.
load(chunk, name, mode, env) — compile Lua code with a custom environment:
local sandbox_env = {
print = print,
string = string,
math = math,
-- omit io, os, debug, package — restrict access
}
local code = [[
print("hello from sandbox")
print(string.upper("hi"))
]]
local chunk, err = load(code, "sandbox", "t", sandbox_env)
if chunk then
chunk()
else
print("error: " .. err)
end
- The chunk only sees what's in
sandbox_env. Noio.open, noos.execute. mode = "t"allows text but not bytecode (bytecode could exploit VM bugs).- Errors from compilation come back as a string.
Tighter sandbox with intercepting metatables:
local env = setmetatable({}, {
__index = function(t, key)
error("access denied: " .. key)
end
})
-- Now any global lookup throws
local chunk = load(code, "sandbox", "t", env)
Resource limits — Lua doesn't have built-in CPU/memory limits, so:
- Set
debug.sethookto interrupt long-running scripts:
local limit = 100000
local count = 0
debug.sethook(function()
count = count + 1
if count > limit then error("timeout") end
end, "", 1)
The "", 1 means "call the hook every 1 instruction." Slow but reliable.
pcall / xpcall for error containment:
local ok, err = pcall(function()
chunk()
end)
if not ok then
print("sandbox error: " .. tostring(err))
end
Real production:
- Roblox: custom Luau (typed Lua) with extensive sandboxing
- Redis: Lua scripts with KEYS/ARGV restrictions, no globals, deterministic-only ops
- Neovim: full Lua, trusted (configs run with full power)
The principle: enumerate what's ALLOWED, not what's blocked. Default-deny.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…