Repository navigation
v1.6.0
Security and robustness release. Everyone on v1.5.1 or earlier should
upgrade. Security advisories for the issues below will be published on the
repository's Security tab once this release is available.
Upgrade notes
Most changes are fixes, but some intentionally tighten behaviour:
- HTTP status codes. A wrong method gets
405withAllow(it used to get
404).HEADis answered fromGETroutes. - Request paths. Handlers receive percent-decoded
uri,queryandparams.
Paths containing an encoded/,\or NUL are rejected with400. - Error responses.
500bodies no longer include exception messages.
on_erroralso receives exceptions thrown by handlers that return values. - CORS. With an allow-list, unlisted origins receive no CORS headers.
- JWT. Only HS256 is accepted, and a future
nbfis rejected.
jwt_auth("")throws. - WebSocket routes now run the app's middleware and validate the handshake.
- OAuth2. Logins must start through
login_handler(), which sets the state cookie
that callbacks now require. - Reverse proxy. HTTPS upstreams with untrusted certificates are rejected. Set
verify_tls = false, or useca_filefor a private CA. - ORM. Values are bound as parameters instead of being spliced into SQL. Code that read
orm::Expr::sqlexpecting inlined values, or passed arbitrary operator strings
or non-identifier column names, must be updated. - Database.
PostgresClientfailures returnResultSet::failure(message); check
ok()/error().to_json()keeps""for empty strings and usesnull
only for SQL NULL. - Distributed rate limiter. Answers
503(fail closed) when Redis is
unreachable. Passallow_when_unavailable = trueto let requests through instead. - Static files.
- Dotfiles (
.env,.git/) are no longer served. - Text types carry
; charset=utf-8. - The ETag format changed, so cached copies revalidate once.
- Dotfiles (
- Timeouts. The single 10-second timer is replaced by
header_timeout,
keep_alive_timeout,idle_timeoutandwebsocket_idle_timeout. - Listen backlog. The default is now
SOMAXCONN(it was 10).
Security
- HTTP/1.1 request framing: conflicting or duplicate
Content-Length, and
Transfer-Encodingcombined withContent-Length, are rejected. This prevents
request smuggling, and a duplicateContent-Lengthno longer terminates the
process. - WebSocket:
- Frame lengths are validated and message size is bounded.
- A connection that drops without a close frame no longer causes a use-after-free.
- The upgrade no longer bypasses middleware and origin checks.
- ORM query builder: values are bound as parameters instead of spliced into SQL
(SQL injection). - CORS: the origin allow-list is honoured and
Vary: Originis sent. - CSRF middleware no longer leaves dangling header references.
- Session IDs, CSRF tokens and QUIC connection IDs, stateless reset tokens and
path challenges are drawn from a CSPRNG. - JWT verification: the algorithm is pinned, signatures are compared in
constant time, andexp/nbfare checked. - OAuth2: adds
stateand PKCE, encodes parameters, and puts timeouts on
provider calls. - Reverse proxy:
- Verifies upstream TLS.
- Forwards headers safely.
- Owns the request it forwards; it used to keep a dangling reference.
- TLS: OpenSSL state is serialised per connection.
- HTTP/2: file responses own their descriptors; the response could read a
descriptor that had already been closed. - Response header and cookie values are validated for CR/LF.
- Exception messages are no longer disclosed to clients.
- The signal handler is async-signal-safe.
- The Redis client no longer deadlocks after losing its connection.
- Connections that are idle, slow, or trickle their headers are bounded by
per-phase timeouts. Long handlers, WebSockets and SSE are no longer cut off.
Fixed
- HTTP/1.1 pipelining.
- A pipelined response could be dropped, or the socket closed before the final
response was sent. - A connection could linger after its last response.
- Pipelined file responses corrupted each other's offset.
- A pipelined response could be dropped, or the socket closed before the final
- A handler that sent a fixed-length response while a chunked request body was
still arriving broke the body decoding. EAGAIN/EINTRfrom a read or write closed the connection. They now retry.- TLS
close_notifyand fatal TLS errors were ignored, so the connection stayed
open until the keep-alive timeout. - Static files.
- The path-containment check rejected every file on Windows. It also accepted
sibling directories that shared a prefix, and symlinks that point outside the
directory. - Many common MIME types were missing.
- The path-containment check rejected every file on Windows. It also accepted
- In-memory rate limiter.
- Memory grew with every client address it saw; it is now bounded.
- Refill is continuous.
- Redis rate limiter. The counter and its expiry are now set atomically, so a
client can no longer be locked out permanently. - Response interceptors and default headers leaked into later responses on a
keep-alive connection. - Nested router groups now register their routes.
- HTTP/2 now matches HTTP/1.1 for query strings, cookies, interceptors and
streaming. - Database query errors are reported, NULL is preserved, and migrations run in a
transaction. - The thread pool's stop flag is set under its mutex.
- Library diagnostics go through the Logger rather than
std::cout, and respect
log_level. - Accept:
- One readiness event now accepts every queued connection.
- Running out of file descriptors no longer sleeps on the event-loop thread.
- Installers.
install.shandinstall.ps1install the latest release rather thanmain.- vcpkg is pinned to the manifest baseline.
- Build parallelism is bounded by available memory.
- Both scripts stop on the first error.
Added
ServerConfig::host(--bind):- Accepts
127.0.0.1,::1, host names, and::for a dual-stack listener. - IPv6 client addresses are reported.
- Accepts
ServerConfig::backlog(--backlog) andServerConfig::max_connections
(--max-connections). When the connection limit is reached, new connections
wait in the backlog until a slot frees.middleware::StaticFilesOptions:index,serve_dotfilesandmax_age.- Responses carry
Last-Modified,Cache-ControlandAccept-Ranges. HEADis supported.- Single byte ranges return
206/416, andIf-Rangeis honoured. If-None-Matchlists andIf-Modified-Sinceare supported.mime_type_for_extension()is exposed.
HttpResponse::file_offset/set_file_range()for partial file responses
over HTTP/1.1 and HTTP/2.middleware::RateLimitOptions: a custom key function and
max_tracked_clients. Both rate limiters sendRetry-After.RedisClient::incr_with_expiry().WebSocketConnection::set_max_message_size(),ws(...)overloads with
middleware, andrequire_origin.JwtOptions, OAuth2 PKCE/state configuration, andProxyOptions
(use_tls,verify_tls,ca_file).ResultSet::ok()/error(), and parameterisedquery/query_async.HttpRequest::set_header()/target.EventLoop::set_tick_hook().Listener::port().- Installer variables
ORBIT_VERSION,ORBIT_PREFIXandORBIT_JOBS.
Removed
- The unused hiredis dependency.
Verifying this release. Every file has a .sigstore.json bundle.
See docs/release-process.md.