* fix(security): trusted-proxy-verified local-origin gate (maintainer fix of #5764)
Rebuild _onboarding_request_is_local with a single, symmetric trust model that
closes the embedded-terminal local-gate bypass AND fixes the direct-LAN lockout
regression from the contributor's #5764 attempt (bounced with these findings):
1. SPOOF CLOSED: a forwarded client IP is honored ONLY when the un-spoofable raw
socket peer is a trusted proxy (loopback, or HERMES_WEBUI_TRUSTED_PROXY_CIDRS).
A public direct client sending X-Forwarded-For: 127.0.0.1 is judged on its raw
public peer -> DENY. New _raw_peer_is_trusted_proxy + _forwarded_client_ip_from
_trusted_proxy consume the FULL XFF chain (get_all, repeated headers), walk it
right-to-left skipping trusted hops, and fail closed on empty/blank/garbage.
2. NO LAN LOCKOUT: a direct loopback/LAN client with no proxy header stays local
(onboarding, first-password/passkey, passwordless terminal keep working).
3. COMPAT: TRUST_FORWARDED_FOR=1 stays the opt-in that consults the chain; the new
TRUSTED_PROXY_CIDRS only WIDENS trust to a non-loopback proxy (loopback always
trusted). Shipped private-peer-with-forwarded-header deny nuance preserved.
Updated the one existing test that encoded the vulnerable behavior; added a
21-case adversarial truth-table regression (tests/test_cvd3_terminal_local_origin_gate.py).
66 gate tests + 720 auth/terminal regression pass.
Co-authored-by: Hinotoi-agent <Hinotoi-agent@users.noreply.github.com>
* fix(security): family-aware trusted-proxy membership (IPv4-mapped-IPv6)
Codex re-gate CORE: a mapped-IPv6 address (::ffff:10.9.9.9) never matched an
IPv4 CIDR allowlist via 'addr in net' -> (1) legit proxy treated as untrusted =
lockout; (2) mapped trusted HOP in the XFF chain mis-returned as client -> a
preceding public client admitted to the terminal gate. New _ip_in_networks
helper checks addr AND addr.ipv4_mapped; both the peer check and the chain-walk
_is_trusted_hop route through it. +3 mapped-IPv6 regression cases (incl the
public-client-hidden-behind-mapped-trusted-hop security case).
---------
Co-authored-by: t <a@b>
Co-authored-by: Hinotoi-agent <Hinotoi-agent@users.noreply.github.com>
The embedded workspace terminal spawns a PTY shell that runs arbitrary
commands as the server-process user. check_auth() returns True
unconditionally when no password/passkey is configured (the default
out-of-the-box state), so without a network-scope gate the terminal
endpoints were reachable by any unauthenticated caller able to hit the
port — which on a passwordless public bind is remote code execution.
Apply the same local-origin gate the onboarding/bootstrap endpoints use
(_onboarding_gate_allows) to /api/terminal/{start,input,resize,close} and
/api/terminal/output: with auth disabled, accept only loopback/private
origins, ignore spoofable X-Forwarded-For/X-Real-IP unless
HERMES_WEBUI_TRUST_FORWARDED_FOR=1, and honor HERMES_WEBUI_ONBOARDING_OPEN=1
as the explicit opt-out for a deliberately-exposed server. Auth-enabled
servers (cookie already verified upstream) and genuine same-host clients
are unaffected.
Also fixes a latent test-isolation leak in
test_extension_route_remains_behind_webui_auth: it set HERMES_WEBUI_PASSWORD
but never invalidated the process-wide password-hash cache, so its result
depended on suite execution order (exposed when the new test file shifted
ordering). Invalidate before+after so it reads the env var deterministically.
12 new gate tests in tests/test_cvd3_terminal_local_origin_gate.py.