Elyra
Elyra The coding agent eTerm The terminal that knows where each command ends Starf An activity monitor for Apple silicon that never invents a number Litr A small, native web browser for macOS Notr A notebook for macOS e The native code editor Elyra Grove Native local development environment Askr The real server for Laravel & PHP Elyra Framework Rust + Svelte 5 framework for desktop apps Elyra Conductor Local project conductor Refr Local-first PDF workspace for macOS Elyra Workspace A desktop workspace for coding agents Elyra SQL Server MySQL-compatible SQL server in Rust Elyra Félagi Agents as teammates on one board Elyra SQL Client Native desktop SQL workbench Elyra SQL Anywhere Replication-ready SQL engine Elyra Sjá SEO & GEO workspace for macOS Elyra DataGrid Server-driven data grid for Laravel
Release notes
Changelog
Elyra
Hardening / sandbox (Linux)

Hardening / sandbox (Linux)

--sandbox shrinks the blast radius of a PHP-level exploit. Even if an attacker gets code execution inside PHP, the worker can't spawn a shell or tamper with your code.

askr serve … \
  --sandbox \
  --sandbox-write /var/www/app/storage --sandbox-write /tmp

Two independent layers (Linux only; no effect elsewhere):

seccomp — no new processes

--sandbox installs a seccomp-BPF filter (all threads) that makes execve/execveat/ptrace/process_vm_* return EPERM. So a compromised request can't launch a shell — shell_exec/exec/Symfony\Process just fail. It's applied before the PHP/tokio threads are created, so it covers the thread PHP runs on.

If your app legitimately shells out (some packages do), those calls will fail under --sandbox. Test first, or don't enable it for such apps.

Landlock — write only where allowed

Add --sandbox-write <dir> (repeatable) to also restrict the filesystem with Landlock: the worker may read everywhere (so PHP, extensions and templates keep working) but may write only under the listed paths. A path-traversal or upload exploit then can't drop a webshell into the docroot or modify your code — writes outside the allowlist get EACCES.

Typical allowlist for a Laravel app:

--sandbox-write /var/www/app/storage      # logs, cache, sessions, compiled views
--sandbox-write /var/www/app/bootstrap/cache
--sandbox-write /tmp                       # uploads (streamed) + sqlite temp

Landlock degrades gracefully: on kernels without it (or an older ABI) the filter is best-effort and never prevents startup — unless sandbox_required says otherwise. Askr asks for ABI V6 (the newest the library knows; V2 added file re-parenting, V3 truncate, V4 network, V5 ioctl) and lets the kernel enforce as much of it as it has. /api/status shows landlock_abi as the ABI requested; a kernel older than that enforces a narrower ruleset and the worker logs a warning saying so. A kernel with no Landlock at all is reported as not applied — under best-effort compatibility the call succeeds while enforcing nothing, which used to look like success.

Fail closed: --sandbox-required

By default the sandbox is advisory. A kernel without Landlock, a container without the seccomp capability, or any other missing feature logs a warning and the worker serves traffic looking exactly like one that hardened successfully. That default is not changing — an upgrade that started refusing to boot would be worse than the warning — but you can opt out of it:

askr serve … --sandbox-write /var/www/app/storage --sandbox-write /tmp --sandbox-required

A worker that cannot fully harden then exits (status 78) instead of serving, and the supervisor's crash-loop guard turns a fleet-wide failure into one clear "giving up".

It requires --sandbox-write, and refuses to start without it. Seccomp alone blocks execve, which is not how a webshell runs here: Askr interprets PHP in-process, so a .php file written into the docroot needs no process creation at all. Landlock write rules are the control for that, so a "required" sandbox without them would be a promise the sandbox cannot keep.

Attestation: what the workers actually got

/api/status reports the sandbox twice, side by side:

"sandbox": {"configured": true, "required": true, "workers": 8, "seccomp": 8, "landlock": 8, "landlock_abi": 1}

configured and required are what you asked for. workers, seccomp and landlock are counted by the workers themselves as they apply it. A fleet where workers is 8 and landlock is 6 is serving two workers unhardened — which, without sandbox_required, is exactly the condition that used to log a warning and otherwise look identical to success. Alert on seccomp < workers or landlock < workers.

Config file

[server]
sandbox = true
sandbox_write = ["/var/www/app/storage", "/var/www/app/bootstrap/cache", "/tmp"]
sandbox_required = false   # true = refuse to serve unhardened (needs sandbox_write)

Verified

In a Linux container: with --sandbox --sandbox-write /tmp, a request that calls shell_exec("id") returns EXEC-BLOCKED, a write to /tmp succeeds, a write into the docroot is DENIED, and normal pages serve unchanged.

Notes

  • Sidecars (queue/scheduler) are not sandboxed — queue jobs may legitimately shell out; only the internet-facing web workers are hardened.
  • Combine with the non-root systemd unit + capabilities in UBUNTU.md for defence in depth.