v0.31.4
This commit is contained in:
1 parent
95c55da2ee
commit
d942c1a35d
42 files changed
+1265
-109
No files matched your search
+3
-3
@@ -65,9 +65,9 @@ The `rustproxy_session` cookie is HttpOnly, SameSite=Strict and Path=/ with the
|
||||
administration adds the Secure attribute; behind a trusted HTTPS-terminating reverse proxy
|
||||
the external forwarded scheme controls that flag. A protected SSH/VPN tunnel remains valid,
|
||||
while direct remote administration should use HTTPS plus firewall/private-network policy.
|
||||
Direct IP/localhost Host values must use the actual administration port. A DNS name explicitly
|
||||
listed in `administration.allowed_hosts` represents the browser-facing authority and may use
|
||||
the trusted reverse proxy's external port. Foreign Origin/cross-site requests are rejected.
|
||||
Direct IP/localhost Host values must use the actual administration port. A DNS name explicitly listed in `administration.allowed_hosts`, or automatically
|
||||
derived from the validated `administration.public_origin`, represents the
|
||||
browser-facing authority and may use the trusted reverse proxy's external port. Foreign Origin/cross-site requests are rejected.
|
||||
There is no permissive CORS mode.
|
||||
Auth request bodies are limited to 4096 bytes.
|
||||
|
||||
|
||||
+5
-4
@@ -9,16 +9,17 @@ new bind address and allow explicit DNS hostnames. A persisted non-loopback bind
|
||||
accepted when HTTPS is enabled. The legacy startup flags remain only for deliberately
|
||||
plain remote HTTP. Public vhost `/docs` paths are not captured by the admin listener.
|
||||
|
||||
When the dashboard is published through a trusted reverse proxy, an explicit
|
||||
`administration.allowed_hosts` DNS entry is matched against the external authority rather
|
||||
than the private admin socket port. `X-Forwarded-Host` is trusted only from loopback or a
|
||||
When the dashboard is published through a trusted reverse proxy, the hostname of
|
||||
the optional `administration.public_origin` is automatically allowed for panel
|
||||
Host validation. Any extra `administration.allowed_hosts` DNS entry is also
|
||||
matched against the external authority rather than the private admin socket port. `X-Forwarded-Host` is trusted only from loopback or a
|
||||
configured trusted proxy; the external `X-Forwarded-Proto` additionally requires the
|
||||
forwarded-proto trust setting except for loopback. Origin/CSRF checks and Secure cookies use
|
||||
that external origin. Direct IP/localhost access still requires the real administration port.
|
||||
|
||||
### Publishing the panel through RustProxy itself
|
||||
|
||||
0.16.0 supports the explicitly opted-in same-process topology where a public HTTPS vhost proxies to the local administration listener. The reserved `rustproxy_session` cookie is passed through only when all of these conditions are true: the browser-facing request is TLS, its DNS hostname is explicitly present in `administration.allowed_hosts`, the selected backend directly addresses the configured administration socket by IP, and that backend has `accept_local_listener_risk=true`. The control-plane route is forced out of shared cache.
|
||||
0.16.0 supports the explicitly opted-in same-process topology where a public HTTPS vhost proxies to the local administration listener. The reserved `rustproxy_session` cookie is passed through only when all of these conditions are true: the browser-facing request is TLS, its DNS hostname either matches the pinned `administration.public_origin` or is explicitly listed in `administration.allowed_hosts`, the selected backend directly addresses the configured administration socket by IP, and that backend has `accept_local_listener_risk=true`. The control-plane route is forced out of shared cache.
|
||||
|
||||
This is a narrow exception to the normal isolation rule. For every ordinary backend RustProxy still removes `rustproxy_session` before forwarding requests and strips an upstream attempt to set that cookie in responses. DNS backend names are not accepted for the special self-admin cookie path. This prevents a normal application backend from reading or replacing the administration session.
|
||||
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
# 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 `Host` against 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 `https` and every hop is `http` or
|
||||
`https`. This permits `https, http` but not `http`, `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 `Origin` comparison, correct
|
||||
`Secure` session cookie and the existing CSRF checks. Existing
|
||||
`Sec-Fetch-Site: cross-site` blocking 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
|
||||
|
||||
```json
|
||||
{
|
||||
"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::validate` normalizes the public URL independently of the
|
||||
optional additional DNS list. `Administration::include_public_origin_host`
|
||||
generates 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_allowed` and `configured_admin_public_authority` continue 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 or `Sec-Fetch-Site: cross-site` or 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_risk` opt-in. No other proxy traffic changes.
|
||||
- No new configuration key or SQLite migration. Older `allowed_hosts` lists
|
||||
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](TEST_REPORT.md).
|
||||
|
||||
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
|
||||
|
||||
1. `bash scripts/resolve_dependencies_0312.sh` on a Rust 1.99+ builder with
|
||||
network/local audited dependencies; inspect regenerated lockfile.
|
||||
2. `./dev.sh check && ./dev.sh test && ./dev.sh release` and Go tests.
|
||||
3. 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.
|
||||
4. 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/session` and `Secure` cookie; no reload
|
||||
loops, blank screens, request floods, or leaked SSE/polling tasks.
|
||||
5. 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.
|
||||
+2
-1
@@ -31,6 +31,7 @@ This is the canonical index for the current project documentation. The authentic
|
||||
- [0.26.3 implementation note](V0.26.3_IMPLEMENTATION.md) - global interface scaling, chart themes, square draft dock and Settings navigation polish.
|
||||
- [0.26.2 implementation note](V0.26.2_IMPLEMENTATION.md) - unified Logs navigation, field-query search, dynamic JSON columns and RAW parsing.
|
||||
- [0.26.1 implementation note](V0.26.1_IMPLEMENTATION.md) - historical vhost-aware protocol logging and Stored Logs UX milestone.
|
||||
- [0.31.4 administration public Origin and settings](IMPLEMENTATION_0.31.4.md) - opt-in HTTPS origin, one-field DNS allowlist, additional hosts in advanced settings, legacy deduplication, CSRF and remaining deployment checks.
|
||||
- [0.31.3 mobile session / invalid-Origin recovery](IMPLEMENTATION_0.31.3.md) - deterministic BFCache and visibility session coordination, 403 diagnostics, recovery and remaining acceptance.
|
||||
- [0.31.1 mobile panel bootstrap](IMPLEMENTATION_0.31.1.md) - panel script readiness, versioned assets, failure recovery and acceptance tests.
|
||||
- [0.31.0 security audit](SECURITY_AUDIT_0.31.0.md) - current API/panel/BAN/PURGE attack-surface review, hardening and remaining runtime verification.
|
||||
@@ -82,7 +83,7 @@ This is the canonical index for the current project documentation. The authentic
|
||||
|
||||
## Historical or version-labelled context
|
||||
|
||||
Version-labelled `V*.md` files are retained as historical implementation/audit records. Their version number describes the audited milestone. The current release implementation note is [IMPLEMENTATION_0.31.3.md](IMPLEMENTATION_0.31.3.md); [IMPLEMENTATION_0.31.1.md](IMPLEMENTATION_0.31.1.md) is retained as the prior mobile milestone; [IMPLEMENTATION_0.31.0.md](IMPLEMENTATION_0.31.0.md) is retained as the previous milestone; [V0.29.1_IMPLEMENTATION.md](V0.29.1_IMPLEMENTATION.md) is retained as historical context, and [V0.29.0_IMPLEMENTATION.md](V0.29.0_IMPLEMENTATION.md) is retained as the prior redirect/health/cache/log milestone; [V0.28.2_IMPLEMENTATION.md](V0.28.2_IMPLEMENTATION.md) is retained as the prior certificate-hardening milestone; [V0.28.1_IMPLEMENTATION.md](V0.28.1_IMPLEMENTATION.md) is retained as the prior external-path milestone; [V0.28.0_IMPLEMENTATION.md](V0.28.0_IMPLEMENTATION.md) is retained as the prior GeoIP/listener/cache milestone, and [V0.27.3_IMPLEMENTATION.md](V0.27.3_IMPLEMENTATION.md) is retained as the prior HTTP/3/cache-maintenance milestone, [V0.27.2_IMPLEMENTATION.md](V0.27.2_IMPLEMENTATION.md) is retained as the prior managed-ACME repair, [V0.27.1_IMPLEMENTATION.md](V0.27.1_IMPLEMENTATION.md) as the prior managed-runtime/list-sorting milestone, and older version notes remain historical evidence. The latest retained audit is [V0.25.5_AUDIT.md](V0.25.5_AUDIT.md); older audit files remain available for traceability.
|
||||
Version-labelled `V*.md` files are retained as historical implementation/audit records. Their version number describes the audited milestone. The current release implementation note is [IMPLEMENTATION_0.31.4.md](IMPLEMENTATION_0.31.4.md); [IMPLEMENTATION_0.31.3.md](IMPLEMENTATION_0.31.3.md) is retained as the previous mobile recovery milestone; [IMPLEMENTATION_0.31.1.md](IMPLEMENTATION_0.31.1.md) is retained as the prior mobile milestone; [IMPLEMENTATION_0.31.0.md](IMPLEMENTATION_0.31.0.md) is retained as the previous milestone; [V0.29.1_IMPLEMENTATION.md](V0.29.1_IMPLEMENTATION.md) is retained as historical context, and [V0.29.0_IMPLEMENTATION.md](V0.29.0_IMPLEMENTATION.md) is retained as the prior redirect/health/cache/log milestone; [V0.28.2_IMPLEMENTATION.md](V0.28.2_IMPLEMENTATION.md) is retained as the prior certificate-hardening milestone; [V0.28.1_IMPLEMENTATION.md](V0.28.1_IMPLEMENTATION.md) is retained as the prior external-path milestone; [V0.28.0_IMPLEMENTATION.md](V0.28.0_IMPLEMENTATION.md) is retained as the prior GeoIP/listener/cache milestone, and [V0.27.3_IMPLEMENTATION.md](V0.27.3_IMPLEMENTATION.md) is retained as the prior HTTP/3/cache-maintenance milestone, [V0.27.2_IMPLEMENTATION.md](V0.27.2_IMPLEMENTATION.md) is retained as the prior managed-ACME repair, [V0.27.1_IMPLEMENTATION.md](V0.27.1_IMPLEMENTATION.md) as the prior managed-runtime/list-sorting milestone, and older version notes remain historical evidence. The latest retained audit is [V0.25.5_AUDIT.md](V0.25.5_AUDIT.md); older audit files remain available for traceability.
|
||||
|
||||
Older context documents such as [INTEGRATIONS_040](INTEGRATIONS_040.md), [MANAGEMENT](MANAGEMENT.md), [SECURITY_AUDIT](SECURITY_AUDIT.md), [ACME_RFC2136](ACME_RFC2136.md) and older version audits remain available for traceability but should not override the current functional/technical documents above.
|
||||
|
||||
|
||||
@@ -114,12 +114,78 @@ accepted only when `administration.tls` is true. Plain remote HTTP remains avail
|
||||
"tls": true,
|
||||
"certificate_id": "admin-example",
|
||||
"allowed_hosts": ["admin.example.com"],
|
||||
"public_origin": null,
|
||||
"session_days": 30,
|
||||
"panel_compression": {"zstd": true, "br": true, "gzip": true}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Fixed public origin for a reverse-proxied panel (0.31.4)
|
||||
|
||||
For a **panel** reached through a trusted reverse proxy, set the optional
|
||||
`administration.public_origin` in **Settings → Administration** to the exact
|
||||
browser-facing HTTPS origin (for example `https://panel.example.test`).
|
||||
**Do not duplicate that hostname in `administration.allowed_hosts`.** Its DNS
|
||||
hostname is implicitly included in the runtime panel Host allowlist. **Restart
|
||||
RustProxy**; this is a restart-bound access-control setting. Omit it (`null`) for
|
||||
direct administration or when forwarded headers are canonical. This setting
|
||||
is not a wildcard, does not accept HTTP or a URL path, and requires a DNS
|
||||
hostname rather than an IP address. SQLite schema 9 remains unchanged.
|
||||
|
||||
The advanced **Additional allowed DNS hostnames** field controls independent,
|
||||
extra hostnames only. Older configurations containing both values are
|
||||
accepted; on editing/saving the redundant duplicate is omitted from the
|
||||
persisted list. If `public_origin` changes or is cleared, its old hostname is
|
||||
no longer implicitly allowed (unless explicitly retained as an additional host).
|
||||
|
||||
Example for the default **private loopback** administration listener behind an
|
||||
HTTPS-terminating proxy:
|
||||
|
||||
```json
|
||||
{
|
||||
"administration": {
|
||||
"allowed_hosts": [],
|
||||
"public_origin": "https://panel.example.test"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The last hop must connect from loopback or an existing explicitly trusted proxy
|
||||
CIDR. If it is not loopback, configure **only the actual proxy peer CIDR** under
|
||||
Forwarding → Trusted proxies; be aware the general trusted-proxy setting also
|
||||
applies to public traffic and must never include untrusted clients. The
|
||||
administration listener should not be reachable directly from external clients.
|
||||
Do **not** grant trust to public networks to work around Origin failures.
|
||||
|
||||
The pinned URL, not an element of `X-Forwarded-Proto`, defines the panel
|
||||
scheme/authority. A trusted, bounded proxy chain `https, http` (HTTPS on the
|
||||
browser side, HTTP on an internal hop) is permitted in this explicit mode.
|
||||
An `http` first hop, malformed/too-long protocol chain, duplicate/conflicting
|
||||
`X-Forwarded-Host`, different `Origin`, `Sec-Fetch-Site: cross-site`, and missing
|
||||
CSRF protection for state-changing requests still result in HTTP 403. The
|
||||
public proxy/vhost data plane is **not changed**, except that an already opted-in
|
||||
self-admin vhost can recognize the pinned hostname for its reserved panel session
|
||||
cookie; other upstreams continue to strip it. Credential-bearing certificate
|
||||
automation retains its original TLS/forwarding checks.
|
||||
|
||||
Prefer fixing the proxy's forwarding headers rather than configuring the pin
|
||||
when practical. For a TLS-only public entry, the closest trusted hop should
|
||||
**replace**, not append, forwarded headers using known values, for example:
|
||||
|
||||
```nginx
|
||||
proxy_set_header Host panel.example.test;
|
||||
proxy_set_header X-Forwarded-Host panel.example.test;
|
||||
proxy_set_header X-Forwarded-Proto https;
|
||||
```
|
||||
|
||||
Only use that `https` setting on an entry that actually verifies external TLS;
|
||||
never trust an arbitrary inbound `X-Forwarded-Proto`. If multiple intermediate
|
||||
proxy hops must append protocol values, the pinned mode provides an explicit
|
||||
server-side comparison target without accepting a client-supplied origin as
|
||||
authoritative. Confirm deployment using a real mobile browser and the safe
|
||||
`administration origin rejected` logs before production rollout.
|
||||
|
||||
**Administration → Panel content compression** (next to the admin listener/TLS settings)
|
||||
controls Zstd, Brotli and gzip for the authenticated HTML document, JavaScript,
|
||||
CSS and language bundles. Each browser request is negotiated using
|
||||
|
||||
+27
-1
@@ -1,4 +1,30 @@
|
||||
# Verification report - RustProxy 0.31.3
|
||||
# Verification report - RustProxy 0.31.4
|
||||
|
||||
## 0.31.4 reissue - origin protection and one-field administration public URL (2026-10-11)
|
||||
|
||||
- **Reissue verification (2026-10-11):** rebased the complete previously prepared simplified-config source onto release identifier `0.31.4` in the manifest, existing resolved-lock snapshot package entry, OpenAPI, source/version checks, documentation and deterministic ZIP metadata. Merged the panel Origin and one-field config release notes; no backend validation logic changes were introduced during this renumbering.
|
||||
- **PASS — executed for this reissue:** `version_contracts.py`, `docs_check.py`, `langs_check.py`, `check_source.py`, `release_smoke.py`, `security_contracts.py`, `dependency_upgrade_0312.py`, `rust_module_contracts.py`, `system_info_no_subprocess_contracts.py`, `source_module_contracts.py`, `memory_contracts.py`, `web_server_contracts.py`, `admin_surface_audit_0310.py`, `security_dependency_contracts.py`; 15 standalone Node frontend/simulation scripts (including the updated original 53-case and expanded 61-case administration simulations and 9 UI scenarios); `go test ./...` for `docker-agent`; `bash -n dev.sh` and OpenAPI JSON parsing.
|
||||
- **Not executed for this reissue:** full Rust compile/test/release (`cargo` and `rustc` absent), real mobile Safari/Chrome or live reverse-proxy verification. Passing Node simulations is **not** proof of compiled Rust behavior. The inherited Cargo.lock requires verification and/or re-resolution on the declared Rust 1.99+ toolchain.
|
||||
|
||||
- **Scope:** panel administration settings/config and narrow self-admin cookie opt-in only. `allowed_hosts` stores only additional DNS hostnames; the HTTPS pin contributes its hostname to the active/desired Host allowlist without persisting a duplicate.
|
||||
- **Compatibility:** older configurations with a duplicate hostname remain valid; validation canonicalizes the in-memory config and the next save omits the duplicate without an automatic SQLite migration; changing/removing the pin does not silently retain a stale implicit hostname.
|
||||
- **Security:** Host, single forwarded Host, trusted reverse proxy, first-hop HTTPS, CSRF and `Origin`/cross-site guard rules are unchanged. No SQLite schema migration, TLS listener or other vhost route changes.
|
||||
- **PASS — source/release gates:** `version_contracts.py`, `rust_module_contracts.py`, `check_source.py`, `release_smoke.py`, `docs_check.py`, `langs_check.py`, `security_contracts.py`, `admin_surface_audit_0310.py`, `security_dependency_contracts.py`, and `dependency_upgrade_0312.py`; SQLite schema-9/upgrade smoke and the source/HTML/Reader/translation checks also passed.
|
||||
- **PASS — focused simulated regressions:** `admin_public_origin_0314_config_sim.mjs`: **61/61 local Node HTTP guard-simulation assertions**, comprising 37 cases and 24 repeated session GETs. Invalid forwarded-proto on 3/12 unpinned GETs produced 403; with a pinned HTTPS public URL and **no duplicate DNS allowlist entry**, the same sequence produced 0/12. This simulation is not compiled RustProxy and cannot prove actual mobile/proxy headers.
|
||||
- **PASS — real frontend helper/UI checks:** `admin_panel_config_0314_ui_test.mjs`: **9/9 settings-normalization scenarios** plus form/HTML/locale/OpenAPI consistency; `panel_cache_http_0310_ui_test.mjs` also executes the updated real administration form callback with a duplicated legacy hostname and verifies it is omitted on save.
|
||||
- **PASS — other standalone checks:** all 14 bundled Node UI/simulation scripts, memory model (300,000 simulated PURGEs), 2,000 HTTP requests to a **Python mock peer** (not RustProxy), and `cd docker-agent && go test ./...`.
|
||||
- **Harness and compiler limitations:** an initial `./dev.sh test` run exposed a legacy UI-test harness that lacked the new form helper; the harness was corrected and passed separately. A subsequent monolithic run exceeded the execution time limit after the memory/load-model stage, so no complete `./dev.sh test` success is claimed. `./dev.sh check` exited **127** because `cargo`/`rustc` are not installed. New Rust tests in `src/settings/tests.rs` and `src/admin/tests/part_11.rs` were **not compiled/executed**. The inherited Cargo.lock still needs resolution on Rust 1.99+; live mobile and real reverse-proxy acceptance remain OPEN.
|
||||
|
||||
### Earlier 0.31.4 validation baseline (2026-10-11)
|
||||
|
||||
- **User scope:** admin panel only; public vhosts, proxy forwarding, streams and database migrations are unchanged. The 0.31.3 BFCache/session resume and visible retry protections are retained.
|
||||
- **Source correction:** optional `administration.public_origin` pins a browser-facing HTTPS panel origin to a configured DNS allowlist; it takes effect after restart and only with a trusted last-hop proxy. It accepts bounded `X-Forwarded-Proto: https, http` without relying on an ambiguous forwarded-proto element for the expected origin. Without the pin, 0.31.3's strict handling remains.
|
||||
- **Security:** actual Host is validated, forwarded Host must be single/matching, the protocol's first hop must be HTTPS, and Origin, cross-site and CSRF checks remain. Untrusted peers and credential-bearing certificate automation retain their previous scheme validation. The earlier build required explicit `allowed_hosts`; the reissue now derives the pinned hostname without wildcard trust.
|
||||
- **Node HTTP guard simulation (not compiled RustProxy): PASS, 53/53 assertions**, including 29 individual scenarios and 24 repeated session GETs: without the pin and with intentionally malformed XFP every fourth request, **3/12 GETs returned 403**; with an explicit pin and otherwise identical simulated headers, **0/12 returned 403**. `tools/checks/admin_public_origin_0314_sim.mjs` checks relevant code anchors and operates a local Node HTTP test peer. This does not confirm production headers or backend runtime behavior.
|
||||
- **PASS (2026-10-11):** `./dev.sh test` passed every available Rust-independent stage: source-module boundaries, version/contracts, SQLite schema-9 migrations and upgrade, security source/dependency checks, 300,000 PURGE-model iterations, 2,000 real HTTP requests to a **Python test peer**, all bundled Node/browser UI regressions (including BFCache/resume), 0.31.4 HTTP simulation, documentation and both language packs. Exit **127** at the missing Rust/Cargo toolchain guard only. The simulated peer is **not** a running RustProxy binary. `./dev.sh check` likewise exits 127 due to missing Cargo.
|
||||
- **PASS:** `(cd docker-agent && go test ./...)`, `node --check tools/checks/admin_public_origin_0314_sim.mjs`; source-package size/integrity checks are performed separately.
|
||||
- **Rust regression tests added, NOT EXECUTED:** `src/settings/tests.rs` and `src/admin/tests/part_11.rs` cover pin validation, restart, strict opt-out, session Secure cookie, CSRF, foreign Origin, cross-site, host spoofing and untrusted peers.
|
||||
- **Remaining release gate OPEN:** the sandbox has no Rust/Cargo/rustfmt and the inherited lockfile remains unresolved against 0.31.2+ dependencies. Before production use: resolve on Rust 1.99+, format, clippy, test/build, then capture actual mobile Safari/Chrome and reverse-proxy traffic and run the acceptance matrix in [IMPLEMENTATION_0.31.4.md](IMPLEMENTATION_0.31.4.md). No claim is made that the original field `auth.invalid_origin` was directly observed or eliminated.
|
||||
|
||||
## 0.31.3 - invalid-Origin resume and mobile panel recovery (2026-10-11)
|
||||
|
||||
|
||||
Reference in new issue
Block a user