Security

Security audit of the own HTTP/1 code (run-2, 2026-10-09)

Scoped standard run of the security-audit workflow over the new code: crates/scalws-h1, crates/scalws-http/src/h1server.rs (+ conn.rs dispatch), crates/scalws-core/src/upgrade.rs and the experimental upstream client (crates/scalws-proxy/src/{own_client.rs,h1.rs,upgrade.rs}). Audited commit d4e3f89; full artifacts in audits/run-2/ (git-ignored): REPORT.md, FINDINGS-DETAIL.md, NEEDS-VALIDATION.md, findings.json, coverage-ledger.json.

Process: 4 reconnaissance agents, 9 hunters in two waves (the wave-1 coverage critic added two units), 3 coverage critics (the last two found no gaps), an independent verifier per candidate and a second independent verifier per final record — 26 agents. Every local check ran in the dev-host sandbox (isolated namespaces, no network, uid nobody, read-only toolchain, resource limits); evidence was promoted with the no-follow tool.

Findings and fixes

FindingVerdictFix
Streamed response kept in memory when the client reads slower than the handler produces (20 MB response → 32 MiB buffer, kept for the next request)confirmed, mediumthe write loop also stops at 256 KiB of buffered bytes; a grown buffer is released after the exchange
Handler not cancelled when the client disconnects (slow upstream/PHP work and the connection slot held until the handler’s own timeout)confirmed, lowwhile a handler runs and no body is read, the socket is watched; EOF or error cancels the handler (as hyper)
Request-body high water counted bytes, not frames (1-byte chunks: 65 536 queued frames)confirmed, lowreading also pauses at 256 queued frames
A final 1xx status (e.g. .htaccess Redirect 103) sent unframed with keep-alive, shifting later responsesneeds validation (cross-user impact depends on the front proxy)a final 1xx other than 101 is answered 500 and the connection closes (as hyper)
100 Continue could be written after or inside the final headneeds validation (no in-tree handler shown to trigger it)once the final head is queued no 100 is sent; a partly written one is completed first; a still-wanted body closes the connection afterwards

Regression tests: crates/scalws-http/src/h1server/tests.rs (one per finding). The full integration suite passes against both servers; workspace clippy is clean.

Hardening notes kept for later

Bare LF accepted in request heads (as hyper); header names with underscores pass through; only the first Expect is read; Rewind keeps the read buffer for the tunnel’s lifetime; 101 responses are not checked for Upgrade; the PRI sniff polls every 1 ms on a partial prefix; experimental client: lazy idle expiry with up to 64 × 256 idle connections per handler, post-write replay of bodyless non-idempotent requests (as the hyper path), upstream CL lists forwarded verbatim; HTTP/1.1 absolute-form without Host is routed (as the hyper path).