Security & Sandboxing
SpyWeb enforces multiple layers of security to prevent Lua hooks from accessing or modifying files outside their intended scope.
CWD Jailing
Section titled “CWD Jailing”All file operations are jailed to the current job’s directory. Hooks cannot read, write, or execute files outside their job folder.
Directoryjobs/
Directorymy-job/
- hooks.lua Can access: my-job/, shared/
- config.toml
- data.csv
Directoryother-job/
- hooks.lua Cannot access: my-job/ files
The shared/ directory is the only cross-job escape hatch, and it requires an explicit shared/ prefix.
Path Validation
Section titled “Path Validation”All paths are validated before any file operation. The following are rejected:
| Attack Vector | Example | Result |
|---|---|---|
| Directory traversal | ../secret.txt |
Rejected |
| Absolute path | /etc/passwd |
Rejected |
| Nested traversal | data/../../etc/passwd |
Rejected |
| Symlink escape | Symlink pointing outside sandbox | Rejected |
Absolute Path Rejection
Section titled “Absolute Path Rejection”Every fs_* function rejects absolute paths:
fs_read("/etc/passwd") -- Error: absolute paths not allowedfs_append("/tmp/data.csv", x) -- Error: absolute paths not allowedrequire("/etc/passwd") -- Error: absolute paths not allowedUse relative paths instead. The path resolves relative to your job’s directory.
Extension Whitelisting
Section titled “Extension Whitelisting”File operations are restricted to safe extensions. Write operations use a stricter list than read operations.
| Function | Allowed Extensions |
|---|---|
fs_append |
csv, json, jsonl, txt, log |
fs_overwrite |
csv, json, jsonl, txt, log |
fs_read |
csv, json, jsonl, txt, log |
fs_read_binary |
csv, json, jsonl, txt, log, png, jpg, jpeg, gif, svg, webp, bmp, ico, woff, woff2, ttf, otf, pdf, zip |
fs_read_binary has a wider allowlist to support images, fonts, PDFs, and archives.
Stripped Luau Libraries
Section titled “Stripped Luau Libraries”SpyWeb loads a minimal subset of Luau’s standard library:
| Library | Status | Alternative |
|---|---|---|
table |
Loaded | - |
string |
Loaded | - |
utf8 |
Loaded | - |
math |
Loaded | - |
os |
Stripped | os.time(), os.date(), os.clock(), os.difftime() available |
bit |
Loaded | - |
coroutine |
Loaded | - |
io |
Disabled | Use fs_append, fs_read, fs_overwrite |
package |
Disabled | Custom require (see below) |
debug |
Disabled | No alternative |
loadlib |
Disabled | No alternative |
Custom require
Section titled “Custom require”SpyWeb replaces Lua’s standard require with a sandboxed version:
- Searches job directory first, then project root
- Caches modules after first load
- Validates paths against traversal attacks
- Dots in module names convert to path separators (
utils.helpers→utils/helpers.lua)
-- This works: checks jobs/my-job/utils.lua, then ./utils.lualocal utils = require("utils")
-- This fails: absolute path rejectedlocal bad = require("/etc/passwd")Environment Variables
Section titled “Environment Variables”Environment variables are read-only and require the SPYWEB_ prefix:
-- Host systemexport SPYWEB_API_KEY="secret-123"
-- In Lua (prefix added automatically)local key = env_get("API_KEY") -- Reads SPYWEB_API_KEYVariables without the SPYWEB_ prefix are inaccessible from Lua.
Shared Directory Scope
Section titled “Shared Directory Scope”The shared/ directory is the only way to access files outside the job directory:
-- Write to shared folder (accessible by all jobs)fs_append("shared/log.txt", "data")
-- Read from shared folderlocal config = fs_read("shared/config.json")
-- Without shared/ prefix, writes go to job directoryfs_append("local.csv", "data") -- writes to jobs/my-job/local.csvAPI Server Authentication
Section titled “API Server Authentication”The built-in API server supports optional authentication via the SPYWEB_API_KEY environment variable:
export SPYWEB_API_KEY="your-secret-key"When set, all API requests must include the key in the X-SpyWeb-Key header.