8.0 KiB
RustProxy 0.31.4 - Administration-panel Origin pinning and simplified HTTPS settings
Scope
Only the administration panel/control-plane authentication and its settings, OpenAPI, translations and regression tests change. No public vhost/proxy routing, backend forwarding, stream, session-store or database schema behavior is altered. Credential-based certificate API automation remains on its existing validation path. The 0.31.3 BFCache, manual retry, SSE/polling and startup error protection remains unchanged.
Evidence and failure mode
0.31.3's external_admin_scheme rejects a trusted
X-Forwarded-Proto: https, http value with auth.invalid_origin and reason
external_scheme before checking a session, even for GET /api/auth/session
without an Origin header. This may appear intermittently if a multi-hop
reverse proxy sometimes appends rather than replaces the header. The previous
Node.js HTTP reconstruction reproduced this with 3/12 intentionally malformed
GETs; it does not prove the real user's reverse proxy behaves that way.
Patch design
administration.public_origin is an optional, explicit HTTPS origin:
- Requires one canonical DNS hostname; that hostname is derived for the effective
panel Host allowlist without repeating it in
administration.allowed_hosts. The setting forbids credentials, userinfo, path, query, fragment, HTTP and ambiguous input. - Is active only after restart and only when the immediate TCP peer is loopback
or is already in the configured trusted-proxy list. It does not implicitly
trust another proxy or the browser's
Host/forwarding headers. - Keeps checking the actual request
Hostagainst the administration listener policy. If a forwarded Host exists, it must be a single authority and match the pinned host and effective port. Malformed/userinfo/duplicate values are rejected. - For the pinned panel, accepts a bounded (maximum eight hops) protocol chain
only if the first hop is literally
httpsand every hop ishttporhttps. This permitshttps, httpbut nothttp,http, https,ftp, empty values, or arbitrarily long chains. Missing XFP is allowed only because the explicit administrator pin defines the external HTTPS identity. - Uses the configured origin for the browser
Origincomparison, correctSecuresession cookie and the existing CSRF checks. ExistingSec-Fetch-Site: cross-siteblocking is unchanged. - Does not alter the validation path when the pin is absent, the peer is untrusted, or certificate-automation credentials are present.
On every rejection, the existing bounded, credential-redacted diagnostics
remain available. New pinned_origin_mismatch identifies a disagreement with
the explicit pin. Do not log cookies, CSRF tokens or Authorization headers.
Simplified hostname configuration (included in this 0.31.4 reissue)
Operator change
For the panel behind a trusted reverse proxy, Settings -> Administration
now needs only the Public panel HTTPS origin such as
https://panel.example.test. The DNS hostname comes from this validated
administrator setting; it need not (and should not) be repeated in
administration.allowed_hosts. Additional DNS hostnames remain in an optional
Advanced section. Only those extra names are stored in the allowlist.
Backend validation also canonicalizes any duplicate hostname in memory. The settings page suppresses this value when loading an older configuration and omits it on the next save. Other explicitly allowed hosts remain. Removing/changing the pin does not automatically retain the old hostname as an extra allowlist entry. For legacy duplicated values, this normalization occurs only when the administrator saves; there is no silent SQLite mutation on startup.
Configuration example
{
"administration": {
"public_origin": "https://panel.example.test",
"allowed_hosts": []
}
}
This field still requires an HTTPS URL with one DNS hostname and an optional port; userinfo, fragment, path, HTTP, IP literals and localhost remain invalid. Changing either access-control field needs a RustProxy restart.
Implementation and security
Administration::validatenormalizes the public URL independently of the optional additional DNS list.Administration::include_public_origin_hostgenerates an effective runtime/desired Host allowlist once on startup / status read, without adding to the persisted configuration or accepting a wildcard. The originally saved allowlist is unchanged.admin_host_allowedandconfigured_admin_public_authoritycontinue to check the actual Host and the trusted last-hop proxy. Forwarded Host remains single and identical to the pin. Only a bounded, first-hop HTTPS X-Forwarded-Proto chain can be used with the explicit pin; no arbitrary Origin orSec-Fetch-Site: cross-siteor missing CSRF is permitted.- The explicitly opted-in self-admin vhost case may pass the reserved session
cookie when the incoming HTTPS host equals the pin; it still requires a
backend targeting the administration listener directly by IP and the prior
accept_local_listener_riskopt-in. No other proxy traffic changes. - No new configuration key or SQLite migration. Older
allowed_hostslists remain accepted, preserving backwards compatibility. Existing 0.31.3 mobile/BFCache recovery is untouched.
Tests and acceptance gate
New Rust source tests also cover pin-only saved configuration, derived runtime
allowlist, additional legacy entries, changes/removal, invalid URL input,
same-origin session and Secure cookie, foreign Origin, untrusted peers,
forwarded-host spoofing and CSRF. Node.js runs a focused real settings-helper
test and a local HTTP simulation of the guard; these do not execute the
Rust server. Exact results are recorded in TEST_REPORT.
Production acceptance remains OPEN until Rust 1.99+ builds/tests pass with a
resolved Cargo.lock; a trusted reverse proxy and real mobile Chrome/Safari
are checked for repeated BFCache resumes, session expiry and negative
Origin/Host/CSRF cases. Never weaken the trusted-proxy list or disable Origin
validation to avoid auth.invalid_origin.
Test coverage
Rust source tests in src/settings/tests.rs and src/admin/tests/part_11.rs
cover config parsing/normalization, persisted legacy defaults, restart gating,
trusted/foreign hosts, unknown/duplicate headers, cross-site metadata, HTTP
first hops, genuine same-origin session GET with multi-hop metadata, HTTPS
Secure session cookie, CSRF enforcement and opt-out behavior.
tools/checks/admin_public_origin_0314_sim.mjs and
tools/checks/admin_public_origin_0314_config_sim.mjs perform additional HTTP
guard simulations, not compiled RustProxy execution.
tools/checks/admin_panel_config_0314_ui_test.mjs checks the settings UI and form behavior. Record its result in
docs/TEST_REPORT.md only after it is run.
Required production acceptance
bash scripts/resolve_dependencies_0312.shon a Rust 1.99+ builder with network/local audited dependencies; inspect regenerated lockfile../dev.sh check && ./dev.sh test && ./dev.sh releaseand Go tests.- Set an exact HTTPS panel origin (no duplicate DNS allowlist entry), restart, and verify with a trusted real edge proxy. Keep RustProxy administration listening on an isolated/private socket. Verify the first trusted hop sanitizes headers.
- On iOS Safari and Android Chrome, perform 20+ visibility/BFCache resumes,
full refreshes, sessions renewed/expired, and a deliberate temporary 403.
Check HTTP 200 for valid
/api/auth/sessionandSecurecookie; no reload loops, blank screens, request floods, or leaked SSE/polling tasks. - Inject cross-origin, duplicate/foreign forwarded Host, downgraded protocol, missing CSRF and untrusted-source requests; they must still fail. Confirm certificate API automation and public vhosts behave identically to 0.31.3.
The original production cause cannot be declared fixed without representative mobile/reverse-proxy request logs. This artifact is not compiler-verified unless the release-host gate above passes.