You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This commit was created on GitHub.com and signed with GitHub’s verified signature.
Security
WAF now inspects every request header, not a fixed allowlist. Header collection in request_filter (lorica/src/proxy_wiring.rs) scanned only nine named headers (user-agent, referer, cookie, x-forwarded-for, content-type, content-length, authorization, origin, transfer-encoding) plus any x-* header. Any other attacker-controllable header that a backend trusts (Forwarded, True-Client-IP, Profile, SOAPAction, application-specific headers) carried SQLi/XSS/traversal payloads straight to the upstream with no WAF event logged, a silent bypass for header-borne injection on Blocking-mode routes. Collection now scans every header whose value is valid UTF-8 (binary values are still skipped); the Aho-Corasick prefilter keeps clean traffic cheap.
The first-run admin password is no longer written to the systemd journal. It was printed to stdout, which the service captures via StandardOutput=journal, leaving the bootstrap credential in /var/log/journal readable by the systemd-journal group long after the one-time login (CWE-532). It is now written to /var/lib/lorica/initial-admin-password with mode 0600 and only the path is printed; if the file write fails the password still falls back to stdout so the sole copy is never lost. .deb and .rpm post-install messages and the install scripts point at the file. must_change_password=true still forces rotation on first login.
Removed the permissive CORS configuration from the management API. The router used AllowOrigin::mirror_request() with allow_headers(Any) (lorica-api/src/server.rs), reflecting any Origin back (CWE-942). It was inert today (the session cookie is SameSite=Strict and the API binds to loopback) but became a credential-bearing cross-origin hole the moment credentials or a non-loopback front-end were added. The CORS layer is removed entirely: the dashboard is served same-origin/same-port, so cross-origin requests are never part of normal operation; the browser now default-denies them and same-origin requests are unaffected.
Config loading fails closed on a corrupt security blob. The optional JSON columns on a route (mtls, rate_limit, geoip, bot_protection, forward_auth, mirror, response_rewrite) decoded with and_then(|s| from_str(&s).ok()) in row_to_route (lorica-config/src/store/row_helpers.rs), silently degrading a malformed blob to None. For a security field that means a route meant to enforce a protection (mTLS, rate limit) silently served it disabled. A new parse_optional_json_field helper keeps None for an absent column (bisect resilience) but returns an error for a present-but-malformed blob; the two-phase config reload keeps the previously committed config active on error rather than serving the route unprotected.
Added ProtectKernelLogs=yes to the systemd unit (dist/lorica.service), a mandatory directive from the unit hardening checklist that was missing. It blocks syslog(2) and /proc/kmsg, /dev/kmsg reads so a post-RCE process cannot harvest kernel log lines (KASLR offsets, pointer leaks) to chain a local privilege escalation.
Aligned the cargo deny advisory ignore list with cargo audit. deny.toml had ignore = [] while .cargo/audit.toml ignored the three route53-only rustls-webpki advisories (RUSTSEC-2026-0098/0099/0104), so cargo deny check would fail on advisories cargo audit correctly passes; the three IDs are now mirrored into deny.toml with a pointer to the authoritative rationale. Floored the lettre requirement at 0.11.22 in lorica-notify/Cargo.toml (the RUSTSEC-2026-0141 fix) so no resolve can drop below it, and refreshed the stale aws-smithy-http-client version reference in the audit rationale (1.1.12 -> 1.1.13).
Fixed
The WAF IP blocklist no longer panics on lock poisoning. IpBlocklist (lorica-waf/src/ip_blocklist.rs) sits on the hot path (is_blocked runs per request) but used std::sync::RwLock with .expect() on poisoning at eleven sites; a writer panicking mid-reload would poison the lock and panic every subsequent request on that worker. Switched to parking_lot::RwLock (already a crate dependency), which does not poison, removing the panic surface entirely.
Changed
Dropped the per-request clean-path WAF log and trimmed two hot-path allocations. The info!("WAF evaluation passed") line fired for every passing request at info level (the systemd default) with no operator value beyond the existing Prometheus latency metric, and is removed. url_decode_once (lorica-waf/src/engine/url_decode.rs) allocated unconditionally via from_utf8_lossy().into_owned(); it now uses from_utf8(), which reuses the buffer when the decoded bytes are already valid UTF-8 (the common case), keeping the lossy U+FFFD fallback only for the invalid path. should_rewrite_response (lorica/src/proxy_wiring/mirror_rewrite.rs) no longer builds a Vec<String> of lowercased content-type prefixes per response: the empty-list common case compares against a static prefix with no allocation, and the configured case lowercases each prefix inline.