A sandboxed Luau runtime for PHP, built as a native extension in Rust.
Replaces the Wikimedia php-luasandbox PECL extension (~4,300 lines of C) with ~840 lines of Rust, using Luau instead of Lua 5.1.
- Luau runtime — Roblox's Lua fork with gradual type checking, better performance, and built-in sandboxing
- CPU time limiting — per-script execution timeout via Luau's native interrupt mechanism (works on macOS and Linux)
- Memory limiting — hard byte limit on Lua heap allocations
- Sandbox hardening — no filesystem, network, or OS access; builtins are read-only
- PHP callable bridge — register PHP functions callable from Lua, with automatic type conversion
- Bidirectional conversion — PHP arrays/scalars to Lua tables and back, with circular reference detection
- Exception hierarchy —
Lua\TimeoutException,Lua\MemoryException,Lua\SyntaxException,Lua\RuntimeException
- PHP 8.4+
- Linux x86_64 or macOS aarch64 (Apple Silicon)
Download from Releases:
| Platform | File |
|---|---|
| Linux x86_64 | liblua_sandbox-linux-x86_64.so |
| macOS Apple Silicon | liblua_sandbox-macos-aarch64.dylib |
Copy to your PHP extension directory and enable:
# Find your extension dir
php-config --extension-dir
# Copy and enable
cp liblua_sandbox-linux-x86_64.so $(php-config --extension-dir)/liblua_sandbox.so
echo "extension=liblua_sandbox.so" > $(php --ini | grep "Scan" | cut -d: -f2 | xargs)/lua-sandbox.iniRequires: Rust 1.75+, PHP 8.4+ development headers, Clang/LLVM
cargo build --release
# Output: target/release/liblua_sandbox.{so,dylib}ARG LUA_SANDBOX_VERSION=v1.0.0
RUN curl -fsSL "https://github.com/OpenCompanyApp/lua-sandbox/releases/download/${LUA_SANDBOX_VERSION}/liblua_sandbox-linux-x86_64.so" \
-o "$(php-config --extension-dir)/liblua_sandbox.so" \
&& echo "extension=liblua_sandbox.so" > /usr/local/etc/php/conf.d/lua-sandbox.ini$sandbox = new \Lua\Sandbox(
memory_limit: 10 * 1024 * 1024, // 10 MB
cpu_limit: 5.0, // 5 seconds
);
// Load and execute Luau code
$script = $sandbox->load('return 1 + 2');
$result = $script(); // [3] — via __invoke
$result = $script->call(); // [3] — explicit
// Register PHP functions callable from Lua
$sandbox->register('api', [
'greet' => fn(string $name) => "Hello, {$name}!",
'add' => fn(int $a, int $b) => $a + $b,
]);
$sandbox->load('return api.greet("World")')(); // ["Hello, World!"]
// Wrap a single PHP callable as a Lua function
$double = $sandbox->wrap(fn($x) => $x * 2);
$double(21); // [42]
// Call a global Lua function by name (supports dot notation)
$sandbox->load('function math.double(x) return x * 2 end')->call();
$sandbox->call('math.double', 21); // [42]| Method | Description |
|---|---|
__construct(int $memory_limit = 0, float $cpu_limit = 0.0) |
Create sandbox. 0 = unlimited. |
load(string $code, ?string $name): Script |
Compile Luau source |
register(string $lib, array $fns): void |
Register PHP callbacks as a Lua library |
wrap(callable $fn): Script |
Wrap single PHP callable |
call(string $name, mixed ...$args): array |
Call global function (dot notation) |
setMemoryLimit(int $bytes): void |
Change memory limit at runtime |
memoryUsage(): int |
Current memory usage in bytes |
peakMemoryUsage(): int |
Peak memory usage in bytes |
setCpuLimit(float $seconds): void |
Change CPU limit at runtime |
cpuUsage(): float |
CPU seconds consumed |
pauseTimer(): bool / resumeTimer(): bool |
Pause/resume CPU timer |
static version(): array |
Version info |
| Method | Description |
|---|---|
call(mixed ...$args): array |
Execute, returns array of return values |
__invoke(mixed ...$args): array |
Alias for call() |
\Exception
└─ Lua\Exception
├─ Lua\RuntimeException
├─ Lua\SyntaxException
├─ Lua\TimeoutException
└─ Lua\MemoryException
Luau supports gradual typing. Type annotations are checked at analysis time:
local function fibonacci(n: number): number
if n <= 1 then return n end
return fibonacci(n - 1) + fibonacci(n - 2)
end- Sandbox: Luau's built-in
sandbox(true)makes all builtins read-only and removesio,package,loadfile,dofile,string.dump, anddebug(excepttraceback/info). Additional hardening filters theostable to only{clock, date, difftime, time}. - CPU timeout: Luau's native interrupt mechanism fires at every function call and loop iteration. No debug hooks or POSIX timers needed.
- Memory:
mlua'sset_memory_limit()enforces a hard byte limit on the Lua allocator. - PHP bridge: Rust closures capture PHP callables and convert arguments bidirectionally. The CPU timer is paused during PHP execution.