Security engineering
What ScalWS does about the threats in its threat model, and where the model says a mitigation is partial or planned. Every statement links to the document it comes from.
Privilege model
- The packaged systemd unit runs scalwsd as user scalws with only CAP_NET_BIND_SERVICE, cgroup delegation and a hardened sandbox. It never needs root and warns when started as root.
- Application processes never inherit its capabilities (setpriv: no inherited or ambient capabilities, no_new_privs) and receive SIGTERM if scalwsd dies.
- Optional per-tenant Unix users (ADR-0026) need four more capabilities, which makes scalwsd root-equivalent if compromised: the same trade-off as a root PHP-FPM master. Without them, all applications run as one user.
Safe path handling
- Paths are decoded once; .., NUL, backslash and encoded slashes are rejected before the filesystem is touched.
- On Linux, files open with openat2(RESOLVE_BENEATH | RESOLVE_NO_MAGICLINKS) from a root directory descriptor, so symlink swaps cannot escape the document root. Other platforms keep a documented residual risk.
- Any path segment starting with a dot is 404, except .well-known. Directory listings are never generated. Route matching uses the decoded, dot-segment-resolved path.
Request smuggling and header trust
- HTTP/1 parsing is hyper’s strict parser by default. With both Content-Length and Transfer-Encoding, Transfer-Encoding wins and the connection closes after the response; tested end to end.
- The opt-in own HTTP/1 server (server.http1_server: experimental) is stricter where two parsers could disagree: Transfer-Encoding with Content-Length, repeated or non-chunked Transfer-Encoding, and Content-Length lists are answered 400 and the connection closes. It is differential-tested against hyper, fuzzed and was security-audited; the five findings were fixed with regression tests.
- The proxy always re-frames upstream requests, strips hop-by-hop headers and never forwards a client’s Transfer-Encoding verbatim.
- Client-supplied Forwarded, X-Forwarded-* and X-Real-IP are removed and replaced unless the peer is a configured trusted proxy. X-Request-Id is regenerated.
Resource limits
- Timeouts for TLS handshakes, header reads, body frames, idle connections and slow readers; limits on connections, header size and count, URI length and body size.
- Per-source connection and request limits (IPv6 grouped per /64) live in sharded, size-capped state that cannot grow with the number of attacking addresses; tested with a 100k-address flood.
- HTTP/2 stream and reset limits against rapid reset (CVE-2023-44487). Tenant concurrency (503) and rate (429) quotas; cgroup v2 limits for application processes.
.htaccess restrictions
- Opt-in per application. The file is written by the tenant, so proxy rules ([P]), RewriteMap, php_admin_* and SetHandler/AddHandler are ignored and reported.
- Regular expressions use a linear-time engine; patterns it cannot express make the rule inactive, not slow. Limits: 64 KiB per file, 1000 directives, 10 rewrite rounds.
- Rewrites stay inside the application and .htaccess itself is never served. This is a subset for common applications, not Apache compatibility.
Control plane separation
- The Adaptive Runtime controller never accepts or proxies client traffic and holds no TLS keys. Its API is a local Unix socket (0600) with no remote mode.
- The admin socket is mode 0660 for group scalws; every change is audited with the caller’s uid, gid and pid. Remote administration exists only with mutual TLS.
- The optional local AI advisor is off by default, sees redacted input only, has no handle to configuration or runtimes, and never acts.
Graceful lifecycle
- Reloads validate and prepare a complete configuration before an atomic swap; a failure changes nothing. Topology changes are refused with “requires a restart”.
- SIGTERM drains: listeners close first, keep-alive connections are told to close, in-flight requests finish within the drain timeout.
- Supervised applications have readiness checks, crash backoff and restart-loop detection, so a crashing application cannot fork-storm the host.
Open items in the threat model
The threat model marks each mitigation as implemented and tested, partial, or planned. Not implemented today include a content-inspecting WAF and trace export. The full threat model lists every row with its status.