v1.1.3
@morojs/engine 1.1.3
Security-hardening release. Closes a third-party defensive audit and a
follow-up review of the new defenses. No breaking changes — every new knob
is additive and off/opt-in by default, so this is a drop-in for 1.1.x.
Security hardening
The receive side was already well defended (request smuggling, Content-Length
overflow, slowloris, header/body caps, WS zip-bombs). This release closes the
gaps the audit found — most importantly the response-delivery side.
- Slow-read response DoS — closed. In-flight responses used to be exempt
from every timeout, so a client that sent a valid request and then stopped
reading (or pinned a zero TCP receive window) could hold its queued response
buffer, kernel socket buffer, and fd indefinitely — the receive-side mirror
of slowloris. A newresponseTimeoutMsbudget (default 300 s, the
response-side twin ofrequestTimeoutMs) sheds a connection whose outbound
queue makes no drain progress for the whole budget. Progress is measured by
the write queue shrinking between sweeps, not by write completion, so a
genuinely slow-but-steady reader of a large download or SSE stream is never
cut off — only a stalled/zero-window peer is. An opt-in
responseBackpressureLimitadditionally hard-caps the outbound queue (the
HTTP mirror ofwsBackpressureLimit). The same delivery deadline now also
bounds a stalled WebSocket consumer and a stalled close-frame flush. - Host header discipline (RFC 9112 §3.2). An HTTP/1.1 request with zero or
more than oneHostheader is now rejected400before any routing sees it —
absent/duplicateHostis a building block for host-confusion and cache
poisoning behind aHost-routing proxy. HTTP/1.0 (which predatesHost) is
unaffected. - Request-target and header-value byte hygiene. Raw control bytes (C0 other
than HTAB in values, and DEL) in the request target or any header value are
now rejected400instead of being handed to the app verbatim — closing a
log-injection / downstream-desync vector. Raw UTF-8 (high bytes) and
absolute-form targets stay accepted, matching Node. Opt-inmaxUriSize
answers414for over-long targets. Sec-WebSocket-Keyvalidation (RFC 6455 §4.1). Malformed keys (not the
base64 of a 16-byte nonce) are refused at the handshake instead of being fed
into the accept-key computation.- Numeric-limit clamp. Operator-supplied size limits are clamped to a sane
ceiling (2^48) so no absurd value can overflow the engine's internal size
arithmetic; the process degrades predictably instead of wrapping. - N-API robustness. V8
Set/Get/string-creation paths that previously
used.Check()(which hard-aborts the Node process on failure under
allocation pressure) now degrade gracefully. - TLS transport. The
SSL_select_next_protoALPN path is documented as not
reaching the CVE-2024-5535 dangling-pointer case, guarded so a future refactor
can't regress it.
New options (all additive)
ssl.ciphers/ssl.ciphersuites/ssl.ecdhCurve— explicit
cipher-list, TLS 1.3 ciphersuite, and key-share group policy for
compliance baselines (PCI/FIPS/hardened profiles). Unset, the host Node's
OpenSSL defaults apply, exactly as before; invalid values throw fromserve()
rather than booting a lax server.responseTimeoutMs,responseBackpressureLimit,maxUriSize—
the response-side / target-size knobs above (see docs/API.md).probe().capabilitiesgainsresponseLimitsandtlsPolicyso consumers
can feature-gate the new options instead of version-sniffing.
MoroJS passes only capability-gated options, so an older framework build simply
doesn't set the new keys — full forward/backward compatibility.
Compatibility
- No API or behavior changes for existing code; all new surface is
additive. Drop-in for 1.1.x; MoroJS picks it up via^1.1.0. - Platform/ABI matrix now includes Linux musl arm64 (Alpine on Graviton no
longer falls back tonode:http): macOS arm64/x64, Linux glibc x64/arm64,
Linux musl x64/arm64, Windows x64 × Node 20–26, prebuilt with npm provenance.
Verification
- Full socket-level wire matrix green: HTTP conformance + edge + hardening +
regression, WebSocket, limits, TLS + TLS-hardening, permessage-deflate
(154 tests), with new regression tests per audit finding — missing/duplicate
Host, control-byte target/value,maxUriSize→ 414, malformed WS key, the
TLS cipher/group knobs, and the slow-read deadline (a steady slow reader
receives the full body over both single-respond()and streamedwrite()
paths, while a zero-window client is still shed). - C++ unit suites under the standalone harness: 141-check HTTP parser +
WebSocket + permessage-deflate. - ASan/UBSan legs and the libFuzzer HTTP/WS/TLS/deflate corpora run in CI;
the new Host/target/value/WS-key validators were re-fuzzed clean. - MoroJS engine integration tests green against this build.
See docs/THREAT_MODEL.md for the full defense inventory and the honest
residuals (e.g. a per-sweep drip peer, bounded by responseBackpressureLimit +
maxConnections).