This commit is contained in:
Mateusz Gruszczyński committed 2026-10-06 23:54:01 +02:00
1 parent 9d6614c1b4
commit a9f9450f80
16 files changed
+171 -47

No files matched your search

+1 -1
View File
@@ -7,7 +7,7 @@
## 0.26.7 ## 0.26.7
- Add HOT and COLD cache occupancy progress bars on `/cache` for configured object and byte limits, with explicit percentage/limit details. - Add HOT and COLD cache occupancy progress bars on `/cache` for configured object and byte limits, with explicit percentage/limit details.
- Verify cache `Age` progression on stored HITs, protect `Age` from custom response-header rules, and clarify that successful validator revalidation legitimately restarts the HTTP freshness age while `X-Cache-Resident-Age` continues to grow. - Verify cache `Age` progression on stored HITs and retain 0.26.6 compatibility for existing custom `Age` rules. Response-side Age rules are deferred to the final wire stage so they cannot alter internal freshness/storage. Successful validator revalidation still legitimately restarts HTTP freshness age while `X-Cache-Resident-Age` continues to grow.
- Restore downstream compression for cookie-bearing responses only when RustProxy is serving a shared-cache HIT already accepted by cache policy or a local static Web Server response; privacy-sensitive dynamic/cookie responses remain excluded. - Restore downstream compression for cookie-bearing responses only when RustProxy is serving a shared-cache HIT already accepted by cache policy or a local static Web Server response; privacy-sensitive dynamic/cookie responses remain excluded.
- Keep `Content-Encoding`/`Vary: Accept-Encoding` negotiation for gzip, Brotli, Zstandard and deflate, and make the bundled compression tester stop forcing `Cache-Control: no-cache` unless explicitly requested. - Keep `Content-Encoding`/`Vary: Accept-Encoding` negotiation for gzip, Brotli, Zstandard and deflate, and make the bundled compression tester stop forcing `Cache-Control: no-cache` unless explicitly requested.
- Preserve compatible backend upstream clients across unrelated active-snapshot rebuilds so generation age, generation request count and rotation totals do not reset spuriously. - Preserve compatible backend upstream clients across unrelated active-snapshot rebuilds so generation age, generation request count and rotation totals do not reset spuriously.
+1
View File
@@ -9,6 +9,7 @@ Current direction: 0.26.6 -> 0.26.7
- [x] Keep request-count and age-based backend client recycling covered by tests and visible diagnostics. - [x] Keep request-count and age-based backend client recycling covered by tests and visible diagnostics.
- [x] Refresh container documentation and include all supported Certbot DNS plugins in the shipped OCI image. - [x] Refresh container documentation and include all supported Certbot DNS plugins in the shipped OCI image.
- [x] Review current installation/cache/backend lifecycle documentation for stale behavior statements. - [x] Review current installation/cache/backend lifecycle documentation for stale behavior statements.
- [x] Keep existing 0.26.6 custom `Age` rules loadable while isolating response-side Age overrides from cache freshness/storage semantics.
- [x] Keep SQLite schema 8 and saved proxy configuration compatibility with 0.26.6. - [x] Keep SQLite schema 8 and saved proxy configuration compatibility with 0.26.6.
- [ ] Run compiler-enabled `cargo fmt --all`, `cargo check --locked --all-targets` and `cargo test --locked --all-targets` on a Rust release host. - [ ] Run compiler-enabled `cargo fmt --all`, `cargo check --locked --all-targets` and `cargo test --locked --all-targets` on a Rust release host.
+15 -15
View File
@@ -2,12 +2,12 @@
4140a2956264b44f259e6a37fc00ba02097bbe68b09296e04d3d243c1287129b .env.example 4140a2956264b44f259e6a37fc00ba02097bbe68b09296e04d3d243c1287129b .env.example
9e6356475bf587091bab72246ec9e325b498f7e48a6220e9a18baa50d0a385a6 .gitignore 9e6356475bf587091bab72246ec9e325b498f7e48a6220e9a18baa50d0a385a6 .gitignore
c39fd74d633dc36cd7caa8d9b6785151b116752b79bd629d4d810cb03e1096cc AGENTS.md c39fd74d633dc36cd7caa8d9b6785151b116752b79bd629d4d810cb03e1096cc AGENTS.md
abe0ebe15057929bec551cc1b92cfa25fb7aca616ba7e3bc8d23a4f02082a446 CHANGELOG.md 6308cefa554fa14a9e456eaeb5c98e329e4f8b922fc4b481062ac4323db8ac93 CHANGELOG.md
91e57afc931cf4cb929c85a5dca872ce1388f93d38645fbeb4bfd77ba89b2787 Cargo.lock 91e57afc931cf4cb929c85a5dca872ce1388f93d38645fbeb4bfd77ba89b2787 Cargo.lock
bd39e691ce35a1677c6c8a540423f83e51351d7aa9bddd090c9a92408ec2fc20 Cargo.toml bd39e691ce35a1677c6c8a540423f83e51351d7aa9bddd090c9a92408ec2fc20 Cargo.toml
239713948df0d54504989f1ab30f5bf0128b90b4e5ac45d50a132aa033323baf Dockerfile 239713948df0d54504989f1ab30f5bf0128b90b4e5ac45d50a132aa033323baf Dockerfile
e88ff59b0e79d0c468178a4d9cbe6405175b61d7201112855c83b72b6e4f0fad README.md e88ff59b0e79d0c468178a4d9cbe6405175b61d7201112855c83b72b6e4f0fad README.md
f47bd96e96b4e87ab5fb7d319b96f3a57f9e24ce34ab9758aaaa17b255d74390 ROADMAP.md 340b8c205b773fbcfb096190bd854b320f05a0ac1e14a011ffb914f65dd87d70 ROADMAP.md
da8311e3db945290666324a2f24bf4521b365272d7ac3f862ded1be993eea646 UPDATE_CHECK.toml da8311e3db945290666324a2f24bf4521b365272d7ac3f862ded1be993eea646 UPDATE_CHECK.toml
928ba6a504bc431ae100c6e587861e5088b28822d2cb19da5d437b39d26c864a build.rs 928ba6a504bc431ae100c6e587861e5088b28822d2cb19da5d437b39d26c864a build.rs
834e8de4387354b23a2cb8c2d484f0f641dfed09472a9db12efcd0e683969a06 debian/README.md 834e8de4387354b23a2cb8c2d484f0f641dfed09472a9db12efcd0e683969a06 debian/README.md
@@ -42,7 +42,7 @@ ec15163f5f51c9212003b7edd1fc380321311c2496fbaa9b45c838281b9af475 docs/ACME_RFC2
d699f2261a0d7854e49e9b8bae44fa37008a7d2f6340a73cdc37e840722ea211 docs/API.md d699f2261a0d7854e49e9b8bae44fa37008a7d2f6340a73cdc37e840722ea211 docs/API.md
96f0cdc4af8961b69c19d0f8204d467b2b70abf3882f1fe1a540c82cac9c063b docs/API_ACCESS.md 96f0cdc4af8961b69c19d0f8204d467b2b70abf3882f1fe1a540c82cac9c063b docs/API_ACCESS.md
0f7f159ad5b4b5277f80c19f3c47f4e7823ea439ef02e5913e1391f1b34d129f docs/ARCHITECTURE.md 0f7f159ad5b4b5277f80c19f3c47f4e7823ea439ef02e5913e1391f1b34d129f docs/ARCHITECTURE.md
fec156479641ffa02c5702dd54f79de8efa1e9768557f2d38e44cd6868bba9e4 docs/CACHE.md 86469d17f9786c3e6f20c66791b47e10e11695d2f1a88c66c9ded157a585c85f docs/CACHE.md
6266163e17d2921468abdae92c1620266af8351321530e2c9b7dca6f3dd89c88 docs/CACHE_DIAGNOSTICS.md 6266163e17d2921468abdae92c1620266af8351321530e2c9b7dca6f3dd89c88 docs/CACHE_DIAGNOSTICS.md
c6003dcbc0b95e2cfa9523b7017186cc8b4826248b588af77111ac5eb15174f9 docs/CHANGELOG_TECHNICAL_ARCHIVE.md c6003dcbc0b95e2cfa9523b7017186cc8b4826248b588af77111ac5eb15174f9 docs/CHANGELOG_TECHNICAL_ARCHIVE.md
d99fddfc982316c4e915cc6437fe339385350c49a2d06d570d42fd0100227e78 docs/CONTAINER.md d99fddfc982316c4e915cc6437fe339385350c49a2d06d570d42fd0100227e78 docs/CONTAINER.md
@@ -68,11 +68,11 @@ e7516646dfb107d6240370f4e7460b1189f2eb21f398740381d5d102847bbfe6 docs/NOT_FOUND
ce5afb4f531bb5fb4c453deb49f643f2a563f32f7908c995e5bc8e0d7de995b7 docs/RENEWAL.md ce5afb4f531bb5fb4c453deb49f643f2a563f32f7908c995e5bc8e0d7de995b7 docs/RENEWAL.md
585ef0ac4492e03adca46949823e5a7538f56d9768090cd4dc080bfce4305b52 docs/ROADMAP_HISTORY.md 585ef0ac4492e03adca46949823e5a7538f56d9768090cd4dc080bfce4305b52 docs/ROADMAP_HISTORY.md
4bf7b3ea0bfe6c6b0ae0b7884b48b10ba29ff3a71611ec96957a3327d3c6ec88 docs/SECRET_STORE_BACKUP.md 4bf7b3ea0bfe6c6b0ae0b7884b48b10ba29ff3a71611ec96957a3327d3c6ec88 docs/SECRET_STORE_BACKUP.md
63e033f08acafa0bb6ac3c6ff58ba56a185e34c9ad29f94725837aba4f36b2be docs/SECURITY.md 5dbdf45fddba198a28f511dbbd2f12bf7d2b88c41c3cbedbc20dfea3858779d3 docs/SECURITY.md
620b69e566d27af04f0604f4ca3f88cf95cdfea7fbc6ae9dd92446d2b2b5d9be docs/SECURITY_AUDIT.md 620b69e566d27af04f0604f4ca3f88cf95cdfea7fbc6ae9dd92446d2b2b5d9be docs/SECURITY_AUDIT.md
0c312c26930153a5e7ee77431d3df4d544b5aadbfcbbb1c8aba2a2d0a7d5e656 docs/SETTINGS.md 0c312c26930153a5e7ee77431d3df4d544b5aadbfcbbb1c8aba2a2d0a7d5e656 docs/SETTINGS.md
3611defd206cde107e75cac006159856d12cd8f15df7ae2eb9ec3592ed2b4abd docs/STREAMS.md 3611defd206cde107e75cac006159856d12cd8f15df7ae2eb9ec3592ed2b4abd docs/STREAMS.md
365d85700ce3672afd419f74f3dfbf774278a08fddfed34e43b35bc168a42eca docs/TEST_REPORT.md 007f768f6989e49ee90c6d3c51036630042bdad3bca69b44d453a3c7f11380e4 docs/TEST_REPORT.md
cfb452d8496839e832b5fbacdd4e52525b0ecc3112fbb62340c1e580bd5c1717 docs/TODO.md cfb452d8496839e832b5fbacdd4e52525b0ecc3112fbb62340c1e580bd5c1717 docs/TODO.md
8ee6187a5460ca45643b7fd789e27bffd264449e3a92ff70e9359edee011bbbb docs/TRANSFORM.md 8ee6187a5460ca45643b7fd789e27bffd264449e3a92ff70e9359edee011bbbb docs/TRANSFORM.md
a1e9c6d38d21067d1ce7115ca3395ae8ff1f6a4c40fb0ebf00a6940ef79df439 docs/TRANSPORT.md a1e9c6d38d21067d1ce7115ca3395ae8ff1f6a4c40fb0ebf00a6940ef79df439 docs/TRANSPORT.md
@@ -106,7 +106,7 @@ bf9b4cf4fec2c8ce287e1b3c2e7d4828e101dafe4d6c173f099819f22493d144 docs/V0.25.5_A
da5ec87b9482f2fc506c4334d9365f3bfaec89254a14e5585dd6460326b20e10 docs/V0.26.4_IMPLEMENTATION.md da5ec87b9482f2fc506c4334d9365f3bfaec89254a14e5585dd6460326b20e10 docs/V0.26.4_IMPLEMENTATION.md
0ddf5215c40c8366844350bc6462d8013dba747b6f3e8fbf477985af215e5e8d docs/V0.26.5_IMPLEMENTATION.md 0ddf5215c40c8366844350bc6462d8013dba747b6f3e8fbf477985af215e5e8d docs/V0.26.5_IMPLEMENTATION.md
dd9b5dcdbd53a04fd32458caaf03b56aa335f0153b1a0e332fdf230fec12680e docs/V0.26.6_IMPLEMENTATION.md dd9b5dcdbd53a04fd32458caaf03b56aa335f0153b1a0e332fdf230fec12680e docs/V0.26.6_IMPLEMENTATION.md
d21acc458442278628e524ba0b45eb7baa06a46633376a4abbcc35a8c34e5ff2 docs/V0.26.7_IMPLEMENTATION.md f9667fe81b31762e64d36126a7e94b609316dfa856de8f8e3b96661dd2a918c6 docs/V0.26.7_IMPLEMENTATION.md
0f0e0e2920c76b2f82f38c6e60ed992a532c63851f593f2a1fa59e8d0014bb41 docs/VHOSTS.md 0f0e0e2920c76b2f82f38c6e60ed992a532c63851f593f2a1fa59e8d0014bb41 docs/VHOSTS.md
09b60fb7b40db4d0139fa2a9e69a2aa9f28d4e61b8643521cfc7e936d7df8774 docs/VHOST_ACCESS.md 09b60fb7b40db4d0139fa2a9e69a2aa9f28d4e61b8643521cfc7e936d7df8774 docs/VHOST_ACCESS.md
f0e76167944861517d1d42b9186ff0b2f184be0371b9e8597827e60895de4a76 docs/WEB_SERVER.md f0e76167944861517d1d42b9186ff0b2f184be0371b9e8597827e60895de4a76 docs/WEB_SERVER.md
@@ -126,8 +126,8 @@ c294a5dcb94c1cf7edf72243bb533cc487a17452a7c0ac8c72f23cce0ec42fc6 grafana/rustpr
30763fc61c38992a44bc24ad6932b88fd3b322724fce799db1f17f9a747eb59a install-debian13.sh 30763fc61c38992a44bc24ad6932b88fd3b322724fce799db1f17f9a747eb59a install-debian13.sh
858b39fa6886b71d20690bf369e76580922364b3cc7f0cf6ba9b040df9cd76e3 install.sh 858b39fa6886b71d20690bf369e76580922364b3cc7f0cf6ba9b040df9cd76e3 install.sh
29cada0936762f26495064d1524a668262ed729b21f20aebddc8b6e5cfea363b langs/README.md 29cada0936762f26495064d1524a668262ed729b21f20aebddc8b6e5cfea363b langs/README.md
5a36a455c9ed45749aca05fe28b3d3036e49cd748c6b89c2334973e7b070f2cb langs/en_US.json 9f367283c849d5217cb9386cf743221cc1d44ea4e76aa50a08234bbe71455837 langs/en_US.json
ec860b4940fd237b8336154824b40b8f09fc009d3baa505771fc56c9ba2bda9e langs/pl_pl.json 028c8d841c8cf246a021675054e638d6ab9f0c9785f823e2e4eec60a70fa8823 langs/pl_pl.json
8b258ac4654a046dc649c7fa8cef273c0f9431c9a12ea9b772dd0a1c95bb281f make_zip.py 8b258ac4654a046dc649c7fa8cef273c0f9431c9a12ea9b772dd0a1c95bb281f make_zip.py
e830d37dd1f81a4b0aa7d535f5bba69e34e433b0fd99cc8ee0b296786c32379a migrations/0002_admin.sql e830d37dd1f81a4b0aa7d535f5bba69e34e433b0fd99cc8ee0b296786c32379a migrations/0002_admin.sql
20112cc82192098b8d602f1c4f7ca08712e9e1366c845fabb92753187c370d35 migrations/0003_certificate_renewals.sql 20112cc82192098b8d602f1c4f7ca08712e9e1366c845fabb92753187c370d35 migrations/0003_certificate_renewals.sql
@@ -147,7 +147,7 @@ edb6ee1445b01cd6f3a74a3a607f39fb107d411b49df028d2e0f4679e093de31 scripts/acme_p
e69813fb1d3a4cdcfb7243964cc323fc21a603087bf2e84188fa42b9d3cee4b5 scripts/acme_rfc2136.py e69813fb1d3a4cdcfb7243964cc323fc21a603087bf2e84188fa42b9d3cee4b5 scripts/acme_rfc2136.py
a5b4451e45ce975685772aa8409b64a57a20a7548466584c3ea7fb975e64729c scripts/admin_audit.py a5b4451e45ce975685772aa8409b64a57a20a7548466584c3ea7fb975e64729c scripts/admin_audit.py
20e83f8b6347b819d1d1ea3de5c691c36f0816954a4210b0d3f39557fefe1931 scripts/backup.py 20e83f8b6347b819d1d1ea3de5c691c36f0816954a4210b0d3f39557fefe1931 scripts/backup.py
f79fa03aa2b5f0c04d37c108af55d8c43f9faf402f16553d5348c33a088e6108 scripts/check_source.py 1d88238e867dcc45c9666f765667674a7dce595a6db578dbfec15311b751f567 scripts/check_source.py
187ee92c8029cdd59e89b4447cf4d170e856017a2091ceb85ae7c22dd1a79c72 scripts/deploy.py 187ee92c8029cdd59e89b4447cf4d170e856017a2091ceb85ae7c22dd1a79c72 scripts/deploy.py
fe28d6d580a53d95a0fc318ebc4da5bbc71a23781bb827158848934985477035 scripts/deploy_core.py fe28d6d580a53d95a0fc318ebc4da5bbc71a23781bb827158848934985477035 scripts/deploy_core.py
f014472588e889488976970dbaa95a8745b6907516aa81a7cca21d0f81eaaad3 scripts/deploy_runtime.py f014472588e889488976970dbaa95a8745b6907516aa81a7cca21d0f81eaaad3 scripts/deploy_runtime.py
@@ -235,7 +235,7 @@ e6c4d91d1542d0a5b47bb8736f9539d43a6713df5a473b635519d5b7eac2eb9c src/admin/web/
0a5fff35555153f59d4346933030a67b6e1a8768cee7a47cfa2fcf1a2c564f63 src/admin/web/favicon.svg 0a5fff35555153f59d4346933030a67b6e1a8768cee7a47cfa2fcf1a2c564f63 src/admin/web/favicon.svg
af6e7a2e890df2fb7385a374bcb3fba52fb5a53dbe18adc1fb7865660fcba842 src/admin/web/file-logs.js af6e7a2e890df2fb7385a374bcb3fba52fb5a53dbe18adc1fb7865660fcba842 src/admin/web/file-logs.js
631860ab3793ffd827c7cf19ddd049d14419509f2c2d8b27994b0e54baa0e32a src/admin/web/host404.js 631860ab3793ffd827c7cf19ddd049d14419509f2c2d8b27994b0e54baa0e32a src/admin/web/host404.js
1167c964000bb5dae1ba92b850ff76162570af340029d8788296160adc9f67b5 src/admin/web/index.html 4e8c9a1a1bd9870f0cd8aad5c9da80ff130497f11dbda116d79f91d9bfc129c6 src/admin/web/index.html
24db88842707f764064657712d3660c6ac129fbfb47d188c0903925cf8244ce5 src/admin/web/integrations.js 24db88842707f764064657712d3660c6ac129fbfb47d188c0903925cf8244ce5 src/admin/web/integrations.js
cdab0c11a28deebdf401c0453fbc76b675e9e643722106fb20e812bb9648ccde src/admin/web/login-keys.json cdab0c11a28deebdf401c0453fbc76b675e9e643722106fb20e812bb9648ccde src/admin/web/login-keys.json
a253d202dc9a147f2add34c64e0f35c38fdc7f667f9908933ce5f959b1a92a2e src/admin/web/login.html a253d202dc9a147f2add34c64e0f35c38fdc7f667f9908933ce5f959b1a92a2e src/admin/web/login.html
@@ -247,7 +247,7 @@ f1ab20afa09f75f3635511756e36afea46a7917f04937d6f4e89c4e9f3f43204 src/admin/web/
f5831c2188233e425beecedc9e5e2829b620f80fc5326b73198024d27b81d1e7 src/admin/web/style.css f5831c2188233e425beecedc9e5e2829b620f80fc5326b73198024d27b81d1e7 src/admin/web/style.css
a8a73a4fa849ae49391fcd6143e900c515c8b577f71b55cbae9701fa9f5bbe47 src/admin/web/traffic-panel.js a8a73a4fa849ae49391fcd6143e900c515c8b577f71b55cbae9701fa9f5bbe47 src/admin/web/traffic-panel.js
e686d68fd6a368110a85dc4c5dc64e8093950d20d63e9aa44790c03dea6fc193 src/admin/web/transform-panel.js e686d68fd6a368110a85dc4c5dc64e8093950d20d63e9aa44790c03dea6fc193 src/admin/web/transform-panel.js
106db335a6dd3e326955241e1d67caa5562a427b0356e7433cd0d92087342ce8 src/admin/web/vhosts/core.js 6856c230c4f6b18145e16efb6636433f8b21ce3ec5d34246d6561f0150ea0554 src/admin/web/vhosts/core.js
4d4ee863a3d77e5213ae4bdcad1f533a6222353b9c32e0b5f12f7ea60ab62bea src/admin/web/vhosts/editor.js 4d4ee863a3d77e5213ae4bdcad1f533a6222353b9c32e0b5f12f7ea60ab62bea src/admin/web/vhosts/editor.js
e0011edc708d245cb514be170c1bbe846a0298b81d86276fe29af05010d215a4 src/admin/web/vhosts/render.js e0011edc708d245cb514be170c1bbe846a0298b81d86276fe29af05010d215a4 src/admin/web/vhosts/render.js
b3d8d3e22e4cbab10c5d588c268da6eb3075a9ec857728ce6b5d56e3ffd3c9a6 src/admin/web/web-server.js b3d8d3e22e4cbab10c5d588c268da6eb3075a9ec857728ce6b5d56e3ffd3c9a6 src/admin/web/web-server.js
@@ -340,8 +340,8 @@ fe571c85a924c827097f4be01b3a827a16bc5dd17a55fbf991ee3ad016641305 src/docker/tes
057e652f216a1478638cbb53b1c64ac88c7f413a65825caacbf893c5af004a63 src/forwarding/tests.rs 057e652f216a1478638cbb53b1c64ac88c7f413a65825caacbf893c5af004a63 src/forwarding/tests.rs
72215ef6911893f55cc82278169268f03d869b94728f118bf007fc555d609fde src/geoip.rs 72215ef6911893f55cc82278169268f03d869b94728f118bf007fc555d609fde src/geoip.rs
e40171e72327bc4350ba95cedacde2f3c9148c26a5a232d9f790f4449f20a685 src/geoip/tests.rs e40171e72327bc4350ba95cedacde2f3c9148c26a5a232d9f790f4449f20a685 src/geoip/tests.rs
4e009ec10151a068fa83cf93cac78d2254d152e0386442586d6f5860ab2710b4 src/headers.rs 13269be4ad8d3e4c91893753f654c3022e6f86d171c5d1ff21eda2d9185ec61f src/headers.rs
01cb98570ec388170420883e4ba18b2bf85ad1af97854dbc3a12b135db511c5c src/headers/tests.rs fbb6859780309f8cca1aeaba43fa67d13a1edf6f5a60c5c982d2067b71fb0846 src/headers/tests.rs
d0cd8589321595309834690fa9ddb98488886bc98f34ea45aa0ccb3035412fb5 src/http3.rs d0cd8589321595309834690fa9ddb98488886bc98f34ea45aa0ccb3035412fb5 src/http3.rs
2165a0a4b27d621985e2030326ad52b8bbd59d7b9e4f092f72f7e92b4e58b497 src/http3/body.rs 2165a0a4b27d621985e2030326ad52b8bbd59d7b9e4f092f72f7e92b4e58b497 src/http3/body.rs
68bc11160bc5275a904e48af8698b37433096fb6d592364d8281d59bb9e2b3f4 src/http3/tests.rs 68bc11160bc5275a904e48af8698b37433096fb6d592364d8281d59bb9e2b3f4 src/http3/tests.rs
@@ -376,7 +376,7 @@ d3c6b08088df0003144544936a55e2a3661aac85292821b594f00adfd19a2951 src/prometheus
e03f0d6b3f35dae6b98c394837aa0d17e4d73a8306767b4c607868bc00c30286 src/prometheus/vhosts_backends.rs e03f0d6b3f35dae6b98c394837aa0d17e4d73a8306767b4c607868bc00c30286 src/prometheus/vhosts_backends.rs
b5856fd38a957b2ca9c7e3606d84d13d5cfcf3a45b487b15ae1d2dc248cbda57 src/proxy/body.rs b5856fd38a957b2ca9c7e3606d84d13d5cfcf3a45b487b15ae1d2dc248cbda57 src/proxy/body.rs
16b9d4db3b20b2ab14c47ac14fcd18d3d8ca77c3ad9a1f37866ebfa60f379862 src/proxy/body/tests.rs 16b9d4db3b20b2ab14c47ac14fcd18d3d8ca77c3ad9a1f37866ebfa60f379862 src/proxy/body/tests.rs
00e50a914511754266b4eae2626244587174bfa71fff91509c530bcde7f0d397 src/proxy/cache_helpers.rs f0cb26ed34c35bacc74b914457acac8a611390de14cf46e2e6d3bcc848e914e8 src/proxy/cache_helpers.rs
cfb0910af8cd868bde3f6c9992319e46d5328d6d8f69c50701df0ea12bef2108 src/proxy/forward/cache_lookup.rs cfb0910af8cd868bde3f6c9992319e46d5328d6d8f69c50701df0ea12bef2108 src/proxy/forward/cache_lookup.rs
212de22f407dd96800afcf65fd1fc7c40fede1b88f80d04850eb8d1227b08a5b src/proxy/forward/response.rs 212de22f407dd96800afcf65fd1fc7c40fede1b88f80d04850eb8d1227b08a5b src/proxy/forward/response.rs
a74deecbdbc5953c46b885a4627b3d1e820752b03921b80406ae11080ea56cc4 src/proxy/forward/route_cache.rs a74deecbdbc5953c46b885a4627b3d1e820752b03921b80406ae11080ea56cc4 src/proxy/forward/route_cache.rs
@@ -389,7 +389,7 @@ c438066616527a3108f2ad53e3698ae9b2a80961714394cdd0a450c92f99d992 src/proxy/head
f6aef54204b5bb9305dd1367818c7ffd3cb273008404c9fab0317af1d13ba7a6 src/proxy/mod.rs f6aef54204b5bb9305dd1367818c7ffd3cb273008404c9fab0317af1d13ba7a6 src/proxy/mod.rs
d75ff3e38affa49f9a8b9d6801454c1449458e5fbf07e08d51aa3f42abebafd5 src/proxy/tests/authority_tests.rs d75ff3e38affa49f9a8b9d6801454c1449458e5fbf07e08d51aa3f42abebafd5 src/proxy/tests/authority_tests.rs
622d44511b82883a34830d0ae3ea665fd40b58eaa85653f58588db92d55a17c3 src/proxy/types.rs 622d44511b82883a34830d0ae3ea665fd40b58eaa85653f58588db92d55a17c3 src/proxy/types.rs
352260ef9e956534bc91b5cb888e3f8e881913c0bb988e6af95e49933d98814d src/proxy/upstream_helpers.rs b09929990cd34db718c6bc82bd7ac9c711cea6dd52c1977febec6d800fbdd991 src/proxy/upstream_helpers.rs
69f775f6746b4c6d729dc10961dbef54168846f4ce9cf7fe453ab5a2b879c9b8 src/proxy/websocket.rs 69f775f6746b4c6d729dc10961dbef54168846f4ce9cf7fe453ab5a2b879c9b8 src/proxy/websocket.rs
d05e655ba0b3b322c657aaa9c2cbe7a0181064a896026b5eeaca347c4a0b7831 src/proxy/websocket/handshake.rs d05e655ba0b3b322c657aaa9c2cbe7a0181064a896026b5eeaca347c4a0b7831 src/proxy/websocket/handshake.rs
b21dc9cf3817dd4e3273ce25887d63d7a772c477b86ca57f8d5b72b8154456e7 src/proxy/websocket/tests.rs b21dc9cf3817dd4e3273ce25887d63d7a772c477b86ca57f8d5b72b8154456e7 src/proxy/websocket/tests.rs
+4 -2
View File
@@ -284,8 +284,10 @@ Cache-Status: RustProxy; hit; ttl=556488; detail="cache_hit"; transformed
Origin lookalike values (`Cache-Status`, `X-Cache-Status` and the other RustProxy Origin lookalike values (`Cache-Status`, `X-Cache-Status` and the other RustProxy
cache diagnostics) are removed before RustProxy emits its own diagnostics. HTTP `Age` cache diagnostics) are removed before RustProxy emits its own diagnostics. HTTP `Age`
remains present on reused cached responses because it is protocol freshness metadata, is generated from cache freshness metadata on reused responses. For 0.26.6 configuration
not a debug header. The Cache dashboard also marks transformed objects explicitly. compatibility an explicit response-header rule may set/append/remove `Age`, but that rule
runs only after cache lookup/revalidation/storage decisions and therefore cannot alter the
internal freshness clock. The Cache dashboard also marks transformed objects explicitly.
## Pool behavior ## Pool behavior
+4 -2
View File
@@ -121,8 +121,10 @@ update and purge paths. Purge is authenticated, CSRF-protected and audited, not
public PURGE/BAN capability. public PURGE/BAN capability.
Compression is opt-in, with authenticated/private/no-transform exclusions. Response Compression is opt-in, with authenticated/private/no-transform exclusions. Response
rules cannot overwrite protected framing/identity fields or the cache-controlled `Age` rules cannot overwrite protected framing/identity fields. For compatibility with 0.26.6,
header. Required Age remains on cache hits even when debug headers are disabled. Server hiding is cosmetic and custom `Age` rules remain accepted. Response-side `Age` is deferred to the final wire stage,
after RustProxy's cache freshness/revalidation logic, so it cannot alter stored age, TTL or
cache eligibility. Server hiding is cosmetic and
must not be considered an access-control or vulnerability mitigation substitute. must not be considered an access-control or vulnerability mitigation substitute.
Standard public 80/443 ports are supported without globally forbidding remote Standard public 80/443 ports are supported without globally forbidding remote
+1 -1
View File
@@ -12,7 +12,7 @@ Static source contracts require all four `/cache` occupancy meters (HOT objects,
The existing upstream-client unit coverage retains age-triggered rotation without user traffic. A new pool regression, `compatible_pool_rebuild_reuses_upstream_clients`, verifies that a compatible rebuild reuses the previous compiled backend/client object so rotation state remains continuous. Source contracts also require the `reusable_with` compatibility check, maintenance calls for normal/WebSocket clients and backend reuse path. These Rust tests require a compiler-enabled release host. The existing upstream-client unit coverage retains age-triggered rotation without user traffic. A new pool regression, `compatible_pool_rebuild_reuses_upstream_clients`, verifies that a compatible rebuild reuses the previous compiled backend/client object so rotation state remains continuous. Source contracts also require the `reusable_with` compatibility check, maintenance calls for normal/WebSocket clients and backend reuse path. These Rust tests require a compiler-enabled release host.
Cache regressions now require a stored HIT/dashboard object to advance `Age`, while compression regressions require `Age` to survive encoding and require cookie-bearing compression to stay blocked unless the internal cache/static safety marker is present. Source contracts protect the `Age` header from custom response rules and require the bounded HIT/static cookie-compression exception. The bundled compression tester no longer forces `Cache-Control: no-cache` unless `--revalidate` is requested. Cache regressions now require a stored HIT/dashboard object to advance `Age`, while compression regressions require `Age` to survive encoding and require cookie-bearing compression to stay blocked unless the internal cache/static safety marker is present. Existing 0.26.6 `Age` rules stay loadable; response-side Age is tagged as a late wire-only override so the generic response-rule pass cannot influence cache freshness/storage. The bundled compression tester no longer forces `Cache-Control: no-cache` unless `--revalidate` is requested.
Documentation checks cover the 0.26.7 release note, Reader index, local Markdown links, cache diagnostics and container/ACME provider guidance. Packaging smoke tests verify the 0.26.7 release identity and deterministic source archive contract. Documentation checks cover the 0.26.7 release note, Reader index, local Markdown links, cache diagnostics and container/ACME provider guidance. Packaging smoke tests verify the 0.26.7 release identity and deterministic source archive contract.
+6 -3
View File
@@ -36,9 +36,12 @@ Current cache, vhost/backend and installation documentation was reviewed alongsi
The cache freshness clock remains RFC-oriented: ordinary stored HITs advance `Age` from The cache freshness clock remains RFC-oriented: ordinary stored HITs advance `Age` from
`initial_age + resident elapsed time`, while a successful validator revalidation starts a `initial_age + resident elapsed time`, while a successful validator revalidation starts a
new freshness calculation. `X-Cache-Resident-Age` remains the monotonic residency clock new freshness calculation. `X-Cache-Resident-Age` remains the monotonic residency clock
across revalidation and tier movement. `Age` is now protected from generic response-header across revalidation and tier movement. Existing 0.26.6 custom `Age` rules remain accepted
rules, and regression coverage explicitly checks that both the wire HIT header and `/cache` for configuration compatibility. Response-side Age rules are isolated to the final wire
object age advance from the stored clock. stage, so set/append/remove cannot feed back into stored freshness, TTL calculation or
validator revalidation. Regression coverage explicitly checks that both the wire HIT header
and `/cache` object age advance from the stored clock before any optional presentation
override.
Compression still refuses Authorization, private/no-store/no-transform, Set-Cookie, ranges, Compression still refuses Authorization, private/no-store/no-transform, Set-Cookie, ranges,
SSE, upgrades and already encoded responses. Cookie-bearing requests now have one bounded SSE, upgrades and already encoded responses. Cookie-bearing requests now have one bounded
+1 -1
View File
@@ -270,7 +270,7 @@
"cacheDefaultTtl": "Fallback TTL for public responses (0 = off)", "cacheDefaultTtl": "Fallback TTL for public responses (0 = off)",
"bypassQuery": "Bypass URLs with a query string", "bypassQuery": "Bypass URLs with a query string",
"cachePrefixes": "Bypass path prefixes, one per line", "cachePrefixes": "Bypass path prefixes, one per line",
"cacheAgeHelp": "Age is always sent on cache hits. Counters remain available even when diagnostic headers are hidden. Only cache-affecting configuration changes invalidate incompatible objects; managed software updates hand hot objects to cold storage when the cold tier is enabled.", "cacheAgeHelp": "Age is sent on cache hits unless an explicit response-header rule removes or replaces it. Such Age rules are wire-only and do not change cache freshness. Counters remain available even when diagnostic headers are hidden. Only cache-affecting configuration changes invalidate incompatible objects; managed software updates hand hot objects to cold storage when the cold tier is enabled.",
"saveCache": "Apply cache policy", "saveCache": "Apply cache policy",
"purgeCache": "Purge all cache", "purgeCache": "Purge all cache",
"customHeaders": "Global custom headers", "customHeaders": "Global custom headers",
+1 -1
View File
@@ -270,7 +270,7 @@
"cacheDefaultTtl": "Zapasowy TTL odpowiedzi publicznych (0 = wył.)", "cacheDefaultTtl": "Zapasowy TTL odpowiedzi publicznych (0 = wył.)",
"bypassQuery": "Pomijaj adresy z parametrami query", "bypassQuery": "Pomijaj adresy z parametrami query",
"cachePrefixes": "Pomijane prefiksy ścieżek, po jednym w wierszu", "cachePrefixes": "Pomijane prefiksy ścieżek, po jednym w wierszu",
"cacheAgeHelp": "Trafienia zawsze zawierają Age. Liczniki działają również przy ukrytych nagłówkach diagnostycznych. Tylko zmiany wpływające na cache unieważniają niezgodne obiekty; podczas zarządzanej aktualizacji hot cache jest przenoszony do cold storage, jeśli cold tier jest włączony.", "cacheAgeHelp": "Trafienia zawierają Age, chyba że jawna reguła nagłówka odpowiedzi go usunie lub zastąpi. Takie reguły Age działają tylko na odpowiedzi wysyłanej do klienta i nie zmieniają świeżości cache. Liczniki działają również przy ukrytych nagłówkach diagnostycznych. Tylko zmiany wpływające na cache unieważniają niezgodne obiekty; podczas zarządzanej aktualizacji hot cache jest przenoszony do cold storage, jeśli cold tier jest włączony.",
"saveCache": "Zastosuj ustawienia cache", "saveCache": "Zastosuj ustawienia cache",
"purgeCache": "Wyczyść cały cache", "purgeCache": "Wyczyść cały cache",
"customHeaders": "Globalne reguły nagłówków", "customHeaders": "Globalne reguły nagłówków",
+11 -2
View File
@@ -801,8 +801,17 @@ for marker in [
assert marker in compression_finalize_source, marker assert marker in compression_finalize_source, marker
headers_source_0267 = rust_source(root, "src/headers.rs") headers_source_0267 = rust_source(root, "src/headers.rs")
vhost_core_0267 = (root / "src/admin/web/vhosts/core.js").read_text() vhost_core_0267 = (root / "src/admin/web/vhosts/core.js").read_text()
assert '| "age"' in headers_source_0267, "Age must be protected from generic response rules" for marker in [
assert '["host", "age", "connection"' in vhost_core_0267, "UI must protect Age consistently" 'pub fn apply_wire_age',
'wire_only: bool',
'wire_only: direction == Direction::Response',
'rule.name.eq_ignore_ascii_case("age")',
'if !rule.wire_only',
]:
assert marker in headers_source_0267, marker
assert '["host", "connection", "content-length"' in vhost_core_0267, "UI protected header set must stay intact"
assert '["host", "age", "connection"' not in vhost_core_0267, "0.26.6 Age rules must remain UI-loadable"
assert 'apply_response_age_rules(' in compression_finalize_source, "Age wire rules must run in final response stage"
compression_tester_0267 = (root / "scripts/test_compression.py").read_text() compression_tester_0267 = (root / "scripts/test_compression.py").read_text()
for marker in ["--revalidate", "if revalidate:", 'headers["Cache-Control"] = "no-cache"', "headers.get('Age', '-')"]: for marker in ["--revalidate", "if revalidate:", 'headers["Cache-Control"] = "no-cache"', "headers.get('Age', '-')"]:
assert marker in compression_tester_0267, marker assert marker in compression_tester_0267, marker
+3 -3
View File
@@ -2172,9 +2172,9 @@
type="checkbox" /><span>Cache-Status (RFC 9211)</span></label><label type="checkbox" /><span>Cache-Status (RFC 9211)</span></label><label
class="check"><input id="cache-resident-age-header" class="check"><input id="cache-resident-age-header"
type="checkbox" /><span>X-Cache-Resident-Age</span></label></div> type="checkbox" /><span>X-Cache-Resident-Age</span></label></div>
<p class="fine" data-i18n="cacheAgeHelp">Age is always sent on cache hits. Counters <p class="fine" data-i18n="cacheAgeHelp">Age is sent on cache hits unless an
remain available even when diagnostic headers are hidden. Every configuration explicit response-header rule removes or replaces it. Such Age rules are
apply invalidates the cache.</p> wire-only and do not change cache freshness.</p>
</div> </div>
<div class="form-actions"><button class="primary" data-i18n="saveCache" <div class="form-actions"><button class="primary" data-i18n="saveCache"
type="submit">Apply cache policy</button><button class="outline" type="submit">Apply cache policy</button><button class="outline"
+1 -1
View File
@@ -72,7 +72,7 @@
for (const direction of ["request", "response"]) { for (const direction of ["request", "response"]) {
const rules = value[direction] || []; if (!Array.isArray(rules) || rules.length > 32) fail(); for (const rule of rules) { const rules = value[direction] || []; if (!Array.isArray(rules) || rules.length > 32) fail(); for (const rule of rules) {
if (!rule || typeof rule !== "object" || Object.keys(rule).some(k => !["op", "name", "value"].includes(k)) || !["set", "append", "remove"].includes(rule.op) || typeof rule.name !== "string" || rule.name.length > 128 || !/^[-!#$%&'*+.^_`|~0-9A-Za-z]+$/.test(rule.name)) fail(); if (!rule || typeof rule !== "object" || Object.keys(rule).some(k => !["op", "name", "value"].includes(k)) || !["set", "append", "remove"].includes(rule.op) || typeof rule.name !== "string" || rule.name.length > 128 || !/^[-!#$%&'*+.^_`|~0-9A-Za-z]+$/.test(rule.name)) fail();
const name = rule.name.toLowerCase(); if (["host", "age", "connection", "content-length", "transfer-encoding", "te", "trailer", "content-encoding", "content-range", "upgrade", "keep-alive", "proxy-connection", "proxy-authorization", "proxy-authenticate", "forwarded", "x-real-ip", "x-request-id", "x-rust-proxy-backend", "x-rust-proxy-backend-time", "x-rustproxy-backend-time"].includes(name) || name.startsWith("x-forwarded-") || name.startsWith("sec-websocket-")) throw new Error(t("headersProtected")); const name = rule.name.toLowerCase(); if (["host", "connection", "content-length", "transfer-encoding", "te", "trailer", "content-encoding", "content-range", "upgrade", "keep-alive", "proxy-connection", "proxy-authorization", "proxy-authenticate", "forwarded", "x-real-ip", "x-request-id", "x-rust-proxy-backend", "x-rust-proxy-backend-time", "x-rustproxy-backend-time"].includes(name) || name.startsWith("x-forwarded-") || name.startsWith("sec-websocket-")) throw new Error(t("headersProtected"));
if (rule.op === "remove") { if (Object.hasOwn(rule, "value") && rule.value !== null) fail(); } else if (typeof rule.value !== "string" || rule.value.length > 8192 || /[\r\n\0]/.test(rule.value)) fail(); if (rule.op === "remove") { if (Object.hasOwn(rule, "value") && rule.value !== null) fail(); } else if (typeof rule.value !== "string" || rule.value.length > 8192 || /[\r\n\0]/.test(rule.value)) fail();
} }
} return value; } return value;
+36 -15
View File
@@ -31,6 +31,7 @@ pub struct CompiledRule {
op: Operation, op: Operation,
name: HeaderName, name: HeaderName,
value: Option<HeaderValue>, value: Option<HeaderValue>,
wire_only: bool,
} }
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
pub struct CompiledHeaders { pub struct CompiledHeaders {
@@ -43,16 +44,20 @@ impl HeaderRules {
} }
pub fn compile(&self) -> Result<CompiledHeaders> { pub fn compile(&self) -> Result<CompiledHeaders> {
Ok(CompiledHeaders { Ok(CompiledHeaders {
request: compile_rules(&self.request)?, request: compile_rules(&self.request, Direction::Request)?,
response: compile_rules(&self.response)?, response: compile_rules(&self.response, Direction::Response)?,
}) })
} }
} }
#[derive(Clone, Copy, PartialEq, Eq)]
enum Direction {
Request,
Response,
}
fn protected(name: &str) -> bool { fn protected(name: &str) -> bool {
matches!( matches!(
name, name,
"host" "host"
| "age"
| "connection" | "connection"
| "content-length" | "content-length"
| "transfer-encoding" | "transfer-encoding"
@@ -74,7 +79,7 @@ fn protected(name: &str) -> bool {
) || name.starts_with("x-forwarded-") ) || name.starts_with("x-forwarded-")
|| name.starts_with("sec-websocket-") || name.starts_with("sec-websocket-")
} }
fn compile_rules(rules: &[Rule]) -> Result<Vec<CompiledRule>> { fn compile_rules(rules: &[Rule], direction: Direction) -> Result<Vec<CompiledRule>> {
ensure!( ensure!(
rules.len() <= 32, rules.len() <= 32,
"at most 32 header rules per direction and scope" "at most 32 header rules per direction and scope"
@@ -102,23 +107,39 @@ fn compile_rules(rules: &[Rule]) -> Result<Vec<CompiledRule>> {
op: rule.op, op: rule.op,
name, name,
value, value,
wire_only: direction == Direction::Response
&& rule.name.eq_ignore_ascii_case("age"),
}) })
}) })
.collect() .collect()
} }
fn apply_rule(headers: &mut HeaderMap, rule: &CompiledRule) {
match (&rule.op, &rule.value) {
(Operation::Remove, _) => {
headers.remove(&rule.name);
}
(Operation::Set, Some(value)) => {
headers.insert(rule.name.clone(), value.clone());
}
(Operation::Append, Some(value)) => {
headers.append(rule.name.clone(), value.clone());
}
_ => unreachable!("only compiled header rules reach the request path"),
}
}
pub fn apply(headers: &mut HeaderMap, rules: &[CompiledRule]) { pub fn apply(headers: &mut HeaderMap, rules: &[CompiledRule]) {
for rule in rules { for rule in rules {
match (&rule.op, &rule.value) { // Response Age rules are a wire-only presentation override. Applying
(Operation::Remove, _) => { // them here would let a configured value enter cache freshness/storage.
headers.remove(&rule.name); if !rule.wire_only {
} apply_rule(headers, rule);
(Operation::Set, Some(value)) => { }
headers.insert(rule.name.clone(), value.clone()); }
} }
(Operation::Append, Some(value)) => { pub fn apply_wire_age(headers: &mut HeaderMap, rules: &[CompiledRule]) {
headers.append(rule.name.clone(), value.clone()); for rule in rules {
} if rule.wire_only {
_ => unreachable!("only compiled header rules reach the request path"), apply_rule(headers, rule);
} }
} }
} }
+58
View File
@@ -45,4 +45,62 @@ mod tests {
}; };
assert!(rules.compile().is_err()); assert!(rules.compile().is_err());
} }
#[test]
fn response_age_is_allowed_but_applied_only_at_wire_stage() {
let request_age = HeaderRules {
request: vec![Rule {
op: Operation::Set,
name: "Age".into(),
value: Some("7".into()),
}],
response: vec![],
};
let request_compiled = request_age.compile().unwrap();
let mut request_headers = HeaderMap::new();
apply(&mut request_headers, &request_compiled.request);
assert_eq!(request_headers["age"], "7");
let response_age = HeaderRules {
request: vec![],
response: vec![Rule {
op: Operation::Remove,
name: "Age".into(),
value: None,
}],
};
let compiled = response_age.compile().unwrap();
let mut headers = HeaderMap::new();
headers.insert("age", HeaderValue::from_static("42"));
apply(&mut headers, &compiled.response);
assert_eq!(headers["age"], "42");
apply_wire_age(&mut headers, &compiled.response);
assert!(!headers.contains_key("age"));
}
#[test]
fn response_age_keeps_0266_rule_compatibility() {
let rules = HeaderRules {
request: vec![],
response: vec![
Rule {
op: Operation::Set,
name: "age".into(),
value: Some("custom".into()),
},
Rule {
op: Operation::Append,
name: "age".into(),
value: Some("7".into()),
},
],
};
let compiled = rules.compile().unwrap();
let mut headers = HeaderMap::new();
headers.insert("age", HeaderValue::from_static("42"));
apply(&mut headers, &compiled.response);
assert_eq!(headers["age"], "42");
apply_wire_age(&mut headers, &compiled.response);
assert_eq!(headers.get_all("age").iter().count(), 2);
}
} }
+14
View File
@@ -219,6 +219,20 @@ fn apply_response_rules(
headers::strip_admin_set_cookie(headers); headers::strip_admin_set_cookie(headers);
} }
} }
fn apply_response_age_rules(
headers: &mut http::HeaderMap,
snapshot: &crate::config::Snapshot,
frontend: Option<&crate::config::CompiledFrontend>,
route: Option<&crate::config::CompiledRoute>,
) {
crate::headers::apply_wire_age(headers, &snapshot.headers.response);
if let Some(frontend) = frontend {
crate::headers::apply_wire_age(headers, &frontend.headers.response);
}
if let Some(route) = route {
crate::headers::apply_wire_age(headers, &route.headers.response);
}
}
/// Final policies apply to cache hits, errors, static pages and ALL transports. /// Final policies apply to cache hits, errors, static pages and ALL transports.
fn finish_headers( fn finish_headers(
response: &mut Response, response: &mut Response,
+14
View File
@@ -87,6 +87,12 @@ async fn observe(State(state): State<Arc<App>>, mut request: Request, next: Next
&request_id, &request_id,
&state.active_listeners, &state.active_listeners,
); );
apply_response_age_rules(
response.headers_mut(),
&snapshot,
frontend_context.as_deref(),
route_context.as_ref(),
);
return response; return response;
}; };
state.metrics.active.fetch_add(1, Ordering::Relaxed); state.metrics.active.fetch_add(1, Ordering::Relaxed);
@@ -356,6 +362,14 @@ async fn observe(State(state): State<Arc<App>>, mut request: Request, next: Next
cache.annotate_transformed(response.headers_mut(), transformed); cache.annotate_transformed(response.headers_mut(), transformed);
} }
} }
if !minimal_welcome {
apply_response_age_rules(
response.headers_mut(),
&snapshot,
frontend_context.as_deref(),
route_context.as_ref(),
);
}
if response if response
.extensions() .extensions()
.get::<crate::pages::PageServed>() .get::<crate::pages::PageServed>()