From 174389e5b1d4d8aa58784a499818210ed4577453 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Gruszczy=C5=84ski?= Date: Sun, 4 Oct 2026 16:47:24 +0200 Subject: [PATCH] v0.25.2 --- AGENTS.md | 2 +- CHANGELOG.md | 6 + Cargo.lock | 2 +- Cargo.toml | 2 +- README.md | 24 +-- ROADMAP.md | 13 ++ SHA256SUMS | 92 +++++------ debian/README.md | 2 +- debian/build-package.sh | 2 +- debian/changelog | 8 + dev.sh | 4 +- docker-agent/model.go | 2 +- docs/API_ACCESS.md | 7 +- docs/INSTALL.md | 2 +- docs/MIGRATION.md | 6 + docs/README.md | 5 +- docs/SECRET_STORE_BACKUP.md | 9 +- docs/TEST_REPORT.md | 4 +- docs/V0.25.2_AUDIT.md | 28 ++++ grafana/rustproxy-overview.json | 2 +- make_zip.py | 2 +- scripts/check_source.py | 26 +-- scripts/deploy_core.py | 2 +- scripts/docs_check.py | 11 +- scripts/preflight.py | 6 +- scripts/release_smoke.py | 12 +- scripts/swagger_assets.py | 2 +- scripts/upgrade.py | 6 +- scripts/web_server_contracts.py | 4 +- src/admin/documentation.rs | 1 + src/admin/openapi.json | 2 +- src/admin/web/app/core.js | 20 ++- src/admin/web/app/runtime.js | 2 + src/admin/web/app/settings.js | 2 +- src/admin/web/index.html | 4 +- src/auth/files.rs | 13 +- src/auth/mod.rs | 2 +- src/backup.rs | 1 + src/backup/archive.rs | 31 +++- src/backup/snapshot.rs | 244 +++++++++++++++++++++++++++++ src/backup/tests.rs | 64 ++++++++ src/backup/validation.rs | 189 +++++++++++++--------- src/db.rs | 20 ++- src/main/bootstrap.rs | 4 +- src/main/tasks.rs | 2 +- src/web_server/fastcgi/protocol.rs | 2 +- src/web_server/response/file.rs | 2 +- src/web_server/tests/part_02.rs | 2 +- 48 files changed, 688 insertions(+), 212 deletions(-) create mode 100644 docs/V0.25.2_AUDIT.md create mode 100644 src/backup/snapshot.rs diff --git a/AGENTS.md b/AGENTS.md index 2d351e0..90c243a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,7 +2,7 @@ Read README.md, ROADMAP.md and docs/TEST_REPORT.md before making changes. The project name is RustProxy; the Cargo package/binary remains `rust-proxy` for -compatibility. Current source version: 0.25.1. +compatibility. Current source version: 0.25.2. ## Delivery diff --git a/CHANGELOG.md b/CHANGELOG.md index 044dbb3..2db589b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ > test-only changes and packaging implementation notes were moved to > [`docs/CHANGELOG_TECHNICAL_ARCHIVE.md`](docs/CHANGELOG_TECHNICAL_ARCHIVE.md). +## 0.25.2 + +- Allow password-encrypted full backups while RustProxy is running by using a consistent SQLite snapshot and a private staged copy of durable filesystem state; restore remains offline and atomic. +- Preserve validated internal relative symlinks in backup/restore, including Certbot `live/` links into `archive/`, while rejecting absolute, escaping and dangling links. Version-1 backup archives remain restorable. +- Recover a restored/resumed authenticated dashboard tab from a transient `auth.invalid_origin` response with one bounded same-origin reload; server-side Host, Origin, request-marker and CSRF validation remain unchanged. + ## 0.25.1 - Reorganize **Traffic** into compact Overview, Clients, Requests and Delivery sub-tabs and visualize all bounded analytics dimensions with donut or horizontal bar charts instead of requiring a long page scroll. diff --git a/Cargo.lock b/Cargo.lock index 817e0d8..7ae43ab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1688,7 +1688,7 @@ dependencies = [ [[package]] name = "rust-proxy" -version = "0.25.1" +version = "0.25.2" dependencies = [ "anyhow", "arc-swap", diff --git a/Cargo.toml b/Cargo.toml index f67daa0..970e15a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rust-proxy" -version = "0.25.1" +version = "0.25.2" edition = "2021" rust-version = "1.88" description = "RustProxy - independent reverse proxy with local administration" diff --git a/README.md b/README.md index d3eb6e5..4ae4db6 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ -# RustProxy 0.25.1 +# RustProxy 0.25.2 Independent Rust reverse proxy and static Web Server with a dashboard, SQLite configuration, streaming HTTP/TLS/WebSocket forwarding, raw TCP/UDP stream hosts, IP-bound vhosts and backend pools. -**0.25.1 release source.** +**0.25.2 release source.** ## Layer-4 streams @@ -11,13 +11,13 @@ Raw TCP/UDP forwarding has its own **Streams** page in the primary navigation, a Named HTTP/HTTPS listener bind addresses use the same chip-style editor as vhost hostnames, including multi-address listeners. -## What changed in 0.25.1 +## What changed in 0.25.2 -- Reorganize **Traffic** into Overview, Clients, Requests and Delivery sub-tabs so per-vhost analytics stay compact without a long page scroll. Render all bounded traffic dimensions as charts: donuts for low-cardinality distributions and horizontal bars for User-Agent, client IP, path, route and byte-volume rankings. -- Keep the Rust source-tree 500-line ceiling as a release contract, isolate unit tests from implementation files and strengthen split-module regression checks for dangling attributes/imports. Split the Docker Agent and oversized dashboard source bundles into bounded source modules while preserving the existing browser asset URLs. -- Fix the first Web Server module split regressions around `htaccess`, `server_protocol`, `anyhow::Context` and dangling derive boundaries; compiler-enabled `cargo check` remains a release-host gate when the packaging workspace lacks Rust. +- Create password-encrypted full backups without stopping RustProxy. SQLite is captured with a consistent snapshot and durable files are copied into private staging before the encrypted archive is written; restore remains offline and atomic. +- Preserve safe internal relative symlinks across backup/restore, including Certbot `live/` -> `archive/` certificate links. Absolute, escaping and dangling symlinks are rejected instead of being followed. +- Recover restored/resumed authenticated dashboard tabs from a transient invalid-origin state with a bounded same-origin reload. Server-side Host, Origin, request-marker and CSRF checks remain strict. -See [Web Server](docs/WEB_SERVER.md) for configuration and security details, [documentation index](docs/README.md) for the current documentation set, and [0.25.1 release audit](docs/V0.25.1_AUDIT.md) for point-by-point release status. Open follow-up work is tracked separately in [TODO](docs/TODO.md). +See [Web Server](docs/WEB_SERVER.md) for configuration and security details, [documentation index](docs/README.md) for the current documentation set, and [0.25.2 release audit](docs/V0.25.2_AUDIT.md) for point-by-point release status. Open follow-up work is tracked separately in [TODO](docs/TODO.md). ## Documentation @@ -25,7 +25,7 @@ See [Web Server](docs/WEB_SERVER.md) for configuration and security details, [do - [docs/README.md](docs/README.md) - canonical documentation index. - [docs/WEB_SERVER.md](docs/WEB_SERVER.md) - Web Server configuration, HTTP behavior and filesystem security. - [docs/PHP_FPM.md](docs/PHP_FPM.md) - implemented FastCGI/PHP-FPM integration, safety model and runtime requirements. -- [docs/V0.25.1_AUDIT.md](docs/V0.25.1_AUDIT.md) - 0.25.1 Traffic/modularization release audit and compiler gate. +- [docs/V0.25.2_AUDIT.md](docs/V0.25.2_AUDIT.md) - 0.25.2 live-backup/symlink/origin recovery release audit and compiler gate. - [docs/V0.25.0_AUDIT.md](docs/V0.25.0_AUDIT.md) - Web Server foundation specification audit. - [docs/TEST_REPORT.md](docs/TEST_REPORT.md) - verification results and environment limitations. - [docs/TODO.md](docs/TODO.md) - unresolved follow-up only. @@ -91,7 +91,7 @@ CARGO_BUILD_JOBS=1 CARGO_PROFILE_RELEASE_LTO=false \ CARGO_PROFILE_RELEASE_CODEGEN_UNITS=8 cargo build --release --locked ``` -The 0.25.1 source archive preserves the resolved `Cargo.lock` supplied with the source tree and +The 0.25.2 source archive preserves the resolved `Cargo.lock` supplied with the source tree and updates only this package's own version entry. This packaging environment has no Cargo, so the dependency graph was not re-resolved here. On the compiler-enabled deployment host, run the locked check/test/release gates below and retain the verified lockfile. `dev.sh` can generate a @@ -308,10 +308,10 @@ language JSON files survive the update; shipped files replace their old names, whose previous bytes remain in the backup. Unknown custom source edits are not merged. Do not overlay individual source files onto an old tree. -RustProxy 0.25.1 keeps the existing SQLite schema version 7. Web Services and vhost/location +RustProxy 0.25.2 keeps the existing SQLite schema version 7. Web Services and vhost/location source selection are stored in the normal revisioned configuration document, so no separate database migration is required; omitted source fields still default to Reverse Proxy. Once a -0.25.1 configuration containing the new fields is saved, an older binary may reject unknown +0.25.2 configuration containing the new fields is saved, an older binary may reject unknown configuration fields. Roll back using the pre-upgrade data backup as well as the old source, not just the binary. @@ -329,7 +329,7 @@ python3 scripts/release_smoke.py # Runs those checks first, then the Rust suite when Cargo is available: bash dev.sh test # Source-only package, with no Git checkout required: -python3 make_zip.py /tmp/RustProxy-0.25.1.zip +python3 make_zip.py /tmp/RustProxy-0.25.2.zip ``` ## New management and test-origin guide diff --git a/ROADMAP.md b/ROADMAP.md index 5fe1736..d5c94e7 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1,5 +1,16 @@ # RustProxy roadmap +## 0.25.2 - Live backup and resumed-session hardening + +Current direction: 0.25.1 -> 0.25.2 + +- [x] Allow encrypted full-backup creation while RustProxy owns the data-directory process lock. +- [x] Snapshot SQLite consistently and stage durable files before archive encryption. +- [x] Preserve only validated relative symlinks that resolve inside the staged data tree, including Certbot `live` -> `archive` links. +- [x] Keep restore offline, validated, staged and atomically activated. +- [x] Recover transient invalid-origin state in restored/resumed admin tabs with a one-shot same-origin reload, without weakening server Host/Origin/CSRF checks. +- [ ] Run compiler-enabled `cargo fmt --all`, `cargo check --locked --all-targets` and `cargo test --locked --all-targets` on the release host. + ## 0.25.1 - Traffic UX and source modularization Current direction: 0.25.0 -> 0.25.1 @@ -9,6 +20,7 @@ Current direction: 0.25.0 -> 0.25.1 - [x] Keep every Rust source file at or below 500 lines and keep unit tests outside implementation files. - [x] Split oversized dashboard source bundles and the Docker Agent into bounded source modules while preserving behavior and URLs. - [x] Guard the Web Server split against dangling attributes and missing `htaccess`, `server_protocol` and `anyhow::Context` imports. +- [ ] Run compiler-enabled `cargo fmt --all`, `cargo check --locked --all-targets` and `cargo test --locked --all-targets` on the release host. ## 0.25.0 - Web Server foundation @@ -35,6 +47,7 @@ Current direction: 0.24.10 -> 0.25.0 - [x] Keep Web Server inside the existing routing, ACL/auth, custom-header/error-page/security and H1/H2/H3 response pipeline; do not create separate routing/error/cache subsystems. - [x] Add unit/source-contract coverage, OpenAPI/UI language coverage and Web Server documentation. - [x] Clean README/Docs/CHANGELOG/ROADMAP material, add a canonical docs index, authenticated Markdown Reader, 0.25 specification audit and open-work TODO. +- [ ] Run compiler-enabled `./dev.sh fmt`, `./dev.sh check`, `./dev.sh test` plus live H1/H2/H3/static-file acceptance on the release host. ## Web Server follow-up diff --git a/SHA256SUMS b/SHA256SUMS index e7fe3c8..b5d33cf 100644 --- a/SHA256SUMS +++ b/SHA256SUMS @@ -1,19 +1,19 @@ 61bf40a0572889dc8b0dda9eae4e93e44b0de9364b3c798106c85700ffb37c23 .dockerignore 4140a2956264b44f259e6a37fc00ba02097bbe68b09296e04d3d243c1287129b .env.example 9e6356475bf587091bab72246ec9e325b498f7e48a6220e9a18baa50d0a385a6 .gitignore -b170ebacc706ae4fd4ccfd1e0e39ec09b32339a1d4c0c09e8c4a256a30b6088a AGENTS.md -c7267b5be9b10b758c56a7bb6201378bb07e92d945c3068ac7057011414eb61b CHANGELOG.md -2de8e335cb69d6e2375d9c40e5dfa92b9259d475bc91db9da1e8aea5178d1c0e Cargo.lock -37ebd727f68dc6552d83927466b41d020677acf8b61cb5c6a8606e6dc0d349a6 Cargo.toml +871884dcd2f86bd4ef429940a03da356836fd9d29af00008bbf4688d10cd79df AGENTS.md +fd5d1f9be9487eb58bfa55a2eb138e1ef675fe32c3bfd069d6d81e486fe5c592 CHANGELOG.md +547274c990951a99d2a40f2ad86363da4dc3110d0d6384c985a61152c90a8024 Cargo.lock +d918e60ed622b589f1ad116e78ecbdf24149d2a144025fbfde4a5b6513b9d181 Cargo.toml d6d22ba63f4bc3d16a13012ecaabff8a156be57c824d2f20f66976502fb27d1f Dockerfile -2e96d60cb489209c0c24344738ee7b2e2228b0c56e5e179b02040df5cf52a0ce README.md -183292392aab167a5fc0172c0059530e8f098d39958f07bcd7b843c0d625b85f ROADMAP.md +a8f3a799da3ef63b9e95109c01e806b6e4ee9e0190ae0b3f046bf0b810f117ee README.md +3d1defbb40639dc707945297165eeab70795d25b6b0bdec1a333f9b59e06a16a ROADMAP.md da8311e3db945290666324a2f24bf4521b365272d7ac3f862ded1be993eea646 UPDATE_CHECK.toml 928ba6a504bc431ae100c6e587861e5088b28822d2cb19da5d437b39d26c864a build.rs -66f7c9aa12782d6e57b6a512c9cb10f623abd8cbb9ddd4582f19013e837964b5 debian/README.md -58e94c1c9fab848aca8ae2b79ab4ed071bcbc7841130e91c3a8e08114e3bf918 debian/build-package.sh +0543cfaad02787b04037321d101cff6052cad68275694386f5bbaa6f35e731d8 debian/README.md +4ca1bc64c9147f8f8705e81f7c3db3376ff84227ebac24c7f5516a0e24e5b203 debian/build-package.sh 0763a4bd8b429f7922b23a7276ff694f3a751b83fb127f6ad7ec654e5e507ecd debian/build-repository.sh -941c7b6c16e59f037c60b26278584819cbf4a898ce11f164ca94d0cac4644df4 debian/changelog +60b631c2ec8299d1dc82f11505a9a541dd46c0bc3acd35895734193331497f91 debian/changelog c90457b19b1b2f7998d3db59a656d215eb0fab23769197405960457b66d95cf7 debian/control dff2ecb9c14623416981f444bc6df1c4e2bf55d330260f36a312d8d42245400f debian/rules c537d54fd92a9b97c5f6e6ec63bc1276cda324513e2946b341b2193f63b62e6a debian/rustproxy.environment @@ -24,7 +24,7 @@ cb0db03a2c1b06fa04a11f7690755a0ca90b17f19ed8fdb228810f505c48c78a debian/rustpro e8e18df40bcd69d6aa404282679186d9b8256ac63a5b4e80d7f081e8a0095a2e debian/source/format 385ac7970fa94f043d0bcf04a91184e6e2c807587b85e55517ed064bb4bddba6 deploy/README.md 7b7fdf383bbaea4f37b5c120e98a24e9a075fba56ae7838a31a5152c7edcde02 deploy/rustproxy.service -565c83dde85cfb32af90950bd57ff0480c467bc97f65145806bb92ed8b1f4ced dev.sh +35a9e2248a62d9d06875a327c6925cdd3880a9e0ee7e9735b566c688ef5fbf8c dev.sh 5f51efe621f45512a466fa7f2656867c6502a135ca040f4bfeec06a6919b39b6 docker-agent/README.md 7fee7d51a7deb67e73e53a0bdff3aa4630aea9bab9b23a0f9b8001b2169892b1 docker-agent/certificate.go 5e4f565c3c9de4dba897af2e801ea646a83395e72eae4717a6d502dff2fcec10 docker-agent/docker-agent.env.example @@ -33,14 +33,14 @@ bf687a851374d8f1fa59d050f3f76532f7c12f16a6daa7d3c36102c2d9a8ec9c docker-agent/d 9d77c90767fa929ee8338734d72ce83284d3d965673270f62cb2b1613d587a33 docker-agent/install.sh b68234472bec5cedd67c7cfe1de7d8806a965dfd4101f944aa6320132f18c14e docker-agent/main.go 3d4051a1f805d145df3877476e4b87773e74be8e123045065955a5133afdc68c docker-agent/main_test.go -fb8c89f015e3a05edb16b84e0a76771a9ed9cb347d339ea41fefd788812f71a9 docker-agent/model.go +91b976d55742ae69bf31558edb5fabf9e6d79e63dfb2d1a10bd6f76cbf3974f5 docker-agent/model.go e52b31d0abf65ae3034f6cb367cca113633014ce8d1d799ad4dd5624d2c7b8bb docker-agent/network.go 634790f35ff4627df2a78cec2ed371f6e9ec9df00afb386075595fc7f3ed72eb docker-agent/rustproxy-docker-agent.service 5220d669171ec3ad65f50e0bc8de5178dadaa4904def8e1a4f61002aa9e9a55b docker-agent/server.go 802a30b793a6ae6a5046489612a23659f33d25e41d0794551381db202daac904 docs/ACME_PROVIDERS.md ec15163f5f51c9212003b7edd1fc380321311c2496fbaa9b45c838281b9af475 docs/ACME_RFC2136.md 6fb6dd879b83380187fc720e2668e412c3eacb66350e466b4aa1c3e6dba76edd docs/API.md -7598602a113e951bc1b0af5f1aedd3dae7c4486270599cc014f17ab552085f5a docs/API_ACCESS.md +c46fa66c4f6b56ce74456d557072c712964e8180d599d10adee4d4f20b6d8329 docs/API_ACCESS.md 0f7f159ad5b4b5277f80c19f3c47f4e7823ea439ef02e5913e1391f1b34d129f docs/ARCHITECTURE.md cd2597465af031cdb47db802f8ad6d3af34ef9e79f679df906a7b84220ea3260 docs/CACHE.md c25f3f6763c35dacf718b4b87c5665c2030b1fb3584e8c20940fc3ca7a2d26a3 docs/CACHE_DIAGNOSTICS.md @@ -50,28 +50,28 @@ c51ae0b6cacdd9b34f7b80872192d4a1e1c4e2ad697006d24855864c9e136a1b docs/CUSTOM_LO 7db65a96f696aff1d7f74e2041cfc6ecdf437b1412ece90cee26da1ceff4cf7e docs/DOCKER_DISCOVERY.md 9a882f07945b207d032936feb843f29467e4654f8255fa0104d988a54987f982 docs/ERROR_LOGGING.md df6ecd4a50796640243676ad4b3a87789f6e5d20cbebea6255e92fbfff1c7131 docs/HTTP3.md -3912d71181c50d4c96f53f592429e188f34bfee757e214c90c59b49acd181e34 docs/INSTALL.md +94dd9a1be26d95de773a0b5f07ad460988cd84e29833f6a724102383903c4c9a docs/INSTALL.md 3b5c31c805e5aee0dc2100b81246a9b631b5abb996faa598ae94f87ba39305f3 docs/INTEGRATIONS_040.md b30e6307d00580471a8ea44cdc88cbe2139676357f736660cb53f7120e8fae24 docs/LIVE_LOGS.md 7c35c4cdbe7bbb81d94f3bf6aeb22b8a866af3e7f0d62bbedc299e4d6f264e0d docs/MANAGEMENT.md -17571cbe8aa7de141bb6225d07231da6b92893c9bf3b654c07474caac8e7c3db docs/MIGRATION.md +75b2de691687af350c5cb6332f9a20d8c15ea1021813dde274a23b7f9a8a1293 docs/MIGRATION.md 5f090a190000c6badab2a74e5d4968e70c34de43b59c1452f5b05503b5c5c18a docs/MOCK_AUTH.md 1f85fac0e06e432ef6f744f0b5f9c1b7a27a1e9e1ad30dc1a218eab96b1caffe docs/MOCK_SERVER.md e7516646dfb107d6240370f4e7460b1189f2eb21f398740381d5d102847bbfe6 docs/NOT_FOUND_HOSTS.md 3da047768f32064c5ef0ad7a4c73c4b14930fe7317c54f5650f426af9f484593 docs/NPM_MIGRATION.md 712cb1cbf84515384b081fd2929e86e40af7c10f65bc69919b78b7b3e2fd12b5 docs/PHP_FPM.md 44e8b1940af190417574866911800ee5385ddce59c1f13c76dff35f02c134af8 docs/PROMETHEUS.md -51dc5a75acb7b58e894dd23427e32f75b062c770b9cf7772aebff90c07800edc docs/README.md +cf89251573e13f47344980c1ca4135dd01794914cb494d237d9d07c404cf6395 docs/README.md 6e46a137b9c88dac8cb9e3fdca877430092392de9913d746278dbff8652460c2 docs/REDIRECTS.md 0dfd0d431c5aedc67abac1ce5de040ffa790ec9578fd7d6f91a987c333d73358 docs/REFERENCES.md ce5afb4f531bb5fb4c453deb49f643f2a563f32f7908c995e5bc8e0d7de995b7 docs/RENEWAL.md 585ef0ac4492e03adca46949823e5a7538f56d9768090cd4dc080bfce4305b52 docs/ROADMAP_HISTORY.md -22c5ac843705fa34b2ed331b42b20b2e5fc657fe2601e42b1663d3d4114f7090 docs/SECRET_STORE_BACKUP.md +4bf7b3ea0bfe6c6b0ae0b7884b48b10ba29ff3a71611ec96957a3327d3c6ec88 docs/SECRET_STORE_BACKUP.md a699fd663bfbf001525f22f0fbacdf03a58c5e825232d8d144e51cbb8b1008b2 docs/SECURITY.md 620b69e566d27af04f0604f4ca3f88cf95cdfea7fbc6ae9dd92446d2b2b5d9be docs/SECURITY_AUDIT.md 559a0653ec7d4bc05d677408f91931881be03bc49b87ead629d2cafab9af2916 docs/SETTINGS.md 3611defd206cde107e75cac006159856d12cd8f15df7ae2eb9ec3592ed2b4abd docs/STREAMS.md -bf9fda355405b92309300cb2639a453b780ec2ebbefc8a9374174af41d8804fa docs/TEST_REPORT.md +68f8985f169620ee6cc571cb4aba146466ffade4bf21af096bef979454f6fb90 docs/TEST_REPORT.md cfb452d8496839e832b5fbacdd4e52525b0ecc3112fbb62340c1e580bd5c1717 docs/TODO.md 8ee6187a5460ca45643b7fd789e27bffd264449e3a92ff70e9359edee011bbbb docs/TRANSFORM.md b02957b708174c9def830ced3c70c943a47af7a33467ef858255d04ab92c4af0 docs/TRANSPORT.md @@ -95,6 +95,7 @@ d0e609febbcd1cfdb8ea0b00b4fa6f081eac01e2d43a81e38a53ae25783152cc docs/V0.24.8_A 6131ef0ac65af2c9544d9d44ecfb680587cb358b37efd9b2d1cf69f26e935861 docs/V0.25.0_AUDIT.md 1a8a07eb150100e6d5ab8443dac664038e1d838033609df168aaf60a15aeb84f docs/V0.25.0_SECURITY_PREPROD_AUDIT.md 32c4053d48e8f24eb6c3395a4bbbc18f1c9790601166c73fb83173a14142789e docs/V0.25.1_AUDIT.md +41793fe2d0fa4a2bd88448f5b3870649f08eeaadd4cd420820647d332e7fdb5a docs/V0.25.2_AUDIT.md d64070b2186f9971862e9c929373d90a38022a3a9f3030e2014e280cd7b392f8 docs/VHOSTS.md e32472cea5ef3cd25c058e55df8adfcc1d7bf03e0d12dba69d2f2b9b94fe04b7 docs/VHOST_ACCESS.md f0e76167944861517d1d42b9186ff0b2f184be0371b9e8597827e60895de4a76 docs/WEB_SERVER.md @@ -110,13 +111,13 @@ b26833cb4251f9390a44192ecc827ff7f052fd19016a5ccf7498c94503ab4ce3 examples/mock- 17e7971fa9d9b141019fba3135d87cb1aa0cbc55d56b0718f879f880b615da26 examples/vhosts-balanced.json e06905b6363103b1bcee1a09b9e02292033f114d88f3edbea22ddab53e9e5071 examples/vhosts-private.json 529b47c6d4ea44d2a25d1cf9561cb5ee1e05f50a568c6dba5889741bcf936434 grafana/README.md -a64b996085d39688a3c5c5dee986a3ecdf576a7ec4b7430091765678b1b44d7b grafana/rustproxy-overview.json +0cbe8c6805f62a884a81d5bf6c2d0ced49f269c1dd055237478f39ffca405f25 grafana/rustproxy-overview.json 30763fc61c38992a44bc24ad6932b88fd3b322724fce799db1f17f9a747eb59a install-debian13.sh 858b39fa6886b71d20690bf369e76580922364b3cc7f0cf6ba9b040df9cd76e3 install.sh 29cada0936762f26495064d1524a668262ed729b21f20aebddc8b6e5cfea363b langs/README.md 402b463e17362363f3199d743286d26fc69b2f4342c23d4b6747974d21451a28 langs/en_US.json 931dbbcb75711d5a52bce45a3e1b5fdd0eec2ae13eb7854ce322b2e3cebd8a5b langs/pl_pl.json -de2a01f795e2ca6dd52ca8de46d9f41b810a2e798678c3f8651a4cbf0df35814 make_zip.py +615786f55505353ffa79930e726eb76001f04070d3f1b719c899df583fe25c3d make_zip.py e830d37dd1f81a4b0aa7d535f5bba69e34e433b0fd99cc8ee0b296786c32379a migrations/0002_admin.sql 20112cc82192098b8d602f1c4f7ca08712e9e1366c845fabb92753187c370d35 migrations/0003_certificate_renewals.sql 5c62da30fa9db594a4d656c77ae676d3c388072b6a85f91170ec7d911d7cde43 migrations/0004_admin_preferences.sql @@ -134,11 +135,11 @@ edb6ee1445b01cd6f3a74a3a607f39fb107d411b49df028d2e0f4679e093de31 scripts/acme_p e69813fb1d3a4cdcfb7243964cc323fc21a603087bf2e84188fa42b9d3cee4b5 scripts/acme_rfc2136.py 7e524285bfa2e3dbde0391257660635b039f23b0b3807e9510df663f757a5ad8 scripts/admin_audit.py 20e83f8b6347b819d1d1ea3de5c691c36f0816954a4210b0d3f39557fefe1931 scripts/backup.py -23fd428ef2285f473b09cf2a945c562a9f0738913614b837190423f4654569fb scripts/check_source.py +23a42be7b6c405aa678576bdc316db50e6f09e8a7288cb04fed44d1bf6102d4e scripts/check_source.py 187ee92c8029cdd59e89b4447cf4d170e856017a2091ceb85ae7c22dd1a79c72 scripts/deploy.py -d1bba7e7e810b4bbd4fd6b0292c00099e93f34dae734b236328230112fac4292 scripts/deploy_core.py +d9eefaad26f831d07a2dc030ac39f13e6e6430c1fd94dace0073dba390c9aae2 scripts/deploy_core.py f014472588e889488976970dbaa95a8745b6907516aa81a7cca21d0f81eaaad3 scripts/deploy_runtime.py -741f49cafc470baab8a00779cb954cb78530ac51cefa344580d463a88dd3226b scripts/docs_check.py +0d6b407fcaa5adf674a9fe425b7c3ef5150d87cdbe18c575648e0f8a7f3e1f2b scripts/docs_check.py 6facb2d65bfab234f09855097d12da8f72669a38ebceb4b1cbd03ab42b8d2469 scripts/langs_check.py 2f846042b98a9148be944fb74a2b023b871ff884693a36847837228613a17679 scripts/memory_contracts.py 2cc658faaa610b21b48942c59b955525539ad4503fd60f2a49c7e626a22faaff scripts/migrate_npm.py @@ -148,8 +149,8 @@ d1418f8e574a7390fd45a2aa879bdf5720b773162d65e295ab0350506aa5220a scripts/mock_a 2d3ff92300b09ce9cecbbaaa9309b4d33160fa4b2291c9952b784e8cfb599ab5 scripts/mock_server.py 2d4c859aae5ba357a6d30024c8487ae8f59f01b192711f13f9c2f8fee0f48b50 scripts/os-acme-client/README.md c73b814a42bb4ff703441dbd177ee625055eeb115f9b5bb0c2150aede573a159 scripts/os-acme-client/opnsense-rustproxy-cert-deploy.sh -693907aa06a608600761b666fb0ee7ed081898c2b4664d2d134b216debdd9005 scripts/preflight.py -29d71ff431637282f540ed36214606443c17bc7e7c0dec0611cb4636e4c561bf scripts/release_smoke.py +e50e1ac1a55f9266da81cdf353638183c59f42fb9561209b18d8ba57dc7de4eb scripts/preflight.py +c0913cceb60ca9f82bb0966f384a966d3c34ad7624c0dcdef4b7a22b8424802e scripts/release_smoke.py 45be151cd85fc2ef2f1fdc3003ace63381af262e73af8a685ab48cb697cc0de1 scripts/routeros-oci-build/.env.routeros.example 3b48cff8506e5a6ae7d3876f47721f5de7d63e2e02264a78cf5b6ddd52d9fbc6 scripts/routeros-oci-build/Dockerfile.routeros 7c7a3e51f43b43e1c962a4b0bb591f99c1f06977bf77f23719f7f867dfe7eb8e scripts/routeros-oci-build/Dockerfile.routeros.dockerignore @@ -161,11 +162,11 @@ ecfe0bb21a7549225a19b0846bb6de8c99cbfbbf5a706d33cf84db2c7146abed scripts/rust_s 1c83054336b3508d7ad72cd259f039394682642439877fe69639c0b76be09154 scripts/security_contracts.py 295b6f2625b1875950ea3eecccd142f49be327afdd00c92641286e6de530ec51 scripts/security_dependency_contracts.py 2470b4b4625070fd896ae8438f7cb672ce3bfa8ffab328e2d83f2fd3b5f0af31 scripts/source_module_contracts.py -732645adaefae38cca28901ce3786d5ac5756030bc5aa879400a9d90801d5225 scripts/swagger_assets.py +e4d58ea628547a9d5b0247f320cd4c655caf83b8a942f07bfdc8faf1ab808a59 scripts/swagger_assets.py 102b23de19a88a8e60de392fb83aefd3dfb33e90cc97ed1690d9f3d4a33d8866 scripts/test_compression.py -1f4d60d918939e7e7cbd9e13b0ed40668f0c40b16317cfc4ab3d3d0733d83670 scripts/upgrade.py +115093b451c8be421652e23d3661f8fe3606eaa7569a65ca97fdfe88c1f07491 scripts/upgrade.py ea20bd340ecf3687c2721c522b4da09e88498de683bc4288bb85202969fd3d75 scripts/web_assets.py -b630b5221730a9e7939c8751acbea8c046ebe2832ae265f3f825785041e9a0f1 scripts/web_server_contracts.py +c2b4ff7f13d1b1e4887d465fd35f1f8c2a431e642c54190dad9644d012238889 scripts/web_server_contracts.py b8ce3e8fd08865015ce312fae9c0457612af18d6ef4513a6281b9e3212f8d458 scripts/web_service_ui_test.mjs d8e2b51c39252dbb4305512fa9b1ef508ec610968aed316b10514fd160298762 src/acl.rs e8ffed9a9f50a467062d0758aec6aefdc8a39615282b2eef73603d08bc33c00a src/acl/tests.rs @@ -177,11 +178,11 @@ b9e57db4bd80ef37564dfc533a96897d8a494671d53acea9fb4bf28d23ba370d src/acme/provi 7acbaf34e7b953b753ab074893e47b5024f0cd756dc4c7a1074067dd936c2e8e src/acme/tests.rs 8a07a8c9d845e55ffa47332d4379e8ffdc2cee09b22e6c6396edb8e768b24540 src/admin/access.rs 21e4ae7e72b0c86ba674bc820aabdd201cd62126fdd09ba22cc4b420e38ad300 src/admin/certificates.rs -e414672b1a7f8c083fe7020405ca05df3cbce193139b7247ed74cbaea8fc8954 src/admin/documentation.rs +ae1ac6507a66bf2d0b27e6e4df99fe31bd81a52e616784eed2803730ba234f6f src/admin/documentation.rs 67b901485d5858d5b5bf78a6260035db32241531d99481754c3533fdf404facb src/admin/documentation/tests.rs cc5c84d8476e2ba11c3a4b1996ca616eb402bb0aea734434ac6e185e1041736e src/admin/logging_api.rs 468b6062cb806ef489bc107f69d4725dbf9c08d7e18d15b4482ae0f309c15d2f src/admin/mod.rs -27543e3a5034cae663827a3ec2bd93c3a0e70c105abbfe45be16c2f8418de1b6 src/admin/openapi.json +4a8df2959f43c71b12a6d5d77613137ae508408a0baf5d30900dffad1c042c58 src/admin/openapi.json 04ac147a823a1b5510977185fc10b4f5e3fddaa76c00ee73ab5209782dd4d859 src/admin/operations.rs b4fa393b780b2676b5d72e0f90cd7290292f424b8062d4b5983e722d6ca72c32 src/admin/public.rs b7a1b9535d01db8b9cb0f97048203dffd55404b331ec8e0386c63e8c417bf19a src/admin/router.rs @@ -195,9 +196,9 @@ b31d594499d7f8164c14b66dd77971b9985cefd99bd4d3686f9ea26e11e5a59c src/admin/test 7c1c18d2d07311f7fcffd2872cca6d5e56212242e4ab7a8912395b4df6d3dcd2 src/admin/tests/part_04.rs 829d0ef87b6d0c4d2a8a9e1e299c27d2d84f83a73ea222df45b0384abc2e2da8 src/admin/tests/part_05.rs 23c55f23deeec844a9abc2289440c9e0583aaeb72416fc5f3632a43a864c0efd src/admin/web/acme.js -c4f879c172e30e569ef2bd4003abdd73b3b3d775943a69c38157f011ae0ef9df src/admin/web/app/core.js -d164400471f97860792fd7c0726df9ca807cff9076b122e80337f7511a31ba80 src/admin/web/app/runtime.js -ad8fc39dc4b3227730d8707b1091f80f366827c17f47dc54567acea6c23c1fdd src/admin/web/app/settings.js +f97188d3c9dcd60b19ff1a18f1e3eb5c942c996fbefe805fdc2ca07fa9785139 src/admin/web/app/core.js +19abc332a1009bcb087fe4ebb58d458739b27480ab50715b03d07fd408302942 src/admin/web/app/runtime.js +006952bbdd922772814276e37b9d3f1ca4d93b1d1ca266972647743aadadf78f src/admin/web/app/settings.js d0be71555de13f732fd6ee44438a0110eb08c91e78bbc1aa36a1a84182780210 src/admin/web/cache-panel.js 50d1ebcbaf4d0b2968da23bb943fc3f67131c5d32df3f61d23ff37aad9da5b81 src/admin/web/demo.html 3b84a7a08f9f68e2c78fed4523b594c31a251addc5480a69c5f6949908230539 src/admin/web/docker.js @@ -212,7 +213,7 @@ f7d0955ab18c15e04369204e789f341d2e832f927c1ff20a3b2b9e35e7898f00 src/admin/web/ e6c4d91d1542d0a5b47bb8736f9539d43a6713df5a473b635519d5b7eac2eb9c src/admin/web/error_504.html 0a5fff35555153f59d4346933030a67b6e1a8768cee7a47cfa2fcf1a2c564f63 src/admin/web/favicon.svg dee3b113907f77138cdf919efdfb097e45b889cff3893bfc6f02ed998643bbae src/admin/web/host404.js -6370e91e80e5ddb049a332e9269afcc60ee9e8160408a82b5778d02c1f989692 src/admin/web/index.html +5ac6490404f1f23dd558d5b3f5a07b9098c2813b502b09b7efc272a6c1fe4f59 src/admin/web/index.html 24db88842707f764064657712d3660c6ac129fbfb47d188c0903925cf8244ce5 src/admin/web/integrations.js cdab0c11a28deebdf401c0453fbc76b675e9e643722106fb20e812bb9648ccde src/admin/web/login-keys.json a253d202dc9a147f2add34c64e0f35c38fdc7f667f9908933ce5f959b1a92a2e src/admin/web/login.html @@ -228,15 +229,16 @@ f42f079f648ce80ec6c55d281d73c9dcc69a120e6cdb7e3eb32edda6bf99aecc src/admin/web/ 3c483d7b1eb69455d7bceb83f44f2b8fd60e02b2ec0dd03f4b69308525fa54a4 src/admin/web/vhosts/editor.js 769383dfc5afa28e01a1e99c9eebcce39fc9457fba0602992f750acab9911236 src/admin/web/vhosts/render.js 0de35a0671f3ac8fed62260d63ae43d04efce4947428ebdb9254cff0295295f9 src/admin/web/web-server.js -42c99af2cd73ab9d257e076d38c1b1a81e743ed04e96c440ad9617a394fbbf6a src/auth/files.rs +98c00d99822f9be54fc58afb6d4b577901712effd2c5eddce014312681864625 src/auth/files.rs b2f948bcc3dbd24952c7b1b02d41d06757f49f0c38a78caf51f8f8a687d8480d src/auth/files/tests.rs -d19f47e061d5965eb3f4100063c396f8438f14e14c0fe5e9d4d32f82f72dddb7 src/auth/mod.rs +e0b37c58d4c391d8f4fa4b1af38a6b53d5107789584797440afb6ef20a711f3b src/auth/mod.rs 1ceb57d97cbff655f185e126d98b8dc7884c7aac865e1f70e3c6d199685c13a7 src/auth/store.rs 745226cabb007819370fc6aa680995a03a8a13fa057dacfab0fcc21d0c43bbe4 src/auth/tests.rs -4f123aa55c69cd3b13db31ae5d9b6984911eecd922b5e8fc60437fd54265d1f1 src/backup.rs -968bf5c4ddb67fd9b53aed051234fb42df35b5045f26add611c144ba399814dc src/backup/archive.rs -b1740707011300581d1e5d414b81ecfb774083567aebfea807b4b53ed425e52a src/backup/tests.rs -4b143f32b96a0640345353f71d69e345b415efbbe89d7b24b00641aae4e5a7d4 src/backup/validation.rs +b0efb68dec4b0e8611684457b21044d136882ddb5c529f934061a55d377d9b28 src/backup.rs +002f8f4ba59684937f7601967b6d88b84c4aed641a13222e50500330917c4916 src/backup/archive.rs +61b0b50d5ed6b4465b8a38ce10f386d877a6c8961a2ec3813976e1a65a511d12 src/backup/snapshot.rs +daf0686d97d6f570f061b601e3904e15d1fcfa4c11fba9c50199cbfe0ab8e6a2 src/backup/tests.rs +32b9da896fd275256ca749189184aa42e82a2ede7e11332ca221b8c2c9f4181f src/backup/validation.rs 426a946b2c9e244f09f6cbc5f9664b8ad0737529a9bb5a3d549d27196a1f8043 src/balance.rs 6521d493edffd36a68a5753d8c808dd66394243f97fd96984b7cbe60588666f1 src/balance/health.rs 35d42b26de1757a490e936b907bae6803c51582aaaa6a9209eb61a701dd586d9 src/balance/lease_runtime.rs @@ -302,7 +304,7 @@ fcd733906d4477911a20ba9315635c88c5fa3f8d515420390024e06a750571a0 src/config/rou 7d94d1bd4eb698e0588b822d93e56e3dc5594c46edac4aa9713f148893ebe6d5 src/config/tests.rs be6852c11041bcea2c387b76ba3d66a8ac06f8425bd1c976075efe5cc802902d src/config/tests/part_01.rs 467b89435d0d115b227c8c8093d06c3443b5c4c063bc18e322e7d2edf635a02e src/config/tests/part_02.rs -236f27a916b278411d0f45408b1489339043c35e8fa9beea1e131821c2588c1a src/db.rs +8568fcbc8e5d06631827fea4fd165c5596bd530c948ec50babca30f7fc4dade0 src/db.rs cef6f5d63aa33433df5acc40c22e55a55c8ea275424b200084eceea3e3432cbd src/db/tests.rs 19ab6c3ae37009c544291cb0b65c1bb9c721e168f95c1ed3fa88155aca7463ad src/demo.rs e69031875ffec32c9f548c43bed5f1a5c4c626e9568c9173e3b093f9a8b73036 src/diagnostics.rs @@ -327,11 +329,11 @@ b1f2fa0df9ffb7e621d0009c30a188402f0741701f68b77977ef1f40421ab659 src/logging/mo d55dddba3f35a4f51a9b224af3b776ce7d25c8e57ae061e130e451aa5769edea src/logging/tests.rs 3836ed14b6710e183f048ee4d7807a35366c27a27338057fe4a2811129b63302 src/main.rs 79e1d39d7be966c22faad99146e384407427f2b1cec917b73786c5808ecc12f1 src/main/admin_runtime.rs -91cc63d6a3404361b2aadaff2899bfe7dba6871bfac28599ab3d5c5266a2718a src/main/bootstrap.rs +077c840d16e90847d8ec41b804405214048b6645d6425034f5da4d8f78d2a4ee src/main/bootstrap.rs 192623ac6d9ef376648d3dc372e7dedc0a37afbaa3dacf737e2e372228de09e5 src/main/proxy_runtime.rs f58e3b75ed39e85fb4f943b970ea5bb271e709bce0979fd96a25e416387a81d9 src/main/shutdown.rs a8783a34c01a288822c71b2a6807cc8d7645e22af5766053fed5c429d4536407 src/main/shutdown/tests.rs -6392ac75b2c4e3adef89cd071f5de24be7ea6f69e9209c49e714cce530ec97d2 src/main/tasks.rs +54fe6de7bf34db0237c1dbd0cf626f114320c27687923c801a1ec8c8b1af1f3f src/main/tasks.rs 49d7e104585c8791ac9a203dddd95802784b68c0ba65d6f90f3d57154b4c7871 src/metrics.rs 201ab702722d6d21585fe8bb49939150d61bfac7ed8dc198b98c2cc6098ffb5f src/metrics/global.rs b1ed4042f2d6c1029523c3977b85e2ab6020e2da7d5079f98051cef5e96ca390 src/metrics/vhost.rs @@ -435,7 +437,7 @@ fae5a47efda9a4ae4dc94711ecc5857e10b3c20aa2bf2c742c59651a85b9d978 src/vhost/test 9c1c8d6eedecb95eadbfb23c3caea562f6f7a995606ef988f25b58c2d03bcdd5 src/web_server/config/model.rs 6191f4d541476c4a3ae296e7dd28bdb9f8a123fc509b47619f6f44519c44d278 src/web_server/config/validation.rs 86c744216d7132d8294a6e90ba9be5fd871646006313d1c62506d40c81db9b12 src/web_server/fastcgi.rs -d06e753a2ac7c3867e4a9311716d5ee071bcc7ad05ba81d3613dbb7e2f4f2c92 src/web_server/fastcgi/protocol.rs +5e3a0148ac9e2d50160c8e655b378c4a061f8912012d1cb272be50300ec8ce8a src/web_server/fastcgi/protocol.rs 134c4998f6a0aa27e121af1ae3355b7e335488b2ae867ac0a934c58b4eb77191 src/web_server/fastcgi/response.rs 8186363315bd998919c6c783e6f7501528f257a5c0da77c5d60f423e8670db5e src/web_server/filesystem.rs 0aafd7f37c4fe784b047646e4ce5ddcb5fe821231a41ed0a9f519529385288a8 src/web_server/htaccess.rs @@ -445,14 +447,14 @@ d06e753a2ac7c3867e4a9311716d5ee071bcc7ad05ba81d3613dbb7e2f4f2c92 src/web_server 80f946570c14c8915fd21bfdb1a84dfb6a14c633ab6060acb828fab4c7c21daa src/web_server/mod.rs 67ab8bef567452f123c4b9b46a942b39c93f1f765c128edca7ee808ff1c5e304 src/web_server/request.rs c5cb561b0f276e8082f904b9446fa5885f6b3c0608f25ea2ef425061e9f7f6b4 src/web_server/response.rs -ce97afa23fcb6ca1fff7327dc8286e293bdecfbf704a98057a7bef8b1a189f0a src/web_server/response/file.rs +d0c5802ed8b1d6248c3fe75f3c1853a649f2407324ec802cd1f066720c5468de src/web_server/response/file.rs 9aa68251a5f549f4ce3b1bda88d4abb9efc629caf444989381b4117c9488e37b src/web_server/response/headers.rs 2268a6aeed2f565f9791af33e1cd21c56c228c8873b031725b39a6c57184f2d1 src/web_server/routing.rs 400e1c8aa2fe857d96036a64499fbc3de44c1e3a53811c5b25101fb843359792 src/web_server/security.rs 3e0b4e92600a7d5abcec92efed532b3a01e0781cb4f2681daac6d6ae13b0a6e9 src/web_server/serve.rs cf86c4692080723bbdf8f4105e1abfae33d7fcf020d2ad9e13ebae3943bd0cbe src/web_server/tests.rs c62752fe2fa6b74ddc9d94d36f8d191376b1575b15d21fc434c5a5da4c932692 src/web_server/tests/part_01.rs -c5ad6d26743fbbd934fe6fa146e92b699a945fb57e28a8666eb017f2c319f247 src/web_server/tests/part_02.rs +f9135f6a387b9b163a96f8b3d7b3da75f13b4f3795be66c0ea7609912816221c src/web_server/tests/part_02.rs e54f44f6ca1ed2c41e620de9195d32c4f8bb6936c3881745316a07160f87b4fa src/web_server/tests/part_03.rs f00743af2cf4a5952caef7a1dcf68e7817b5599a252a9973e1c22045517c0b0e tests/fixtures/argon2id.json 1725c10331745dc04a07675784d385260da7b14567b8dba3cbae5e891eb5dadf tests/http2.rs diff --git a/debian/README.md b/debian/README.md index dc7c1f0..619b333 100644 --- a/debian/README.md +++ b/debian/README.md @@ -1,6 +1,6 @@ # Debian packaging -This directory builds the `rustproxy` binary package for RustProxy 0.25.1. +This directory builds the `rustproxy` binary package for RustProxy 0.25.2. The package installs: - `/usr/bin/rust-proxy` diff --git a/debian/build-package.sh b/debian/build-package.sh index 1db3f63..69ce296 100755 --- a/debian/build-package.sh +++ b/debian/build-package.sh @@ -21,6 +21,6 @@ cargo fetch --locked dpkg-buildpackage -us -uc -b mkdir -p dist -find .. -maxdepth 1 -type f \( -name 'rustproxy_0.25.1_*.deb' -o -name 'rustproxy_0.25.1_*.changes' -o -name 'rustproxy_0.25.1_*.buildinfo' \) \ +find .. -maxdepth 1 -type f \( -name 'rustproxy_0.25.2_*.deb' -o -name 'rustproxy_0.25.2_*.changes' -o -name 'rustproxy_0.25.2_*.buildinfo' \) \ -exec cp -f {} dist/ \; echo "Packages copied to $ROOT/dist" diff --git a/debian/changelog b/debian/changelog index 9e18dba..bc8a925 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,11 @@ +rustproxy (0.25.2) stable; urgency=medium + + * Allow consistent encrypted backup creation while the service is running. + * Preserve safe internal Certbot symlinks across backup and restore. + * Recover resumed admin tabs from transient invalid-origin state without relaxing origin checks. + + -- RustProxy Release Sun, 04 Oct 2026 16:26:00 +0200 + rustproxy (0.25.1) stable; urgency=medium * Reorganize Traffic into compact Overview, Clients, Requests and Delivery tabs with donut and bar charts. diff --git a/dev.sh b/dev.sh index d919fef..606c0d8 100755 --- a/dev.sh +++ b/dev.sh @@ -18,7 +18,7 @@ if [[ $# -gt 0 ]]; then shift; fi case "$mode" in help|-h|--help) cat <<'HELP' -RustProxy 0.25.1 +RustProxy 0.25.2 ./dev.sh Build and start proxy + local panel ./dev.sh run [serve flags] Same, with extra server options ./dev.sh admin setup-token Generate/print a one-time setup capability before first login @@ -139,7 +139,7 @@ if [[ "$mode" == "test" ]]; then printf 'Checking all Rust targets before tests...\n' >&2 cargo check --locked --all-targets fi -printf 'Building RustProxy 0.25.1...\n' >&2 +printf 'Building RustProxy 0.25.2...\n' >&2 cargo build --locked BIN="$CARGO_TARGET_DIR/debug/rust-proxy" case "$mode" in diff --git a/docker-agent/model.go b/docker-agent/model.go index dc74a61..03a9dec 100644 --- a/docker-agent/model.go +++ b/docker-agent/model.go @@ -1,6 +1,6 @@ package main -const version = "0.25.1" +const version = "0.25.2" type config struct { Listen string diff --git a/docs/API_ACCESS.md b/docs/API_ACCESS.md index ca67e05..f5cdef1 100644 --- a/docs/API_ACCESS.md +++ b/docs/API_ACCESS.md @@ -1,4 +1,4 @@ -# Administration API access - 0.25.1 +# Administration API access - 0.25.2 ## Sign-in boundary @@ -41,7 +41,10 @@ Exact anonymous methods/paths: The document allowlist above is intentionally session-aware: a browser refresh on a protected panel path first validates the cookie server-side instead of rendering a false logged-out state. The login document also rechecks `/api/auth/session` when restored from browser history, preventing -a stale back/forward-cache copy from contradicting a still-valid session. Every other admin +a stale back/forward-cache copy from contradicting a still-valid session. The authenticated shell +also treats `auth.invalid_origin` during startup/session revalidation as a stale restored-page signal +and performs at most one reload of the current same-origin URL. A repeated failure is shown normally; +server Host/Origin, request-marker and CSRF validation is unchanged. Every other admin path/method, including full assets, `/api/metrics`, `/metrics`, unknown endpoints and the OpenAPI schema, requires an active database session. Missing/invalid/expired cookies return `401 {"error":"auth.required"}` before diff --git a/docs/INSTALL.md b/docs/INSTALL.md index f30d9d8..1a3e905 100644 --- a/docs/INSTALL.md +++ b/docs/INSTALL.md @@ -1,4 +1,4 @@ -# Installing and updating RustProxy 0.25.1 +# Installing and updating RustProxy 0.25.2 The scripts prepare a Linux/systemd installation. This source archive is **not a verified production build**: Rust/Cargo, real CA operations, live systemd startup diff --git a/docs/MIGRATION.md b/docs/MIGRATION.md index 89dbf51..d9926ef 100644 --- a/docs/MIGRATION.md +++ b/docs/MIGRATION.md @@ -1,5 +1,11 @@ # Deployment and configuration migration history +## 0.25.1 -> 0.25.2 + +There is no SQLite schema or saved-configuration migration. Full backup creation may now run while the service is active; restore still requires RustProxy to be stopped. Backup archive version 2 preserves validated internal relative symlinks such as Certbot `live/` entries pointing into the profile `archive/` tree. Existing archive-version-1 backups remain accepted for restore. + +The administration server keeps the same strict Host/Origin/request-marker/CSRF checks. The browser shell only adds a bounded one-shot same-origin reload when a restored or resumed authenticated tab receives `auth.invalid_origin`. + ## 0.25.0 -> 0.25.1 There is no SQLite schema or saved-configuration migration. 0.25.1 reorganizes the Traffic dashboard and source tree only; existing Web Services, vhosts, traffic settings, cache state and administrator data remain compatible. Browser asset URLs remain `/assets/app.js` and `/assets/vhosts.js` even though their source is now split into bounded modules. diff --git a/docs/README.md b/docs/README.md index a8b5334..3824ba5 100644 --- a/docs/README.md +++ b/docs/README.md @@ -8,7 +8,8 @@ This is the canonical index for the current project documentation. The authentic - [ROADMAP](../ROADMAP.md) - planned and completed release scope. - [CHANGELOG](../CHANGELOG.md) - user/operator-visible changes by release. - [TODO](TODO.md) - open work only; completed work is not retained as TODO. -- [0.25.1 release audit](V0.25.1_AUDIT.md) - Traffic/module-split release scope, compiler-regression guards and remaining release-host gate. +- [0.25.2 release audit](V0.25.2_AUDIT.md) - live backup, internal symlink and resumed-origin recovery scope plus release gates. +- [0.25.1 release audit](V0.25.1_AUDIT.md) - historical Traffic/module-split release scope and compiler-regression guards. - [0.25.0 specification audit](V0.25.0_AUDIT.md) - Web Server foundation point-by-point implementation status. - [0.25.0 pre-production security audit](V0.25.0_SECURITY_PREPROD_AUDIT.md) - threat-oriented source/configuration review, fixes made in-session and mandatory runtime security gates. - [TEST_REPORT](TEST_REPORT.md) - verification commands, results and environment limitations. @@ -56,7 +57,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; they are not current-release documentation. The current audit is [V0.25.1_AUDIT.md](V0.25.1_AUDIT.md); [V0.25.0_AUDIT.md](V0.25.0_AUDIT.md) remains the Web Server foundation audit. +Version-labelled `V*.md` files are retained as historical implementation/audit records. Their version number describes the audited milestone; they are not current-release documentation. The current audit is [V0.25.2_AUDIT.md](V0.25.2_AUDIT.md); [V0.25.1_AUDIT.md](V0.25.1_AUDIT.md) records the prior Traffic/module-split milestone and [V0.25.0_AUDIT.md](V0.25.0_AUDIT.md) remains the Web Server foundation audit. 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. diff --git a/docs/SECRET_STORE_BACKUP.md b/docs/SECRET_STORE_BACKUP.md index 45114a5..f601d7e 100644 --- a/docs/SECRET_STORE_BACKUP.md +++ b/docs/SECRET_STORE_BACKUP.md @@ -18,7 +18,7 @@ Do not copy only `config.sqlite3` as a Secret Store backup: the local key materi ## Create a full backup -Stop RustProxy first so the data directory lock is available. The output file must be outside the RustProxy data directory and must not already exist. +`backup create` may run while RustProxy is serving traffic. If the service is stopped, the CLI takes the data-directory lock; if the service is running, it creates a consistent SQLite snapshot and stages durable filesystem state without taking ownership of that lock. The output file must be outside the RustProxy data directory and must not already exist. ```bash rust-proxy --data-dir /var/lib/rustproxy backup create \ @@ -36,15 +36,16 @@ The encrypted backup contains the durable application state needed for recovery, - backup-wrapped Secret Store key material, - certificates and private keys, - ACME state and durable provider data, -- other durable RustProxy data accepted by the backup manifest. +- other durable RustProxy data accepted by the backup manifest, +- validated internal relative symlinks, including Certbot `live/` links into the same profile's `archive/` tree. -It excludes rebuildable/runtime data such as cache, logs, temporary files, sockets, PID/lock files and SQLite WAL/SHM sidecars. +It excludes rebuildable/runtime data such as cache, logs, temporary files, sockets, PID/lock files and SQLite WAL/SHM sidecars. Symlinks are accepted only when their target is relative and resolves inside the staged RustProxy data tree; absolute, escaping or dangling links fail backup/restore. Restore recreates accepted links as links rather than dereferencing them. The outer container derives a backup key with Argon2id and encrypts authenticated chunks with AES-256-GCM. The backup password is independent from the local Secret Store key. ## Restore -Restore is CLI-only and requires an interactive TTY for the password: +Restore is CLI-only, requires RustProxy to be stopped, and requires an interactive TTY for the password: ```bash rust-proxy --data-dir /var/lib/rustproxy backup restore \ diff --git a/docs/TEST_REPORT.md b/docs/TEST_REPORT.md index bf16bd6..0668fd0 100644 --- a/docs/TEST_REPORT.md +++ b/docs/TEST_REPORT.md @@ -1,8 +1,8 @@ -# Verification report - RustProxy 0.25.1 +# Verification report - RustProxy 0.25.2 ## Scope -RustProxy 0.25.1 is a focused Traffic-UX and source-modularization release on top of the reusable Web Server foundation from 0.25.0. Web Services are independent configuration resources; vhosts and custom locations only choose a source type and reference a Web Service ID. The current development tree provides static GET/HEAD streaming, directory/index behavior, validators, single ranges, document-root confinement, ordered `try_files`, Web Service rewrites, a bounded migration-oriented `.htaccess` compatibility layer, expanded MIME policy, per-extension/MIME Cache-Control rules, fixed source headers, precompressed sidecars, forced-download extensions and optional bounded FastCGI/PHP-FPM. The Web Service editor is organized into General, Routing, .htaccess, MIME, Cache & headers, Delivery, PHP-FPM and Security tabs, with one-shot presets for Static/SPA/PHP/WordPress/Drupal/Laravel/Symfony/Joomla/PrestaShop/Downloads. Existing 0.25 work also includes repeatable directory listing, Web Server vhost-wide metrics, Logging sub-tabs, corrected structured-log source validation, bounded per-vhost Traffic analytics with effective-client-IP rankings/donut charts, User-Agent/optional-country logging, stable Web Service rendering after fast refreshes, generic/customizable 403 handling and automatic RSS relief for reclaimable allocator/HOT-cache memory. The `/docs` surface separates the Markdown Reader from API/OpenAPI and documents the implemented PHP-FPM runtime. +RustProxy 0.25.2 is a focused live-backup, backup-symlink and resumed-admin-session hardening release on top of the 0.25.1 source tree. Web Services are independent configuration resources; vhosts and custom locations only choose a source type and reference a Web Service ID. The current development tree provides static GET/HEAD streaming, directory/index behavior, validators, single ranges, document-root confinement, ordered `try_files`, Web Service rewrites, a bounded migration-oriented `.htaccess` compatibility layer, expanded MIME policy, per-extension/MIME Cache-Control rules, fixed source headers, precompressed sidecars, forced-download extensions and optional bounded FastCGI/PHP-FPM. The Web Service editor is organized into General, Routing, .htaccess, MIME, Cache & headers, Delivery, PHP-FPM and Security tabs, with one-shot presets for Static/SPA/PHP/WordPress/Drupal/Laravel/Symfony/Joomla/PrestaShop/Downloads. Existing 0.25 work also includes repeatable directory listing, Web Server vhost-wide metrics, Logging sub-tabs, corrected structured-log source validation, bounded per-vhost Traffic analytics with effective-client-IP rankings/donut charts, User-Agent/optional-country logging, stable Web Service rendering after fast refreshes, generic/customizable 403 handling and automatic RSS relief for reclaimable allocator/HOT-cache memory. The `/docs` surface separates the Markdown Reader from API/OpenAPI and documents the implemented PHP-FPM runtime. ## Source contracts reviewed diff --git a/docs/V0.25.2_AUDIT.md b/docs/V0.25.2_AUDIT.md new file mode 100644 index 0000000..27cfc8d --- /dev/null +++ b/docs/V0.25.2_AUDIT.md @@ -0,0 +1,28 @@ +# RustProxy 0.25.2 release audit + +RustProxy 0.25.2 is a focused backup-integrity and resumed-admin-session hardening release. It does not change the SQLite schema or saved configuration format. + +## Backup and restore + +| Requirement | Status | Evidence | +| --- | --- | --- | +| Create a full encrypted backup while RustProxy is running | IMPLEMENTED | `backup create` no longer requires ownership of `process.lock`; SQLite is captured with `VACUUM INTO` and validated before archiving. | +| Keep offline behavior deterministic | IMPLEMENTED | When the service is stopped, the CLI still acquires and holds the data-directory lock during backup. | +| Preserve Certbot-style internal symlinks | IMPLEMENTED | Archive version 2 records file/directory/symlink entry type and restores validated relative symlink targets as symlinks. | +| Prevent symlink escape | IMPLEMENTED | Absolute or lexically escaping targets are rejected; the staged tree is canonicalized and every link must resolve inside it before archive creation/activation. | +| Reject dangling backup symlinks | IMPLEMENTED | Live snapshot validation requires every staged symlink target to exist. | +| Keep configured certificate files coherent with the DB snapshot | IMPLEMENTED | Certificate files referenced by the exact SQLite snapshot are required to exist as regular files in the staged filesystem snapshot; capture retries on concurrent rotation. | +| Restore older backups | IMPLEMENTED | Archive version 1 remains accepted; version-1 entries continue to deserialize as regular files. | +| Restore safely | IMPLEMENTED | Restore still requires the service to be stopped, verifies authenticated encryption, manifest/checksums, SQLite/Secret Store state, stages privately, then atomically activates. | + +## Administration origin recovery + +The administration server's Host, externally visible scheme/authority, Origin, request-marker and CSRF validation is unchanged. The authenticated browser shell now recognizes only `auth.invalid_origin` during startup/session revalidation and performs at most one reload of the current same-origin URL. A successful session check clears the recovery marker; a repeated failure is surfaced normally instead of looping. + +This addresses browser-restored/BFCache-style tabs where a manual refresh previously repaired the page without weakening the server trust boundary. + +## Verification gates + +Rust-independent source, documentation, web-contract and release-smoke checks are part of the release tree. Backup tests include a simulated live process lock and round-trip of a Certbot-style `live/fullchain.pem -> ../../archive/...` symlink, plus escaping/absolute-link rejection. + +Compiler-enabled `cargo fmt --all`, `cargo check --locked --all-targets` and `cargo test --locked --all-targets` remain mandatory on a release host with the Rust toolchain installed. diff --git a/grafana/rustproxy-overview.json b/grafana/rustproxy-overview.json index 6cd555d..9199ba6 100644 --- a/grafana/rustproxy-overview.json +++ b/grafana/rustproxy-overview.json @@ -3672,7 +3672,7 @@ }, "timepicker": {}, "timezone": "browser", - "title": "RustProxy 0.25.1 - Operations", + "title": "RustProxy 0.25.2 - Operations", "uid": "rustproxy-0170", "version": 2, "weekStart": "" diff --git a/make_zip.py b/make_zip.py index 72025eb..5f060f8 100755 --- a/make_zip.py +++ b/make_zip.py @@ -88,7 +88,7 @@ def add_file(archive: zipfile.ZipFile, rel: Path, data: bytes, timestamp: tuple[ def main() -> None: parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument('output', nargs='?', type=Path, default=ROOT.parent / 'RustProxy-0.25.1.zip') + parser.add_argument('output', nargs='?', type=Path, default=ROOT.parent / 'RustProxy-0.25.2.zip') output = parser.parse_args().output.resolve() paths = files() checksum = ROOT / 'SHA256SUMS' diff --git a/scripts/check_source.py b/scripts/check_source.py index 9bf0dc5..76b664b 100755 --- a/scripts/check_source.py +++ b/scripts/check_source.py @@ -24,7 +24,7 @@ def read_docker_agent_source() -> str: preflight(root) manifest = tomllib.loads((root / "Cargo.toml").read_text()) -assert manifest["package"]["version"] == "0.25.1" +assert manifest["package"]["version"] == "0.25.2" assert manifest["dependencies"]["quinn"]["version"] == "0.11.12" h3dep = manifest["dependencies"]["h3"] assert h3dep["package"] == "hpx-h3" and h3dep["version"] == "=2.5.19" and "quinn" in h3dep["features"] @@ -32,14 +32,14 @@ assert "h3-quinn" not in manifest["dependencies"] lock_path = root / "Cargo.lock" if lock_path.exists(): lock = lock_path.read_text() - assert 'name = "rust-proxy"\nversion = "0.25.1"' in lock + assert 'name = "rust-proxy"\nversion = "0.25.2"' in lock assert 'name = "systemstat"' in lock and 'name = "metrics-process"' in lock and 'name = "sysinfo"' in lock, "Cargo.lock must be resolver-generated after adding cross-platform statistics libraries" for release_doc, marker in { - "README.md": "# RustProxy 0.25.1", - "CHANGELOG.md": "## 0.25.1", - "ROADMAP.md": "## 0.25.1", - "AGENTS.md": "Current source version: 0.25.1.", - "docs/TEST_REPORT.md": "# Verification report - RustProxy 0.25.1", + "README.md": "# RustProxy 0.25.2", + "CHANGELOG.md": "## 0.25.2", + "ROADMAP.md": "## 0.25.2", + "AGENTS.md": "Current source version: 0.25.2.", + "docs/TEST_REPORT.md": "# Verification report - RustProxy 0.25.2", }.items(): assert marker in (root / release_doc).read_text(), f"stale release identity in {release_doc}" assert tomllib.loads((root / "rust-toolchain.toml").read_text())["toolchain"]["channel"] == "stable" @@ -224,7 +224,7 @@ assert 'Opt-in health decisions based on ordinary user traffic. Defaults to disa assert '"force_ssl"' in openapi and '"forward_client_ip"' in openapi assert '"backend_header"' in openapi and '"backend_timing_header"' in openapi assert '"StreamMetrics"' in openapi and '"streams"' in openapi -for marker in ['"version": "0.25.1"', '"/api/traffic"', '"capture_user_agent"', '"capture_country"', '"hot_auto_trim_enabled"', '"hot_auto_trim_after_seconds"', '"hot_auto_trim_target_percent"', '"/api/config/{section}"', '"ConfigSectionUpdate"', '"cold_on_idle"', '"cold_on_age"', '"cold_max_hot_seconds"', '"cold_on_lru"', '"cold_on_shutdown"', '"cold_gc_interval_seconds"', '"cold_gc_max_delete"', '"collapse_enabled"', '"collapse_wait_timeout_ms"', '"collapse_timeout_fallback"', '"stale_if_error_timeout"', '"stale_if_error_connection"', '"stale_if_error_dns"', '"stale_if_error_tls"', '"stale_if_error_statuses"', '"proactive_refresh_enabled"', '"proactive_refresh_before_expiry_seconds"', '"proactive_refresh_percent"', '"proactive_refresh_max_inflight"', '"TransformSettings"', '"TransformOverrides"', '"/api/cache/cold/cleanup"', '"/api/cache/invalidate"', '"/api/cache/bans"', '"/api/cache/bans/{id}"', '"CacheInvalidationRequest"', '"CacheInvalidationResponse"', '"ColdCleanupJob"', '"match_type"', '"regex_insensitive"', '"rewrite"', '"response"', '"covered_hosts"', '"default": true']: +for marker in ['"version": "0.25.2"', '"/api/traffic"', '"capture_user_agent"', '"capture_country"', '"hot_auto_trim_enabled"', '"hot_auto_trim_after_seconds"', '"hot_auto_trim_target_percent"', '"/api/config/{section}"', '"ConfigSectionUpdate"', '"cold_on_idle"', '"cold_on_age"', '"cold_max_hot_seconds"', '"cold_on_lru"', '"cold_on_shutdown"', '"cold_gc_interval_seconds"', '"cold_gc_max_delete"', '"collapse_enabled"', '"collapse_wait_timeout_ms"', '"collapse_timeout_fallback"', '"stale_if_error_timeout"', '"stale_if_error_connection"', '"stale_if_error_dns"', '"stale_if_error_tls"', '"stale_if_error_statuses"', '"proactive_refresh_enabled"', '"proactive_refresh_before_expiry_seconds"', '"proactive_refresh_percent"', '"proactive_refresh_max_inflight"', '"TransformSettings"', '"TransformOverrides"', '"/api/cache/cold/cleanup"', '"/api/cache/invalidate"', '"/api/cache/bans"', '"/api/cache/bans/{id}"', '"CacheInvalidationRequest"', '"CacheInvalidationResponse"', '"ColdCleanupJob"', '"match_type"', '"regex_insensitive"', '"rewrite"', '"response"', '"covered_hosts"', '"default": true']: assert marker in openapi, marker for marker in ['"NotFoundHost"', '"not_found_hosts"', '"PrometheusSettings"', '"allow_trusted_network_without_token"', '"include_docker_vhosts"', '"include_streams"', '"include_logging"']: assert marker in openapi, marker @@ -470,7 +470,7 @@ for marker in ['"/api/config/{section}"', 'async fn update_section', 'section_up for route in ['/overview', '/vhosts', '/redirects', '/404-hosts', '/streams', '/acl-profiles', '/configuration', '/tls', '/cache', '/transform', '/logging', '/settings']: assert f'.route("{route}", get(public::index))' in admin, route app_js = read_app_js(root) -for marker in ['const PANEL_PATHS', 'history.pushState', 'window.addEventListener("popstate"', 'async function saveSection', 'async function revalidateSession', 'Object.values(PANEL_PATHS), "/configuration"', 'certificateCovers', 'covered_hosts', 'refreshCertificateInventory', 'certificateDetails', 'certificate-domain-pills', 'certificateIssuerName', 'certificateDaysRemaining', 'certificateRenewalStatus']: +for marker in ['const PANEL_PATHS', 'history.pushState', 'window.addEventListener("popstate"', 'async function saveSection', 'async function revalidateSession', 'Object.values(PANEL_PATHS), "/configuration"', 'certificateCovers', 'covered_hosts', 'refreshCertificateInventory', 'certificateDetails', 'certificate-domain-pills', 'certificateIssuerName', 'certificateDaysRemaining', 'certificateRenewalStatus', 'recoverInvalidOrigin', 'auth.invalid_origin', 'ORIGIN_RECOVERY_KEY']: assert marker in app_js, marker for marker in ['.certificate-details', '.certificate-domain-pills', '.certificate-card .pill', '.certificate-operation-card', '.renewal-meta', '#panel-tls .certificate-tabs']: assert marker in css, marker @@ -521,9 +521,11 @@ for feature_marker in [ assert feature_marker in secret_store_source or feature_marker in db_source, feature_marker for feature_marker in [ "Argon2", "RPROXYB1", "RPROXYA1", "backup create", "secret-store-key.enc", - "validate_backup_version", "stage", + "validate_backup_version", "stage", "ARCHIVE_VERSION: u32 = 2", "EntryKind::Symlink", + "try_lock_directory", "validate_snapshot_symlinks", "validate_symlink_target", ]: assert feature_marker in backup_source or feature_marker in main_source, feature_marker +assert "VACUUM INTO" in db_source and "consistent SQLite snapshot" in db_source for feature_marker in [ "ErrorLogSettings", "ERROR_LOG_QUEUE", "RedactingStderr", "open_error_file", "ErrorLogLayer", "ERROR", @@ -1156,7 +1158,7 @@ assert '"error_log" => {' not in admin_0241 and 'request("/api/config/error_log" assert '"error_log"' in openapi and 'logging, error_log, forwarding' not in openapi assert 'logging_section_persists_error_log_settings_atomically' in admin_0241 and 'error_log_is_not_a_standalone_configuration_section' in admin_0241 assert 'location.pathname === "/logging"' in settings_js_0241 -assert '"RuntimeInformation"' in openapi and '"version": "0.25.1"' in openapi +assert '"RuntimeInformation"' in openapi and '"version": "0.25.2"' in openapi # 0.24.1 compiler and hostname-cardinality regressions. tls_0241 = rust_source(root, "src/tls.rs") @@ -1201,5 +1203,5 @@ for schema_name in ["RedirectHost", "NotFoundHost"]: node = openapi_0241["components"]["schemas"][schema_name]["properties"]["frontend"]["properties"]["hosts"] assert "maxItems" not in node, schema_name -print("PASS: 0.25.1 manifest/package, shared system-statistics/runtime metadata, Settings logging, retained H2, Debian/Grafana/Prometheus/404-host/custom-location/path/cache/dashboard/Nagios UI/source contracts, stopped legacy-renewal removal, session/admin/transport contracts, deterministic package source and embedded SQLite schema-v7 migrations.") +print("PASS: 0.25.2 manifest/package, shared system-statistics/runtime metadata, Settings logging, retained H2, Debian/Grafana/Prometheus/404-host/custom-location/path/cache/dashboard/Nagios UI/source contracts, stopped legacy-renewal removal, session/admin/transport contracts, deterministic package source and embedded SQLite schema-v7 migrations.") print("NOT VERIFIED: Rust parsing, types, compilation, linking or runtime behavior. Run ./dev.sh test.") diff --git a/scripts/deploy_core.py b/scripts/deploy_core.py index 1df03d1..3c983f4 100755 --- a/scripts/deploy_core.py +++ b/scripts/deploy_core.py @@ -97,7 +97,7 @@ def digest(path: Path) -> str: def fetch_https(url: str, max_bytes: int) -> bytes: if urllib.parse.urlparse(url).scheme != 'https': raise ValueError('GeoIP downloads require HTTPS.') - request = urllib.request.Request(url, headers={'User-Agent': 'RustProxy-installer/0.25.1'}) + request = urllib.request.Request(url, headers={'User-Agent': 'RustProxy-installer/0.25.2'}) with urllib.request.urlopen(request, timeout=30) as response: if urllib.parse.urlparse(response.geturl()).scheme != 'https': raise ValueError('GeoIP download redirected away from HTTPS.') diff --git a/scripts/docs_check.py b/scripts/docs_check.py index f57032e..c80321e 100755 --- a/scripts/docs_check.py +++ b/scripts/docs_check.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Rust-independent documentation/Reader consistency checks for the 0.25.1 release.""" +"""Rust-independent documentation/Reader consistency checks for the 0.25.2 release.""" from pathlib import Path import re from rust_source import rust_source @@ -14,6 +14,7 @@ required = [ DOCS / "README.md", DOCS / "WEB_SERVER.md", DOCS / "TODO.md", + DOCS / "V0.25.2_AUDIT.md", DOCS / "V0.25.1_AUDIT.md", DOCS / "V0.25.0_AUDIT.md", DOCS / "TEST_REPORT.md", @@ -24,7 +25,7 @@ for path in required: assert path.is_file(), f"missing required documentation: {path.relative_to(ROOT)}" index = (DOCS / "README.md").read_text(encoding="utf-8") -for marker in ["Functional documentation", "Development and testing", "Technical documentation", "Historical or version-labelled context", "TODO.md", "V0.25.1_AUDIT.md", "V0.25.0_AUDIT.md", "CHANGELOG_TECHNICAL_ARCHIVE.md", "ROADMAP_HISTORY.md", "/docs"]: +for marker in ["Functional documentation", "Development and testing", "Technical documentation", "Historical or version-labelled context", "TODO.md", "V0.25.2_AUDIT.md", "V0.25.1_AUDIT.md", "V0.25.0_AUDIT.md", "CHANGELOG_TECHNICAL_ARCHIVE.md", "ROADMAP_HISTORY.md", "/docs"]: assert marker in index, marker for document in sorted(DOCS.glob("*.md")): if document.name == "README.md" or (document.name.startswith("V") and document.name.endswith("_AUDIT.md")): @@ -51,13 +52,13 @@ assert '"/docs/content.json"' in admin readme = (ROOT / "README.md").read_text(encoding="utf-8") roadmap = (ROOT / "ROADMAP.md").read_text(encoding="utf-8") changelog = (ROOT / "CHANGELOG.md").read_text(encoding="utf-8") -assert readme.startswith("# RustProxy 0.25.1") +assert readme.startswith("# RustProxy 0.25.2") assert "docs/WEB_SERVER.md" in readme and "docs/README.md" in readme assert readme.count("## What changed in ") == 1, "README duplicates historical release notes; use CHANGELOG/archive" assert "## Release history" in readme and "docs/ROADMAP_HISTORY.md" in readme -assert roadmap.startswith("# RustProxy roadmap") and "## 0.25.1" in roadmap and "## 0.25.0" in roadmap +assert roadmap.startswith("# RustProxy roadmap") and "## 0.25.2" in roadmap and "## 0.25.1" in roadmap and "## 0.25.0" in roadmap assert "## 0.24." not in roadmap and "## Web Server follow-up" in roadmap and "docs/ROADMAP_HISTORY.md" in roadmap -assert changelog.startswith("# Changelog\n") and "## 0.25.1" in changelog[:800] +assert changelog.startswith("# Changelog\n") and "## 0.25.2" in changelog[:800] and "## 0.25.1" in changelog[:1200] assert "CHANGELOG_TECHNICAL_ARCHIVE.md" in changelog for developer_only in ["Clean the remaining compiler warnings", "Update package/source/UI/OpenAPI/release identity", "source regressions and update release identity"]: assert developer_only not in changelog, f"developer-only CHANGELOG detail: {developer_only}" diff --git a/scripts/preflight.py b/scripts/preflight.py index bb32589..f6b194f 100755 --- a/scripts/preflight.py +++ b/scripts/preflight.py @@ -73,12 +73,12 @@ def check(root: Path, repair_known: bool = False) -> list[str]: flat = module.parent.with_suffix(".rs") if flat.is_file(): raise ValueError(f"Conflicting Rust modules: {flat.relative_to(root)} and {module.relative_to(root)}") - for path in ["src/auth/mod.rs", "src/auth/store.rs", "src/upstream.rs", "src/tls.rs", "src/proxy/websocket.rs", "src/cache/mod.rs", "src/cache/model.rs", "src/cache/lifecycle.rs", "src/cache/cold.rs", "src/cache/dashboard.rs", "src/cache/tickets.rs", "src/cache/lookup.rs", "src/cache/policy.rs", "src/cache/fill.rs", "src/cache/tests.rs", "src/settings.rs", "src/compression.rs", "src/diagnostics.rs", "src/traffic.rs", "src/system_stats/mod.rs", "src/system_stats/linux.rs", "src/vhost.rs", "src/web_server/mod.rs", "src/web_server/config.rs", "src/web_server/filesystem.rs", "src/web_server/security.rs", "src/web_server/response.rs", "src/web_server/request.rs", "src/web_server/routing.rs", "src/web_server/fastcgi.rs", "src/web_server/htaccess.rs", "src/web_server/serve.rs", "src/web_server/tests.rs", "src/balance.rs", "src/security.rs", "src/admin/web/vhosts/core.js", "src/admin/web/vhosts/editor.js", "src/admin/web/vhosts/render.js", "src/admin/web/web-server.js", "src/admin/web/streams.js", "src/admin/web/host404.js", "src/admin/web/app/core.js", "src/admin/web/app/runtime.js", "src/admin/web/app/settings.js", "src/admin/web/management.js", "src/pages.rs", "scripts/mock_server.py", "scripts/mock_auth.py", "src/forwarding.rs", "src/tinyauth.rs", "src/http3.rs", "src/stream.rs", "src/admin/web/integrations.js", "src/admin/web/traffic-panel.js", "src/prometheus.rs", "src/admin/web/settings.js", "src/admin/web/docker.js", "src/docker.rs", "docker-agent/main.go", "docker-agent/model.go", "docker-agent/docker.go", "docker-agent/network.go", "docker-agent/certificate.go", "docker-agent/server.go", "docker-agent/main_test.go", "docker-agent/rustproxy-docker-agent.service", "docker-agent/install.sh", "docs/DOCKER_DISCOVERY.md", "docs/WEB_SERVER.md", "docs/PHP_FPM.md", "docs/README.md", "docs/TODO.md", "docs/V0.25.1_AUDIT.md", "docs/CHANGELOG_TECHNICAL_ARCHIVE.md", "docs/ROADMAP_HISTORY.md", "scripts/docs_check.py", "scripts/web_server_contracts.py", "scripts/rust_source.py", "scripts/rust_module_contracts.py", "scripts/source_module_contracts.py", "scripts/acme_provider.py", "src/basic_auth.rs", "scripts/deploy.py", "scripts/deploy_core.py", "scripts/deploy_runtime.py", "scripts/migrate_npm.py", "scripts/migrate_npm_core.py", "scripts/migrate_npm_api.py", "docs/NPM_MIGRATION.md", "docs/STREAMS.md", "install.sh", "install-debian13.sh", "update.sh", "build.rs", "scripts/swagger_assets.py", "src/admin/documentation.rs", "src/admin/openapi.json", "langs/en_US.json", "langs/pl_pl.json", "Dockerfile", ".dockerignore", "docs/CONTAINER.md", "docs/PROMETHEUS.md", "docs/NOT_FOUND_HOSTS.md", "debian/control", "debian/rules", "debian/rustproxy.service", "debian/rustproxy.logrotate", "grafana/rustproxy-overview.json"]: + for path in ["src/auth/mod.rs", "src/auth/store.rs", "src/backup/snapshot.rs", "src/upstream.rs", "src/tls.rs", "src/proxy/websocket.rs", "src/cache/mod.rs", "src/cache/model.rs", "src/cache/lifecycle.rs", "src/cache/cold.rs", "src/cache/dashboard.rs", "src/cache/tickets.rs", "src/cache/lookup.rs", "src/cache/policy.rs", "src/cache/fill.rs", "src/cache/tests.rs", "src/settings.rs", "src/compression.rs", "src/diagnostics.rs", "src/traffic.rs", "src/system_stats/mod.rs", "src/system_stats/linux.rs", "src/vhost.rs", "src/web_server/mod.rs", "src/web_server/config.rs", "src/web_server/filesystem.rs", "src/web_server/security.rs", "src/web_server/response.rs", "src/web_server/request.rs", "src/web_server/routing.rs", "src/web_server/fastcgi.rs", "src/web_server/htaccess.rs", "src/web_server/serve.rs", "src/web_server/tests.rs", "src/balance.rs", "src/security.rs", "src/admin/web/vhosts/core.js", "src/admin/web/vhosts/editor.js", "src/admin/web/vhosts/render.js", "src/admin/web/web-server.js", "src/admin/web/streams.js", "src/admin/web/host404.js", "src/admin/web/app/core.js", "src/admin/web/app/runtime.js", "src/admin/web/app/settings.js", "src/admin/web/management.js", "src/pages.rs", "scripts/mock_server.py", "scripts/mock_auth.py", "src/forwarding.rs", "src/tinyauth.rs", "src/http3.rs", "src/stream.rs", "src/admin/web/integrations.js", "src/admin/web/traffic-panel.js", "src/prometheus.rs", "src/admin/web/settings.js", "src/admin/web/docker.js", "src/docker.rs", "docker-agent/main.go", "docker-agent/model.go", "docker-agent/docker.go", "docker-agent/network.go", "docker-agent/certificate.go", "docker-agent/server.go", "docker-agent/main_test.go", "docker-agent/rustproxy-docker-agent.service", "docker-agent/install.sh", "docs/DOCKER_DISCOVERY.md", "docs/WEB_SERVER.md", "docs/PHP_FPM.md", "docs/README.md", "docs/TODO.md", "docs/V0.25.2_AUDIT.md", "docs/CHANGELOG_TECHNICAL_ARCHIVE.md", "docs/ROADMAP_HISTORY.md", "scripts/docs_check.py", "scripts/web_server_contracts.py", "scripts/rust_source.py", "scripts/rust_module_contracts.py", "scripts/source_module_contracts.py", "scripts/acme_provider.py", "src/basic_auth.rs", "scripts/deploy.py", "scripts/deploy_core.py", "scripts/deploy_runtime.py", "scripts/migrate_npm.py", "scripts/migrate_npm_core.py", "scripts/migrate_npm_api.py", "docs/NPM_MIGRATION.md", "docs/STREAMS.md", "install.sh", "install-debian13.sh", "update.sh", "build.rs", "scripts/swagger_assets.py", "src/admin/documentation.rs", "src/admin/openapi.json", "langs/en_US.json", "langs/pl_pl.json", "Dockerfile", ".dockerignore", "docs/CONTAINER.md", "docs/PROMETHEUS.md", "docs/NOT_FOUND_HOSTS.md", "debian/control", "debian/rules", "debian/rustproxy.service", "debian/rustproxy.logrotate", "grafana/rustproxy-overview.json"]: if not (root / path).is_file(): - raise ValueError(f"Incomplete RustProxy 0.25.1 source tree: missing {path}") + raise ValueError(f"Incomplete RustProxy 0.25.2 source tree: missing {path}") text = (root / "src/lib.rs").read_text(encoding="utf-8") if '#[path = "auth/mod.rs"]' not in text: - raise ValueError("src/lib.rs is not the 0.25.1 entry point; extract the complete package, not individual files.") + raise ValueError("src/lib.rs is not the 0.25.2 entry point; extract the complete package, not individual files.") return messages diff --git a/scripts/release_smoke.py b/scripts/release_smoke.py index d163361..efcbb85 100755 --- a/scripts/release_smoke.py +++ b/scripts/release_smoke.py @@ -122,7 +122,7 @@ def upgrade_smoke(tmp: Path) -> None: assert backup.is_dir() assert (backup / "src/obsolete.rs").is_file() assert not (target / "src/obsolete.rs").exists() - assert 'version = "0.25.1"' in (target / "Cargo.toml").read_text(encoding="utf-8") + assert 'version = "0.25.2"' in (target / "Cargo.toml").read_text(encoding="utf-8") assert (target / "data/keep.txt").read_text(encoding="utf-8") == "preserve\n" assert (target / ".env").read_text(encoding="utf-8") == "RUST_PROXY_DEMO=0\n" assert (target / "langs/site.json").is_file() @@ -133,7 +133,7 @@ def upgrade_smoke(tmp: Path) -> None: assert (target / "docker-agent/certificate.go").is_file() assert (target / "docker-agent/server.go").is_file() manifest = json.loads((backup / "upgrade.json").read_text(encoding="utf-8")) - assert manifest["to_version"] == "0.25.1" + assert manifest["to_version"] == "0.25.2" assert manifest["removed_obsolete_runtime_state"] is True assert not legacy.exists() assert not (backup / "legacy-data").exists() @@ -404,7 +404,7 @@ def ui_contract_smoke() -> None: assert 'viewBox: "0 0 300 60"' in app and 'M0 15H300 M0 30H300 M0 45H300' in app assert '.network-interface-row {' in css and 'background: transparent' in css assert "hostTagEditor" in vhosts and "vhost-chart-line" in vhosts and "vhLifetimeChart" in vhosts - for marker in ["refreshCertificateInventory", "certificateDetails", "certificate-domain-pills", "certificateCoverageUnknown", "async function revalidateSession", 'Object.values(PANEL_PATHS), "/configuration"']: + for marker in ["refreshCertificateInventory", "certificateDetails", "certificate-domain-pills", "certificateCoverageUnknown", "async function revalidateSession", 'Object.values(PANEL_PATHS), "/configuration"', "recoverInvalidOrigin", "auth.invalid_origin", "ORIGIN_RECOVERY_KEY"]: assert marker in app, marker for marker in ['data-certificate-tab="installed"', 'data-certificate-tab="renewal"', 'data-certificate-tab="generate"', 'id="certificate-page-installed"', 'id="certificate-page-renewal"', 'id="certificate-page-generate"']: assert marker in html, marker @@ -544,7 +544,7 @@ def source_contract_smoke() -> None: assert marker in admin, marker anonymous_block = admin.split("fn anonymous_allowed", 1)[1].split("const CERTIFICATE_API_KEY_HEADER", 1)[0] assert '"/api/status"' not in anonymous_block - assert '"RuntimeInformation"' in openapi_0241 and '"version": "0.25.1"' in openapi_0241 + assert '"RuntimeInformation"' in openapi_0241 and '"version": "0.25.2"' in openapi_0241 assert 'id="panel-logging"' not in html for marker in ['id="settings-tab-logging"', 'id="settings-page-logging"', 'id="logging-form"', 'id="error-log-form"']: assert marker in html, marker @@ -988,7 +988,7 @@ def source_contract_smoke() -> None: assert "removed_obsolete_runtime_state" in upgrade_source and "legacy-data" not in upgrade_source and "certs_dir.is_symlink()" in upgrade_source assert '.managed-update' in main and "flush_hot_to_cold_for_update" in main openapi = (ROOT / "src/admin/openapi.json").read_text(encoding="utf-8") - for marker in ['"version": "0.25.1"', '"memory_preset"', '"cold_preset"', '"memory_budget_bytes"', '"max_inflight"', '"preset"', '"session_days"', '"session_lifetime_seconds"', '"accept_local_listener_risk"', '"backend_header"', '"backend_timing_header"', '"cold_on_age"', '"match_type"', '"regex_insensitive"', '"NotFoundHost"', '"not_found_hosts"', '"PrometheusSettings"', '"allow_trusted_network_without_token"', '"include_docker_vhosts"', '"include_streams"', '"include_logging"']: + for marker in ['"version": "0.25.2"', '"memory_preset"', '"cold_preset"', '"memory_budget_bytes"', '"max_inflight"', '"preset"', '"session_days"', '"session_lifetime_seconds"', '"accept_local_listener_risk"', '"backend_header"', '"backend_timing_header"', '"cold_on_age"', '"match_type"', '"regex_insensitive"', '"NotFoundHost"', '"not_found_hosts"', '"PrometheusSettings"', '"allow_trusted_network_without_token"', '"include_docker_vhosts"', '"include_streams"', '"include_logging"']: assert marker in openapi, marker assert '"software_update"' in openapi and '"update_available"' in openapi for marker in ['"/api/certificates/deploy"', '"CertificateDeploy"', '"CertificateDeployResult"', '"/api/certificates/reload"']: @@ -1163,7 +1163,7 @@ def main() -> None: source_contract_smoke() container_contract_smoke() debian_grafana_contract_smoke() - print("PASS: 0.25.1 SQLite/upgrade, local-response 404 hosts, Prometheus/Grafana diagnostics, Debian packaging, cache/dashboard/logging/Nagios, retained admin/H3/stream/TLS contracts and reproducible source archives.") + print("PASS: 0.25.2 SQLite/upgrade, local-response 404 hosts, Prometheus/Grafana diagnostics, Debian packaging, cache/dashboard/logging/Nagios, retained admin/H3/stream/TLS contracts and reproducible source archives.") if __name__ == "__main__": diff --git a/scripts/swagger_assets.py b/scripts/swagger_assets.py index f557a85..629dbed 100755 --- a/scripts/swagger_assets.py +++ b/scripts/swagger_assets.py @@ -85,7 +85,7 @@ def download(destination: Path) -> None: """TLS verification uses Python's default trust store; never executes a shell.""" started = time.monotonic() opener = build_opener(HttpsRedirects()) - request = Request(DOWNLOAD_URL, headers={"User-Agent": "RustProxy-build/0.25.1"}) + request = Request(DOWNLOAD_URL, headers={"User-Agent": "RustProxy-build/0.25.2"}) with opener.open(request, timeout=30) as response, destination.open("wb") as output: length = response.headers.get("Content-Length") if length and int(length) > MAX_ARCHIVE: diff --git a/scripts/upgrade.py b/scripts/upgrade.py index 34f1020..c76d532 100755 --- a/scripts/upgrade.py +++ b/scripts/upgrade.py @@ -46,7 +46,7 @@ def upgrade(source: Path, target: Path, *, stopped: bool, data_dir: Path | None for name in MANAGED: if (target / name).is_symlink(): raise ValueError(f"Managed target {name} is a symlink; refusing to replace it.") - backup = target / ".upgrade-backup" / ("before-0.25.1-" + uuid.uuid4().hex[:12]) + backup = target / ".upgrade-backup" / ("before-0.25.2-" + uuid.uuid4().hex[:12]) backup.mkdir(parents=True, mode=0o700) installed: list[str] = [] moved: list[str] = [] @@ -84,7 +84,7 @@ def upgrade(source: Path, target: Path, *, stopped: bool, data_dir: Path | None remove_legacy = legacy.exists() if remove_legacy and (legacy.is_symlink() or not legacy.is_file()): raise ValueError("Obsolete renewal observation sidecar must be a regular file.") - (backup / "upgrade.json").write_text(json.dumps({"from_directory":str(target), "to_version":"0.25.1", "managed_paths":MANAGED, "removed_obsolete_runtime_state": remove_legacy}, indent=2)+"\n", encoding="utf-8") + (backup / "upgrade.json").write_text(json.dumps({"from_directory":str(target), "to_version":"0.25.2", "managed_paths":MANAGED, "removed_obsolete_runtime_state": remove_legacy}, indent=2)+"\n", encoding="utf-8") if remove_legacy: legacy.unlink() except BaseException: @@ -115,7 +115,7 @@ def main() -> None: backup = upgrade(Path(__file__).resolve().parents[1], args.target, stopped=args.stopped, data_dir=data_dir) except (OSError, ValueError) as error: parser.exit(2, f"Upgrade refused or rolled back: {error}\n") - print(f"Installed RustProxy 0.25.1. Previous managed source files: {backup}") + print(f"Installed RustProxy 0.25.2. Previous managed source files: {backup}") print("Preserved data, .env, .git, target and other unmanaged files. No database migration was run.") print(f"Next: cd {args.target}; ./dev.sh test; ./dev.sh") diff --git a/scripts/web_server_contracts.py b/scripts/web_server_contracts.py index 5cdf5ff..99e81e4 100755 --- a/scripts/web_server_contracts.py +++ b/scripts/web_server_contracts.py @@ -96,7 +96,7 @@ need(web, 'HeaderValue::from_static("no-store")', "builtin_error_response(StatusCode::FORBIDDEN, &method)", "empty_index_can_go_directly_to_directory_listing", - "RustProxy 0.25.1", + "RustProxy 0.25.2", "traversal_and_symlink_escape_are_not_served", "try_files_rewrites_cache_rules_headers_and_downloads_work", "precompressed_sidecars_keep_mime_validators_and_range_semantics", @@ -178,7 +178,7 @@ assert "Source" in openapi.get("components", {}).get("schemas", {}) assert "client_ips" in openapi["components"]["schemas"]["TrafficCounters"]["properties"] assert (ROOT / "docs/WEB_SERVER.md").is_file() assert (ROOT / "docs/PHP_FPM.md").is_file() -assert (ROOT / "docs/V0.25.1_AUDIT.md").is_file() +assert (ROOT / "docs/V0.25.2_AUDIT.md").is_file() assert (ROOT / "docs/V0.25.0_AUDIT.md").is_file() assert (ROOT / "docs/TODO.md").is_file() docs_html = (ROOT / "src/admin/web/docs.html").read_text() diff --git a/src/admin/documentation.rs b/src/admin/documentation.rs index bd8bf0d..1fcdbaf 100644 --- a/src/admin/documentation.rs +++ b/src/admin/documentation.rs @@ -72,6 +72,7 @@ static READER_DOCUMENTS: &[(&str, &str, &str)] = &[ ("docs/V0.24.8_AUDIT.md", "audits", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/V0.24.8_AUDIT.md"))), ("docs/V0.25.0_AUDIT.md", "audits", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/V0.25.0_AUDIT.md"))), ("docs/V0.25.1_AUDIT.md", "audits", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/V0.25.1_AUDIT.md"))), + ("docs/V0.25.2_AUDIT.md", "audits", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/V0.25.2_AUDIT.md"))), ("docs/V0.25.0_SECURITY_PREPROD_AUDIT.md", "audits", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/V0.25.0_SECURITY_PREPROD_AUDIT.md"))), ("docs/VHOSTS.md", "guides", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/VHOSTS.md"))), ("docs/VHOST_ACCESS.md", "guides", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/VHOST_ACCESS.md"))), diff --git a/src/admin/openapi.json b/src/admin/openapi.json index 2a51d0c..1f406a0 100644 --- a/src/admin/openapi.json +++ b/src/admin/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "RustProxy Administration API", - "version": "0.25.1", + "version": "0.25.2", "description": "Administration API for the active panel origin. Sign in through the panel; the same-origin HttpOnly cookie authenticates all private operations. Mutations also require the exact Origin, X-RustProxy-Request: 1 and a session-bound X-CSRF-Token. Loopback HTTP is the default; Settings can enable HTTPS before using a non-loopback administration bind. This document contains no live configuration or credentials. Object schemas marked extensible are summaries: full configuration validation is authoritative in Rust." }, "servers": [ diff --git a/src/admin/web/app/core.js b/src/admin/web/app/core.js index a66d5f9..62a477a 100644 --- a/src/admin/web/app/core.js +++ b/src/admin/web/app/core.js @@ -3,6 +3,23 @@ const $ = (id) => document.getElementById(id); let language = null; try { language = localStorage.getItem("rp-language"); } catch { } let csrf = "", authenticated = false, setupRequired = false, sessionInfo = null; +const ORIGIN_RECOVERY_KEY = "rp-origin-recovery-v1"; +function recoverInvalidOrigin(error) { + if (error?.code !== "auth.invalid_origin" || typeof location === "undefined") return false; + const marker = `${location.origin}${location.pathname}${location.search}`; + try { + if (sessionStorage.getItem(ORIGIN_RECOVERY_KEY) === marker) return false; + sessionStorage.setItem(ORIGIN_RECOVERY_KEY, marker); + } catch { + if (recoverInvalidOrigin.attempted) return false; + recoverInvalidOrigin.attempted = true; + } + location.reload(); + return true; +} +function clearInvalidOriginRecovery() { + try { sessionStorage.removeItem(ORIGIN_RECOVERY_KEY); } catch { recoverInvalidOrigin.attempted = false; } +} const ADMIN_THEMES = new Set(["dark", "light"]); let adminPreferences = null, adminPreferencesLoading = null, adminPreferencesWrite = Promise.resolve(); function normalizeAdminTheme(value) { return ADMIN_THEMES.has(value) ? value : "dark"; } @@ -400,10 +417,11 @@ async function revalidateSession() { const epoch = authEpoch; try { const info = await api("/api/auth/session"); + clearInvalidOriginRecovery(); if (epoch !== authEpoch) return; if (!authenticated) { await activate(info); return; } sessionInfo = info; csrf = info.csrf_token; renderSession(); await refresh(true); startLive(); - } catch (error) { if (error.status !== 401) toast(error.message, true); } + } catch (error) { if (recoverInvalidOrigin(error)) return; if (error.status !== 401) toast(error.message, true); } } window.addEventListener("pageshow", event => { if (event.persisted) void revalidateSession(); }); document.addEventListener("visibilitychange", () => { if (document.hidden) stopLive(); else if (authenticated) { void revalidateSession(); } }); diff --git a/src/admin/web/app/runtime.js b/src/admin/web/app/runtime.js index 8133956..071e742 100644 --- a/src/admin/web/app/runtime.js +++ b/src/admin/web/app/runtime.js @@ -4,12 +4,14 @@ // app.js belongs to the authenticated shell and must not fall back to its // legacy embedded sign-in form when another startup step fails. const info = await api("/api/auth/session"); + clearInvalidOriginRecovery(); await loadLanguages(); setupRequired = false; translate(); syncAuthForm(); await activate(info); } catch (e) { + if (recoverInvalidOrigin(e)) return; if (e.status === 401) { location.replace(`${location.pathname}${location.search}`); return; diff --git a/src/admin/web/app/settings.js b/src/admin/web/app/settings.js index a1b5596..00fbb2d 100644 --- a/src/admin/web/app/settings.js +++ b/src/admin/web/app/settings.js @@ -6,7 +6,7 @@ function renderSettings() { if (!settingsDirty.has("response-form")) { const server = config.server_header || {}, compression = config.compression || {}, diagnostics = config.diagnostic_headers || {}; $("expose-request-id").checked = !!diagnostics.request_id; - $("server-mode").value = server.mode || "preserve"; $("server-value").value = server.value || "RustProxy/v0.25.1"; + $("server-mode").value = server.mode || "preserve"; $("server-value").value = server.value || "RustProxy/v0.25.2"; for (const name of ["gzip", "br", "zstd", "deflate"]) $(`compress-${name}`).checked = !!compression[name]; $("compress-min").value = compression.min_bytes ?? 1024; syncServerMode(); } diff --git a/src/admin/web/index.html b/src/admin/web/index.html index b7efdc7..602a40d 100644 --- a/src/admin/web/index.html +++ b/src/admin/web/index.html @@ -1472,7 +1472,7 @@

Diagnostic headers

@@ -2219,7 +2219,7 @@
-