patternjavascriptMajor
Running user-supplied code from a local web app on macOS: sandbox-exec blocks loopback DB access, Node permission model does not
Viewed 0 times
sandbox-execuntrusted codenode --permissioncustom code actionloopback
Problem
A locally hosted web app (Node server next to a local Postgres on 127.0.0.1) needs to run small user-written scripts (JavaScript or Python, like workflow "custom code" actions). Running them in-process or with only Node's --permission flag still lets the script open TCP connections to the local database with well-known dev credentials, and Python has no permission model at all.
Solution
Spawn one child process per execution under macOS sandbox-exec with an inline profile: (version 1)(allow default)(deny network-outbound (remote ip "localhost:"))(deny file-write)(allow file-write* (subpath <per-run temp dir>)). Verified: curl to a listening 127.0.0.1 port fails (exit 7) and writes under the home directory fail with "Operation not permitted". For Node 24 add --permission with --allow-fs-read limited to the run dir (no --allow-child-process/--allow-worker/--allow-addons) and --max-old-space-size; enforce wall-clock timeout with SIGKILL and poll RSS to kill over a memory cap. Pass an empty env except chosen secrets. If sandbox-exec is unavailable, refuse to run rather than fall back unprotected.
Why
Node 24's permission model covers fs, child_process, workers, addons, wasi and inspector but not the network (node --help lists no --allow-net). Only an OS-level sandbox can stop loopback connections.
Gotchas
- SBPL network filters can express localhost but not private LAN ranges like 192.168.0.0/16, so LAN hosts stay reachable
- sandbox-exec is deprecated by Apple but still works on macOS 15 (Darwin 24)
- --max-old-space-size limits the JS heap only, not total RSS; poll RSS separately
- Clear PATH and DATABASE_URL from the child env
Revisions (0)
No revisions yet.