This commit is contained in:
Mateusz Gruszczyński committed 2026-10-11 23:09:59 +02:00
1 parent 79fd198949
commit 427d23cb6e
64 files changed
+138 -2979

No files matched your search

+2
View File
@@ -5,6 +5,8 @@
## 0.31.7
- Allow intentionally pruned historical release audits and implementation notes in the 0.31.7 source archive. Update preflight, documentation/source contract checks, and the embedded Reader registry to require actual runtime/compiled documents instead. Remove stale documentation links, update Reader integration assertions, and add a regression check that still rejects missing embedded documents or runtime modules. No runtime configuration, storage format or UI behavior changed.
- Refine revision history dialogs: place **Full JSON comparison** directly below **Restore revision**, allow long change lists to scroll without overflowing the modal, and use the standard dialog border for JSON comparison. No revision data, restore logic or API changes.
- Group Global User-Agent blocklist, Global custom headers and Client response under a new **Settings → Rules & headers** tab with three sub-tabs, using the existing persistent Settings forms. Security continues to own session and hostname access controls.
+6 -10
View File
@@ -15,17 +15,18 @@ Named HTTP/HTTPS listener bind addresses use the same chip-style editor as vhost
## What changed in 0.31.7
- **Source-package cleanup:** old per-version audits and implementation reports may be omitted; preflight still requires active sources and docs embedded in the panel Reader. This fixes the removed `docs/V0.25.5_AUDIT.md` preflight error without restoring obsolete files.
- **Settings → Rules & headers:** the global User-Agent blocklist, global custom header editor and client response settings now share a Cache-style tabbed page (three persistent sub-tabs). Settings → Security keeps sessions/password and host access policies.
- **Vhosts → Traffic policy:** optional User-Agent-specific per-client-IP request limits. First matching case-insensitive substring or `*` wildcard rule overrides the default/country rate for that request, even when ordinary throttling is disabled. Each vhost, UA rule and IP has independent bounded fixed-window accounting. Up to 64 rules per vhost; patterns are validated and the feature is opt-in.
- **Revision history:** show changed leaf fields rather than object-sized JSON previews, including stable IDs for array members. Full before/after JSON is available through a separate comparison dialog. Restore behavior unchanged.
- **Listeners:** space apart HTTP/2 and HTTP/3 tuning shortcuts. **ACL profiles:** “Attached to” is initially collapsed but expandable.
- **`update.sh`:** retain exactly two installed binary release directories (including `current`) and three private pre-update backups. Correct the previous mtime-sort path deletion bug; old releases were only printed, not actually deleted. After a successful update, optionally purge only incremental Cargo state unused for 30+ days, never fingerprint, dependency, build-script or compiled executable artifacts. A source-tree upgrade retains its own backups and target directory.
- **Graceful managed update:** trace marker → systemd stop → forced best-effort HOT→COLD handoff when COLD is enabled; record completion, even when zero items were demoted. Existing update flush works regardless of `cold_on_shutdown`; no persisted schema or cold format change.
- **Compatibility:** retains all 0.31.6 H3/iPhone diagnostic fixes, 0.31.5 mobile Logs and protocol-independent cache behavior. SQLite schema 9 unchanged. Rust/Cargo and live reload/update verification remain mandatory. See [0.31.7 implementation](docs/IMPLEMENTATION_0.31.7.md) and [test report](docs/TEST_REPORT.md).
- **Compatibility:** retains all 0.31.6 H3/iPhone diagnostic fixes, 0.31.5 mobile Logs and protocol-independent cache behavior. SQLite schema 9 unchanged. Rust/Cargo and live reload/update verification remain mandatory. See [test report](docs/TEST_REPORT.md) and [configuration](docs/SETTINGS.md).
## Previously in 0.31.6
- HTTP/3 full-navigation GET/HEAD receive-stream handling, client cancellation classification and diagnostic events. Keep logo behavior unchanged. See [0.31.6 implementation](docs/IMPLEMENTATION_0.31.6.md).
- HTTP/3 full-navigation GET/HEAD receive-stream handling, client cancellation classification and diagnostic events. Keep logo behavior unchanged. See [HTTP/3](docs/HTTP3.md) and [test report](docs/TEST_REPORT.md).
## Previously in 0.31.5
@@ -33,7 +34,7 @@ Named HTTP/HTTPS listener bind addresses use the same chip-style editor as vhost
- Make Traffic table/chart values directly copyable, including User-Agent, IP, host/path and other displayed dimensions, to speed up blocklist creation and investigation.
- Add **Advanced parser · repeated values** in Stored Logs. It counts repeated values for `user_agent`, `client_ip`, `host`, `path` or any supported parsed field across the bounded matching scan before pagination.
- Add a default-enabled **Consolidate rotated logs per vhost** switch. Active `vhost.jsonl` and matching `vhost.jsonl.*.gz` rotations are queried as one logical vhost source while retaining individual source IDs internally.
- Preserve SQLite schema 9 and existing saved configurations through serde defaults. See [Stored Logs](docs/FILE_LOGS.md) and [0.31.5 implementation and tests](docs/IMPLEMENTATION_0.31.5.md).
- Preserve SQLite schema 9 and existing saved configurations through serde defaults. See [Stored Logs](docs/FILE_LOGS.md) and [test report](docs/TEST_REPORT.md).
- Make `/logs` usable on phones with expandable log cards, full parsed-field details and no global horizontal scrolling; dynamic columns remain available.
- Treat H3 peer-cancelled pre-header requests (e.g. F5 navigation) as stream cancellations, not fabricated HTTP 400 results. This corrects error reporting and avoids redundant internal response resets; it does not yet prove the reported mobile white-screen symptom resolved.
- Keep both memory and cold cache across per-vhost H2/H3 switches; changing frontend protocols alone does not invalidate cached representations.
@@ -46,7 +47,7 @@ Named HTTP/HTTPS listener bind addresses use the same chip-style editor as vhost
- Set **only** `administration.public_origin`, for example `https://panel.example.test`, in Settings → Administration. Its DNS hostname is automatically included in the panel Host allowlist; it does **not** need to be repeated in `allowed_hosts`.
- The advanced **Additional allowed DNS hostnames** section remains optional. Older configurations with a duplicate pinned hostname still load, and editing/saving omits that duplicate while preserving independent entries. Removing or changing the pinned URL never silently keeps the previous hostname implicitly allowed.
- Keep strict checks for foreign `Origin`, mismatched or duplicate `X-Forwarded-Host`, cross-site requests, CSRF and untrusted proxies. The narrow opt-in self-admin session-cookie path may use the derived hostname only under its existing local-listener-risk conditions. Public proxy traffic and SQLite schema 9 are unchanged.
- Restart RustProxy after modifying the setting. See [Administration HTTPS](docs/SETTINGS.md) and [0.31.4 implementation and tests](docs/IMPLEMENTATION_0.31.4.md).
- Restart RustProxy after modifying the setting. See [Administration HTTPS](docs/SETTINGS.md) and [test report](docs/TEST_REPORT.md).
- **Release gate remains open:** Rust/Cargo compilation, resolved dependency lockfile and real mobile/reverse-proxy acceptance are still required before production deployment.
@@ -207,7 +208,7 @@ Named HTTP/HTTPS listener bind addresses use the same chip-style editor as vhost
- Replace backend sort text (`A-Z` / `Z-A`) with direction arrows (`↑` / `↓`).
- Keep SQLite schema 8 and the saved proxy configuration format unchanged; upgrading from 0.26.1 requires no data migration.
The detailed 0.25.5 implementation audit remains available as historical baseline evidence. Current 0.28.1 verification status is maintained in [docs/TEST_REPORT.md](docs/TEST_REPORT.md).
Historical release information remains in [CHANGELOG.md](CHANGELOG.md) and [docs/CHANGELOG_TECHNICAL_ARCHIVE.md](docs/CHANGELOG_TECHNICAL_ARCHIVE.md). Current verification status is maintained in [docs/TEST_REPORT.md](docs/TEST_REPORT.md).
## Documentation
@@ -216,11 +217,6 @@ The detailed 0.25.5 implementation audit remains available as historical baselin
- [docs/WEB_SERVER.md](docs/WEB_SERVER.md) - Web Server configuration, HTTP behavior and filesystem security.
- [docs/FILE_LOGS.md](docs/FILE_LOGS.md) - stored-log explorer, protocol-error records, bounds and filesystem security.
- [docs/PHP_FPM.md](docs/PHP_FPM.md) - implemented FastCGI/PHP-FPM integration, safety model and runtime requirements.
- [docs/V0.25.5_AUDIT.md](docs/V0.25.5_AUDIT.md) - 0.25.5 Cloudflare country analytics and Revision History styling release audit.
- [docs/V0.25.4_AUDIT.md](docs/V0.25.4_AUDIT.md) - historical 0.25.4 configuration-history restore release audit and compiler gate.
- [docs/V0.25.3_AUDIT.md](docs/V0.25.3_AUDIT.md) - historical method-policy/location-ACL/request-ID/config-history release audit.
- [docs/V0.25.2_AUDIT.md](docs/V0.25.2_AUDIT.md) - historical 0.25.2 live-backup/symlink/origin recovery audit.
- [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.
+16 -62
View File
@@ -2,17 +2,17 @@
4140a2956264b44f259e6a37fc00ba02097bbe68b09296e04d3d243c1287129b .env.example
9e6356475bf587091bab72246ec9e325b498f7e48a6220e9a18baa50d0a385a6 .gitignore
685f856d31bf33f724aaa4afc23a6b3d9d9ca9e8e99338792db4f3563621f449 AGENTS.md
43c9ccd8768c6b388c6cb29441f4e59a16422e5d3c82a9e52761b3f1c6bc46a0 CHANGELOG.md
5910c76f4aacab46365bb9a549d90a5b47b1e67986efeb36dbbc6b2fbb510229 CHANGELOG.md
b8e0b191c849a1c4eac46b0091c228a0c0cc52de4c708f9e9ca23a621b870e3a Cargo.lock
3f8c093e5a499d526df5f81ee246df70b3512a8fa1cbb1e071210b6eae0af706 Cargo.toml
25c7f3c8162cbf5cc156439774118a81dae9fdc52b611ec7e40c230fc99a772c Dockerfile
31b53a45263c72510c145b4b144bd32e7b4d5d720969f3c1dcd02ead20ebdcff README.md
4c58d8ad6cdbcc643616a25e5d6e3711124e0164aaaedfc1a9efa74715fdab77 README.md
db3235387db9e11f8193dfeeb853767f27487107a903cfbb0b5873b1b5124be8 ROADMAP.md
da8311e3db945290666324a2f24bf4521b365272d7ac3f862ded1be993eea646 UPDATE_CHECK.toml
f645f218dd062bf6cb54a8ee634290562cad364a4cccf09f16e3c8303582af8c build.rs
ea8d091843f86f4c66a5c76b20268b2feb397070851b6e05c5bc7d7a8d9157eb deploy/README.md
4834c278430ddf3bf842995735195368f1ac2c3b5c1d749df4f9238caf1e2b25 deploy/rustproxy.service
8aa17b34eaedba76295f1b82201a4d38ca1dde8be8fdeda6b8c28371adf34d1a dev.sh
c1addab11162ba4a763ac40548ac8ae29db5a6909940309e4bba4c0abc334e7a dev.sh
5f51efe621f45512a466fa7f2656867c6502a135ca040f4bfeec06a6919b39b6 docker-agent/README.md
7fee7d51a7deb67e73e53a0bdff3aa4630aea9bab9b23a0f9b8001b2169892b1 docker-agent/certificate.go
5e4f565c3c9de4dba897af2e801ea646a83395e72eae4717a6d502dff2fcec10 docker-agent/docker-agent.env.example
@@ -34,18 +34,11 @@ e9e2327684a4ae2d1ca4a0b3fb67d0ff83b5a150d38d4de4cfad2c5d3102eab6 docs/CACHE.md
9113f3abbfe81bafc4855d8680df8aa2a381ecc358cc3e1a0f78b6b756421117 docs/CACHE_DIAGNOSTICS.md
3822d72e12f82c37a9e70ab40c7e89e89145ad37f29914e4702db4bf024a5501 docs/CHANGELOG_TECHNICAL_ARCHIVE.md
c8e69e492179ce39958cc63615cf46c4ac08cfdb7f3483aaab66868a3abcd687 docs/CONTAINER.md
befa50579e3475b22589f26380d85b3345b80e4fa947d49649a3b91a8dd8b8c2 docs/CUSTOM_LOCATIONS.md
833ddb12f23dcac796223521388b19c6ec3ea04c5550cf1c5f4ea4eb7c58f3a0 docs/CUSTOM_LOCATIONS.md
16d8f59908c66e595d6003495cbf5c068c68b10aa9495eb8612c38b2de0e4a7c docs/DOCKER_DISCOVERY.md
5324b215075214a5bb9b388bc82d6491eacfc507df95856d1aecedd184e25b80 docs/ERROR_LOGGING.md
74dfbc0d67241d3ec47f17850e69cb8348c10e55e53bb26176b715f36d01883e docs/FILE_LOGS.md
36599e0b59164e662442f3e8203c16b3123d09b7684d8ecdbffce42d22cc221b docs/HTTP3.md
5301e321611d494fd97b04ab648df1f66a6a0d1f1f4c3475eab485e138d5605b docs/IMPLEMENTATION_0.31.0.md
eb52e72325e1307d05cb04878b6d22dfedea491908c5f36110b32421765096c8 docs/IMPLEMENTATION_0.31.1.md
991ff3c09ca1e2bcf73b8fa2975405b955dd640c3f1e1246e75038c043c17183 docs/IMPLEMENTATION_0.31.3.md
db2087608a1225c56e42c416d949b777fb15b1ed8b69a1e71fc2a6af6750a1e5 docs/IMPLEMENTATION_0.31.4.md
ab2633deb3ad3c6bad056970363d3227213f919915a2298d9549e8029b5e0c78 docs/IMPLEMENTATION_0.31.5.md
684471c3d40bf832d460d42506adbba3e4a128540463738a575eff3ebcf22001 docs/IMPLEMENTATION_0.31.6.md
bfea31bfffcd6d64dd9322ceb0f99cd1818556fca0d3c2886efc3459b9e34b1c docs/IMPLEMENTATION_0.31.7.md
a00ec3c1d3b28ba10677435128e031e5f18fe8c5cef2ac90eb0f6779cefeb434 docs/INSTALL.md
3b5c31c805e5aee0dc2100b81246a9b631b5abb996faa598ae94f87ba39305f3 docs/INTEGRATIONS_040.md
4bb56add544f15d54695cf797cd16a51c09024360cdeef803a88988f4a54b3a5 docs/LIVE_LOGS.md
@@ -59,62 +52,22 @@ e7516646dfb107d6240370f4e7460b1189f2eb21f398740381d5d102847bbfe6 docs/NOT_FOUND
e68d5a5cf0991de9308f78afb0264e02979f7463a5f395eb7a6e9ec2bd3a3d54 docs/PHP_FPM.md
bc068a176056f5ea4d3b637071e644ed58a2140494c233ef22aa66e9700e3299 docs/PHP_FPM_ISOLATION.md
572c70fbd2f61fecd80a8ff9fa5de4a94c4f158de5e90da8e920f99b051241fa docs/PROMETHEUS.md
0b85364de02fea804434ff0c592a3968c9fc9e6d8840491243ea7ffa0fea2fcd docs/README.md
bb15713876184682cf1907d8c5edd18e657735b6de6cc5f8ca976dbdec4dd936 docs/README.md
6e46a137b9c88dac8cb9e3fdca877430092392de9913d746278dbff8652460c2 docs/REDIRECTS.md
0dfd0d431c5aedc67abac1ce5de040ffa790ec9578fd7d6f91a987c333d73358 docs/REFERENCES.md
d85c5567fc6a7714b96ad7cfe4ee9ff2085f84c4f31534a6484340d03a73e0f7 docs/RENEWAL.md
a2bb837cdd3559d58158880c1f832badf64035376a3623050bb4e40c2bca104c docs/ROADMAP_HISTORY.md
72b95592b310ced607cdaf8a334e2732cb29341aed12f9024eaf95884737d947 docs/ROADMAP_HISTORY.md
759bf2987f2b602488829d1856fa33d57c2a3b23bc681ef5a931b9633af3a533 docs/SECRET_STORE_BACKUP.md
ec7e32fd19e6ba6ed1732f1f3dd92ac0860609e8c0bbd3c1998840cdfb24bf54 docs/SECURITY.md
620b69e566d27af04f0604f4ca3f88cf95cdfea7fbc6ae9dd92446d2b2b5d9be docs/SECURITY_AUDIT.md
007537ee971463072b2482dfa4fdcf1955f837ced0b711d4eef3b6267d16d4f6 docs/SECURITY_AUDIT_0.31.0.md
7f27d782534455eebce538f10c902346f7d194acd75b6106c7f8dec3c44857e9 docs/SETTINGS.md
3611defd206cde107e75cac006159856d12cd8f15df7ae2eb9ec3592ed2b4abd docs/STREAMS.md
031d7e1570fb9cfa036ef4958c4458046b7fbf33ec758b92f28d8d80886f5d2f docs/TEST_REPORT.md
cb440d5f2ce8fb77e00e75f4bff9a768044b19bad3188888b6221759975c099f docs/TEST_REPORT.md
cfb452d8496839e832b5fbacdd4e52525b0ecc3112fbb62340c1e580bd5c1717 docs/TODO.md
751c9b13b47d0959a8575d6e82bdecba988b950a374ba0b084fbba08b4b65ebe docs/TRANSFORM.md
660687fc961f71aae2bdf71595aa47a336fa782f4e78ceee8c1d1e63e46393ee docs/TRANSPORT.md
96a6816915d526f00e261846131ee3f942ef332af996cf0f73c79ec1ba9f9c7a docs/V0.21.0_AUDIT.md
0c1c3ba6e8724d85ec54e254e70bbf05e063db025280ef4af866b774fb43f76b docs/V0.22.0_AUDIT.md
3f811c457bf6be662969fbac32c3cfcc49b13c2f00d0125e4f2eeab768f59f0e docs/V0.22.1_AUDIT.md
36c2fbab9ec1276715f7d4a7a4aa49725b2fec5e1ba58d97d00a87b259257ea7 docs/V0.22.2_AUDIT.md
579547e18982bb1a527417aaa8bf88b40d02d2e68dc71215b9815d1134a4f865 docs/V0.22.3_AUDIT.md
0fd3d7bad0b3b437cddf40f2dd184b7a1f62f0340a319eb6f6d966960b666e46 docs/V0.23.0_AUDIT.md
ab7d48c2a359928da653263e69b4739cfff849a03fa1270491cb1ce553989788 docs/V0.23.1_AUDIT.md
9cb1b028be2a4a9cf105fb14617ac5776d131814a5e92b497ff816929aa3db41 docs/V0.23.2_AUDIT.md
e4d47b50fb0ce33d26817fc0afcc2f05ac53bc9b62c7e50e61b9fa9734e817b0 docs/V0.23.3_AUDIT.md
984092eeb3899ed696f94059d88f7834703b151682d5c8af9fabacc7a823ea0b docs/V0.24.0_AUDIT.md
5782adf99a292783149882e844ad07b49d4ac0ea478e8393758cff849d48c13d docs/V0.24.1_AUDIT.md
cc64ae2b85add081deb043f144caa4f10f6576475eada65302a56296ce2d75a1 docs/V0.24.3_AUDIT.md
b891db1453cd7cf6b10f79879f4edaf7c83f14e91e4e95826919d40cb5dca2d4 docs/V0.24.4_AUDIT.md
8d6519b00e01e5cd41ee6772941fdc7e9a70fe29626df71cefced6eefe6a94d0 docs/V0.24.5_AUDIT.md
f6ffaa9b72d86029b1629f8fd89efeac99780831f95d434b2ad549913633dcb9 docs/V0.24.6_AUDIT.md
1c9f3ab84700482820379268ea367600a0c110a4578504f0cc2ce54149fe1046 docs/V0.24.7_AUDIT.md
d0e609febbcd1cfdb8ea0b00b4fa6f081eac01e2d43a81e38a53ae25783152cc docs/V0.24.8_AUDIT.md
a840652a660adeb93999c01ae285f01fd13cdecff69f3ae58ddf9ade9acca258 docs/V0.25.0_AUDIT.md
e5a08bf5d34338a3e5f3b14b65c6f3630ccb0ef9c56c26f39bbb73fa724bf227 docs/V0.25.0_SECURITY_PREPROD_AUDIT.md
a709ec5e740b3da73149ba9843e3a184611f8be74e06bd19105684a57e3e68f8 docs/V0.25.1_AUDIT.md
41793fe2d0fa4a2bd88448f5b3870649f08eeaadd4cd420820647d332e7fdb5a docs/V0.25.2_AUDIT.md
9dbb66e408bcd4d32267724e557fa9f7f44ef9b70db4ab170c61a7a839b7b157 docs/V0.25.3_AUDIT.md
583d548b4a5db9f35a2bbf8ec00ec22397764c5850a8e883a3d12680da517df7 docs/V0.25.4_AUDIT.md
bf9b4cf4fec2c8ce287e1b3c2e7d4828e101dafe4d6c173f099819f22493d144 docs/V0.25.5_AUDIT.md
05e0b4cc074b07ff37d45fabc20c7272bada217286b3db3d7997cae25c1f6f11 docs/V0.26.1_IMPLEMENTATION.md
91774f2c23f587a502c65fe2ffaf009aba81b221cb3ede4861ea79abb4d4a528 docs/V0.26.2_IMPLEMENTATION.md
57edc3d5ca6c268b2b6b2e8027e81fb543c0dab86e8f69c4ee5776ea8df901c7 docs/V0.26.3_IMPLEMENTATION.md
da5ec87b9482f2fc506c4334d9365f3bfaec89254a14e5585dd6460326b20e10 docs/V0.26.4_IMPLEMENTATION.md
0ddf5215c40c8366844350bc6462d8013dba747b6f3e8fbf477985af215e5e8d docs/V0.26.5_IMPLEMENTATION.md
c7785826cf7b20555ece5b23a3f360ce4a8a014cf976c14336770f74d630834f docs/V0.26.6_IMPLEMENTATION.md
f9667fe81b31762e64d36126a7e94b609316dfa856de8f8e3b96661dd2a918c6 docs/V0.26.7_IMPLEMENTATION.md
efd5935852d4cfd09758bf67c70e437490cfce2baade46c8733201e506dbcc42 docs/V0.27.0_IMPLEMENTATION.md
9ba6417417a0eb293879b26b019449f143e773d1482434bee88f5eaf1fe34b90 docs/V0.27.1_IMPLEMENTATION.md
845cc7b7fcd553ef99613817e70fcecc4e50dd8fa77066a75577d63a7224cdec docs/V0.27.2_IMPLEMENTATION.md
49fca8928f3ed6169df4480f9658b65b8d9dd5d1331ef5bb2b1f9adaca8212eb docs/V0.27.3_IMPLEMENTATION.md
713518c1a09969703d7b09b83380d5e659f5d1587ef23585abfa1fa87c033ae5 docs/V0.28.0_IMPLEMENTATION.md
8e1b45415a2bffcf26fe113bbc25627daf5d8758eb9e44c38abb060a4a402a46 docs/V0.28.1_IMPLEMENTATION.md
097a4ff47634e5b09180012135b49a8ed16193fadc36113cad0c8dffb9e47cca docs/V0.28.2_IMPLEMENTATION.md
bfe9b83d2292bae1e579c1841a396173258496063dbd0f3953780dedeebc2c40 docs/V0.29.0_IMPLEMENTATION.md
1bf34805a56508bb7e60745be8e872ebdc458f03bd0f56941af9c18d94ad87ce docs/V0.29.1_IMPLEMENTATION.md
a236de3c92bdd0248c12b0224bf29ddafe6baf88b4f7f3f13f179fdad339b371 docs/VHOSTS.md
88a4adeb99003681a5aaf8d9d1f252d59f76e61d4c62d5542e62ae7b49eccb21 docs/VHOSTS.md
09b60fb7b40db4d0139fa2a9e69a2aa9f28d4e61b8643521cfc7e936d7df8774 docs/VHOST_ACCESS.md
04a42e4629fdfb7224173dcf57cd0992cba2edd109204bbf503a255886605542 docs/WEB_SERVER.md
c336f998027330c58d5afbbbb3a3a682fd44ae3997643882fe342633822f8bb3 docs/screenshots/README.md
@@ -187,7 +140,7 @@ e38331a1b186b49fe9d04051de52b603e1fa028def37e4e2f52e386b4d7b29ac src/acme/tests
b6f38f3fd75d6685bbc882ed1077a38bf495cfcf01c397f69ed9b98be68b446f src/admin/access.rs
019f4aa33e3596bc9c11ef8cf5703bcfffe46c15288c0eba83c8dd9e38055fb2 src/admin/certificates.rs
5e150d4ef0413a2f92db5b6631669933b84d4b4796633d01476c4b39188cd05c src/admin/documentation.rs
d60fc57931a51df3a1006fb7b694ea7682e0815a030256d8fa9b22f3c3013f47 src/admin/documentation/documents.rs
958546c43c05bf4bed8121b24db3b3096f9243f3c56a71e1762592b755684013 src/admin/documentation/documents.rs
67b901485d5858d5b5bf78a6260035db32241531d99481754c3533fdf404facb src/admin/documentation/tests.rs
1ce779b84aa8f47bdb3c34f83593c1f2f06d2235f0319847096cfcd035434168 src/admin/file_log_search.rs
b3aad0db47d930ee177253cb5d5eb772272452bab4385a34566b111c239a6302 src/admin/file_logs.rs
@@ -215,7 +168,7 @@ cadd7dca29b9aad8c77c7bc23dd249ea37a15adfa4d636a8ef00273a91ce35dc src/admin/tail
20abde2c1fa72d0712c2962479d66ae0131fb67ed12850c541306fe5ed1d2b84 src/admin/tests/part_02.rs
34cf08cafb63cf7a8a6336e5c81fc99abfce8669662e387a3702c3a532be8056 src/admin/tests/part_03.rs
2137a53991c9337988545c4472b4ffcb9a111cac9c460ebbec7e51c0be8f5630 src/admin/tests/part_04.rs
5bf2a7c2a9415b04eaba915a920efb5c60a1d6b6837a30481dd993c6ceb074da src/admin/tests/part_05.rs
334b1409f2c7f934b613c6613a0e0de7d2b65b8282b96255e072431373cd93b4 src/admin/tests/part_05.rs
0274038c2b18f1c9ab0d990c0d636cf6d4255454018463f49984898086f74bdb src/admin/tests/part_06.rs
402a219dbd70d6654bd1afc3d2ede2f5d3add96ba3f9867f7f51fba870efa404 src/admin/tests/part_07.rs
54ed2130e85239ffa4576417dd68fbb9349e396210b75f5da862897137d7e876 src/admin/tests/part_08.rs
@@ -542,11 +495,11 @@ e5c58fc9b7685ec911bfa86f6ca4f0cc97c19a90c0cff5b60ea9887f1afe9ced tools/checks/a
fd082812411cff7afb71b2289a6f1a469b8d3c8a7cfdcdd9e567a2a4baf81be4 tools/checks/admin_surface_audit_0310.py
412255d383fcc8306c6086784fb416d9814e3ec790e440a0927431ca615f3411 tools/checks/backend_cache_0297_ui_test.mjs
2a493dbce2991a23d807c01952f58608a364f541560836b4cb385e75e108e9e7 tools/checks/backend_health_0300_ui_test.mjs
2865412f91aabd488934d264bc1c2bde7299894013bef776cb5fac4eade27668 tools/checks/check_source.py
a1e0e4466d0d0acbdcc9db611a1a4183cdc148bae195dedabb18a2892f4faff5 tools/checks/check_source.py
dc4df51fc15d4b6345c6625890ddbe014103851ebfa16abc583fbb0d809f4015 tools/checks/dashboard_boot_0311_ui_test.mjs
259d4b0985c82b631e151a07e8ef3b4c760019cb99bb2af458ce577497f7df58 tools/checks/dashboard_startup_0310_ui_test.mjs
03d399d7f75b3f6aa8b6f68fddd0e791ab0071d657b9ba7255988a1d8a812836 tools/checks/dependency_upgrade_0312.py
a95ad016d46197c6c7b183b10c7528dd883a16132c475c76e0ce1b2e07a9dd5d tools/checks/docs_check.py
e7f4519116fcd5edfe3251a4ac10f6e3afcbabcc303f9613fddfb566f7f93e86 tools/checks/docs_check.py
6ca66c0998d1da852ad66976c91e4d7d3628195aa49ddb44cea95f531eb50033 tools/checks/file_logs_tabs_0315_ui_test.mjs
83c74f0027a6d2f51f637d552593f9904e60ccd69665f6460136c48b159afb60 tools/checks/h3_cache_mobile_0315_test.mjs
157c3d630fe0846dda55c57d9e0640cbff2831988ccee8757854774b05e8b8e7 tools/checks/h3_navigation_0316.py
@@ -555,24 +508,25 @@ e33a9cc51d58bc92a637c0e6a1c88cbd24ba4605be55748212e296c3c35a4060 tools/checks/h
f8f87ba891338b565249d02c3cf5f02cec44e606b4205de99fcbea1eb2c24d57 tools/checks/memory_contracts.py
af98579ee942e774d88775c261ac351847c28bb4a5396dc67e458bd6b1ff7963 tools/checks/origin_resume_0313_ui_test.mjs
1ac812929c2a8c0699ae3bdebd0d7e05b5bcaf86c87533bb669b5ba7955e3245 tools/checks/panel_cache_http_0310_ui_test.mjs
73554fe6ec7234aeda65e04c53084547d79c6268218516be2d220f9ad4348cad tools/checks/preflight.py
0e8375cf190440091362c62a1e11865504b281fb03b8dce74e522f171c033033 tools/checks/preflight.py
021a97bd6763b59f3f7eca15f51efdf88d0c863f1f81a2e42ba80a9a09ca3fec tools/checks/refresh_0310_ui_test.mjs
c11583dad0899f8b945cae6e2f6cb0bdb55f5526bd5f42c248ac41badec83679 tools/checks/release_0317.py
b84b66a6f8f63cd0a354ef607b96ff60c77ef37f80f49ea9a9036b33147c0b6a tools/checks/release_0317_ui_test.mjs
763d9b02213e61ca503ccef1612beb962c340ccffe92e9f5b8427530764aa89b tools/checks/release_smoke.py
17b28a819bc8cc175fa1fb45b9aa14fa1dc8426b28f65969e7fb786dea8c2306 tools/checks/release_smoke.py
f69845bc26ec33160fc756953991cf32aa8ad6a7fb4a131caa2093db91e783ef tools/checks/rust_module_contracts.py
ecfe0bb21a7549225a19b0846bb6de8c99cbfbbf5a706d33cf84db2c7146abed tools/checks/rust_source.py
adf84a983e0dbba228e47d71ec2d5c9964f7ca9989d5139de58f96ee961b035a tools/checks/security_contracts.py
23b9250245e6bec5ef728c73cee4c88f64924afc3597f8a9b58a1924235df98e tools/checks/security_dependency_contracts.py
ff69e1381a759e07effdc25f8401ba92730be6116fe15c7470539e50ddf4f3e5 tools/checks/security_logs_0315_ui_test.mjs
70e5632f36e2387be8638c33f371aacca57bf264ef67846a6e0bd7f2dd19a5e0 tools/checks/source_module_contracts.py
37eebe90c0a60f7d7f4fbdd09986171b7dbd50c4d3760d94c31595dd93a2e680 tools/checks/source_pruning_0317.py
e4b6f095ade55881d1d7fe0b8b821e9c5c7312e13ee062db44804dd2b6f727f9 tools/checks/system_info_0301_ui_test.mjs
543a488951ea6ec8fe06e04f168ccc4d4f2af3cfc281c9a728ab7bd87377fa98 tools/checks/system_info_no_subprocess_contracts.py
a5105ab53de67e6b1114974c92b019014e1aa93acc445bc5dd6c82af0b0d3a16 tools/checks/version_contracts.py
161637cb5e2e613e3c71cf8489cc936fa84913a2a02649463f8893a28a674242 tools/checks/vhost_origin_errors_0297_ui_test.mjs
0c071c26fc316934bdfd2bfd19f5fed9521790d8c887760eaabda81a9be96703 tools/checks/vhost_www_ui_test.mjs
c6777d83976f021d689857a0d53a9a30f4a4dafdf68cdcc672dd995279b09d1f tools/checks/web_assets.py
35464d3d2c4af0c972b99e3cf4c7b8423b68260a8c7bc6a929f8e7ab2a164a96 tools/checks/web_server_contracts.py
618f9c6b20c043d151eac319d1712ec50358ae86b2e7fa23418c8e4bbcc45963 tools/checks/web_server_contracts.py
35f76060e585a96c623ef83bd5f771969f5be16a0e1ae626a3092d7458b84e84 tools/checks/web_service_ui_test.mjs
adaa1ed9e5958739df40190055c780c80d0a8e270ec8dc105ed3fa2dbb8fb74f tools/checks/www_redirect_ui_test.mjs
7c30c280d0b0a8bf855eeffab583ca34dc161255f7ef6124148aa979c17c26d3 tools/dev/memory_load.py
+1
View File
@@ -92,6 +92,7 @@ if [[ "$mode" == "test" ]]; then
python3 tools/checks/rust_module_contracts.py
python3 tools/checks/h3_navigation_0316.py
python3 tools/checks/release_0317.py
python3 tools/checks/source_pruning_0317.py
python3 tools/checks/system_info_no_subprocess_contracts.py
python3 tools/checks/source_module_contracts.py
python3 tools/checks/version_contracts.py
+1 -1
View File
@@ -114,7 +114,7 @@ Authentication, vhost rate limits, security headers, compression, custom error p
RustProxy 0.29.1 can mark a Custom location as `internal`. An internal location is addressed by its stable location `id` and is never selected by a public URI match. The vhost main source or another Custom location can use `error_fallback` to internally dispatch configured `403/404/500/502/503/504` responses to that target. The default intercepted status is `404`.
This is an internal request handoff, not an HTTP redirect. The original method, URI/query and request context are retained. The fallback target re-enters normal access/header/cache/compression/response processing. Fallback targets must be enabled internal locations in the same vhost; validation rejects cycles and runtime dispatch is depth-limited. See [0.29.1 implementation note](V0.29.1_IMPLEMENTATION.md).
This is an internal request handoff, not an HTTP redirect. The original method, URI/query and request context are retained. The fallback target re-enters normal access/header/cache/compression/response processing. Fallback targets must be enabled internal locations in the same vhost; validation rejects cycles and runtime dispatch is depth-limited. See [current vhost documentation](VHOSTS.md).
### Replace origin error bodies without an internal location (0.29.7)
-152
View File
@@ -1,152 +0,0 @@
# RustProxy 0.31.0 – implementation and verification report
### Security/compilation follow-up
- Correct the user-reported `E0716` lifetime error in the HTTP BAN/PURGE
error path by returning an owned-independent static response and logging the
detailed validation error.
- Restrict invalidation to the selected HTTP/HTTPS scheme, Host and vhost;
reject duplicate maintenance headers, invalid bodies and broad BAN without
confirmation. Execute shared cache maintenance in a bounded blocking pool
of two operations, rather than scanning on Tokio core workers. Audit
authenticated BAN-rule deletion before mutation.
- Add localized BAN/PURGE `curl` help next to the Cache allowlist and Rust
API/auth/security regression tests. Full review, exposure matrix and live
test plan: [SECURITY_AUDIT_0.31.0](SECURITY_AUDIT_0.31.0.md). Source-level
coverage is **not** Rust runtime/compile verification.
### Panel compression and cache HTTP invalidation follow-up
The 0.31.0 maintenance follow-up introduces one shared, precompressed panel
content handler for authenticated HTML, CSS, JS and language bundles (negotiated
Zstd/Brotli/gzip with identity fallback; settings under Administration), plus
optional IP/CIDR-authorized public `BAN`/`PURGE` scoped to the selected vhost.
Existing cache invalidation, revisioned settings and auth/ACL behavior are reused.
The System information Refresh button now sits next to the API badge. See
[SETTINGS](SETTINGS.md), [CACHE](CACHE.md) and [TEST_REPORT](TEST_REPORT.md) for
behavior and verification limitations. This note does not assert a Rust build.
Date: 2026-10-10. Input: user-provided `RustProxy-0.30.1.zip`, which **does not contain `.git` metadata**. This is a complete edited source snapshot, **not** a verified checkout of remote `master` or a compiled/released binary. No commit or tag was created.
## Changes implemented
1. **One dashboard coordinator:** `src/admin/web/app/refresh.js` replaces recurring independent panel timers. Every task has a stable ID, active-view guard, sequential `setTimeout` scheduling after completion, `AbortController`, request epoch/generation guards, independent error handling, and manual execution. Auto polling and SSE pause on hidden tabs and stop on logout; timers and `visibilitychange` listener are removed.
2. **SSE with fallback:** `/api/events?interval=1|2|5|10` preserves one shared live channel per browser with individually selected frequency, session checking and heartbeats. It carries lightweight status and the existing log snapshot. `/api/status?view=live` and `/api/logs` polling run only while SSE is unavailable, and are stopped on recovery. The full `/api/status` remains available to existing clients.
3. **Traffic / Cache:** retained `/api/traffic` and `/api/cache`; they poll only in their active view, while both per-view Auto and account Live switches allow it. The interval comes from per-account preferences. `0` selects manual operation without disabling refresh buttons, paging, charts, filters or operations.
4. **System diagnostics:** new authenticated `/api/system-info` sourced from cached `sysinfo`/diagnostic and existing ACME-runtime metadata; OS identification is cached. Normal live snapshots no longer include costly system information, memory accounting, certificate runtime details or full configuration. System info polls at the selected lower cadence for Overview and the relevant Settings tabs; manual refresh is always available.
5. **Preferences and UI:** existing `/api/preferences` and account-specific SQLite storage extended with defaulted `refresh` settings; strict server/browser allowlists; UI controls, reset, persistence and immediate rescheduling. English/Polish translations maintained. Old preference documents are accepted without migration.
6. **Release:** main Cargo manifest/lock package, OpenAPI/version checks and active docs identify `0.31.0`. Historical changelog entries and third-party crate version numbers remain intact. Existing SQLite schema 9, cold-cache format 3 and proxy data path are unchanged.
## Refresh defaults and boundaries
| Key | Default | Allowed values (seconds) |
|---|---:|---|
| `live_enabled` | `true` | `true`, `false` |
| `overview_seconds` | 2 | 1, 2, 5, 10 |
| `traffic_seconds` | 5 | 0, 3, 5, 10, 30 |
| `cache_seconds` | 5 | 0, 3, 5, 10, 30 |
| `system_info_seconds` | 60 | 0, 15, 30, 60, 300 |
`live_enabled=false` pauses **all automatic** fetching, including SSE and system info. Every manual refresh remains possible. Server stream cadence is client-local, not global. The existing `/api/live-logs` remains independent, in accordance with its existing logs subscription model.
## Verification executed
| Action | Result | Scope/limit |
|---|---|---|
| `./dev.sh test` | **Partial, exit 127** | All Rust-independent preflight, source/module/security/version checks, SQLite schema-9 migration/upgrade, 8 Node UI tests (including new refresh test), docs and language checks passed. **Rust toolchain check then failed**: `cargo`/`rustc` unavailable. |
| `node tools/checks/refresh_0310_ui_test.mjs` | **PASS** | Deterministic simulated browser clock: 2/3/5/0-s cadence, manual mode, Live toggle, 20 rapid tab transitions, no duplicate requests/timers, aborting obsolete work, hidden/visible, SSE error/recovery fallback, independent failures, logout/cleanup, browser preference validation. |
| `go test ./...` (in `docker-agent/`) | **PASS** | Go agent unit tests. |
| `node --check` over all `src/admin/web/**/*.js` | **PASS** | JavaScript syntax only, not execution in Chrome/Firefox. |
| `python3 tools/checks/check_source.py` | **PASS** | Static contracts (not Rust type checking). |
| `python3 tools/checks/release_smoke.py` | **PASS** | Offline SQLite/migration and release integration/source contracts. |
| `bash -n dev.sh` and Python compile checks | **PASS** | Shell and Python syntax. |
| `./dev.sh check`, `./dev.sh fmt`, `./dev.sh release` | **NOT EXECUTED (exit 127)** | Rust/Cargo/rustfmt absent. No format, typecheck, Rust unit/integration suite, linked release binary. |
| Live Rust SSE/API/Traffic/Cache + multi-user persistence | **NOT EXECUTED** | Cannot start the Rust service in this environment. Rust integration regression tests were added to source but not run. |
| Real bandwidth/serialized SSE bytes/CPU, long-running resource metrics | **NOT MEASURED** | No Rust server. The repo's 2,000 HTTP-request memory-load smoke uses a Python **peer**, not the RustProxy binary; it is not RustProxy performance evidence. |
**Important:** all Rust runtime, compile and real end-to-end security, SSE heartbeat/reconnect and production proxy regressions remain open; this source archive cannot be declared production-ready before those checks.
## Request-rate analysis (source-derived forecast, not wire measurement)
Rates below are **steady-state theoretical** at default intervals, for a single visible administrator with no manual clicks; the first fetch after entering a view is extra. They are not byte-size/CPU benchmark results.
| Mode | Previously | v0.31.0 default |
|---|---|---|
| Overview status + logs | ~30 full-status SSE snapshots/min (each contained system data), with log payload | ~30 compact SSE snapshots/min with logs; no concurrent fallback |
| Overview system diagnostics | embedded in each SSE status | ~1 GET `/api/system-info`/min |
| Traffic active | ~20 GET `/api/traffic`/min at 3 s | ~12 GET/min at 5 s |
| Cache active | ~20 GET `/api/cache`/min at 3 s | ~12 GET/min at 5 s |
| Other views / hidden tab | older per-view guard behavior; background SSE not globally paused | zero scheduled Traffic/Cache; hidden tab closes SSE and pauses all automatic polling |
| Settings not requiring diagnostics | independent legacy status data carried by SSE | no automatic GET `/api/system-info` |
Actual SSE payload lengths, encoding/cost, network traffic and memory footprint **must be measured against a running binary**. No byte reduction percentage is asserted.
## Changed files (40 edited, 8 new, 0 deleted)
- `AGENTS.md`
- `CHANGELOG.md`
- `Cargo.lock`
- `Cargo.toml`
- `README.md`
- `ROADMAP.md`
- `SHA256SUMS`
- `dev.sh`
- `docs/API.md`
- `docs/README.md`
- `docs/SETTINGS.md`
- `docs/TEST_REPORT.md`
- `langs/en_US.json`
- `langs/pl_pl.json`
- `src/admin/documentation/documents.rs`
- `src/admin/logging_api.rs`
- `src/admin/openapi.json`
- `src/admin/preferences.rs`
- `src/admin/router.rs`
- `src/admin/status.rs`
- `src/admin/tests.rs`
- `src/admin/tests/part_04.rs`
- `src/admin/web/acme.js`
- `src/admin/web/app/core.js`
- `src/admin/web/app/preferences.js`
- `src/admin/web/app/runtime.js`
- `src/admin/web/cache-panel.js`
- `src/admin/web/docker.js`
- `src/admin/web/drafts.js`
- `src/admin/web/index.html`
- `src/admin/web/settings.js`
- `src/admin/web/traffic-panel.js`
- `src/admin/web/transform-panel.js`
- `tools/checks/check_source.py`
- `tools/checks/docs_check.py`
- `tools/checks/preflight.py`
- `tools/checks/release_smoke.py`
- `tools/checks/system_info_0301_ui_test.mjs`
- `tools/checks/system_info_no_subprocess_contracts.py`
- `tools/dev/memory_load.py`
- `docs/IMPLEMENTATION_0.31.0.md`
- `src/admin/preferences_columns.rs`
- `src/admin/refresh_preferences.rs`
- `src/admin/status_live.rs`
- `src/admin/status_system.rs`
- `src/admin/tests/part_09.rs`
- `src/admin/web/app/refresh.js`
- `tools/checks/refresh_0310_ui_test.mjs`
## Required remaining release gate
On a host with stable Rust toolchain and a configured development environment, run:
```sh
./dev.sh fmt
./dev.sh check
./dev.sh test
./dev.sh release
cd docker-agent && go test ./... && cd ..
```
Test real session login and per-account persistence, permission denial for `/api/system-info`, SSE connect/heartbeat/reconnect/disconnect, active-vs-hidden browser polling via DevTools, real `/api/status` backwards compatibility and live/proxy/cache/traffic behavior. Measure serialized SSE bytes and generation time, CPU and request counts by Overview/Traffic/Cache/Settings/hidden tab. Correct any Rust compilation/runtime failures before deploying or creating a release tag.
## Same-version mobile startup follow-up (2026-10-10)
The authenticated dashboard replaces the previous all-body `visibility: hidden` with a visible CSS-only spinner, a 12-second stalled-start hint with reload link, and an EN/PL explicit failure screen. It reveals the shell and starts the existing refresh coordinator before awaiting noncritical preferences/configuration/status; failed configuration cannot hide the shell. `Refresh now` is now aligned with the Overview heading. The fixed authenticated HTML is gzip-compressed once into cached bytes, negotiated per request; unchanged identity responses and public login remain. Browser UI/source checks are documented in `docs/TEST_REPORT.md`; Rust compilation and real wire/mobile tests remain open.
-64
View File
@@ -1,64 +0,0 @@
# RustProxy 0.31.1 — mobile dashboard initialization
## Scope
This patch changes only the authenticated administration panel bootstrap and its
release documentation. It does not modify reverse-proxy traffic processing,
authentication, stored configuration, SQLite migrations or preference values.
## Root-cause analysis
Previously, the authenticated dashboard HTML requested 17 independent deferred
JavaScript files. The first one (`/assets/app.js`) starts asynchronous session
and configuration requests from `web/app/runtime.js`. Deferred ordering only
orders *script evaluation*, not asynchronous continuation: a completed API call
can resume while later JavaScript resources are still downloading. In that
window, a render path may access `window.rustProxyIntegrations.bindings`
before `integrations.js` has finished evaluating. The browser then reports an
undefined-object exception. A failed/missing script has the same symptom.
The reported iPhone/reverse-proxy incident is consistent with this race. It was
not possible to capture the user's mobile network trace, so the precise
combination of mobile transport behavior, timing, and content encodings is not
proven.
## Changes
1. The dashboard HTML now requests only `/assets/app.js?v=0.31.1`. On the
backend, the same existing `/assets/app.js` route assembles all existing
UI source modules in their prior load order. `/assets/vhosts.js` and the
other private asset URLs remain available for compatibility.
2. Startup waits for `DOMContentLoaded` and verifies the required modules
before requesting a session or configuration. A missing module produces
a visible startup failure with the existing retry link instead of a
partially initialized workspace.
3. Authenticated HTML also requests `/assets/style.css?v=0.31.1`; versioned
URLs separate release assets in intermediary caches. No cache bypass
or potentially unsafe public caching is introduced on the server.
4. The initial `/api/config` load retries at most twice after a transient
failure (400 ms / 800 ms delays). If it cannot load, the existing startup
recovery UI is displayed. Session rejection (`401`) does not trigger retries.
5. Public login, CSP, compression negotiation, SSE, polling, Traffic and Cache
APIs, user preferences and the proxy request path are unchanged.
## Acceptance checks
- Node.js parses each source module and the complete emitted dashboard bundle.
- A JavaScript startup test verifies **zero API requests before DOM readiness**,
successful activation with complete modules, and recovery when a module is
missing. A second test verifies configuration retry success/failure.
- Chromium smoke test on the authenticated HTML with simulated session and
configuration endpoints, at desktop and mobile widths, when available.
- Run `./dev.sh test` and `./dev.sh check` in an environment with Rust/Cargo.
- On the production reverse-proxy path, test initial load, 20+ rapid reloads,
navigation between tabs, Safari on iPhone (Wi-Fi and cellular), and a
deliberately delayed/failed `/api/config` response. Confirm no uncaught JS
errors and no mixed asset versions.
- Compare actual HTTP `Content-Encoding`, `Content-Length`, `Vary`, `Status`
and response bodies for document, script and stylesheet behind the reverse.
Disable any reverse cache rule that ignores version/query keys.
## Environment limitations
Compiler/type-checking, live RustProxy API tests and Safari/iPhone tests require
the target runtime; they cannot be inferred from source or Chromium alone.
-43
View File
@@ -1,43 +0,0 @@
# RustProxy 0.31.3 - Mobile admin session recovery
## Scope and evidence
The supplied 0.31.2 source was inspected and executed with a deterministic Node.js VM harness. The original functions permitted two simultaneous `/api/auth/session` requests from `pageshow` and `visibilitychange`. In the reproduced race, one `auth.invalid_origin` response called `location.reload()`, another successful response cleared the reload-protection marker, and a subsequent 403 triggered another reload. The event ordering and client-side failure were reproduced; the original server-side rejection on the reported mobile device remains unexplained because no captured mobile HTTP exchange was provided.
The dashboard's prior visibility handler also restarted the SSE channel and refresh timers before it checked the session. A full dashboard navigation rejected at the server could return raw JSON as the document, rather than an actionable page.
## Changes
- `src/admin/web/app/session-resume.js`: shared in-flight session validation; one visible retry path after 403/network errors, no `location.reload()` on resume; successful checks update CSRF/session display and release polling only if the auth epoch is unchanged. An HTTP 401 keeps the existing login/lock flow.
- `src/admin/web/app/refresh.js`: `visibilitychange`, `pagehide`, and BFCache `pageshow` share a resume gate. Pause/abort polling and close SSE before validating; allow background tasks only after success. Repeated callbacks during the same visible period do not initiate another check.
- `src/admin/web/app/runtime.js` and `core.js`: initial-startup reload marker is cleared only after successful full activation, including preferences/configuration. Startup errors stop the refresh coordinator and display a visible failure. Session failures show a localized manual retry button.
- `src/admin/session.rs`: unchanged Origin matching and `Sec-Fetch-Site` enforcement; 403 from invalid Origin now has bounded structured diagnostics showing method, path (without query), sanitized Origin, Host, expected external scheme/authority, fetch site, trust status, and safe forwarding metadata. No Cookie, Authorization, CSRF token, secret, query parameter or untrusted forwarded Host value is logged. Browser document navigation requesting HTML receives a standalone 403 error/retry page; API responses remain JSON.
- Localization (`en_US`, `pl_pl`), versioned panel assets, developer checks and release identity were updated. No schema/config/proxy data-plane or public route change.
## Test matrix
| Scenario | Source simulation | Live acceptance still needed |
| --- | --- | --- |
| Visibility + BFCache in one event sequence | One `/api/auth/session` check | iOS Safari / Android Chrome |
| 403 during resume | No automatic reload; error screen; paused SSE and tasks | Capture browser headers and proxy forwarding |
| Manual retry after temporary rejection | Single recheck; restored shell/SSE on success | Network interruption/recovery |
| HTTP 401 | Existing lock/login behavior | Expired/revoked real session |
| Pagehide/restore, 10 repeated cycles | One check per return; no leaked event handlers or parallel SSE | Real BFCache lifecycle |
| Stale session response after logout | Ignored without restoring session state | Concurrent logout/restore |
| Startup failure | Visible error, recovery marker retained | Missing JS or language/config response |
| Foreign Origin / cross-site | Rust regression test added, exact checks retained | Real server test with Rust/Cargo |
| Document navigation blocked by Origin | Rust regression test added, 403 HTML response | Mobile top-level navigation |
The deterministic browser suite uses actual application code and a simulated API. It is *not* a real browser, reverse proxy, or compiled Rust backend test. `Cargo.lock` comes from the prior 0.31.2 candidate, remains unresolved against the current dependency constraints and must be generated by a real Rust 1.99+ Cargo resolution. Do not deploy on the basis of static/source-only testing.
## Release-host validation
```bash
bash scripts/resolve_dependencies_0312.sh
./dev.sh check
./dev.sh test
./dev.sh release
(cd docker-agent && go test ./...)
```
Then verify on a real administrator endpoint with correct trusted reverse-proxy forwarding. Compare the external scheme/authority with `Origin`, `Host`, `Sec-Fetch-Site` and **only trusted** `X-Forwarded-Host` / `X-Forwarded-Proto` in the diagnostic log. Test at least 20 return-to-tab cycles on actual mobile browsers, including BFCache, session expiration, temporary 403 responses and recovery. Confirm that disallowed origins still return 403 and that polling/SSE do not grow across cycles. Do not turn off Origin/CSRF or loosen forwarded-header trust to hide the symptom.
-151
View File
@@ -1,151 +0,0 @@
# RustProxy 0.31.4 - Administration-panel Origin pinning and simplified HTTPS settings
## Scope
Only the **administration panel/control-plane authentication** and its settings,
OpenAPI, translations and regression tests change. No public vhost/proxy routing,
backend forwarding, stream, session-store or database schema behavior is altered.
Credential-based certificate API automation remains on its existing validation
path. The 0.31.3 BFCache, manual retry, SSE/polling and startup error protection
remains unchanged.
## Evidence and failure mode
0.31.3's `external_admin_scheme` rejects a trusted
`X-Forwarded-Proto: https, http` value with `auth.invalid_origin` and reason
`external_scheme` before checking a session, even for `GET /api/auth/session`
without an `Origin` header. This may appear intermittently if a multi-hop
reverse proxy sometimes appends rather than replaces the header. The previous
Node.js HTTP reconstruction reproduced this with 3/12 intentionally malformed
GETs; it does **not** prove the real user's reverse proxy behaves that way.
## Patch design
`administration.public_origin` is an **optional, explicit HTTPS origin**:
- Requires one canonical DNS hostname; that hostname is derived for the effective
panel Host allowlist without repeating it in `administration.allowed_hosts`.
The setting forbids credentials, userinfo, path, query, fragment, HTTP and ambiguous input.
- Is active only after restart and only when the immediate TCP peer is loopback
or is already in the configured trusted-proxy list. It does not implicitly
trust another proxy or the browser's `Host`/forwarding headers.
- Keeps checking the actual request `Host` against the administration listener
policy. If a forwarded Host exists, it must be a **single** authority and
match the pinned host and effective port. Malformed/userinfo/duplicate values
are rejected.
- For the pinned panel, accepts a bounded (maximum eight hops) protocol chain
only if the *first* hop is literally `https` and every hop is `http` or
`https`. This permits `https, http` but not `http`, `http, https`, `ftp`,
empty values, or arbitrarily long chains. Missing XFP is allowed only
because the explicit administrator pin defines the external HTTPS identity.
- Uses the configured origin for the browser `Origin` comparison, correct
`Secure` session cookie and the existing CSRF checks. Existing
`Sec-Fetch-Site: cross-site` blocking is unchanged.
- Does **not** alter the validation path when the pin is absent, the peer is
untrusted, or certificate-automation credentials are present.
On every rejection, the existing bounded, credential-redacted diagnostics
remain available. New `pinned_origin_mismatch` identifies a disagreement with
the explicit pin. Do not log cookies, CSRF tokens or Authorization headers.
## Simplified hostname configuration (included in this 0.31.4 reissue)
### Operator change
For the panel behind a trusted reverse proxy, **Settings -> Administration**
now needs only the **Public panel HTTPS origin** such as
`https://panel.example.test`. The DNS hostname comes from this validated
administrator setting; it need not (and should not) be repeated in
`administration.allowed_hosts`. Additional DNS hostnames remain in an optional
**Advanced** section. Only those extra names are stored in the allowlist.
Backend validation also canonicalizes any duplicate hostname in memory.
The settings page suppresses this value when loading an older configuration and omits it on the next save.
Other explicitly allowed hosts remain. Removing/changing the pin does not automatically retain the old
hostname as an extra allowlist entry. For legacy duplicated values, this
normalization occurs **only when the administrator saves**; there is no
silent SQLite mutation on startup.
### Configuration example
```json
{
"administration": {
"public_origin": "https://panel.example.test",
"allowed_hosts": []
}
}
```
This field still requires an HTTPS URL with one DNS hostname and an optional
port; userinfo, fragment, path, HTTP, IP literals and localhost remain invalid.
Changing either access-control field needs a RustProxy restart.
### Implementation and security
- `Administration::validate` normalizes the public URL independently of the
optional additional DNS list. `Administration::include_public_origin_host`
generates an **effective runtime/desired** Host allowlist once on startup /
status read, without adding to the persisted configuration or accepting a
wildcard. The originally saved allowlist is unchanged.
- `admin_host_allowed` and `configured_admin_public_authority` continue to
check the actual Host and the trusted last-hop proxy. Forwarded Host remains
single and identical to the pin. Only a bounded, first-hop HTTPS
X-Forwarded-Proto chain can be used with the explicit pin; no arbitrary
Origin or `Sec-Fetch-Site: cross-site` or missing CSRF is permitted.
- The explicitly opted-in self-admin vhost case may pass the reserved session
cookie when the incoming HTTPS host equals the pin; it still requires a
backend targeting the administration listener directly by IP and the prior
`accept_local_listener_risk` opt-in. No other proxy traffic changes.
- No new configuration key or SQLite migration. Older `allowed_hosts` lists
remain accepted, preserving backwards compatibility. Existing 0.31.3
mobile/BFCache recovery is untouched.
### Tests and acceptance gate
New Rust source tests also cover pin-only saved configuration, derived runtime
allowlist, additional legacy entries, changes/removal, invalid URL input,
same-origin session and `Secure` cookie, foreign Origin, untrusted peers,
forwarded-host spoofing and CSRF. Node.js runs a focused real settings-helper
test and a local HTTP simulation of the guard; these **do not** execute the
Rust server. Exact results are recorded in [TEST_REPORT](TEST_REPORT.md).
Production acceptance remains OPEN until Rust 1.99+ builds/tests pass with a
resolved Cargo.lock; a trusted reverse proxy and real mobile Chrome/Safari
are checked for repeated BFCache resumes, session expiry and negative
Origin/Host/CSRF cases. Never weaken the trusted-proxy list or disable Origin
validation to avoid `auth.invalid_origin`.
## Test coverage
Rust source tests in `src/settings/tests.rs` and `src/admin/tests/part_11.rs`
cover config parsing/normalization, persisted legacy defaults, restart gating,
trusted/foreign hosts, unknown/duplicate headers, cross-site metadata, HTTP
first hops, genuine same-origin session GET with multi-hop metadata, HTTPS
`Secure` session cookie, CSRF enforcement and opt-out behavior.
`tools/checks/admin_public_origin_0314_sim.mjs` and
`tools/checks/admin_public_origin_0314_config_sim.mjs` perform additional HTTP
guard *simulations*, not compiled RustProxy execution.
`tools/checks/admin_panel_config_0314_ui_test.mjs` checks the settings UI and form behavior. Record its result in
`docs/TEST_REPORT.md` only after it is run.
## Required production acceptance
1. `bash scripts/resolve_dependencies_0312.sh` on a Rust 1.99+ builder with
network/local audited dependencies; inspect regenerated lockfile.
2. `./dev.sh check && ./dev.sh test && ./dev.sh release` and Go tests.
3. Set an exact HTTPS panel origin (no duplicate DNS allowlist entry), restart, and verify
with a trusted real edge proxy. Keep RustProxy administration listening on
an isolated/private socket. Verify the first trusted hop sanitizes headers.
4. On iOS Safari and Android Chrome, perform 20+ visibility/BFCache resumes,
full refreshes, sessions renewed/expired, and a deliberate temporary 403.
Check HTTP 200 for valid `/api/auth/session` and `Secure` cookie; no reload
loops, blank screens, request floods, or leaked SSE/polling tasks.
5. Inject cross-origin, duplicate/foreign forwarded Host, downgraded protocol,
missing CSRF and untrusted-source requests; they must still fail. Confirm
certificate API automation and public vhosts behave identically to 0.31.3.
The original production cause cannot be declared fixed without representative
mobile/reverse-proxy request logs. This artifact is not compiler-verified unless
the release-host gate above passes.
-55
View File
@@ -1,55 +0,0 @@
# RustProxy 0.31.5 implementation and verification
## Scope
0.31.5 adds an operator workflow from traffic/log inspection to a bounded User-Agent blocklist without changing SQLite schema 9 or existing saved configuration requirements.
## User-Agent blocking
`user_agent_blocking` is available globally and on each vhost. Both levels use the same bounded policy:
- disabled by default;
- at most 128 patterns, 256 bytes per pattern and 16 KiB total pattern text;
- case-insensitive ASCII matching;
- a pattern without `*` matches a substring;
- `*` is a wildcard and may be used at the start, middle or end;
- a request with no `User-Agent` header is not denied by this rule;
- global and per-vhost policies are cumulative.
The check runs after vhost lookup but before redirects, cache/origin processing and before an upstream is opened. A match returns HTTP 403. The global policy is edited under **Settings -> Security**; the vhost policy is edited under **Vhosts -> Security**.
This is an operator blocklist, not a browser identity/authentication mechanism. User-Agent values are client-controlled and can be changed trivially.
## Traffic copy workflow
Traffic dimension labels are rendered as copy controls. The copied value is the raw displayed dimension value rather than a formatted count, so IP addresses, User-Agent values, paths, hosts, methods, status values and other traffic dimensions can be pasted directly into investigation notes or configuration fields.
## Stored Logs repeated-value aggregation
`GET /api/file-logs/query` accepts optional `aggregate_field`, `aggregate_min_count` and `aggregate_limit` parameters. Aggregation is computed across matching records in the same bounded scan used by Stored Logs, before page offset/limit are applied.
The field may be a canonical field such as `user_agent`, `client_ip`, `host` or `path`, or another parsed/nested field accepted by the Stored Logs field resolver. Scalar strings, booleans and numbers are counted; bounded array scalar values are counted individually; objects are not flattened into invented text values.
Safety limits are retained: the scan remains limited to 8 MiB/20,000 lines/32 sources, aggregate output is at most 200 rows, and aggregate cardinality is capped at 4,096 distinct values. The response reports when aggregation was truncated by those limits.
## Consolidated per-vhost Stored Logs
Each per-vhost active JSONL file and matching `.jsonl.*.gz` rotation receives the same logical `group_id`, for example `vhost:example.com`. Individual physical files still have unique source IDs and metadata.
When `consolidate_vhosts=true` and `source=vhost:<id>`, the query scans all discovered members in that group subject to the existing global source/byte/line bounds. The panel enables this behavior by default and presents one logical vhost option with the member count and summed discovered size. Turning consolidation off exposes the physical files separately again.
Combined access/error logs keep their existing source behavior; this switch only changes per-vhost grouping.
## Compatibility and security
- Existing configuration files deserialize with `user_agent_blocking` disabled through serde defaults.
- No SQLite migration or cache-format migration is introduced.
- Stored Logs still accept server-generated source IDs only, never arbitrary filesystem paths.
- Existing symlink rejection, gzip input/output caps and blocking-worker isolation remain in force.
- User-Agent denial occurs before origin contact, so a blocked request cannot cause a new upstream connection.
## Verification status
Rust-independent checks cover language packs, JavaScript syntax/UI source contracts, OpenAPI/source-version consistency, Stored Logs grouping/aggregation source contracts and existing migration/release smoke tests. The source also contains Rust unit/regression tests for User-Agent matching and Stored Logs aggregation/grouping.
The validation environment used for this source package does not contain Rust/Cargo. Therefore `cargo fmt`, `cargo clippy`, Rust compilation and Rust unit/integration execution remain a release gate. Run `./dev.sh check` and `./dev.sh test` on the supported Rust toolchain before production deployment, then verify real HTTP 403 behavior, vhost/global precedence, clipboard actions and gzip consolidation against representative production logs.
-21
View File
@@ -1,21 +0,0 @@
# RustProxy 0.31.6 implementation and acceptance gate
## Scope
This patch is based on the delivered 0.31.5 source archive. It is a narrow HTTP/3 navigation follow-up. It does not change the logo link, normal panel SPA navigation, administrator security checks or the HTTP/3 listener controls. The report of iOS `21.0.1` is operator-provided device metadata and has not been independently verified.
## Code changes
1. `src/proxy/cache_helpers.rs`: the cache/404 empty-body preflight is now bounded to 35 ms per initial frame for H3 GET/HEAD. A delayed FIN bypasses cache rather than blocking the entire route; all frames remain available for streaming. Separately, the H3-to-H1 unknown-length request-body normalization now includes `GET` and `HEAD`. The first meaningful frame is inspected up to eight times, and GET/HEAD wait for at most 35 ms per frame. If end-of-stream is observed, the body becomes an exact, empty H1 request. Nonempty DATA and trailers are replayed via the existing `ReplayBody` wrapper. A slow stream is not discarded or buffered and stays on the existing streaming path. This makes the H3 receive half complete before H1 forwarding when FIN is already available, without relying on a claim that H1 GET/HEAD were chunked (Hyper already special-cases those methods).
2. `src/http3.rs`: a failure of the H3 accept loop logs the event and stops accepting, but lets the existing bounded drain handle responses already in progress instead of aborting all response tasks immediately.
3. `src/http3.rs`: pre-HEADERS peer stream cancellations are logged at HTTP status **0**, never fabricated HTTP 400. Response failures record the transport stage, H3 stream identifier, method, path without query, advertised response status, bytes successfully sent and bounded/redacted error in per-vhost protocol-error logs. Peer-initiated cancellation remains distinct from a true truncated/error response.
4. A full HTTP/3 `GET /` emits `http3_navigation_started` and `http3_navigation_complete` at INFO in the process/service logs (or `http3_navigation_interrupted` at WARN) with SNI, connection endpoints, stream ID, response status and sent bytes. No query, Cookie, Authorization or body content is included. Per-vhost HTTP/3 protocol errors are logged separately in JSONL when enabled. Correlate a missing completion for the new stream rather than assuming any cancellation caused a blank screen.
5. No change to the logo anchor `href="/"`, browser code paths, SSE on-page behavior, SQL schema, settings JSON, H1 WebSockets, QUIC RFC 9220 support, HOT/COLD cache or the `/logs` responsive view.
## Tests and risk
Rust unit tests cover empty and nonempty GET and HEAD forwarding, cache/404 preflight and stalled-client bounds; these are **source-level additions**, not proof of execution unless Cargo is available. The repository ships static checks, Node UI tests, SQLite upgrade smoke and Go tests. The pinned Cargo.lock dependency graph has not been re-resolved in this environment; a compiler-enabled build host must run `scripts/resolve_dependencies_0312.sh`, `./dev.sh check`, `./dev.sh test`, and a release build before deploying.
Reproduce on the actual iPhone with H3 **enabled**: open `https://rp.h.linuxiarz.pl`, sign in, click the original RustProxy logo repeatedly, compare successful GET `/` and JS/CSS access events with `http3_*` diagnostic events from the same peer and timestamp. In a failure capture Safari Web Inspector Network entries for document, JS and CSS, a QUIC packet/qlog trace if available, and the vhost JSONL. With systemd installations, view navigation breadcrumbs with `journalctl -u rustproxy --since '10 minutes ago' | grep 'http3_navigation_'`; the service log is separate from vhost JSONL. Old `H3_REQUEST_CANCELLED` events may be harmless; a failed new navigation or incomplete response is the acceptance condition. Test H1/H2, unrelated vhosts, cache retention and `/logs` mobile width as regressions.
**Acceptance remains OPEN** until compiled and tested on a real HTTP/3 listener with mobile devices; the original white-screen cause is not proven from one cancellation log alone.
-29
View File
@@ -1,29 +0,0 @@
# RustProxy 0.31.7 implementation and acceptance gate
## Settings and vhost throttling
- **Settings → Rules & headers** includes the Global User-Agent blocklist, Global custom headers, and Client response sections. Forms and request endpoints are unchanged, so there is no data migration. Settings → Security retains password, session and host-policy controls.
- `vhosts[].rate_limit.user_agent_limits` is an optional list (default `[]`) of `{pattern, requests, window_seconds, burst}`. At most 64 rules. Patterns use case-insensitive substring matching when no `*` is present, otherwise wildcard matching. Values are normalized and validated before persistence. The first matching rule has priority over the country override and base limiter for that request. No matching UA uses existing country/base behavior; missing UA does not select a UA override. Every vhost/UA rule/client IP has an independent fixed-window bucket within the shared 65,536-bucket cap. Cache, ACL, and global/vhost UA deny policies run with unchanged precedence.
- **Important:** UA is client-supplied. These rules provide traffic shaping, not trustworthy identity or bot protection. An absent/changed/oversized UA may avoid matching. Use ACL and base limits for security enforcement.
## Revision history and layout
- Revision-history UI polish: comparison control sits under Restore without extra dividers; long change lists scroll within the bounded desktop detail pane, while the stacked narrow-screen layout scrolls as one modal. Full JSON comparison uses the shared dialog border and borderless code blocks.
- Diff preview displays only changed leaf paths, sorted by object key. Array entries with stable `id` or `name` keys are compared by identity, avoiding spurious changes on reorder. Output is limited to 200 changes; large scalar previews truncate after 320 characters. The separate full JSON modal is opened explicitly and uses `textContent`, never HTML injection. Restore remains CAS-protected.
- Listener protocol buttons have a gap and may wrap; ACL profile attachments are in a closed-by-default native `<details>` element.
## Installed release retention and Cargo safety
- `update.sh` executes installer/update first, then prunes under the existing `.deploy.lock`. It retains `current` and one other latest release; three private pre-update backups remain (separate retention). Old updater incorrectly passed `find` sort keys into `rm` paths, so it printed removals but removed nothing; fixed by splitting the mtime key before path validation/removal.
- Cargo `target` is not cleared. Only directories under `target/{debug,release}/incremental` older than 30 days are eligible, and only if the corresponding Cargo profile lock is available and no symlinked target path is used. `deps`, `.fingerprint`, `build`, `.cargo-lock`, binaries and Cargo.lock are preserved. This cleanup does not promise meaningful disk recovery for release builds (incremental compilation is usually disabled for release mode).
- Managed updates create `.managed-update` before `systemctl stop`, and the service checks it during graceful shutdown. With an enabled cold tier, it forces a **best-effort** hot-to-cold direct persistence independent of the ordinary `cold_on_shutdown` checkbox. systemd grants 120 seconds, and runtime allows up to 90 seconds for the transfer after draining. The reported moved count reflects successful COLD insertions rather than merely attempted writes. Entries may still fail to persist (I/O/disk limits/timeouts); verify logs and COLD objects in an actual update. Disabling COLD does not allow automatic persistence to disk.
## Upgrade and live acceptance (open)
1. Compile and run `./dev.sh check`, `./dev.sh test` and `./dev.sh release` on Rust 1.99+ and verify the lockfile resolves. Do not call this release production-tested without those results.
2. Test Config revision CAS, UA matching and rate 429/Retry-After across two vhosts, clients, country policies and ordinary rate limits. Check `/settings?tab=policies` in EN/PL, including unsaved forms and the new full JSON modal.
3. On a staging installation with HOT+COLD enabled and `cold_on_shutdown=false`, cache a cacheable GET, run `bash update.sh`, confirm managed-update stop log and cold recovery after restart. Repeat rollback on a failed staged binary; backup retention must not remove the rollback candidate.
4. Verify `releases/` retains current and one predecessor, backups still retain three; verify `target/release/deps`, `.fingerprint`, `build` and the compiled binary remain intact and next `bash dev.sh release` is incremental.
5. Re-test H3 logo navigation on iPhone and log regressions; prior H3/iOS acceptance is still open.
SQLite schema 9 and COLD format 3 unchanged. No persisted setting is removed; old JSON without the new `user_agent_limits` field still deserializes.
+12 -37
View File
@@ -8,38 +8,6 @@ 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.5 release audit](V0.25.5_AUDIT.md) - Cloudflare country analytics consistency, Revision History modal styling and release gates.
- [0.25.4 release audit](V0.25.4_AUDIT.md) - historical retained configuration restore, CAS/audit behavior and release gates.
- [0.25.3 release audit](V0.25.3_AUDIT.md) - historical request-method policy, location ACL/country controls, hashed request IDs and configuration-history scope.
- [0.25.2 release audit](V0.25.2_AUDIT.md) - historical live backup, internal symlink and resumed-origin recovery scope.
- [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.
- [0.29.1 implementation note](V0.29.1_IMPLEMENTATION.md) - internal error fallbacks, multipart byte ranges and PHP PATH_INFO.
- [0.29.0 implementation note](V0.29.0_IMPLEMENTATION.md) - canonical host/custom-location redirects, health Host defaults, forced cache and gzip Stored Logs.
- [0.28.2 implementation note](V0.28.2_IMPLEMENTATION.md) - external certificate ACL/path hardening and least-privilege Certificate API grants.
- [0.28.1 implementation note](V0.28.1_IMPLEMENTATION.md) - external certificate paths, Certbot symlink reloads, backup semantics and certificate-source UX.
- [0.28.0 implementation note](V0.28.0_IMPLEMENTATION.md) - manual/hot-reloaded GeoIP, listener status UX, cache Via/narrow invalidation and header-rule layout.
- [0.27.3 implementation note](V0.27.3_IMPLEMENTATION.md) - HTTP/3 HEAD cache parity, COLD/rate-limit memory reduction, backup scope and package-path update.
- [0.27.2 implementation note](V0.27.2_IMPLEMENTATION.md) - managed Certbot relocation/renewal repair.
- [0.27.1 implementation note](V0.27.1_IMPLEMENTATION.md) - first-admin command, managed/fallback certificate runtime and account-scoped route-list sorting.
- [0.27.0 implementation note](V0.27.0_IMPLEMENTATION.md) - compression defaults/scoped policy, persistent Docker publications and visible cache occupancy meter fill.
- [0.26.7 implementation note](V0.26.7_IMPLEMENTATION.md) - HOT/COLD occupancy meters, backend rotation continuity and container Certbot packaging.
- [0.26.6 implementation note](V0.26.6_IMPLEMENTATION.md) - composed ACL allow semantics, certificate-form reset, backend table preferences and NPM migration helper documentation.
- [0.26.5 implementation note](V0.26.5_IMPLEMENTATION.md) - interface-scaled form/select alignment and corrected Streams listener navigation.
- [0.26.4 implementation note](V0.26.4_IMPLEMENTATION.md) - 404 drafts, consistent modal controls, compact Overview settings and idle age-based backend rotation.
- [0.26.3 implementation note](V0.26.3_IMPLEMENTATION.md) - global interface scaling, chart themes, square draft dock and Settings navigation polish.
- [0.26.2 implementation note](V0.26.2_IMPLEMENTATION.md) - unified Logs navigation, field-query search, dynamic JSON columns and RAW parsing.
- [0.26.1 implementation note](V0.26.1_IMPLEMENTATION.md) - historical vhost-aware protocol logging and Stored Logs UX milestone.
- [0.31.7 UA throttling, Settings, revision history and safe update retention](IMPLEMENTATION_0.31.7.md) - rollout, storage and acceptance.
- [0.31.6 H3 iOS navigation hardening and diagnostic events](IMPLEMENTATION_0.31.6.md) - bounded empty GET/HEAD handling, H3 error draining and mobile reproduction.
- [0.31.5 User-Agent blocking and consolidated log analysis](IMPLEMENTATION_0.31.5.md) - global/per-vhost UA blocklists, Traffic copy actions, repeated-value aggregation and per-vhost gzip consolidation.
- [0.31.4 administration public Origin and settings](IMPLEMENTATION_0.31.4.md) - opt-in HTTPS origin, one-field DNS allowlist, additional hosts in advanced settings, legacy deduplication, CSRF and remaining deployment checks.
- [0.31.3 mobile session / invalid-Origin recovery](IMPLEMENTATION_0.31.3.md) - deterministic BFCache and visibility session coordination, 403 diagnostics, recovery and remaining acceptance.
- [0.31.1 mobile panel bootstrap](IMPLEMENTATION_0.31.1.md) - panel script readiness, versioned assets, failure recovery and acceptance tests.
- [0.31.0 security audit](SECURITY_AUDIT_0.31.0.md) - current API/panel/BAN/PURGE attack-surface review, hardening and remaining runtime verification.
- [0.31.0 implementation report](IMPLEMENTATION_0.31.0.md) - consolidated changes, exact file inventory, validation results and outstanding Rust runtime acceptance.
- [TEST_REPORT](TEST_REPORT.md) - verification commands, results and environment limitations.
## Functional documentation
@@ -84,15 +52,22 @@ This is the canonical index for the current project documentation. The authentic
- [CACHE_DIAGNOSTICS](CACHE_DIAGNOSTICS.md) - cache diagnostics and interpretation.
- [REFERENCES](REFERENCES.md) - implementation references.
## Historical or version-labelled context
Version-labelled `V*.md` files are retained as historical implementation/audit records. Their version number describes the audited milestone. The current release implementation note is [IMPLEMENTATION_0.31.7.md](IMPLEMENTATION_0.31.7.md); [IMPLEMENTATION_0.31.6.md](IMPLEMENTATION_0.31.6.md) is retained as the prior H3 milestone; [IMPLEMENTATION_0.31.5.md](IMPLEMENTATION_0.31.5.md) is retained as the previous log/cache milestone; [IMPLEMENTATION_0.31.4.md](IMPLEMENTATION_0.31.4.md) is retained as the previous administration-Origin milestone; [IMPLEMENTATION_0.31.3.md](IMPLEMENTATION_0.31.3.md) is retained as the previous mobile recovery milestone; [IMPLEMENTATION_0.31.1.md](IMPLEMENTATION_0.31.1.md) is retained as the prior mobile milestone; [IMPLEMENTATION_0.31.0.md](IMPLEMENTATION_0.31.0.md) is retained as the previous milestone; [V0.29.1_IMPLEMENTATION.md](V0.29.1_IMPLEMENTATION.md) is retained as historical context, and [V0.29.0_IMPLEMENTATION.md](V0.29.0_IMPLEMENTATION.md) is retained as the prior redirect/health/cache/log milestone; [V0.28.2_IMPLEMENTATION.md](V0.28.2_IMPLEMENTATION.md) is retained as the prior certificate-hardening milestone; [V0.28.1_IMPLEMENTATION.md](V0.28.1_IMPLEMENTATION.md) is retained as the prior external-path milestone; [V0.28.0_IMPLEMENTATION.md](V0.28.0_IMPLEMENTATION.md) is retained as the prior GeoIP/listener/cache milestone, and [V0.27.3_IMPLEMENTATION.md](V0.27.3_IMPLEMENTATION.md) is retained as the prior HTTP/3/cache-maintenance milestone, [V0.27.2_IMPLEMENTATION.md](V0.27.2_IMPLEMENTATION.md) is retained as the prior managed-ACME repair, [V0.27.1_IMPLEMENTATION.md](V0.27.1_IMPLEMENTATION.md) as the prior managed-runtime/list-sorting milestone, and older version notes remain historical evidence. The latest retained audit is [V0.25.5_AUDIT.md](V0.25.5_AUDIT.md); older audit files remain available for traceability.
Older context documents such as [INTEGRATIONS_040](INTEGRATIONS_040.md), [MANAGEMENT](MANAGEMENT.md), [SECURITY_AUDIT](SECURITY_AUDIT.md), [ACME_RFC2136](ACME_RFC2136.md) and older version audits remain available for traceability but should not override the current functional/technical documents above.
The pre-cleanup developer-level changelog is retained as [CHANGELOG_TECHNICAL_ARCHIVE](CHANGELOG_TECHNICAL_ARCHIVE.md) when low-level historical detail is needed. The root `CHANGELOG.md` remains the operator-facing release record.
Completed roadmap history is retained separately as [ROADMAP_HISTORY](ROADMAP_HISTORY.md); the root `ROADMAP.md` contains the current release scope and future direction.
## Historical or version-labelled context
### Retained audits and historical context
- [SECURITY_AUDIT](SECURITY_AUDIT.md) and [SECURITY_AUDIT_0.31.0](SECURITY_AUDIT_0.31.0.md) - current security audit material and the 0.31.0 audit.
- [CHANGELOG_TECHNICAL_ARCHIVE](CHANGELOG_TECHNICAL_ARCHIVE.md) and [ROADMAP_HISTORY](ROADMAP_HISTORY.md) - retained long-form technical history and completed milestones.
- [ACME_RFC2136](ACME_RFC2136.md) - historical DNS provider details.
- [INTEGRATIONS_040](INTEGRATIONS_040.md) and [MANAGEMENT](MANAGEMENT.md) - retained design and integration notes.
- [TEST_REPORT](TEST_REPORT.md) - verification history and release gates.
The old per-version implementation notes and audits removed from this trimmed source archive are no longer required to build the application or use the documentation Reader. Only registered documents are embedded at compile time.
## Maintenance rules
- Put user/operator-visible release changes in `CHANGELOG.md`.
+10 -10
View File
@@ -134,7 +134,7 @@ Current direction: 0.24.0 -> 0.24.1
- [ ] Generate and commit a resolver-produced `Cargo.lock`, then run compiler-enabled `cargo fmt --all`, `cargo check --locked --all-targets` and `cargo test --locked --all-targets` on the release host.
- [ ] Run native FreeBSD/OpenBSD/NetBSD diagnostics acceptance and final H2/H3/WebSocket/proxy-statistics regression.
See [the 0.24.1 specification audit](V0.24.1_AUDIT.md).
The 0.24.1-era notes are summarized in the retained [technical changelog archive](CHANGELOG_TECHNICAL_ARCHIVE.md).
## 0.24.0
@@ -148,7 +148,7 @@ Current direction: 0.23.3 -> 0.24.0
- [x] Surface platform in Proxy Process and Prometheus metadata.
- [x] Update source/package/UI identity and source-side regression contracts to 0.24.0.
See [the 0.24.0 specification audit](V0.24.0_AUDIT.md).
The 0.24.0-era notes are summarized in the retained [technical changelog archive](CHANGELOG_TECHNICAL_ARCHIVE.md).
## 0.23.3
@@ -161,7 +161,7 @@ Current direction: 0.23.2 -> 0.23.3
- [x] Update package/source identity and Rust-independent release gates to 0.23.3.
- [ ] Run compiler-enabled `./dev.sh check` / `./dev.sh test`, release build and live iLO4/legacy WebSocket acceptance on the release host.
See [the 0.23.3 specification audit](V0.23.3_AUDIT.md).
The 0.23.3-era notes are summarized in the retained [technical changelog archive](CHANGELOG_TECHNICAL_ARCHIVE.md).
## 0.23.2
@@ -174,7 +174,7 @@ Current direction: 0.23.1 -> 0.23.2
- [x] Update package/source identity and Rust-independent release gates to 0.23.2.
- [ ] Run compiler-enabled `./dev.sh check` / `./dev.sh test`, release build and live HTTPS `--livelogs` acceptance on the release host.
See [the 0.23.2 specification audit](V0.23.2_AUDIT.md).
The 0.23.2-era notes are summarized in the retained [technical changelog archive](CHANGELOG_TECHNICAL_ARCHIVE.md).
## 0.23.1
@@ -187,7 +187,7 @@ Current direction: 0.23.0 -> 0.23.1
- [x] Update package/source identity and Rust-independent release gates to 0.23.1.
- [ ] Run compiler-enabled `./dev.sh check` / `./dev.sh test`, release build and browser acceptance with sustained Cache/Transform traffic on the release host.
See [the 0.23.1 specification audit](V0.23.1_AUDIT.md).
The 0.23.1-era notes are summarized in the retained [technical changelog archive](CHANGELOG_TECHNICAL_ARCHIVE.md).
## 0.23.0
@@ -201,7 +201,7 @@ Current direction: 0.22.3 -> 0.23.0
- [ ] Run compiler-enabled `rust-proxy`/Cargo check, tests and release build plus live token-auth certificate rotation and sustained high-rate live-log acceptance on the release host.
- [ ] Upstream mTLS/client certificate remains deliberately deferred.
See [the 0.23.0 specification audit](V0.23.0_AUDIT.md).
The 0.23.0-era notes are summarized in the retained [technical changelog archive](CHANGELOG_TECHNICAL_ARCHIVE.md).
## 0.22.3
@@ -216,7 +216,7 @@ Current direction: 0.22.2 -> 0.22.3
- [ ] Run compiler-enabled `./dev.sh check` / `./dev.sh test`, release build and live H1 -> h2c Upgrade -> H2 plus TLS/H2/H3 certificate-rotation acceptance on the release host.
- [ ] Upstream mTLS/client certificate remains deliberately deferred.
See [the 0.22.3 specification audit](V0.22.3_AUDIT.md).
The 0.22.3-era notes are summarized in the retained [technical changelog archive](CHANGELOG_TECHNICAL_ARCHIVE.md).
## 0.22.2
@@ -231,7 +231,7 @@ Current direction: 0.22.1 -> 0.22.2
- [ ] Add a distinct maximum TCP connection/socket limit per backend; request concurrency is implemented but is intentionally not presented as a socket limit.
- [ ] Run compiler-enabled `./dev.sh check` / `./dev.sh test` and live H1/H2/H3 plus H2 -> proxy -> H2 acceptance on the release host.
See [the 0.22.2 specification audit](V0.22.2_AUDIT.md).
The 0.22.2-era notes are summarized in the retained [technical changelog archive](CHANGELOG_TECHNICAL_ARCHIVE.md).
## 0.22.1
@@ -247,7 +247,7 @@ Current direction: 0.22.0 -> 0.22.1
- [x] Update 0.22.1 source/package identity and Rust-independent release gates.
- [ ] Run the compiler-enabled `./dev.sh check` / `./dev.sh test` gate plus live H1/H2/H3/h2c/RFC8441 acceptance on the release host.
See [the 0.22.1 specification audit](V0.22.1_AUDIT.md).
The 0.22.1-era notes are summarized in the retained [technical changelog archive](CHANGELOG_TECHNICAL_ARCHIVE.md).
## 0.21.0
@@ -264,7 +264,7 @@ Current direction: 0.20.0 -> 0.21.0
- [x] Finish release polish: backend timing header rename, human-readable uptime, transformed Live traffic series, Docker Passive Backend Health layout fix, warning cleanup and cache module split.
- [x] Keep package/source identity and OpenAPI at 0.21.0 and package the completed feature scope as the 0.21.0 release source.
See [the 0.21.0 specification audit](V0.21.0_AUDIT.md).
The 0.21.0-era notes are summarized in the retained [technical changelog archive](CHANGELOG_TECHNICAL_ARCHIVE.md).
## 0.20.0
+11 -3
View File
@@ -1,5 +1,13 @@
# Verification report - RustProxy 0.31.7
## 0.31.7 — pruned source package correction (2026-10-11)
- **Reported:** deleting 47 historical audit/implementation Markdown files made `dev.sh` fail at source preflight (`docs/V0.25.5_AUDIT.md`). They were not needed at runtime, but hardcoded documentation/source checks still demanded them.
- **Additional build hazard fixed:** the embedded documentation Reader still contained seven `include_str!` registrations for deleted `IMPLEMENTATION_0.31.x.md` files. These would cause actual Rust compilation failures even if the Python preflight were bypassed.
- **Changed:** remove obsolete audit/implementation requirements from preflight, source/release smoke tests and documentation checks; keep strict runtime source checks and validate the existence of every Markdown file still embedded in the Reader. Update Reader integration test, documentation index and broken relative links. No SQL, proxy runtime, UI or packaging policy changes.
- **PASS:** `./dev.sh test` passed Python source/upgrade/SQLite/security/memory regressions, Node UI checks, docs/locale checks and the new source-pruning test. It terminated **127 only because Cargo is not installed here**. New regression exercises the expected failure when a live Reader document or source module is deleted. `GOPROXY=off go test ./...` passed in `docker-agent`; `bash -n` for `dev.sh`/`update.sh` passed.
- **OPEN:** Rust compilation/linking and real update on the user's host remain unverified. After deploying this source archive, run `bash dev.sh release` and `bash update.sh` in the usual release workflow.
## 0.31.7 — revision history UI follow-up
- Move **Full JSON comparison** directly below **Restore revision** inside the existing action group, without additional separators. Preserve existing revision selection, restore handlers, diff logic and JSON display behavior.
@@ -85,7 +93,7 @@
- **PASS (2026-10-11):** `./dev.sh test` passed every available Rust-independent stage: source-module boundaries, version/contracts, SQLite schema-9 migrations and upgrade, security source/dependency checks, 300,000 PURGE-model iterations, 2,000 real HTTP requests to a **Python test peer**, all bundled Node/browser UI regressions (including BFCache/resume), 0.31.4 HTTP simulation, documentation and both language packs. Exit **127** at the missing Rust/Cargo toolchain guard only. The simulated peer is **not** a running RustProxy binary. `./dev.sh check` likewise exits 127 due to missing Cargo.
- **PASS:** `(cd docker-agent && go test ./...)`, `node --check tools/checks/admin_public_origin_0314_sim.mjs`; source-package size/integrity checks are performed separately.
- **Rust regression tests added, NOT EXECUTED:** `src/settings/tests.rs` and `src/admin/tests/part_11.rs` cover pin validation, restart, strict opt-out, session Secure cookie, CSRF, foreign Origin, cross-site, host spoofing and untrusted peers.
- **Remaining release gate OPEN:** the sandbox has no Rust/Cargo/rustfmt and the inherited lockfile remains unresolved against 0.31.2+ dependencies. Before production use: resolve on Rust 1.99+, format, clippy, test/build, then capture actual mobile Safari/Chrome and reverse-proxy traffic and run the acceptance matrix in [IMPLEMENTATION_0.31.4.md](IMPLEMENTATION_0.31.4.md). No claim is made that the original field `auth.invalid_origin` was directly observed or eliminated.
- **Remaining release gate OPEN:** the sandbox has no Rust/Cargo/rustfmt and the inherited lockfile remains unresolved against 0.31.2+ dependencies. Before production use: resolve on Rust 1.99+, format, clippy, test/build, then capture actual mobile Safari/Chrome and reverse-proxy traffic and run the acceptance matrix in [SETTINGS.md](SETTINGS.md). No claim is made that the original field `auth.invalid_origin` was directly observed or eliminated.
## 0.31.3 - invalid-Origin resume and mobile panel recovery (2026-10-11)
@@ -117,7 +125,7 @@
- **Implemented:** one ordered embedded JS payload at `/assets/app.js?v=0.31.1`; CSS URL also versioned; DOMContentLoaded/module gating and explicit startup recovery; up to two bounded retries of initial configuration (400/800 ms). Old private asset URLs remain available. No API, authentication, saved preferences, database or proxy data path changes.
- **PASS:** `./dev.sh test` completes all source checks, SQLite migration/backup and upgrade smoke tests, security and language checks, Node UI tests (including new startup-order and retry test), and documentation checks **before Cargo**. `python3 tools/checks/source_module_contracts.py` checks each JS module and the assembled 608 KB bundle with `node --check`. `cd docker-agent && go test ./...` passed. Shell syntax passed.
- **Not verified:** `./dev.sh test` terminates **127** at Cargo; `./dev.sh check` also **127** (`cargo`/`rustc` absent). Browser smoke against a local simulated API was attempted, but Chromium navigation was blocked by sandbox policy (`ERR_BLOCKED_BY_ADMINISTRATOR`); no successful browser smoke run is claimed. Playwright WebKit browser binaries are not installed here. No real iPhone Safari or production reverse-proxy test was possible.
- **Release-host checks:** `./dev.sh check && ./dev.sh test && ./dev.sh release`, followed by Safari/Chrome iPhone tests behind the reverse: 20+ full reloads from Overview/Cache/Settings, initial config API failures, SSE reconnect, browser console errors, and response encoding/version consistency for HTML, `/assets/app.js?v=0.31.1` and CSS. Review the detailed plan in [IMPLEMENTATION_0.31.1.md](IMPLEMENTATION_0.31.1.md).
- **Release-host checks:** `./dev.sh check && ./dev.sh test && ./dev.sh release`, followed by Safari/Chrome iPhone tests behind the reverse: 20+ full reloads from Overview/Cache/Settings, initial config API failures, SSE reconnect, browser console errors, and response encoding/version consistency for HTML, `/assets/app.js?v=0.31.1` and CSS. Review the detailed plan in [HTTP3.md](HTTP3.md).
## 0.31.0 Axum handler `Send` correction (2026-10-10)
@@ -157,7 +165,7 @@
- Source delivery based on the supplied `RustProxy-0.30.1.zip` (no `.git`; remote `master` not verifiable). Implemented one per-browser scheduler, per-account/validated preferences, per-client SSE interval and fallback isolation, active-view Traffic/Cache, separated `/api/system-info`, compatible full `/api/status`, version/docs and English/Polish UI.
- **PASS:** source/version/module/security checks, SQLite schema-9/upgrade smoke, translations/docs, all bundled Node UI tests including deterministic refresh scheduler, Go agent unit tests, all admin JS syntax. Browser simulation checks 20 view transitions, no duplicated timers/in-flight requests, 0/manual, hidden tab, SSE failure/recovery, independent errors and logout.
- **NOT RUN:** Rust compilation, Rust tests and release build because `cargo`/`rustc`/`rustfmt` are missing (`./dev.sh test`/`check`/`fmt`/`release` exit 127 at toolchain gate). Real RustProxy API/SSE/traffic/regression tests and SSE bandwidth/CPU measurement remain unverified. The stress suite's Python HTTP test peer is **not** a RustProxy runtime test.
- See [full 0.31.0 implementation and verification report](IMPLEMENTATION_0.31.0.md). No database schema changes, git commit or tag.
- See [current verification report](TEST_REPORT.md). No database schema changes, git commit or tag.
## 0.30.1 sysinfo compiler-feature fix (2026-10-10)
-77
View File
@@ -1,77 +0,0 @@
# RustProxy 0.21.0 specification audit
## Status vocabulary
- **SOURCE-FIXED**: a concrete source defect found during this audit was changed and regression coverage was added. Rust execution is still pending in this workspace.
- **SOURCE-AUDITED**: the required implementation and source-level regression coverage are present; live/runtime acceptance is still pending where applicable.
- **DEFERRED**: intentionally moved out of the closed 0.21.0 feature scope.
- **RUNTIME OPEN**: source work is present, but the required compiler/live test cannot be completed in this workspace.
## Main TODO audit
| # | Requirement | Status | Audit result |
|---:|---|---|---|
| 1 | Transformed response stored in cache | SOURCE-FIXED | `Plan::restrict()` uses transformed `Content-Length`; transformed bytes are stored and reused. |
| 2 | Full request `max-stale` semantics | SOURCE-FIXED | Freshness and retention are separated; bounded/unbounded `max-stale` and revalidation flags have regressions. |
| 3 | Request `max-age=N` | SOURCE-FIXED | Only `max-age=0` forces validation unconditionally; other values are checked against cached age. |
| 4 | HEAD + transform + cache | SOURCE-FIXED / RUNTIME OPEN | Cacheable HEAD fills through internal GET; cache-disabled empty HEAD keeps origin representation headers. H1/H2/H3 live acceptance remains open. |
| 5 | Incomplete body / HEAD cache fill | SOURCE-FIXED | Dropped/error/trailer-incomplete fills are rejected and counted; a later complete fill can succeed. |
| 6 | Route cache tri-state | SOURCE-AUDITED | `Option<bool>` preserves inherit/true/false through model and UI; effective routing diagnostics are logged. |
| 7 | Parser-based JavaScript minifier | **DEFERRED** | 0.21.0 keeps the conservative scanner; a parser-backed replacement is deferred rather than changing the resolved dependency graph during release closure. |
| 8 | Parser-based CSS minifier | **DEFERRED** | 0.21.0 keeps the conservative scanner; a parser-backed replacement is deferred rather than changing the resolved dependency graph during release closure. |
| 9 | HTML minification | SOURCE-AUDITED | Optional/default-off conservative transform preserves whitespace and raw-text elements and removes only safe comments. |
| 10 | SVG optimization | SOURCE-AUDITED | Optional/default-off conservative comment cleanup preserves text/tspan whitespace and CDATA; it is not documented as a full optimizer. |
| 11 | JSON compacting | SOURCE-AUDITED | JSON is parsed and compact-serialized; malformed JSON fails open; transformed length/ETag/cache behavior is covered. |
| 12 | Formal transform pipeline | SOURCE-AUDITED | Proxy flow is origin -> decode -> transform -> canonical cache representation -> client encoding. |
| 13 | Content-Encoding | SOURCE-FIXED / RUNTIME OPEN | gzip/br/zstd decoding is implemented and transformed identity responses remove the `Accept-Encoding` Vary dimension only after canonicalization. Live all-codec acceptance remains open. |
| 14 | ETag / Last-Modified validators | SOURCE-FIXED | Origin validators are retained separately; client conditionals use the representation ETag; origin revalidation uses origin validators. |
| 15 | `Cache-Control: no-transform` | SOURCE-AUDITED | Origin policy is captured before response rules; final rules may add protection; cache storage remains allowed and origin representation/validators are retained. |
| 16 | `stale-if-error` | SOURCE-AUDITED | Separate metadata/window and fallback paths for origin errors/timeouts are present; revalidation flags block stale. |
| 17 | `stale-while-revalidate` | SOURCE-AUDITED | Immediate stale delivery, bounded background refresh and exact-variant single-flight are implemented. |
| 18 | `must-revalidate` | SOURCE-AUDITED | Storage remains allowed; stale serving through SWR/SIE/max-stale is blocked. |
| 19 | `proxy-revalidate` | SOURCE-AUDITED | Shared-cache stale behavior mirrors `must-revalidate`. |
| 20 | Cache metadata | SOURCE-AUDITED | TTL/stale windows/revalidation flags/origin validators persist in RAM and Cold Cache metadata. |
| 21 | Explicit cache object states | SOURCE-AUDITED | `Fresh`, `StaleButServable`, and `Expired` are explicit decisions rather than a single expiry comparison. |
| 22 | `s-maxage` precedence | SOURCE-AUDITED | Shared-cache parsing keeps `s-maxage` ahead of `max-age` and regression coverage exists. |
| 23 | `immutable` | SOURCE-FIXED | Duplicate/argument forms no longer invalidate Cache-Control; freshness is not extended and explicit client validation still works. |
| 24 | Cold Cache sharding | SOURCE-AUDITED | `00..ff` SHA-256 sharding, lazy directories, startup scan and no flat-cache migration are implemented. |
| 25 | Cold Cache GC | SOURCE-AUDITED | Async bounded GC uses configured interval, high/low watermarks and shared eviction accounting. |
| 26 | Cold read vs GC race | SOURCE-FIXED | Reads are pinned with entry generation; an old read guard cannot unpin a replacement using the same cache key. |
| 27 | Manual selective cleanup | SOURCE-AUDITED | Async cleanup reuses GC/eviction selection and reports removed objects/freed bytes. |
| 28 | Global transform settings | SOURCE-AUDITED | Global engine/type/extensions/body-limit settings exist. |
| 29 | Per-vhost transform opt-in | SOURCE-AUDITED | Per-vhost transform remains disabled by default even when the global engine is enabled. |
| 30 | Docker transform opt-in | SOURCE-AUDITED | Docker assignments have separate default-off transform configuration propagated to runtime vhosts. |
| 31 | Cache reasons | SOURCE-FIXED | `fill_incomplete`, `transform_length_mismatch`, `transform_error` and other bounded reasons are recorded. |
| 32 | Routing / transform diagnostics | SOURCE-FIXED | Access logs carry vhost, route, effective cache/transform state; the admin UI now has a dedicated Transform runtime dashboard, and transformed cache objects are marked in both Cache UI and `Cache-Status`. |
| 33 | Transform + cache integration | SOURCE-FIXED / RUNTIME OPEN | Source regression now exercises MISS -> transform -> store -> HIT for JS/CSS/HTML/SVG/JSON with ETag/length/body checks. Rust execution remains open. |
| 34 | Transform + compression | SOURCE-AUDITED / RUNTIME OPEN | Canonical identity cache representation and codec negotiation exist; live identity/gzip/br/zstd end-to-end acceptance remains open. |
| 35 | `Vary` | SOURCE-FIXED | Canonicalized identity drops only `Accept-Encoding`; custom Vary dimensions are retained and exact-variant SWR locking is preserved. |
| 36 | Query string | SOURCE-FIXED | `?v=1`, `?v=2` and `?F` remain distinct cache keys; `bypass_query` is a request policy and transform does not rewrite the key. |
| 37 | Conditional requests | SOURCE-FIXED / RUNTIME OPEN | Representation ETag vs origin validator behavior is covered at source-test level; live integration remains open. |
| 38 | Restart | SOURCE-FIXED / RUNTIME OPEN | Cold restart regression preserves transformed ETag, origin validators, stale metadata and `no-transform` original representation. |
| 39 | Rust compiler quality gate | **RUNTIME OPEN** | `cargo`, `rustc` and `rustfmt` are unavailable here; fmt/check/test/clippy/release build cannot be claimed. |
| 40 | Remaining tests | PARTIAL | Source/package/SQLite/upgrade/JS/JSON/OpenAPI/shell and Docker-agent gates can run here; Rust-backed suites remain open. |
| 41 | Packaging | SOURCE-AUDITED / RUNTIME OPEN | 0.21.0 source packaging is deterministic and the feature scope is closed; compiler/live release-host verification remains open. |
## Passive Backend Health addendum
The later Passive Backend Health specification is implemented as an opt-in policy:
- ordinary request failures remain observable when passive health is disabled;
- `passive_health.enabled` defaults to `false` for normal and Docker-generated vhosts;
- active TCP/HTTP health remains independent;
- connect/timeout/I/O/HTTP-5xx/slow-response classes have individual switches;
- passive `fall`/`rise` can override active thresholds;
- active and passive counters/state are separate;
- current unhealthy source/reason hands off correctly when one health domain recovers while the other remains unhealthy;
- Docker assignments persist and reapply the policy across discovery rebuilds;
- OpenAPI documents passive health for both Docker assignments and ordinary vhost `load_balancing`;
- Prometheus keeps unconditional observation counters and exposes passive-health aliases without making observation itself a health decision.
## Deferred follow-up and release-host verification
- Parser-backed JavaScript and CSS minifiers are deferred to a later development cycle; 0.21.0 intentionally retains the conservative implementations and the existing resolved dependency graph.
- The release host must run the complete Rust 1.88+ gate: `cargo fmt --check`, `cargo check --all-targets`, `cargo test --all-targets`, `cargo clippy --all-targets -- -D warnings`, and `cargo build --release`.
- The compiled binary must pass the required live H1/H2/H3/cache/compression/restart acceptance tests before deployment.
The 0.21.0 source is feature-complete and packaged as release source; runtime acceptance remains explicitly external to this toolchain-less workspace.
-106
View File
@@ -1,106 +0,0 @@
# RustProxy 0.22.0 specification audit
This audit maps the 0.22 cache work directly to the requested scope. The implementation status below is based on the current source tree. Rust unit/integration tests are present, but this packaging workspace has no `cargo`, `rustc` or `rustfmt`; compiler/runtime acceptance therefore remains open until the release-host gate runs.
## P0 - request collapsing / cache stampede protection
| Requirement | Status | Implementation |
|---|---|---|
| Per-cache-key lock / single-flight | IMPLEMENTED | `src/cache/collapse.rs` tracks one foreground fill flight per primary cache key. |
| First request fills, followers wait | IMPLEMENTED | Leader owns `FillLeaderGuard`; followers await the retained completion state. |
| Wait timeout | IMPLEMENTED | `cache.collapse_wait_timeout_ms`, validated to 10..60000 ms. |
| Controlled timeout fallback | IMPLEMENTED | `cache.collapse_timeout_fallback`: origin or stale-if-available. |
| Release on error/cancel | IMPLEMENTED | Guard `Drop` removes the flight and publishes completion even after purge cancellation. |
| SWR integration | IMPLEMENTED | Foreground collapsing is separate from and compatible with the existing background-revalidation dedupe. |
| Metrics | IMPLEMENTED | Collapsed fills, timeouts and current waiters are exposed in cache metrics. |
| Concurrent MISS/variant regressions | SOURCE-TESTED | Rust tests cover wait-until-publish, timeout/leader drop, cancelled-follower metric cleanup and retry after a different `Vary` representation wins the first fill. |
## P1 - Range / HTTP 206
| Requirement | Status | Implementation |
|---|---|---|
| `Range: bytes=...` | IMPLEMENTED | One byte range, including bounded, open-ended and suffix forms. |
| `206 Partial Content` | IMPLEMENTED | Local cache slicing returns 206 with exact body length. |
| `Content-Range` / `Accept-Ranges` | IMPLEMENTED | Emitted for satisfiable cached ranges; unsatisfied ranges return 416 with `bytes */len`. |
| `If-Range` | IMPLEMENTED | Strong ETag and HTTP-date handling; weak validators do not match. |
| One range without full response buffering | IMPLEMENTED | Cache HIT slices the already-complete cached object; origin Range responses remain streamed. |
| Multipart decision | DEFINED | Multipart/multiple/unsupported Range forms are passed through to origin and are not synthesized or cached as separate range objects. |
| Compression/transform policy | DEFINED | Origin Range path requests identity encoding and skips transformation/client compression; local range is cut from the canonical complete cached representation. |
| Cache HIT + Range without origin | IMPLEMENTED | Complete cached representation serves the requested single range directly. |
| Preserve validators/Age | IMPLEMENTED | Cached range response retains cache validators and cache Age; only range framing/length/status is rewritten. |
| Revalidation + Range | IMPLEMENTED | A stale complete object revalidated by origin 304 is then evaluated again for 206/416/If-Range behavior. |
| Range regressions | SOURCE-TESTED | Rust tests cover 206, suffix/open-ended ranges, 416, validators, `If-Range`, duplicate fields and multipart rejection. |
## P2 - extended stale-if-error policy
| Requirement | Status | Implementation |
|---|---|---|
| Timeout / connection / DNS / TLS classes | IMPLEMENTED | Independent booleans select eligible transport failure classes. |
| Optional HTTP status list | IMPLEMENTED | `stale_if_error_statuses`, max 32 unique 5xx values; default `500,502,503,504`. |
| Backward-compatible default | IMPLEMENTED | Default transport classes remain enabled and the previous 5xx set is preserved. |
| Regression coverage | SOURCE-TESTED | Policy test covers class/status inclusion and exclusion; existing RFC 5861 window tests remain. |
## P2 - proactive cache refresh
| Requirement | Status | Implementation |
|---|---|---|
| Threshold before expiry | IMPLEMENTED | `proactive_refresh_before_expiry_seconds`. |
| Limit fraction without traffic | IMPLEMENTED | Deterministic `proactive_refresh_percent` sampling, default disabled. |
| Reuse existing revalidation | IMPLEMENTED | Reuses validator-based `refresh_cache_once` and background-revalidation guard. |
| Avalanche protection | IMPLEMENTED | Per-key schedule dedupe plus `proactive_refresh_max_inflight`; a just-filled object can schedule refresh without another client request. |
| Regression coverage | SOURCE-TESTED | Guard test covers schedule dedupe and global concurrency limit. |
## P1 - selective Cache PURGE / BAN
### PURGE
| Requirement | Status | Implementation |
|---|---|---|
| Exact URL | IMPLEMENTED | Exact path/query or absolute URL selector. |
| Host + URL | IMPLEMENTED | Optional host/authority filter combines with exact/prefix/pattern selection. |
| URL prefix | IMPLEMENTED | Prefix matches the actual request target, including query text. |
| Cache key | IMPLEMENTED | Exact 64-hex primary cache key selector. |
| Cache tag | IMPLEMENTED | `Cache-Tag` values are parsed/stored and can be purged by tag. |
| Regex/pattern | IMPLEMENTED | Rust `regex` engine with 512-character input bound and 1 MiB compiled-size bound; every regex purge/BAN requires explicit broad-operation confirmation. |
| RAM + persistent/cold | IMPLEMENTED | Matching hot and Cold Cache objects are removed; cold file deletion is queued. |
| Query + real cache identity | IMPLEMENTED | Stored target and primary cache key are both available to invalidation. |
| `Vary` variants | IMPLEMENTED | Matching one representation removes the whole primary-key variant family. |
| Pre-PURGE fill race | IMPLEMENTED | Selective PURGE advances the publication generation, so any fill started before the operation cannot publish afterward; matching single-flight waiters are also released. |
### BAN
| Requirement | Status | Implementation |
|---|---|---|
| Rule without immediate full scan | IMPLEMENTED | BAN creation only records a rule. |
| Lookup enforcement | IMPLEMENTED | Hot/cold lookup rejects objects whose creation predates a matching active BAN. |
| GC | IMPLEMENTED | Lifecycle maintenance removes banned objects incrementally with a bounded batch. |
| Remove one rule | IMPLEMENTED | Authenticated `DELETE /api/cache/bans/{id}` and Cache-panel action. |
| Rule count/TTL bound | IMPLEMENTED | Maximum 1024 active rules; TTL is bounded to 1..604800 seconds. |
| New objects after BAN | IMPLEMENTED | BAN intentionally targets representations that existed before the rule; a fresh post-BAN fill may be stored. |
### API, panel and observability
| Requirement | Status | Implementation |
|---|---|---|
| Dedicated admin endpoint | IMPLEMENTED | `POST /api/cache/invalidate`. |
| PURGE/BAN type | IMPLEMENTED | `operation: purge|ban`. |
| exact/prefix/tag/regex/cache_key | IMPLEMENTED | Typed selector in API/OpenAPI and UI. |
| Vhost selector | IMPLEMENTED | Optional validated `vhost`; optional host filter is separate. |
| Explicit broad warning | IMPLEMENTED | Regex and prefix `/` require `confirm_wide=true`; UI shows a danger confirmation. |
| Result data | IMPLEMENTED | Operation, selector, scope inputs, affected count, optional BAN id, elapsed milliseconds and status. |
| Audit log | IMPLEMENTED | Operator and requested operation/scope/value are written through the existing administrative audit store. |
| Metrics | IMPLEMENTED | PURGE counts/entries, BAN create/remove/matches/active count plus collapsing/proactive metrics. |
| `Cache -> Purge / Ban` | IMPLEMENTED | Dedicated controls and active-BAN table in the Cache panel. |
| Per-object `Purge URL` | IMPLEMENTED | Cache object table exposes a targeted purge action. |
## Release gate
The 0.22.0 source scope is implemented and covered by Rust-independent package checks plus source-level Rust regression definitions. The following gate remains mandatory before treating the build as runtime-accepted:
```bash
bash dev.sh doctor
bash dev.sh check
bash dev.sh test
```
That gate must run on a host with the Rust toolchain and must be followed by the project's live H1/H2/H3/cache/compression/restart acceptance checks.
-43
View File
@@ -1,43 +0,0 @@
# RustProxy 0.22.1 specification audit
## Scope
This audit covers the 0.22.1 delta over 0.22.0: Cache administration UI polish, HTTP/2 RFC 8441 WebSocket, cleartext HTTP/2 prior knowledge, and identifiable/killable active tunnels. The 0.22.0 cache audit remains historical evidence for the retained cache implementation.
## Requirement matrix
| Requirement | Source implementation | Regression / verification |
|---|---|---|
| PURGE/BAN form in a modal beside **Purge selected scope** | `src/admin/web/index.html`, `cache-panel.js` | JS syntax + source-release checks |
| Active BAN list in a separate modal | `src/admin/web/index.html`, `cache-panel.js` | JS syntax + source-release checks |
| BAN/PURGE messages cannot dereference a missing status node | null-safe `operationMessage(...)` | source-release marker + UI syntax checks |
| RFC 8441 `CONNECT` + `:protocol=websocket` | `proxy/websocket.rs`, `proxy/mod.rs` | `tests/http2.rs` + module tests |
| RFC 8441 per-vhost opt-in beside RFC 9220 | `vhost.rs`, `config.rs`, `vhosts.js` | config/unit source regressions |
| Listener advertises `SETTINGS_ENABLE_CONNECT_PROTOCOL` only where required | `config::Snapshot::websocket_rfc8441_on`, `server.rs` | source/config regression; Hyper builder contract |
| H2 client -> H1 upstream WebSocket | H2 request DATA <-> upgraded H1 byte bridge in `bridge_h2_to_h1` | source regression + compiler/live release-host gate |
| H2 client -> explicit H2 upstream | preserve H2 CONNECT/Protocol and meter request/response bodies | source regression + compiler/live release-host gate |
| WebSocket cancellation / idle timeout | `TunnelSession` cancellation + H2 idle watcher + existing H1/H3 timeout loops | source review; live release-host gate |
| h2c explicit enable/disable | `Listener.h2c`, Settings UI and validation | `transport_config` / vhost tests |
| h2c prior knowledge | HTTP/2 connection-preface detection in `server.rs` | server prefix unit test + live release-host gate |
| HTTP/1.1 `Upgrade: h2c` | deliberately deferred; no fake `101` path is shipped | docs + source-release guard |
| Stable active tunnel ID | request/exchange ID stored in `TunnelSession` | source review/API contract |
| List client/host/upstream/protocol/duration/RX/TX | `TunnelInfo`, `GET /api/tunnels`, modal | OpenAPI + source-release checks |
| Kill one tunnel | `POST /api/tunnels/{id}/kill` cancellation | OpenAPI + source-release checks |
| Audit close reason | `tunnel.kill` audit with operator reason | admin source regression |
| Main dashboard H1/H2/H3 tunnel stats | metrics + overview card | JS/source checks + Prometheus contract |
| Overview card geometry is shared across both visual rows | one `stats-grid` containing primary + WebSocket/H2/H3/TLS cards | source-release check |
| Full Cache Target / Cache key + tags remain retrievable when cells ellipsize | keyboard/click copy cells in `cache-panel.js`, full value in `title` | JS/source-release check |
## Protocol notes
RFC 8441 Extended CONNECT is connection-scoped at the HTTP/2 SETTINGS level. RustProxy therefore advertises the capability on a listener connection only when at least one bound vhost enables RFC 8441; request routing still enforces the selected vhost's own flag.
`h2c` in 0.22.1 means HTTP/2 prior knowledge. The legacy HTTP/1.1 `Upgrade: h2c` bootstrap is not accepted because a correct implementation must carry the initiating H1 request into HTTP/2 stream 1 after the `101` response. The current Hyper integration does not expose a safe bootstrap path for that operation, so the release does not claim partial support.
## Accept-Ranges policy
RustProxy does not globally inject `Accept-Ranges: bytes` into ordinary responses. It preserves the origin header where representation semantics remain unchanged and synthesizes `Accept-Ranges: bytes` for a cached single-range response handled by RustProxy. Transform/compression paths remove incompatible range advertising.
## Release gate
The packaging workspace has no Rust toolchain. JavaScript/JSON/language/source/package checks can be executed here, but `cargo check --locked`, `cargo test --locked`, Clippy, release build and live H2/h2c/RFC8441 interoperability remain mandatory on a Rust-enabled release host before deployment.
-43
View File
@@ -1,43 +0,0 @@
# RustProxy 0.22.2 specification audit
## Scope
This audit covers the 0.22.2 delta over 0.22.1: replay-safe backend failover retry, explicit HTTP/2 upstream request handling, bounded per-backend request concurrency, richer upstream diagnostics, an expanded backend operations view, and public X.509 expiry metadata. The retained 0.22.1 RFC 8441/h2c-listener/tunnel work and 0.22.0 cache work remain historical evidence for retained functionality.
## Requirement matrix
| Requirement | Source implementation | Regression / verification |
|---|---|---|
| Retry only replay-safe methods | `proxy/mod.rs::retry_safe_method` permits GET/HEAD/OPTIONS only | proxy source regression; compiler gate open here |
| Never replay body-bearing writes | retry requires an exact empty request body; POST/PUT/PATCH fail the method gate | source regression + release checks |
| Retry another backend | `Pool::select_retry_wait(..., excluded)` excludes attempted IDs | `failover_selection_never_reuses_an_attempted_backend` |
| Bounded retry policy | `RetryPolicy.max_retries` validates 0..3; bounded 5xx list | balance policy regressions |
| Explicit H2 upstream | explicit `protocol=http2` uses HTTP/2 request versioning | proxy regression + real Hyper h2c upstream test source |
| Per-backend request concurrency | `PoolMember.max_concurrent_requests`, atomic full-exchange lease acquisition | `backend_concurrency_limit_queues_then_rejects_and_recovers` |
| Bounded capacity queue / 503 | `queue_timeout_ms` validates 0..60000; async selection waits in bounded intervals and returns `SelectionFailure::Capacity` | capacity timeout regression |
| Reject vs queue-timeout observability | `capacity_rejections_total` always counts final capacity rejection; `queue_timeouts_total` only counts an elapsed non-zero queue | `zero_capacity_queue_timeout_rejects_without_counting_a_queue_timeout` |
| No false TCP connection claim | limit is documented and named as concurrent proxy requests/exchanges, not `max_connections` | TODO/roadmap explicitly keep socket-count limit open |
| Failure classification | DNS, TLS, connect, response-header timeout, read/send timeout and generic I/O flow through `Lease::error_detail` | source contract checks + detailed failure regression |
| Last failure | backend view exposes class, bounded detail and timestamp | `detailed_backend_failures_are_exposed_without_losing_aggregate_counters` |
| Health ejection/recovery timestamps | health transitions store last ejected/recovered timestamps | source contract checks |
| Backend operational view | dashboard combines status, active/limit, requests/errors, latency, traffic, last failure and last recovery | JS/language/source checks |
| X.509 validity metadata | public leaf PEM parser extracts SAN, issuer, notBefore/notAfter and days remaining without reading keys | X.509 time + generated-certificate regression sources |
| Expiry warning | inventory reports `ok`, `warning` (<=30 days), `expired`, or `unknown` | source/OpenAPI/UI checks |
| Renewal status in certificate view | inventory flattens current renewal status, last check and last failure message | OpenAPI/UI/source checks |
| Full downstream H2 -> RustProxy -> upstream H2 interoperability | implementation path present | **OPEN:** live/compiler-enabled E2E required |
## Capacity semantics
`max_concurrent_requests` is a full proxy-exchange permit. The permit remains held for the response body/tunnel lifetime through the existing backend lease, preventing a streaming response from releasing capacity after headers only. A value of `0` preserves the historical unlimited behavior. `queue_timeout_ms=0` rejects immediately when no eligible backend has capacity; a positive value performs a bounded wait and then returns HTTP 503.
Pool readiness remains a health/availability signal rather than a transient-capacity signal. Backup members can receive traffic when eligible primary members are saturated. Sticky affinity is honored only when the matched backend has capacity; otherwise normal eligible selection can move the request to another backend.
A physical TCP connection/socket cap is deliberately not claimed in 0.22.2 because Hyper connection pooling requires a distinct transport-level mechanism.
## Certificate safety
Certificate inventory parsing opens only the configured public certificate PEM. Private keys are not opened by the metadata path. The parser is deliberately narrow: it extracts the leaf issuer, RFC 5280 validity values and DNS SAN coverage while rustls remains responsible for certificate/key validation in serving paths.
## Release gate
This packaging workspace has no `cargo`, `rustc` or `rustfmt`, and outbound DNS is unavailable, so Rust parsing/type checking, compilation, linking and execution cannot be claimed here. Rust-independent JavaScript/JSON/language/source/package/Go gates are run locally. A Rust-enabled release host must still run `./dev.sh check`, `./dev.sh test`, release build and live H1/H2/H3/H2-to-H2 acceptance before deployment.
-49
View File
@@ -1,49 +0,0 @@
# RustProxy 0.22.3 specification audit
## Scope
This audit covers the 0.22.3 delta over 0.22.2: downstream HTTP/1.1 `Upgrade: h2c`, dedicated certificate hot reload, and deployment of certificate/key pairs owned by an external local issuer such as system Certbot. Upstream mTLS/client-certificate support is intentionally deferred.
## Requirement matrix
| Requirement | Source implementation | Regression / verification |
|---|---|---|
| Preserve h2c prior knowledge | cleartext listeners still sniff the HTTP/2 connection preface before entering HTTP/1 | existing prior-knowledge regression plus source/release checks |
| Accept HTTP/1.1 `Upgrade: h2c` only when enabled | H1 service checks the listener `h2c` capability before returning `101` | `malformed_h2c_upgrade_is_rejected_before_switching_protocols` and source checks |
| Validate the Upgrade request | requires HTTP/1.1, `Connection: Upgrade, HTTP2-Settings`, exactly one `Upgrade: h2c`, one bounded base64url settings payload, Host/authority, no request body | `h2c_upgrade_validation_rejects_missing_settings_and_bodies` |
| Reconstruct H1 request as H2 stream 1 | bounded HPACK bootstrap encodes pseudo/header fields and injects a stream-1 HEADERS frame after client preface + initial SETTINGS | `http1_h2c_upgrade_bootstraps_request_as_http2_stream_one` source regression |
| Keep later streams on normal H2 engine | upgraded I/O is handed to the same Hyper HTTP/2 server builder used by native H2 | source contract review; runtime gate open here |
| Bound malformed bootstrap traffic | initial preface/SETTINGS/header block have explicit size/shape checks; invalid input closes/rejects rather than entering routing | source checks + malformed request test source |
| Dedicated certificate reload API | authenticated `POST /api/certificates/reload` invokes `App::reload_certificates` | admin regression + OpenAPI/source checks |
| Reload does not mutate desired config/revision | recompile uses the existing revision and only swaps the immutable effective snapshot | `certificate_reload_keeps_configuration_revision_stable` |
| Failed reload preserves active snapshot | compilation completes before `ArcSwap` publication; an error returns without `store` | source review; release-host negative runtime acceptance required |
| Public H1/H2/H3 new handshakes use refreshed cert | public TCP TLS reads the current snapshot per ClientHello; QUIC config is rebuilt in the published snapshot | source review; live release-host acceptance required |
| Existing sessions are not interrupted | reload swaps configuration state only; it does not cancel public connection/tunnel tokens | source review |
| External Certbot/manual pair can be deployed | `tls deploy --id ... --cert ... --key ...` reads local PEMs and calls authenticated `POST /api/certificates/deploy`; the service stages a generation and publishes the existing manual certificate id | CLI/API/source checks + external pair staging regression |
| Certbot `live/*.pem` symlinks work | local CLI canonicalizes the supplied path once, then opens the resolved regular file with bounded reads | source checks; live Certbot-path acceptance recommended |
| Private key safety | external key is size-bounded, must be a regular file and on Unix may not grant group/world permissions; staged files use private creation semantics | source review + existing TLS file safety contracts |
| Pair/SAN validation before publication | `stage_external_pair` calls the existing certificate/key parser and extracts DNS SANs | `external_pair_stages_generation_and_rolls_back_cleanly` |
| Atomic multi-vhost switch | existing certificate id keeps all vhost assignments; only its generation filenames/coverage change in one revision-CAS config update | source/API review |
| No dual owner with built-in ACME | `tls deploy` rejects entries carrying RustProxy `issuer` metadata | source contract checks |
| Roll back staged files if publication fails | failed config/API publication invokes `remove_external_pair` for the staged generation | source review + helper regression |
| Administration TLS caveat is explicit | reload/deploy report that startup-fixed administration TLS needs restart when it selects the affected inventory certificate | API/CLI/UI/OpenAPI checks |
| Upstream mTLS | intentionally not part of 0.22.3 | deferred by product decision |
## h2c bootstrap semantics
The Upgrade path is enabled only on a cleartext listener that already opts into `h2c`. It does not make ordinary HTTP listeners opportunistically negotiate HTTP/2. A valid H1 request receives `101 Switching Protocols`; the client then sends the normal HTTP/2 connection preface and its initial non-ACK SETTINGS frame. RustProxy inserts a synthetic body-free HEADERS frame for stream 1 and lets Hyper own subsequent H2 framing, flow control, stream lifecycle and graceful shutdown.
The bootstrap deliberately rejects H1 request bodies. This avoids replaying or buffering a body across the protocol switch and keeps the transition bounded. Prior-knowledge h2c remains unchanged.
## External certificate ownership model
There are two supported external workflows:
1. **Generation deploy (recommended for Certbot):** a local deploy hook calls `tls deploy`. The CLI reads Certbot's current `live/` targets and sends the bounded PEM pair to authenticated loopback `POST /api/certificates/deploy`; the running service validates and creates a new immutable pair in its private certificate directory, then switches an existing manual certificate id through the normal revisioned configuration API. Every vhost already assigned to that id moves together.
2. **In-place file replacement:** an operator replaces files already referenced by the desired configuration through another safe local mechanism, then calls `POST /api/certificates/reload` or `tls reload`. The desired configuration and revision do not change.
RustProxy-managed ACME ids are excluded from external deploy so the same certificate lineage cannot have two renewal owners. The API does not accept arbitrary host filesystem paths from a remote request.
## Release gate
This packaging workspace has no `cargo`, `rustc` or `rustfmt`. Rust parsing/type checking, compilation, linking and Rust test execution cannot be claimed here. Rust-independent JavaScript/JSON/language/source/package/Go gates are run locally. A Rust-enabled release host must still run `bash dev.sh doctor`, `bash dev.sh check`, `bash dev.sh test`, a release build, the raw H1 -> h2c Upgrade -> H2 stream-1 test, native h2c prior-knowledge, TLS/H2/H3 certificate hot reload, invalid-certificate rollback and normal H1/H2/H3 acceptance before deployment.
-48
View File
@@ -1,48 +0,0 @@
# RustProxy 0.23.0 specification audit
## Scope
0.23.0 builds on 0.22.3 and adds three control-plane/operations changes: scoped external certificate automation credentials, interactive all-vhost live logs, and a reorganized cache settings UX. Upstream mTLS/client-certificate support remains intentionally deferred.
## External certificate automation
- The administrator can create/rotate/revoke one scoped certificate credential from **Settings → Certificate API**.
- The credential is represented by a non-secret Key ID plus a Bearer Token. Only the token hash is persisted in SQLite schema v5; the cleartext token is returned only by the rotate operation.
- Token authentication is accepted only for `POST /api/certificates/deploy` and `POST /api/certificates/reload`.
- Token authentication does not grant `/api/config`, logs, metrics, certificate inventory, ACME or other administration access.
- Non-loopback token requests require HTTPS. Loopback HTTP remains available for local Certbot hooks.
- Normal administrator session + CSRF authentication continues to work on deploy/reload.
- Direct production CLI operations are `rust-proxy tls deploy ...` and `rust-proxy tls reload`; `dev.sh` is not required.
- Rotation, revoke, deploy and reload operations are audited. Credential status exposes Key ID, creation time and last-used time but never the hash/token.
## Live logs
- `rust-proxy --livelogs` creates a bounded local CLI session and opens an authenticated SSE stream from the running daemon.
- Request handling publishes complete access-log records to a bounded `tokio::sync::broadcast` channel with no await/block in the proxy path.
- The CLI supports structured interactive filters without regex syntax: host, vhost, backend, route, client, method, transport, cache result, exact/status class, path prefix, errors-only and minimum duration.
- `pause`, `resume`, `reset`, `filters`, `clear`, `detail` and `show <id>` provide interactive control and full JSON inspection of recent displayed entries.
- The detailed view includes upstream errors/status, cache reason/effective state, transform effective state, WebSocket bootstrap/phase/error, H3 reset code and byte counters.
- A lagging receiver gets an explicit SSE `gap` event with the number of entries discarded by the bounded channel.
- Session validity is periodically rechecked by the stream endpoint; logout/password-generation changes terminate access.
## Cache settings UX
The existing cache settings IDs and one atomic `cache-form` submission remain unchanged. The UI only groups them into four persistent DOM sub-pages:
1. General
2. Resilience
3. Cold cache
4. Static & diagnostics
No cache setting was removed or split into an independently saved document.
## Production memory hardening
- Inbound HTTP/2 connections now have a five-minute I/O inactivity ceiling. This prevents idle TLS/H2 sessions from accumulating indefinitely toward the process-wide connection semaphore while preserving active streams and normal connection reuse.
- Runtime metrics expose `http2_active_connections`, and Prometheus exports `rust_proxy_http2_active_connections`; the overview HTTP/2 tile shows the live count.
- Hot-cache accounting uses the response body backing allocation capacity before `BytesMut::freeze()`, not only the visible body length, so geometric buffer slack cannot silently bypass `cache.max_bytes`.
- Existing cold-cache demotion/purge allocator trimming remains in place on glibc Linux.
## Open release-host gate
The source includes Rust regression definitions for certificate-token scoping/hash-only persistence and structured live-log filtering/SSE parsing. A compiler-enabled release host must still run the normal `dev.sh check`, `dev.sh test`, release build, live external-token certificate deploy/reload and sustained live-log load acceptance before production deployment.
-31
View File
@@ -1,31 +0,0 @@
# RustProxy 0.23.1 specification audit
## Scope
0.23.1 is a patch release on top of 0.23.0. It adds live operational charts to the existing Cache and Transform workspaces without changing proxy-path behavior, configuration schemas or administration API payloads. The 0.23.0 Certificate API, Bearer-token scoping, `--livelogs` and four-page Cache settings contracts remain unchanged.
## Cache live activity chart
- The Cache workspace contains one full-width `<details>` chart and leaves it collapsed by default.
- The chart uses the existing authenticated `/api/cache` refresh path and the selected vhost scope; it does not add a new endpoint or proxy-path query.
- HIT, MISS and BYPASS cumulative counters are converted in the browser to per-second rates from consecutive samples.
- The chart keeps at most 60 in-memory samples, reuses the dashboard SVG grid/line styling and resets history when the selected scope changes or the administration session is reset.
- The existing 3-second Live toggle remains the refresh cadence; disabling Live stops automatic chart updates together with the rest of the Cache workspace.
## Transform live activity chart
- The Transform workspace contains one matching full-width `<details>` chart and leaves it collapsed by default.
- The chart uses the existing authenticated `/api/transform` payload and selected vhost scope.
- Applied transformations, transformed cache HITs, skipped/fail-open responses and errors are converted from cumulative counters to per-second rates between samples.
- The chart keeps at most 60 in-memory samples, uses the existing dashboard chart visual language and resets history on scope/session changes.
- No transformation body, request body, cache object body or unbounded request-derived label is loaded into the chart.
## Retained 0.23.0 contracts
- Certificate API credentials remain Key ID + Bearer Token, hash-only at rest and restricted to deploy/reload.
- `rust-proxy --livelogs` remains a bounded structured all-vhost live stream with explicit lag reporting.
- Cache settings remain one atomic document split visually into General, Resilience, Cold cache and Static & diagnostics pages.
## Open release-host gate
Rust-independent source, language, JavaScript, packaging and release-smoke checks are part of this source delivery. A compiler-enabled release host must still run `./dev.sh check`, `./dev.sh test`, a release build and live browser acceptance with sustained Cache/Transform traffic before production deployment.
-27
View File
@@ -1,27 +0,0 @@
# RustProxy 0.23.2 specification audit
## Scope
0.23.2 is a patch release on top of 0.23.1. It extends the Cache/Transformation live charts with an operator-selectable bounded history window and fixes local CLI access when the administration listener uses HTTPS. No proxy-path, SQLite schema or administration API payload change is introduced.
## Cache / Transformation chart history
- Both live charts remain default-collapsed and full-width when expanded.
- Existing cumulative counters are still converted to per-second rates with the existing 3-second refresh.
- Each workspace offers 60, 120, 300 and 600 retained samples, corresponding to roughly 3, 6, 15 and 30 minutes.
- The selected limit is stored in browser local storage separately for Cache and Transformation.
- Reducing the limit trims the in-memory arrays immediately; increasing it grows only with future samples. The maximum remains bounded at 600 samples per series.
- Scope/session resets still clear the corresponding chart history.
## Local CLI / live-log HTTPS
- The root cause of the 0.23.0/0.23.1 `rust-proxy --livelogs` reconnect loop is the CLI's previous hard-coded `http://{admin}/api/live-logs` URI. A TLS-enabled administration socket intentionally returns HTTP 308 on plaintext requests, and the CLI treated that expected redirect as a stream failure.
- The CLI now probes the loopback `/healthz` endpoint before creating authenticated API requests. HTTP success selects `http`; the administration listener's 308 with an HTTPS `Location` selects `https`.
- The probe carries no CLI session cookie. After scheme selection, the live SSE stream and retained-history API use the same selected transport.
- HTTPS certificate verification is pinned to leaf certificates read from the private `<data-dir>/certs` directory. This supports the app-managed self-signed default certificate without disabling TLS verification globally or relying on public DNS/SAN routing to reach the loopback listener.
- The same local client is used by `config get/reload/apply` and `tls reload/deploy`, eliminating the same hard-coded-HTTP failure mode there.
- The local administration address remains required to be loopback.
## Verification status
Rust-independent source, JavaScript/JSON, shell, Go-agent and release-smoke checks are run in the packaging workspace. `cargo`, `rustc` and `rustfmt` are unavailable here, so compiler/type tests, a live TLS handshake against the built binary and sustained browser/live-log runtime acceptance remain release-host gates.
-25
View File
@@ -1,25 +0,0 @@
# RustProxy 0.23.3 specification audit
## Scope
0.23.3 is a compatibility patch on top of 0.23.2. It adds an opt-in, per-vhost legacy WebSocket mode intended for older appliance and embedded WebSocket servers. Cache/Transformation history controls and HTTPS-aware local CLI/live logs from 0.23.2 are retained unchanged. No SQLite schema change is introduced.
## Legacy WebSocket mode
- `websocket_legacy_support` defaults to false and is valid only when `websockets` is enabled.
- The vhost editor exposes **Enable WebSocket legacy support** and persists the field in the normal vhost document/API.
- The switch is compiled into the immutable per-vhost frontend snapshot; there is no global compatibility downgrade.
- For WebSocket exchanges only, legacy mode forces the upstream hop to classic HTTP/1.1, including when the normal backend protocol setting prefers HTTP/2. HTTP/2 or HTTP/3 clients are bridged to the HTTP/1.1 upstream path.
- Before the upstream handshake, the WebSocket upstream `Host` is forced to the configured backend authority and `Origin` is rewritten to `<upstream-scheme>://<backend-authority>`. This intentionally overrides Preserve Host for the legacy WebSocket handshake only, so old same-origin checks see the appliance address they expect.
- Legacy upstream `101 Switching Protocols` validation tolerates historical syntax differences: missing `Connection: Upgrade`, repeated `Upgrade: websocket`, transfer-encoding metadata and zero Content-Length. Non-zero Content-Length is still rejected.
- The `Sec-WebSocket-Accept` proof remains exact and any selected `Sec-WebSocket-Protocol` must still have been offered by the client.
- Legacy response framing headers are removed before the downstream response.
- Ordinary HTTP requests and WebSocket vhosts without the switch use the existing strict path unchanged.
## iLO4 evidence / intended acceptance
The supplied browser HAR shows repeated attempts to `wss://ilo.lab.danio298.com/wss/ircport` with a standard RFC 6455 request (`Upgrade: websocket`, `Connection: Upgrade`, `Origin: https://ilo.lab.danio298.com`) and no HTTP response (`status: 0`). That is consistent with an upstream handshake being closed or rejected before a browser-visible status is returned. The new mode specifically addresses the two proxy-sensitive compatibility points identified in source review: upstream Origin/Host mismatch and strict rejection of legacy `101` response framing. Live iLO4 acceptance remains a release-host/runtime gate.
## Verification status
Rust-independent source, JavaScript/JSON, language-pack, shell, Go-agent and release-smoke checks are run in the packaging workspace. `cargo`, `rustc` and `rustfmt` are unavailable here, so compiler/type tests, release binary build and live iLO4/legacy-WebSocket runtime acceptance remain release-host gates.
-40
View File
@@ -1,40 +0,0 @@
# RustProxy 0.24.0 specification audit
## Scope
0.24.0 changes downstream HTTP/2 connection retirement and extracts runtime system statistics into platform backends. Existing H1/H2/H3 routing, RFC 8441 WebSocket support, legacy WebSocket compatibility (including iLO4-class origins), cache and proxy behavior are retained unless stated below.
## HTTP/2 lifecycle
- `http2.idle_timeout_seconds` defaults to 300 seconds and remains independent from maximum lifetime.
- `http2.max_connection_lifetime_seconds` defaults to `0`, which disables the absolute lifetime limit.
- `http2.drain_timeout_seconds` defaults to 10 seconds and bounds graceful draining after shutdown begins.
- Idle qualification checks both recent H2 transport activity and the active stream count immediately before retirement.
- Idle or lifetime retirement calls Hyper graceful shutdown, which emits HTTP/2 GOAWAY, refuses newer streams and continues polling already accepted streams.
- Active response bodies, including RFC 8441 Extended CONNECT/WebSocket bodies, hold a stream lease until end-of-stream/error/drop.
- After accepted streams finish, the H2 connection future completes and the TCP transport is dropped normally. A drain timeout forces transport closure if a stream does not finish.
- Process shutdown uses the same GOAWAY/drain path; its drain is additionally capped by the process grace period.
Source regressions cover idle GOAWAY, active-stream idle suppression, lifetime GOAWAY plus forced drain timeout, and successful active-stream completion during lifetime drain.
## Settings organization
The former top-level **HTTP/2 connections** Settings tab is now **HTTP connections** with HTTP/2 and HTTP/3 sub-tabs. HTTP/2 lifecycle settings still save through the live `http2` section. HTTP/3 remains stored per TLS listener and requires restart, but its QUIC enablement, advertisement lifetime, connection/stream limits and idle timeout are edited from the HTTP/3 sub-tab. The Listeners editor retains bind/TLS/H2/h2c controls and preserves each listener's existing H3 configuration when unrelated listener fields are saved.
Legacy `/settings?tab=http2` links are normalized to `/settings?tab=connections`.
## System statistics/platform runtime
`system_stats` is the shared sampling module. Linux retains the existing `linux_procfs` source and the same names, units and `/proc`/cgroup semantics for CPU, process CPU, memory, RSS breakdown, FDs, threads, load, network, process I/O and cgroup memory.
FreeBSD, OpenBSD and NetBSD use the BSD backend and native OS mechanisms where available: `sysctl`, `getrusage`, `ps` and `netstat`. A metric that cannot be obtained with the same meaning is left `None`; values are not synthesized. Other Unix targets report the platform and leave unavailable system metrics unsupported/null.
The status payload now includes `platform`; Proxy Process displays it. Prometheus also exposes `rust_proxy_platform_info{platform="..."} 1`.
## Retained WebSocket compatibility contracts
The 0.23.3 legacy WebSocket mode remains opt-in per vhost. It continues to use HTTP/1.1 upstream, align Host and Origin with the configured backend, tolerate the documented legacy 101 framing, and keep Sec-WebSocket-Accept/subprotocol proof strict. This retained path is separate from RFC 8441 H2 WebSocket Extended CONNECT, whose accepted stream participates in the new H2 drain accounting.
## Release verification
Source-side Python/OpenAPI/language/JavaScript/shell/release-package checks are run in this workspace. The workspace does not provide `cargo`, `rustc` or `rustfmt`, so `cargo fmt --all`, `cargo check --locked --all-targets`, `cargo test --locked --all-targets`, release build acceptance, and native BSD compiler/runtime execution remain release-host gates. No passing Rust compiler result is claimed here.
-48
View File
@@ -1,48 +0,0 @@
# RustProxy 0.24.1 specification audit
## Scope
0.24.1 is a maintenance release over 0.24.0. It keeps the existing HTTP/2 graceful shutdown and HTTP/3 behavior, replaces duplicated BSD system-statistics collection with shared library APIs, restores network interface/RX/TX diagnostics through a dedicated cross-platform network API plus library fallback, exposes resolved runtime installation metadata to authenticated administrators, moves Logging into Settings, fixes compiler issues reported from a Rust-enabled release host, and removes artificial hostname-count ceilings from certificate/routing hostname lists.
## Shared system statistics API
| Requirement | Source implementation | Status |
|---|---|---|
| Avoid separate metric implementations per BSD family | `src/system_stats/mod.rs` collects portable host values through `systemstat`, network inventory/counters through `sysinfo::Networks` merged with a `systemstat` library supplement/fallback, and process values through `metrics-process`; the old `src/system_stats/bsd.rs` command/parser backend is removed | implemented in source |
| Linux compatibility | `src/system_stats/linux.rs::augment` keeps `/proc/stat`, `/proc/meminfo`, `/proc/self/status`, `/proc/self/io` and cgroup-v2 details after common sampling | implemented in source; compiler/runtime gate open |
| FreeBSD | `systemstat` host API + `sysinfo::Networks` + `metrics-process`; network inventory/counters are supplemented by `systemstat` when useful | source-integrated; native build/runtime gate open |
| OpenBSD | `systemstat` host API plus available `metrics-process` values; `sysinfo` network inventory may be unavailable so the shared adapter uses the merged `systemstat` source | source-integrated; native gate open |
| NetBSD | `systemstat` host API + `sysinfo::Networks` + shared `metrics-process` dummy fallback; unavailable process values remain null | source-integrated; native gate open |
| DragonFly BSD | `systemstat` host API plus shared `metrics-process` dummy fallback; network inventory merges `sysinfo` when available with the `systemstat` source | source-integrated; native gate open |
| macOS / Windows | same common host API and the process API on their supported targets | source-integrated; native gate open |
| Do not invent unsupported values | all portable fields are `Option`; failed/unsupported library calls leave values `None` | implemented in source |
The portable view now includes CPU, logical CPU count, RAM, swap, system uptime, load average, network totals/rates/per-interface values, process CPU, RSS, virtual memory, descriptor count/limit, process start time and thread count where the selected libraries provide them. Linux-only detailed RSS categories, process data/swap, process I/O and cgroup limits remain explicit additive fields rather than guessed equivalents on BSD.
The release-host `systemstat` compile failure in uptime conversion is fixed by using the duration's `as_secs()` API. The stale `std::future::Future` import in `src/server.rs` is also removed.
### Network inventory regression fix
The first 0.24.1 library adapter built the interface list only from `systemstat::network_stats()` successes. A backend error for a discovered interface therefore removed that interface entirely, which could yield `0 interfaces` and null aggregate RX/TX on both Linux and BSD installations. The corrected adapter uses `sysinfo::Networks::new_with_refreshed_list()` as the primary interface/counter API, supplements it with `systemstat`, merges counters by interface name, filters only loopback (`lo`, `loN`), and never shells out. This also preserves interfaces when either library returns only a partial inventory. The old 64-interface truncation is removed so discovered non-loopback interfaces are not silently hidden. Rate calculation remains based on deltas between the existing low-frequency snapshots.
## Hostname/certificate cardinality
RustProxy no longer imposes fixed item-count ceilings on certificate SNI hostname bindings, generated/imported certificate hostname checks, ACME domain requests, vhost/redirect/404 hostname lists, administration allowed hosts, host ACL hostname lists or Docker aliases. These lists remain constrained by DNS syntax and length rules plus the existing bounded control-plane inputs/global configuration size. Certificate PEM/key size limits, certificate-chain bounds and abandoned-upload storage bounds are deliberately retained as resource-safety controls. External ACME CAs/providers may enforce their own certificate/SAN quotas.
## Runtime installation information
`/api/status` now includes a `runtime` object with `platform`, `statistics_source`, `data_directory` and `database_path`. It is not added to the anonymous route allowlist; the existing administrator guard protects the status endpoint. These values describe installation/runtime state and contain no credentials or certificate/private-key contents.
The browser displays the returned values and performs no path/platform detection. For local CLI operations, an explicit `--data-dir` always wins. When using the default local path without a database, installed-data discovery checks platform conventions: `/var/lib/rustproxy` first on Linux/non-BSD and `/var/db/rustproxy` first on BSD, then the alternate convention.
## Settings navigation
The standalone Logging primary-navigation panel is removed. Its existing form is mounted as **Settings -> Logging**. `/logging` remains accepted as a legacy URL and is canonicalized to `/settings?tab=logging`; logging persistence and sink behavior are unchanged.
## Retained 0.24.0 transport behavior
The 0.24.0 H2 connection state/stream lease tracking, idle recheck, GOAWAY, bounded drain, maximum connection lifetime and accepted-stream completion path are unchanged by this patch. HTTP/3/QUIC remains under **Settings -> HTTP connections -> HTTP/3** and retains listener-scoped restart semantics.
## Open release gates
This workspace has no `cargo`, `rustc` or `rustfmt`. A Rust-enabled release-host attempt exposed the uptime API mismatch and unused import; both are fixed in source, but this workspace cannot rerun compilation to prove that no later compiler error remains. A release host must generate/preserve the resolver-produced lockfile and rerun `cargo fmt --all`, `cargo check --locked --all-targets`, `cargo test --locked --all-targets`, a release build, native BSD sampling tests and the final H2/H3/WebSocket/proxy-statistics regression before production deployment.
-56
View File
@@ -1,56 +0,0 @@
# RustProxy 0.24.3 specification audit
## Scope
0.24.3 is a focused runtime/UI maintenance release over 0.24.2. It reduces long-running Linux/glibc RSS retention in the transform-heavy path, keeps HTTP/2/H2C and HTTP/3 enablement in the listener editor while making both tuning pages listener-scoped, makes OS diagnostics render only values actually supplied by the current platform, expands external certificate automation, and improves overview/network observability.
## Memory-retention changes
| Requirement | Source implementation | Status |
|---|---|---|
| Bound glibc arena proliferation for managed systemd installs | `deploy/rustproxy.service` and `scripts/deploy/deploy.py::service_unit` set `MALLOC_ARENA_MAX=2`; Debian packaging moved to the external packaging repository in 0.27.0, and `/etc/rustproxy/environment` remains the administrator override | implemented in source |
| Avoid a duplicate identity transform buffer | `src/transform/mod.rs::decode` returns `Cow<[u8]>`; identity input is borrowed and compressed input owns only the decoded buffer | implemented in source |
| Reduce JS/CSS minifier peak allocations | `src/transform/js.rs` and `src/transform/css.rs` iterate with `Peekable<Chars>` rather than collecting `Vec<char>`, and return the completed `String` allocation with `into_bytes()` | implemented in source |
| Release temporary transform buffers earlier | successful changed transforms drop the decoded/collected origin buffers before final response construction | implemented in source |
| Give large glibc transform churn a bounded RSS-release opportunity | transformed responses that release at least 256 KiB can call `malloc_trim(0)` no more than once per 30 seconds on Linux GNU targets | implemented in source; sustained-RSS acceptance remains open |
| Keep non-glibc targets portable | allocator-specific tuning/trim code is guarded for Linux GNU; other targets keep the portable allocation path | implemented in source |
`MALLOC_ARENA_MAX=2` is an allocator-tuning default, not an application memory limit. It is useful for this multi-threaded long-running proxy because it limits how many glibc malloc arenas can retain freed pages. It does not replace cache limits, cgroup limits or sustained-load testing.
The cache already calls `malloc_trim(0)` after bulk demotion/purge paths. 0.24.3 does not change cache policy semantics: in particular `cold_on_age` remains an explicit operator choice and defaults to false. This release reduces allocator/transformation retention without silently changing cache freshness or tiering behavior.
## HTTP/2 and HTTP/3 settings placement
Both downstream protocol families now use the named-listener ownership model. `binding.http2` / `binding.h2c` enable HTTP/2, `binding.http2_settings` stores its lifecycle limits, and `binding.http3` is either `null` (disabled) or an object containing QUIC limits. In 0.24.3:
- **Settings -> Listeners** owns the H2, H2C and **Enable HTTP/3 over QUIC (UDP)** switches.
- An H2/H2C listener links to **Settings -> HTTP connections -> HTTP/2**; an enabled H3 listener links to the HTTP/3 page.
- The HTTP/2 page edits idle, absolute-lifetime and graceful-drain limits independently for every H2/H2C listener. The HTTP/3 page continues to edit advertisement, connection, bidirectional/unidirectional stream and idle-timeout limits per enabled TLS listener.
- The legacy top-level `http2` object remains a fallback when an older listener has no explicit `http2_settings`, so existing configurations keep their effective behavior until saved through the new UI.
- Listener/H2/H3 tuning changes remain restart-required. Each tuning page is blocked while the listener form has unsaved changes, preventing stale copies of the listener section from overwriting each other.
## Dynamic diagnostics
The dashboard card is named **Process**. Optional process/system fields are backed by nullable statistics. The browser hides rows and history charts when a field is unavailable on the current platform instead of substituting `unknown`, `N/A` or a synthetic zero. Linux continues to expose its `/proc`/cgroup details; other supported systems show only values supplied by the shared collectors.
The same rule is applied to optional system CPU/RAM/swap/load/uptime and network/latency chart rows where the data source can be absent. This is a presentation change only; the authenticated status API continues to represent unsupported metrics as `null`.
## Certificate automation extensions
| Requirement | Source implementation | Status |
|---|---|---|
| Reload one manual/file-backed certificate after an external file replacement | `POST /api/certificates/{certificate_id}/reload` calls `App::reload_certificate`; ACME-managed IDs are rejected and the configuration revision stays unchanged | implemented in source |
| Permit the same reload through Certificate API authorization | `certificate_api_operation` recognizes the validated per-ID reload path and preserves the external HTTPS/loopback restriction | implemented in source |
| Support multiple independently managed automation tokens | SQLite schema v6 introduces `certificate_api_credentials`, bounded to 32 rows, with Key ID, hash, comment and usage metadata | implemented in source |
| Individual and bulk revocation | `DELETE /api/certificate-api/{key_id}` revokes one; `DELETE /api/certificate-api` revokes all | implemented in source |
| Never persist cleartext automation tokens | PUT returns the clear token once; only SHA-256 hashes and non-secret metadata/comments are stored | implemented in source |
## Dashboard extensions
- **Core State** is replaced by a same-footprint live connection chart for open connections and active exchanges, with backend count retained as chart context. **Uptime** moves to **Process**, directly below process start.
- **Payload transferred** keeps the aggregate value and adds compact download (`bytes_out`) / upload (`bytes_in`) counters.
- Each network-interface chart has a **Hide** action. Hidden interface names are stored only in browser `localStorage` (`rp-hidden-network-interfaces`) and can be restored from the dashboard; no server configuration is changed.
## Open release gates
This workspace does not provide `cargo`, `rustc` or `rustfmt`. Static source, language-pack, JavaScript, shell, Python, deployment/archive and Go-agent gates can run here, but a Rust-enabled release host must still run formatting, `cargo check --locked --all-targets`, `cargo test --locked --all-targets`, a release build, live H2/H3/WebSocket regression and a sustained 10-12 hour cache/transform RSS comparison before production promotion.
-21
View File
@@ -1,21 +0,0 @@
# RustProxy 0.24.4 specification audit
## Scope
0.24.4 is a focused Linux memory-retention maintenance release over 0.24.3. Production diagnostics showed that a large share of anonymous RSS could be backed by 2 MiB Transparent Huge Pages while the live RustProxy cache residency was much smaller. A host-wide switch from THP `always` to `madvise` removed those huge-page mappings after restart. 0.24.4 internalizes that mitigation for RustProxy only.
## Source/spec matrix
| Requirement | Source contract | Status |
|---|---|---|
| Do not require a host-wide THP policy change | `src/main.rs` calls `prctl(PR_SET_THP_DISABLE, 1, 0, 0, 0)` on Linux | implemented in source |
| Apply the policy before runtime worker heaps are created | `disable_transparent_huge_pages()` runs before `tokio::runtime::Builder::new_multi_thread()` | implemented in source |
| Keep non-Linux platforms unchanged | the THP call is guarded by `cfg(target_os = "linux")`; other platforms use a no-op helper | implemented in source |
| Avoid turning kernel capability into a startup dependency | a failed `prctl` emits a warning and startup continues | implemented in source |
| Retain the 0.24.3 glibc mitigation | managed systemd units still set `MALLOC_ARENA_MAX=2`; existing bounded `malloc_trim(0)` paths remain | retained |
## Runtime acceptance
Source inspection cannot establish steady-state RSS. On a Linux release host, return `/sys/kernel/mm/transparent_hugepage/enabled` to the host's normal policy, restart the 0.24.4 binary, and verify `/proc/<pid>/status` reports `THP_enabled: 0`. Under representative sustained traffic, `/proc/<pid>/smaps_rollup` should keep `AnonHugePages` at zero or negligible levels without changing the global THP setting. Record RSS, anonymous RSS, cache resident/transient bytes and request rate for the same 10-12 hour acceptance window used by 0.24.3.
Compiler/type/runtime acceptance remains open in this source workspace because the Rust toolchain is unavailable.
-59
View File
@@ -1,59 +0,0 @@
# RustProxy 0.24.5 specification audit
## Scope
0.24.5 fixes HTTP/3 request-body framing when an H3 request has no declared `Content-Length`, carries no DATA, and is forwarded to an HTTP/1.1 upstream. Before the fix, the H3 receive body remains length-unknown until its stream end is polled; Hyper can therefore serialize body-capable HTTP/1.1 methods such as `DELETE` with `Transfer-Encoding: chunked`. Some upstream servers reject chunked request bodies even when the logical body is empty.
## Source/spec matrix
| Requirement | Source contract | Status |
|---|---|---|
| Preserve HTTP method and URI | normalization changes only the request body; method/URI rewriting remains in the existing proxy path | implemented in source |
| Detect an actually empty H3 body | for H3 -> H1 with no declared/exact length, `normalize_h3_h1_unknown_body()` polls until EOF or the first semantically significant frame | implemented in source |
| Represent empty H3 body as zero length | EOF replaces the streaming wrapper with `Body::empty()`, giving Hyper an exact zero-length size hint | implemented in source |
| Preserve non-empty bodies | the first non-empty DATA/trailer frame is wrapped in `ReplayBody`; the remaining tail stays streaming | implemented in source |
| Do not change H2 upstream framing | normalization exits before polling unless the selected upstream version is HTTP/1.1 | implemented in source |
| Do not preconsume declared bodies | requests carrying `Content-Length` are left unchanged | implemented in source |
| Avoid DELETE-only/Proxmox-only behavior | normalization applies generically to body-capable H3 methods forwarded to H1; GET/HEAD/CONNECT retain Hyper's existing implicit-empty behavior | implemented in source |
## Regression coverage added
`src/proxy/mod.rs` contains focused async tests for:
- empty length-unknown H3 `POST`, `PUT`, `PATCH` and `DELETE` to HTTP/1.1 becoming exact zero-length bodies;
- non-empty length-unknown H3 `DELETE` preserving its payload through `ReplayBody`;
- H3 request bodies remaining untouched for HTTP/2 upstreams;
- H1/H2 downstream request bodies remaining untouched by the H3-specific normalization;
- declared-length H3 `DELETE` bodies not being preconsumed.
## Runtime acceptance
Compiler/type/runtime acceptance remains open in this source workspace because the Rust toolchain is unavailable. On the test host, run `./dev.sh check` and `./dev.sh test`, then verify the full H1/H2/H3 method matrix. For the original Proxmox case, compare `/var/log/pveproxy/access.log` and a raw/mock HTTP/1.1 upstream. The H3 bodyless DELETE must arrive as `DELETE` with the same URI and without chunked request framing; a zero-length representation such as `Content-Length: 0` is expected for HTTP/1.1.
## Cache RAM hardening included in the same 0.24.5 source release
The cache keeps the existing HOT RAM -> COLD disk lifecycle. The changes are admission/accounting safeguards rather than a COLD format redesign:
- `cache.max_bytes` is a hard published-HOT charge based on the body backing allocation retained by `Bytes`, directly owned entry metadata and a conservative charge for HOT hash/LRU/variant indexes. `hot_logical_bytes` remains the visible payload total; `hot_allocated_bytes`/`resident_bytes` are the charged allocation view, not a duplicate RSS measurement.
- response fill uses a `Vec<u8>` whose actual `capacity()` is reserved before growth and transferred zero-copy into `Bytes`; `max_object_bytes` is checked before every chunk append;
- active fills reserve from `transient_max_bytes` and compare HOT + active fill against HOT capacity plus a small allocation-rounding headroom (about 1/64 HOT, capped at 1 MiB and further bounded for small caches); LRU entries are removed before further fill allocation where possible;
- one admission can remove at most `hot_eviction_max_objects`; if sufficient space cannot be made safely, the response continues uncached;
- ordinary HOT -> COLD demotion removes the entry from HOT and moves the same `Entry` into `ColdDraft`. The payload is not cloned. A transient lease follows the draft until persistence completes; the persisted `ColdEntry` has paths/metadata and no `Bytes` body;
- the COLD writer queue remains count-bounded and is additionally capped by `cold_queue_max_bytes`; COLD reads/promotions and revalidation reads reserve transient bytes before reading full bodies;
- global `cache.enabled=false` cannot be overridden by a vhost `enabled=true`;
- HOT/object settings no longer have the old artificial 1 GiB UI/source ceiling. Safety comes from the configured byte budgets rather than an arbitrary maximum.
Default memory values are 256 MiB HOT, 16 MiB maximum object, 64 MiB transient and 32 MiB COLD queue, with 4096 HOT entries and a 64-object LRU admission bound. The administration panel can calculate conservative/recommended/performance settings from the lower of detected host RAM and a finite cgroup memory maximum. For example, the recommended profile on an effective 2 GiB VM selects approximately 400 MiB HOT, 96 MiB transient, 16 MiB maximum object, 48 MiB COLD queue, 6400 entries and a 64-object LRU admission limit. Presets are disabled below 256 MiB effective RAM. The panel also checks the configured budgets against current host/cgroup memory headroom and warns without blocking manual configuration. COLD disk capacity is intentionally not changed by RAM presets.
Monitoring exposes HOT objects, HOT logical bytes, HOT allocated bytes, HOT limit, transient current/limit, COLD queue current/limit, COLD read/write activity, evicted/expired/rejected counts and existing process RSS/RssAnon/VmData/VmSize diagnostics. Cache RAM gauges and process RSS are overlapping views and must not be summed.
### Cache runtime acceptance still required
The source-level/offline gates verify configuration, UI/API wiring and static contracts. Because this workspace has no Rust toolchain, compile/type/runtime acceptance must still be performed on the test host. In addition to `./dev.sh check` and `./dev.sh test`, exercise at least:
- HOT 8 MiB under sustained traffic and verify HOT allocated stays bounded and RSS plateaus;
- HOT 2 GiB with a deliberately high preset and verify admission remains bounded;
- `cold_after_seconds=5`, verify HOT allocated falls after demotion and transient/COLD queue bytes return to zero after writes drain;
- an object larger than `max_object_bytes`, verify the client receives the full streamed response while the cache fill aborts before full buffering;
- concurrent COLD hits/writes under a small transient limit, verify memory-pressure rejection/pass-through rather than unbounded RAM growth;
- all four global/vhost enabled combinations, especially global OFF + vhost ON => effective OFF.
-57
View File
@@ -1,57 +0,0 @@
# RustProxy 0.24.6 memory and cache-preset audit
## Scope
0.24.6 persists cache-preset intent and hardens temporary/background memory use. The cache numeric limits remain the runtime source of truth; `memory_preset` and `cold_preset` are metadata stored in the same revisioned JSON configuration row in SQLite so the administration UI can restore the selected state after reload.
This audit is source/static in the packaging workspace because Rust/Cargo and heap profilers are unavailable here. It adds compiler/runtime tests to the source tree and an offline memory-contract gate, but sustained RSS/heap acceptance must still run on a compiler-enabled release host.
## Cache preset persistence
| Area | Contract |
|---|---|
| Cache model | `Settings.memory_preset` accepts conservative/recommended/performance; `Settings.cold_preset` accepts recommended; old configurations deserialize both as `None`. |
| SQLite | `Store::replace` serializes the complete cache settings into `configuration.document`; the database reopen test now verifies both preset values survive close/reopen. |
| Cache identity | Preset metadata is excluded from `cache_revision()`, so changing only the selected label cannot purge HOT/COLD data. Effective numeric/policy changes still rotate cache identity normally. |
| UI | Settings restores the selected button/badge from configuration. Manual edits clear the affected preset metadata. |
| Recommended COLD | Enables COLD, idle/LRU/shutdown demotion, disables age demotion, sets 300 s idle, 3600 s max-hot, 60 s GC and 256 max-delete, and chooses a queue around half transient RAM. Disk path, disk bytes and entry/object limits are preserved. |
## Transformation memory review
| Path | 0.24.6 behavior |
|---|---|
| Concurrent transforms | A process-wide 128 MiB semaphore budget accounts for collected input plus expected decoded/output buffers. Transform is fail-open with `memory_pressure` when the budget is busy. |
| Response aggregation | `http_body_util::Limited` enforces `max_body_bytes` even when upstream `Content-Length` is false; over-limit aggregation becomes a body error instead of allocating without bound. |
| Decompression | Fixed 16 KiB reads and exact bounded reserve checks reject expansion beyond `max_body_bytes`. |
| JSON | `IgnoredAny` validates syntax without materializing `serde_json::Value`; whitespace compaction is one pass and preserves number lexemes/key order. |
| HTML | Raw element detection compares tag slices case-insensitively instead of allocating/lowercasing a tag name for every element. |
| CSS | Tracks the last output character instead of repeatedly scanning the full output string. |
| Large-buffer release | Existing glibc `malloc_trim(0)` throttling remains after successful large transforms. |
## Background-worker memory/task review
| Facility | Bound/ownership reviewed |
|---|---|
| Cache COLD writer | Existing object-count and byte budgets remain; delete fallback is limited to 2 detached tasks and otherwise applies bounded synchronous backpressure. |
| COLD cleanup | Manual/automatic cleanup share a single-flight semaphore. Candidate selection uses a bounded heap sized to the requested deletion limit; delete path metadata is flushed per 32-entry batch. |
| Stale-while-revalidate | Distinct background revalidations are capped at 8, limiting retained cached `Bytes` when objects are concurrently evicted/replaced. |
| Proactive refresh | Sleeping scheduled-key tasks are capped at 64..256 depending on configured refresh concurrency; actual refreshes keep the existing `proactive_refresh_max_inflight` bound. |
| Logging | History and broadcast buffers are fixed at 4096; sink delivery uses a bounded channel and `try_send`. |
| Diagnostics | One cancellation-aware sampler loop; each blocking sample is awaited before the next iteration. |
| DNS | Shared semaphore of 16 blocking resolver workers plus timeout; a timed-out OS resolver still owns its permit until the worker exits. |
| Backend health | One supervisor with a `JoinSet`, maximum 16 probes, at most one claimed probe per member, abort/drain on shutdown. |
| TCP/UDP streams | Per-stream connection/session semaphore, bounded UDP channel, `JoinSet` ownership and cancellation. |
| HTTP listeners | Accepted connections are gated by `max_connections`; connection tasks are owned by the listener `JoinSet`. App-owned tunnel/background tasks use `TaskTracker`. |
| Configuration writes | Validation/publication work is bounded by 8 update slots and serialized publication lock. |
| ACME | Issuance/renewal is single-flight through a semaphore; the scheduler is one cancellation-aware loop. |
| Docker discovery / update checker | One cancellation-aware periodic loop each; no per-tick detached task accumulation. |
| Admin live streams | Viewer permits and bounded broadcast/snapshot semantics prevent per-client unbounded queues. |
| Tunnel registry | Stores weak references and removes dead sessions on drop/list cleanup. |
## Added regression coverage
Rust tests cover preset serialization/database reopen, preset-only cache identity stability, transform memory-budget math, bounded decompression, false Content-Length aggregation, proactive pending-task limits, stale revalidation limits and single-flight COLD cleanup. `tools/checks/memory_contracts.py` independently verifies the source-level bounds above when Cargo is unavailable and is part of `./dev.sh test`.
## Release-host acceptance gate
Run `./dev.sh fmt`, `./dev.sh check` and `./dev.sh test`, then exercise representative transform/cache traffic while recording RSS/heap. Acceptance target: RSS reaches a plateau after warm-up; transform concurrency never exceeds the 128 MiB temporary budget estimate; COLD cleanup and revalidation counts remain bounded; no monotonically growing task/map/queue is observed after traffic stops.
-77
View File
@@ -1,77 +0,0 @@
# RustProxy 0.24.7 transform resource-control audit
## Scope
0.24.7 makes the transformation resource envelope configurable without changing transformation semantics. The global transform configuration now persists a UI resource preset, a process-wide temporary-memory ceiling, and a process-wide concurrent-transform ceiling. Existing configurations inherit the former 128 MiB memory behavior and a 32-attempt concurrency ceiling through serde defaults.
This workspace still has no Rust/Cargo toolchain, so compiler/type/runtime acceptance and sustained RSS profiling remain release-host gates.
## Resource controls
| Control | Default | Range | Behavior |
|---|---:|---:|---|
| `memory_budget_bytes` | 128 MiB | 1 MiB..2 GiB | Process-wide ceiling for estimated origin/decode/output buffers. It is not pre-allocated. If a new transform would exceed the current configured ceiling, the response fails open unchanged with `memory_pressure`. |
| `max_body_bytes` | 2 MiB | 1 KiB..32 MiB | Hard aggregation/decompression ceiling for one transformable response. |
| `max_inflight` | 32 | 1..1024 | Process-wide active transform-attempt ceiling. New work fails open unchanged with `concurrency_pressure` instead of waiting. |
The memory limiter uses atomic byte reservations so a live configuration change takes effect for new attempts without resizing or replacing a semaphore. Existing reservations release their exact charge on drop.
## Presets
Presets persist only administrator intent; numeric values are authoritative. They do not change enabled transform kinds or the extension allow-list.
| Preset | Memory | Max body | Max concurrent |
|---|---:|---:|---:|
| RAM saver | 32 MiB | 1 MiB | 8 |
| Recommended | 128 MiB | 2 MiB | 32 |
| Performance | 512 MiB | 8 MiB | 64 |
| Uber performance | 1 GiB | 32 MiB | 128 |
Manual edits of any of the three resource fields clear the preset label to Custom. Settings stores the selected preset in the same revisioned SQLite configuration document and restores it after reopen.
## Cache compatibility
Global transform representation/eligibility settings are now included in cache compatibility identity. Changing enabled transform kinds, extensions or `max_body_bytes` rotates the identity so cached representations cannot outlive a semantic policy change.
Resource-only fields (`preset`, `memory_budget_bytes`, `max_inflight`) are normalized out of the compatibility hash. Capacity tuning therefore does not purge HOT/COLD objects by itself.
## UI safeguards
The Transform settings page exposes all three resource controls and four presets. It warns when the configured temporary-memory budget is smaller than the worst-case estimate for one maximum-size compressed response, and when the budget exceeds 50% of detected effective host/cgroup RAM. Warnings do not block an intentional expert configuration.
## Runtime dashboard accounting
The same 0.24.7 release now exposes live resource accounting without changing any
resource limit. `GET /api/status` reports a `transform_resources` view and a
`memory_accounting` view. The Overview Process block shows process RSS, HOT cache RAM,
cache transient RAM, the transform reservation/budget, active transform slots and the
remaining non-cache RSS. The transform reservation is an intentionally conservative
limiter charge, so it is shown as an overlapping diagnostic rather than added to RSS.
`non_cache_rss_bytes` is `process RSS - accounted HOT/transient cache RAM` with saturating
subtraction. It therefore still includes transform buffers, connections, TLS, logging,
Tokio/runtime state, allocator overhead and any other untracked process allocations. This
is preferable to inventing per-subsystem heap precision that the allocator does not
provide.
The cache runtime view now also exposes `cold_limit_bytes` and `cold_limit_entries`; the
overview cache block shows current COLD disk bytes and objects against those limits. COLD
is explicitly disk occupancy and is never added to process RAM.
The dedicated Transform dashboard also shows current process-wide reserved bytes versus
`memory_budget_bytes` and active transform attempts versus `max_inflight`.
## Regression coverage
- transform settings validation and serde preset round-trip;
- local atomic memory reservation obeys a runtime byte ceiling;
- local atomic inflight reservation obeys a runtime concurrency ceiling;
- SQLite close/reopen preserves transform preset, memory budget and concurrency limit;
- cache identity ignores resource-only changes but rotates for transform representation/eligibility changes;
- `tools/checks/memory_contracts.py` verifies the new dynamic memory/concurrency bounds without Rust;
- source/release smoke checks verify the Transform UI, OpenAPI fields and release identity.
- source/release smoke checks reject `anyhow::Context` on the boxed bounded-body collection error and require explicit `anyhow::Error` mapping.
## Release-host acceptance gate
Run `./dev.sh fmt`, `./dev.sh check` and `./dev.sh test`, then stress each preset with identity and compressed JS/CSS/HTML/SVG/JSON bodies. Acceptance target: RSS plateaus after warm-up, active transform work never exceeds the configured byte/concurrency ceilings, `memory_pressure`/`concurrency_pressure` appear only when expected, and cache contents remain valid after changing resource-only settings.
-23
View File
@@ -1,23 +0,0 @@
# RustProxy 0.24.8 dashboard cache-memory audit
## Scope
0.24.8 is a dashboard-focused patch. It does not change cache admission, HOT/COLD ownership, persistence, memory limits or cache protocol behavior. It exposes the existing runtime counters more clearly in the overview and reduces permanent explanatory text.
## Overview cache charts
The Cache RAM card now retains the HIT/MISS/BYPASS activity chart and adds/extends resource histories:
- **HOT RAM usage / limit** uses the cache resident/charged byte history and scales it against `hot_limit_bytes` when a finite limit is available.
- **Transient RAM usage / limit** tracks total transient bytes and overlays fill bytes, the byte-bounded COLD writer queue and active COLD read/write I/O buffers on one shared scale. These series are diagnostic lines, not additive stacked accounting.
- **COLD storage usage / limit** tracks persistent COLD bytes against `cold_limit_bytes`. This is disk/storage occupancy and is deliberately not included in process RAM accounting.
All histories keep the existing bounded 60-sample browser-side window and are populated from the already-authenticated `/api/status` payload. No new endpoint or cache-runtime allocation is introduced.
## Memory-accounting explanation
The translated `memoryAccountingHint` text remains unchanged in meaning but is no longer displayed as a permanent paragraph. It is shown on demand from an accessible information control in the Process heading.
## Verification
Offline source checks assert the new chart IDs/history arrays, the retained accounting hint, the information control, release identity and language keys. JavaScript syntax, language JSON parsing, source-package checks and release smoke checks remain part of the standard release verification. Compiler/browser/live-load acceptance remains a release-host gate where those runtimes are available.
-122
View File
@@ -1,122 +0,0 @@
# RustProxy 0.25.0 specification audit
This document maps every numbered section of the RustProxy 0.25 plan to the implementation. It distinguishes **implemented source** from **release-host runtime verification**. The workspace used for this package has no Rust/Cargo toolchain, so no compiler or live H1/H2/H3 result is claimed here.
Status legend:
- **IMPLEMENTED** - the required source/configuration/UI/documentation exists and Rust-independent contracts pass.
- **RELEASE GATE** - implementation exists, but compiler/runtime acceptance still has to run on a Rust-enabled host.
## Sections 1-22
| Spec section | Status | Implementation / evidence |
|---|---|---|
| 1. Goal of 0.25 | IMPLEMENTED | Web Server is integrated as a source in the existing proxy request pipeline rather than as a second HTTP server. |
| 2. Target concept | IMPLEMENTED | `CompiledSource` selects Reverse Proxy or Web Server; Reverse Proxy remains the default for old/missing `source` fields. |
| 3. Separate Web Service | IMPLEMENTED | `Config.web_services` contains reusable `web_server::Service` objects independent of vhosts. |
| 4. Attach service to vhost | IMPLEMENTED | Vhost/location `Source::WebServer` stores only `web_service_id`; filesystem settings remain in the service. |
| 5. Full usage flow | IMPLEMENTED | Dashboard workflow is Web Server -> create service -> Vhosts -> select Web Server -> select service. |
| 6. Reuse one service | IMPLEMENTED | One Web Service can be referenced by multiple vhosts/locations; a dedicated configuration test covers two vhosts using the same service. |
| 7. Locations integration | IMPLEMENTED | Existing location matcher selects static/proxy source; one vhost can mix `/` static and `/api` reverse proxy without a second URL router. |
| 8. First-stage configuration | IMPLEMENTED | Model, validation, normal revisioned storage, section API and CRUD UI are present. No SQLite schema migration is needed because the existing serialized configuration document stores `web_services`. |
| 9. Static File Server | IMPLEMENTED | GET/HEAD, expanded built-in + per-service MIME, Content-Length, streamed bodies, directories, ordered static/PHP index files, slash redirect, listing, precompressed sidecars and forced-download policy are implemented. |
| 10. Directory Listing | IMPLEMENTED | Service-level switch defaults OFF; listing escapes HTML, percent-encodes names/URLs, distinguishes directories and does not reveal filesystem paths. Each request uses a fresh directory description so refreshes do not inherit an exhausted `readdir` offset; generated listings send `Cache-Control: no-store`. Disabled listing returns a normal error that remains eligible for the existing custom-error pipeline. |
| 11. Index | IMPLEMENTED | The ordered index list is optional (0..16). Every requested directory checks configured names in order and serves the first regular file; only that directory's empty/no-match result falls through to listing/error policy. |
| 12. HTTP validators | IMPLEMENTED | Metadata-derived ETag/Last-Modified and If-None-Match/If-Modified-Since -> 304 are implemented without hashing full files. |
| 13. Range | IMPLEMENTED | Single byte Range, If-Range, Accept-Ranges, Content-Range, 206 and 416 are implemented for the selected static/precompressed representation; multipart ranges remain intentionally outside the current scope. |
| 14. Filesystem security | IMPLEMENTED | Routing rejects malformed/ambiguous encoding and dot segments. Default Unix access uses `openat` + `O_NOFOLLOW`; Linux can optionally follow a symlink only when each opened descriptor resolves inside the canonical root. PHP-FPM requires the deny policy. |
| 15. Existing RustProxy architecture | IMPLEMENTED | Host/location routing, ACL, Basic Auth/Tinyauth, response/security headers, custom error pages, compression and H1/H2/H3 transport stay in the existing outer pipeline. |
| 16. Cache | IMPLEMENTED | No Web Server object cache was added. Static files use filesystem/OS page cache and HTTP validators/ranges. Web Services support a default `Cache-Control` plus ordered `ext:`/`mime:` rules; existing vhost/location response-header rules apply later. Reverse Proxy cache remains unchanged. |
| 17. Tests | IMPLEMENTED / RELEASE GATE | Rust unit/integration source plus Rust-independent source/UI/docs contracts cover static behavior, configuration, security and integration ordering. Rust tests cannot be executed here without Cargo; live H1/H2/H3 acceptance is still required. |
| 18. Future extensibility | IMPLEMENTED | `try_files`, Web Service rewrites, bounded `.htaccess` migration compatibility and bounded FastCGI/PHP-FPM were added without changing the Web Service / source-selection architecture. Remaining follow-up is limited to named locations, optional `PATH_INFO`, multipart Range, full Apache compatibility and non-Unix confinement. |
| 19. Documentation cleanup | IMPLEMENTED | `docs/README.md` is canonical; functional, admin/operations, development/testing, technical, audits and historical records are separated. Duplicated historical release notes were removed from README, completed roadmap history was moved to `docs/ROADMAP_HISTORY.md`, and `/docs` embeds the Markdown set in Reader. |
| 20. CHANGELOG | IMPLEMENTED | Root CHANGELOG is now operator-facing. Pre-cleanup compiler/refactor/test detail is preserved separately in `docs/CHANGELOG_TECHNICAL_ARCHIVE.md`. |
| 21. README / Docs / Reader | IMPLEMENTED | README now describes the current 0.25 release instead of duplicating release history; ROADMAP contains active/future work with completed milestones archived. The docs index, Reader/API split, local Markdown links and current version references are checked automatically. Migration history includes 0.24.10 -> 0.25.0. |
| 22. Definition of Done | IMPLEMENTED / RELEASE GATE | All source/documentation items below are implemented; compiler/runtime and live transport acceptance remain the only unexecuted release gate in this workspace. |
## Definition of Done - item by item
- [x] Dedicated **Web Server** administration section exists.
- [x] Web Service can be created.
- [x] Document root is configurable.
- [x] Ordered index-file list is configurable.
- [x] Directory listing can be enabled/disabled and defaults to disabled.
- [x] Web Service can be edited and deleted; referenced deletion is rejected by validation and blocked in the UI.
- [x] Existing vhost/source configuration offers `Reverse Proxy` / `Web Server`.
- [x] Selecting Web Server offers an existing Web Service reference.
- [x] Vhost does not store document root/index/listing copies.
- [x] One Web Service can be reused by more than one vhost.
- [x] Web Server uses the existing locations system.
- [x] One vhost can mix Web Server and Reverse Proxy locations.
- [x] GET implemented.
- [x] HEAD implemented.
- [x] MIME implemented.
- [x] Web Service MIME overrides and fallback MIME implemented.
- [x] Optional static `Cache-Control` default and ordered extension/MIME cache rules implemented without a second object cache; vhost/location header rules can override them.
- [x] Fixed Web Service response headers implemented with framing/transport protections.
- [x] `try_files` and ordered internal/redirect rewrite rules implemented inside the selected Web Service.
- [x] Bounded `.htaccess` compatibility implemented for common rewrite/front-controller, index/listing, MIME, header, Expires and PHP AddHandler migration rules; security-sensitive unsupported handlers/access scopes fail closed.
- [x] Web Service application presets added as one-shot normal-configuration generators (Static, SPA, PHP generic, WordPress, Drupal, Laravel, Symfony, Joomla, PrestaShop, Downloads) with optional explicit hardening for common exploit probes/sensitive paths and fingerprint-header removal.
- [x] Precompressed `.br`/`.gz` sidecars and configured forced-download extensions implemented.
- [x] FastCGI/PHP-FPM implemented with bounded concurrency/timeouts, Unix socket or loopback TCP endpoints, advanced request/header/stderr limits, Authorization forwarding control, validated custom FastCGI/PHP_VALUE parameters and no PHP source fallback.
- [x] Content-Length implemented.
- [x] File streaming implemented without loading the whole file into RAM.
- [x] Index lookup implemented.
- [x] Directory listing implemented.
- [x] Repeated directory-listing refreshes use fresh directory offsets; nested directories independently prefer index files before listing.
- [x] Directory slash redirect implemented with query preservation.
- [x] ETag implemented from metadata.
- [x] Last-Modified implemented.
- [x] 304 implemented.
- [x] Single Range implemented.
- [x] 206 implemented.
- [x] 416 implemented.
- [x] Document-root confinement implemented.
- [x] Symlink handling cannot escape the root; deny is the default and Linux can optionally follow only targets verified inside the canonical root. PHP-FPM requires deny.
- [x] Web Server uses the existing RustProxy request/response pipeline.
- [x] No second URL router was added.
- [x] No second error-page subsystem was added.
- [x] No second object-cache subsystem was added.
- [~] Existing vhost features remain on the shared code path and source contracts verify ordering; full compiler/runtime regression execution is a release-host gate.
- [x] Appropriate Rust tests and Rust-independent contract checks are present.
- [x] Configuration is stored/read through the existing revisioned configuration store; old configs default to Reverse Proxy.
- [x] Documentation has been reorganized.
- [x] Docs and Reader are wired to the same embedded Markdown set and API is separated into its own tab.
- [x] CHANGELOG has been cleaned of developer-only churn; detailed old material is archived as technical history.
- [x] README and ROADMAP describe the actual 0.25 state, historical release/roadmap material is separated into CHANGELOG/audits/archives, and the remaining release-host gate is explicit.
## Reported Axum E0277 correction
The reported `.fallback(forward)` error was caused by passing `&Request<Body>` into the asynchronous Web Server function. That shared borrow survived across `.await`; because the body is not `Sync`, the `forward()` future no longer satisfied Axum 0.8's `Send` handler requirement.
Static dispatch now clones only the request metadata needed by the local filesystem source (`Method`, `HeaderMap`, `Uri`) and moves those owned values into `web_server::serve`. The Web Server no longer holds a shared `Request<Body>` reference across an await. The Rust-independent contract explicitly rejects the former call shape.
## Reported Reader build-artifact correction
The Reader initially embedded a generated `OUT_DIR/docs-content.json`. A clean/incremental build could therefore reach `include_bytes!` without that artifact being present. Reader documents are now direct `include_str!` compiler inputs from the project and `docs/` tree; the JSON response is serialized once from those embedded strings. The Reader no longer depends on a generated documentation file in `target/`, and `docs_check.py` verifies that every top-level Markdown document is embedded.
## Verification performed in this workspace
- `python3 tools/checks/check_source.py` - PASS.
- `python3 tools/checks/release_smoke.py` - PASS.
- `python3 tools/checks/memory_contracts.py` - PASS.
- `python3 tools/checks/web_server_contracts.py` - PASS.
- `python3 tools/checks/docs_check.py` - PASS.
- `python3 tools/checks/langs_check.py` - PASS.
- `node --check` for app, Settings, Docs Reader, Web Server and vhost administration JavaScript - PASS.
- OpenAPI and both language JSON files parse - PASS.
- `bash -n dev.sh` - PASS.
- `go test ./...` in `docker-agent` - PASS.
- `./dev.sh test` - all Rust-independent phases PASS, then exits because `cargo` is not installed in this workspace.
## Remaining release-host gate
Before a production release, run on a host with the stable Rust toolchain:
```bash
./dev.sh fmt
./dev.sh check
./dev.sh test
```
Then run live static-file acceptance over HTTP/1.1, HTTP/2 and HTTP/3, including ACL/auth/custom-error/header behavior and a large streamed file. This is verification work, not a missing source feature.
-131
View File
@@ -1,131 +0,0 @@
# RustProxy 0.25.0 pre-production security audit
Date: 2026-10-04
This audit is a source/configuration/dependency review of the current 0.25.0 development tree. It is **not** a completed penetration test because the audit environment has no Rust/Cargo toolchain and therefore cannot build or start the RustProxy binary. Production acceptance remains blocked until the runtime gates at the end of this document are executed on an isolated release host.
## Result
No source-reviewed issue remains that is known to provide an unauthenticated direct path to RustProxy process takeover. Additional runtime/fuzzing gates remain open, so this is not production approval. Concrete pre-production issues found during this review were fixed in the same 0.25.0 development tree:
1. The Debian one-line bootstrap and reference unit exposed plain-HTTP administration on `0.0.0.0:9090` by default. They now default to `127.0.0.1:9090`; plain remote HTTP requires explicit opt-in and Administration HTTPS remains the preferred remote mode.
2. Recursive source packaging could include a local `scripts/.../.env`. Exact `.env` files are now excluded from release archives while `.env.example` templates remain included.
3. FastCGI `HTTP_PROXY`/httpoxy protection depended on optional Web Service hardening. Client `Proxy` is now unconditionally discarded before FastCGI parameter generation.
4. PHP-FPM CGI output could supply downstream framing headers. `Content-Length`, transfer/connection framing, TE/Trailer and `Proxy-*` transport headers are now discarded so RustProxy owns downstream framing.
5. `.htaccess` compatibility could read a symlinked control file during Apply. `.htaccess` files are now opened with no-follow semantics, symlink control files are rejected and recursive discovery verifies that scanned directories remain below the canonical Web Service root.
6. PHP-FPM CGI `Status:` accepted informational codes such as `101`. FastCGI output is now restricted to final HTTP status codes `200..=599`, preventing an external PHP worker from trying to drive protocol-switch semantics through the normal HTTP response path.
7. The Debian unit used `UMask=0027`; it is now `0077`, matching the reference installer unit so newly created process files default to owner-only access.
8. Web Service CMS presets previously changed only the preset description until the separate Apply button was pressed. Selecting a non-Custom preset now immediately materializes the ordinary form fields, and a behavioral JS regression test verifies that WordPress index/`try_files`/PHP/cache values survive the Save serialization path.
The reference/generated systemd units were also aligned with the stronger Debian sandbox baseline (`PrivateDevices`, `ProtectKernelLogs`, namespace/realtime restrictions and native syscall architecture).
## Tests executed
Passed in the audit environment:
- `python3 tools/checks/check_source.py`
- `python3 tools/checks/web_server_contracts.py`
- `python3 tools/checks/security_contracts.py`
- `python3 tools/checks/security_dependency_contracts.py` (offline minimum-version gates for RustSec items reviewed on 2026-10-04)
- `node tools/checks/web_service_ui_test.mjs` (preset selection + Save serialization regression)
- `python3 tools/checks/memory_contracts.py`
- `python3 tools/checks/docs_check.py`
- `python3 tools/checks/langs_check.py`
- `python3 tools/checks/release_smoke.py`
- `node --check` for all administration JavaScript
- `bash -n` for managed shell scripts
- `go test ./...` for `docker-agent`
- deterministic source archive generation plus assertion that no exact `.env` is packaged
- `systemd-analyze security --offline=yes` for the reference and Debian units: exposure score 3.3 (`OK`) in the audit environment; both now use `UMask=0077`
- targeted source review of administration authentication/CSRF/Host-Origin handling, backend SSRF/address policy, HTTP framing limits, Web Server path confinement, `.htaccess`, PHP-FPM, backup/secret storage, command execution and unsafe/FFI surfaces
- hard-coded secret scan for common private-key/token patterns: no production secret found; documentation placeholders only
- manual review of the locked major network/security dependencies against current RustSec advisories, plus an offline regression deny-list for the reviewed 2026 malicious-crate campaign
`./dev.sh test` runs every non-Rust phase successfully and then stops because `cargo/rustc` are unavailable. `tools/audit/admin_audit.py` was also attempted, but no compiled/running RustProxy administration listener exists in this workspace, so its live probes are **blocked/unexecuted**, not passed.
## Dependency review
Important locked versions reviewed manually:
- `rustls 0.23.45` - includes the fix for RUSTSEC-2026-0285 (`>=0.23.45`)
- `rustls-webpki 0.103.15` - above the RUSTSEC-2026-0104 fix (`>=0.103.13`)
- `quinn-proto 0.11.18` - above RUSTSEC-2026-0037 (`>=0.11.14`) and RUSTSEC-2026-0185 (`>=0.11.15`) fixes
- `h2 0.4.19` - above the RUSTSEC-2026-0258 fix (`>=0.4.16`)
- `bytes 1.12.1` - above the RUSTSEC-2026-0007 fix (`>=1.11.1`)
- `time 0.3.55` - above the RUSTSEC-2026-0009 fix (`>=0.3.47`)
- `sqlx 0.8.6` - above the RUSTSEC-2024-0363 fix (`>=0.8.1`)
- `tungstenite 0.29.0` - above the historical RUSTSEC-2023-0065 fix (`>=0.20.1`)
- `ring 0.17.14` - above the RUSTSEC-2025-0009 fix (`>=0.17.12`)
This manual review is not a substitute for `cargo audit`/OSV scanning of the complete resolved graph on the release host.
## Security boundaries reviewed
### Administration
- default bind is loopback; persisted non-loopback configuration requires Administration TLS;
- private routes require a browser session; mutations additionally require exact Origin, `X-RustProxy-Request: 1` and a session-bound CSRF token;
- cookies are HttpOnly/SameSite=Strict and become Secure on Administration HTTPS;
- passwords use Argon2id and password work has bounded workers;
- unknown users receive equivalent password-verification work;
- certificate API credentials are scoped and require HTTPS for non-loopback requests.
Residual operational risks: browser sessions can be configured for 7-30 days, there is no 2FA/role model yet, and the global login throttle can be used for availability lockout if a remote admin listener is exposed. Keep administration private or tightly firewalled and use the shortest session lifetime for production.
### Backend SSRF / routing
- backend origins are HTTP/HTTPS only;
- metadata/link-local/special targets remain blocked; private/loopback addresses require explicit opt-in;
- local RustProxy listener targeting has an additional explicit risk gate;
- DNS answers are validated before use and a validated address is used without a second DNS lookup.
Production requirement: enable `tls_verify=true` for HTTPS origins whenever the backend network is not fully trusted. Verification-off remains a deliberate compatibility default and permits an on-path attacker to impersonate that origin.
### Web Server / filesystem
- decoded and encoded traversal/separator ambiguity is rejected;
- Unix file access uses document-root descriptors and `openat`; default symlink policy uses `O_NOFOLLOW`;
- only regular files/directories are served; PHP source extensions are denied when PHP-FPM is unavailable;
- `.ht*` files are protected; `.htaccess` parsing is bounded, control files are opened/rejected with no-follow symlink handling, and unsupported access/handler directives fail closed;
- directory listings re-open entries through the same confinement rules.
### PHP-FPM
FastCGI is restricted to Unix sockets or loopback TCP, script selection is rooted in the Web Service document root and symlink-following PHP mode is rejected. Client `Proxy` cannot create CGI `HTTP_PROXY`; CGI response framing cannot override RustProxy downstream framing, and informational/101 CGI statuses are rejected. Operator-defined FastCGI parameters cannot override core CGI/HTTP fields or `PHP_VALUE`/`PHP_ADMIN_VALUE`; php.ini overrides use a separate validated `PHP_VALUE` map. Optional request-body, response-header and stderr limits bound PHP connector resource usage.
The most important production boundary is external to RustProxy: run each PHP-FPM pool as a dedicated non-root, non-`rustproxy` user. A compromised CMS/plugin must not be able to read `/var/lib/rustproxy`, SQLite, the Secret Store, TLS keys or backups. Separate writable upload directories from executable PHP source where the application permits it.
### Build / update supply chain
The one-line Debian bootstrap uses HTTPS but clones the moving repository head and then invokes repository deployment scripts with root privileges. That is convenient for development but is not a strong production provenance boundary: compromise of the repository/default branch at install time would become host compromise. Production installation should use a reviewed immutable commit/tag or the audited source archive, verify its SHA-256 out of band, build with `--locked` as an unprivileged builder, and only then run the reviewed installation step. The exact `.env` files are excluded from generated source archives.
### Resource / protocol DoS
- public connection and inflight request counts are bounded;
- HTTP/1 header count/buffer and header-read time are bounded;
- HTTP/2 concurrent streams/header list/windows and connection lifetime/drain controls are bounded;
- WebSocket connections retain the public connection lease and have inactivity limits;
- FastCGI concurrency/timeouts and traffic analytics queues/cardinality are bounded;
- cache/transformation background work has bounded queues/permits.
A real hostile load test is still required before production because source bounds do not prove allocator/kernel behavior under mixed H1/H2/H3/WebSocket/PHP traffic.
One residual DoS gap is intentionally recorded rather than hidden: RustProxy currently streams request bodies but has no general per-vhost/location byte ceiling equivalent to `client_max_body_size`. Connection/inflight/body-idle/FastCGI timeout bounds prevent an unbounded in-process request-body buffer, but a client can still spend bandwidth and backend/PHP upload resources with a very large body. Before exposing public CMS upload endpoints, set conservative PHP/application limits (`post_max_size`, `upload_max_filesize`) and add/test a RustProxy request-body byte limit.
## Production blockers / release-host gates
Do not treat this audit as production approval until all of these pass on the exact release artifact:
1. Install stable Rust/Cargo and run `./dev.sh doctor`, `./dev.sh check`, `./dev.sh test`, `cargo test --locked` and a release build.
2. Run `cargo audit` (and preferably OSV/Trivy or equivalent) against the exact `Cargo.lock`; resolve vulnerabilities and review unmaintained/yanked transitive crates.
3. Start the exact release binary under the target systemd unit and run `tools/audit/admin_audit.py` against loopback.
4. Run active malformed HTTP tests for duplicate/ambiguous framing, oversized headers, slow headers/bodies, oversized request bodies, H2/H3 stream floods, WebSocket idle/connection exhaustion and Range edge cases.
5. Fuzz/property-test `routing_path`, rewrite/`try_files`, `.htaccess`, CGI header parsing and backup restore parsers.
6. Run filesystem escape tests using symlink races, nested links, renames and mount/bind-mount edge cases on the production kernel/filesystem.
7. Exercise a real PHP-FPM pool with hostile headers/body sizes, disconnects, timeouts and an intentionally compromised test PHP application; verify that the PHP user cannot read RustProxy private state.
8. Perform sustained mixed-protocol load/soak testing while monitoring RSS, FDs, tasks, cache memory and latency.
9. Verify firewall policy: public only intended frontend TCP/UDP ports; admin 9090 loopback/private only unless Administration HTTPS is intentionally exposed; PHP-FPM never public. For CMS uploads, enforce tested request-body limits in PHP/application and preferably RustProxy before public launch.
10. Back up and restore on an isolated host, then rotate/revoke restored sessions and credentials.
Until those runtime gates pass, status remains **pre-production / runtime security verification open**.
-56
View File
@@ -1,56 +0,0 @@
# RustProxy 0.25.1 release audit
RustProxy 0.25.1 is a focused Traffic-UX and source-modularization release on top of the 0.25.0 Web Server foundation. No configuration-schema migration is introduced.
## Implemented scope
- Traffic is split into **Overview**, **Clients**, **Requests** and **Delivery** sub-tabs so the page no longer requires a long vertical scroll.
- All bounded Traffic dimensions are visualized: donuts for methods/status/cache/countries and horizontal bars for User-Agent, effective client IP, path, route and byte volume.
- Every Rust source file is release-guarded at **500 lines maximum** and unit tests must live outside implementation files.
- Oversized dashboard JavaScript is split into bounded modules while `/assets/app.js` and `/assets/vhosts.js` remain stable browser URLs assembled by the Rust admin router.
- Docker Agent implementation is split into model, Docker, network, certificate, server and entry-point modules; tests remain in `main_test.go`.
- Deployment and NPM migration utilities are split into bounded implementation modules while keeping their existing command-line entry points and helper API used by release tests.
- The Web Server split regressions reported after 0.25.0 are guarded explicitly: dangling attributes, unresolved `htaccess`, inaccessible `server_protocol`, missing `anyhow::Context`, broken `include!` targets and test code embedded in implementation modules.
## Compiler-error regression status
The current source tree contains the intended corrections:
- `src/web_server/response.rs` ends with valid `include!` items and cannot end with a dangling `#[derive(...)]` under the structural contract.
- the FastCGI logical module imports `htaccess` and `anyhow::Context`;
- `server_protocol` is exposed inside the Web Server module through `request::server_protocol` and imported by FastCGI;
- `serve.rs` does not retain the previously unused `str::FromStr` import.
- split runtime documentation comments are attached to their actual methods instead of ending an `include!` fragment immediately before `}`;
- included Rust fragments cannot start with inner `//!` module documentation;
- the split Web Server response-header fragment imports sibling `htaccess` explicitly and no longer retains the stale `ACCEPT_ENCODING`/`FromStr` imports;
- split proxy/config/Prometheus fragments no longer export unnecessary `mut` bindings or unused values from their `include!` tuples.
`python3 tools/checks/rust_module_contracts.py` checks these conditions without a compiler. A real `cargo check --locked --all-targets` remains mandatory on the release host.
## Verification gates
Rust-independent acceptance for this package includes:
```text
python3 tools/checks/rust_module_contracts.py
python3 tools/checks/source_module_contracts.py
python3 tools/checks/check_source.py
python3 tools/checks/release_smoke.py
python3 tools/checks/memory_contracts.py
python3 tools/checks/web_server_contracts.py
python3 tools/checks/security_contracts.py
python3 tools/checks/security_dependency_contracts.py
python3 tools/checks/docs_check.py
python3 tools/checks/langs_check.py
go test ./... # from docker-agent/
```
The packaging workspace has no Rust/Cargo toolchain. Before production/release acceptance, run:
```bash
cargo fmt --all -- --check
cargo check --locked --all-targets
cargo test --locked --all-targets
```
No claim of compiler success is made until those commands pass on a Rust-enabled host.
-28
View File
@@ -1,28 +0,0 @@
# 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.
-29
View File
@@ -1,29 +0,0 @@
# RustProxy 0.25.3 release audit
RustProxy 0.25.3 adds request-method policy, location-scoped access controls, end-to-end request correlation and retained manual-configuration history. SQLite moves from schema 7 to schema 8 through an additive migration.
## Access policy
| Requirement | Status | Evidence |
| --- | --- | --- |
| Allow/block HTTP methods per vhost | IMPLEMENTED | `Vhost.method_policy` validates normalized HTTP methods; the request path returns 405 before origin work when the host policy denies a method. |
| Allow/block HTTP methods per Custom location | IMPLEMENTED | `PathRoute.method_policy` is compiled and enforced after route selection and before authentication/upstream selection. |
| Reuse ACL profiles per Custom location | IMPLEMENTED | Locations accept `acl_profile_id`; profile IP and country policies are resolved during configuration compilation. |
| Add inline location IP/country policy | IMPLEMENTED | Location inline CIDR ACL and country allow/block policy compose with vhost/profile policy and fail before origin work. |
| Preserve additive security semantics | IMPLEMENTED | A location cannot weaken a denial made by vhost/profile policy; all applicable policies must allow the request. |
## Request correlation
Each accepted exchange receives a fresh 64-hex-character SHA-256 request identifier derived from an internal sequence plus cryptographic OS randomness. RustProxy overwrites the protected `X-Request-ID` request header with that value before origin forwarding. The same value is available to diagnostic response headers when enabled and to access logging as `request_id`.
## Manual configuration history
SQLite schema 8 adds `configuration_history`. On upgrade, the current configuration is seeded if no history row exists. Each successful CAS configuration replacement records revision, timestamp, actor and document in the same transaction and trims retained history to the newest 512 rows.
The authenticated `GET /api/config/history` endpoint returns retained revision metadata or a selected revision with its previous retained document. The Manual config dashboard provides a revision-history dialog grouped by month/day; selecting a revision renders actor/time metadata and bounded path-level before/after changes. The endpoint uses the existing administrator session guard.
## Compatibility and release gates
Missing new policy fields deserialize to disabled/default behavior. Existing schema-7 databases upgrade automatically. Old binaries may reject a configuration saved with new fields, so rollback should restore both the old source and the pre-upgrade data backup.
Rust-independent source, documentation, language, UI and release-smoke checks are part of the release tree. A follow-up compiler run outside this packaging workspace found and prompted correction of the history-capacity module visibility plus a dead location GeoIP assignment; the corrected source uses `history::CONFIG_HISTORY_CAPACITY` with parent-module visibility and no redundant assignment. Compiler-enabled `cargo fmt --all`, `cargo check --locked --all-targets` and `cargo test --locked --all-targets` remain mandatory on a release host with Rust installed.
-27
View File
@@ -1,27 +0,0 @@
# RustProxy 0.25.4 release audit
RustProxy 0.25.4 adds restore from retained manual-configuration history on top of the schema-8 history introduced in 0.25.3. There is no SQLite schema change.
## Configuration history restore
| Requirement | Status | Evidence |
| --- | --- | --- |
| Restore an older retained revision | IMPLEMENTED | `POST /api/config/history/restore` loads the retained document and passes it through the normal configuration validation/update path. |
| Preserve monotonic revision history | IMPLEMENTED | Restore uses the current `expected_revision` and commits the restored document as the next revision; it never updates or deletes the selected historical row. |
| Reject stale restore actions | IMPLEMENTED | The endpoint compares `expected_revision` with the current published revision and returns conflict before restore when configuration changed. The storage write retains the existing CAS guard. |
| Keep restore private and CSRF-protected | IMPLEMENTED | The endpoint is outside the anonymous allowlist and is routed through the existing administrator session, Origin/request-marker and CSRF guard. |
| Audit restores distinctly | IMPLEMENTED | Restore persistence records `config.restore` with `source_revision=<old> revision=<new>` in the same SQLite transaction as the configuration write/history insert. |
| Reuse hot-publish behavior | IMPLEMENTED | Restore calls the same compile, listener-lock, certificate-removal, cache-invalidation and immutable snapshot publication path used by ordinary configuration updates. |
| Provide dashboard restore UX | IMPLEMENTED | The revision-history detail view exposes a restore action only for an older revision, shows an explicit confirmation, refreshes the current envelope after success and reloads history. |
## Compatibility
SQLite remains at schema version 8 and the history capacity remains 512 revisions. Upgrading from 0.25.3 requires no SQL migration. History retention still begins with the schema-8 migration introduced in 0.25.3.
A restore can fail current validation when an older document is no longer valid in the current runtime environment, for example because referenced certificate files, listener constraints or other external prerequisites changed. In that case the active configuration and revision remain unchanged.
## Verification gates
Rust-independent source, language, documentation, OpenAPI, browser JavaScript, security-contract and release-smoke checks are included in the release tree. Rust tests include an authenticated end-to-end restore case that applies two configurations, restores the first as revision 3 and checks the `config.restore` audit entry and retained previous document.
This packaging workspace does not provide the Rust/Cargo toolchain. Compiler-enabled `cargo fmt --all`, `cargo check --locked --all-targets` and `cargo test --locked --all-targets` therefore remain mandatory on a release host before production deployment.
-27
View File
@@ -1,27 +0,0 @@
# RustProxy 0.25.5 release audit
RustProxy 0.25.5 is a patch release for Traffic country consistency behind Cloudflare, broader bounded Prometheus/Grafana observability, dashboard Revision History styling, compact mobile navigation and top-bar project information. SQLite remains at schema 8 and no saved-configuration migration is introduced.
## Implemented changes
- `cloudflare_client_ip` remains opt-in per vhost and still accepts `CF-Connecting-IP` only when the direct transport peer belongs to the built-in Cloudflare edge CIDRs.
- A trusted Cloudflare request may additionally provide one `CF-IPCountry` header. RustProxy accepts only a two-letter ASCII country code, normalizes it to uppercase, rejects duplicates and treats `XX`/non-country values as unresolved.
- When the Cloudflare client-IP override succeeds, the trusted Cloudflare country is preferred for country policy evaluation, retained country logging and bounded Traffic country analytics. Missing or invalid country values fall back to the local GeoIP lookup of the verified effective client IP.
- Requests that do not use verified Cloudflare client-IP mode keep the previous GeoIP behavior. Untrusted clients cannot make `CF-IPCountry` authoritative by sending it directly.
- Revision History now participates in the same shared square-corner `dialog` styling as vhost, logs, audit and certificate dialogs. Its duplicate color/background/border/backdrop/shadow declarations and local rounded corners were removed.
- At dashboard widths up to 850 px, the main navigation is collapsed behind a square-corner Menu control instead of rendering every section across the page header. It expands as a two-column list and closes after section selection, Escape or an outside click; desktop navigation remains unchanged.
- The authenticated dashboard top bar now includes an About dialog identifying author Mateusz Griszczynski, the project as Open Source, and the canonical source repository. The former short Docs action is labeled Documentation; no new public administration route is added.
- Prometheus now has an independent `include_traffic` collector (default on) for bounded Traffic analytics. It exports per-vhost totals plus countries, fixed User-Agent families, fixed HTTP method families, status classes, cache outcomes, configured routes and a fixed serving-source dimension (`web_server`, `reverse_proxy`, `local_response`, `unresolved`).
- Raw client IPs, request paths and full User-Agent strings remain available only to the authenticated Traffic/logging surfaces and are intentionally never emitted as Prometheus labels. Country values are restricted to two-letter uppercase codes; route labels are restricted to currently configured route identifiers.
- Built-in Web Server observability now includes configured service inventory, PHP/directory-listing flags, vhost/route source metadata and request traffic by serving source. Pure Web Server vhosts continue to use the same vhost-wide request/status/byte/duration counters as proxy-backed vhosts.
- The bundled Grafana dashboard adds a Traffic-vhost filter plus Web Server request rate, Traffic queue drops, top countries, User-Agent families, serving-source rates, method rates, status classes, cache outcomes, capture-state gauges and configured-route traffic.
## Regression coverage
- `src/forwarding/tests.rs` checks that Cloudflare country is accepted only from a Cloudflare peer, normalized, rejected for `XX`, malformed values and duplicate headers.
- Source-contract checks verify the 0.25.5 release identity, shared Revision History modal rule, absence of the old history-only backdrop/radius declarations, the Cloudflare country preference/fallback path, collapsed mobile navigation behavior, About dialog metadata/link, and the Documentation top-bar label.
- Traffic aggregation now also keeps fixed User-Agent-family, method-family and serving-source counters for Prometheus while preserving the existing raw bounded top tables used by the authenticated Traffic UI.
## Verification gate
Run the repository checks included with the release archive. Static/source-contract, language, documentation, packaging, shell, JavaScript and Docker Agent checks may run without Rust where supported. A compiler-enabled release host must still run `cargo fmt --all`, `cargo check --locked --all-targets` and `cargo test --locked --all-targets` before production acceptance.
-104
View File
@@ -1,104 +0,0 @@
# RustProxy 0.26.1 implementation note
## Scope
Version 0.26.1 is a focused follow-up to the persistent protocol diagnostics and Stored Logs explorer introduced in 0.26.0. It does not change SQLite schema 8 or the saved proxy configuration format.
The release has two goals:
1. Attribute persistent `protocol_error` records to a configured vhost when that attribution is safe and deterministic.
2. Make the Stored Logs explorer practical on installations with many vhosts and useful for protocol diagnostics.
Live Logs remains a separate bounded in-memory request view. Low-level `protocol_error` records are persistence-only and must not be published to its history/broadcast path.
## One shared vhost-resolution function
All protocol adapters use the same resolver; TLS, HTTP/1, HTTP/2 and HTTP/3 must not implement independent hostname matching.
```rust
resolve_logging_vhost(
local,
tls,
routed_vhost,
authority,
host,
sni,
) -> Option<ResolvedLogVhost>
```
Resolution priority is:
1. a vhost already selected by normal routing;
2. HTTP `:authority`;
3. HTTP `Host`;
4. TLS/QUIC SNI;
5. no match, which means `_unmatched.jsonl` in per-vhost file mode.
The helper normalizes authority/Host/SNI with the existing host parser and delegates selection to the same `Snapshot::frontend` exact/wildcard and listener-binding logic used by normal request routing. It returns the configured internal vhost ID and `matched_by`; client input is never turned directly into a filename.
## Persistent protocol records
A protocol record keeps the compatibility fields from 0.26.0 and also exposes canonical diagnostics when available:
- `event = protocol_error`;
- `reason` and compatibility `protocol_error`;
- `error` and compatibility `error_detail`;
- `client_ip` and `local_addr`;
- `transport` and `http_version`;
- `vhost` when safely resolved;
- `sni` and compatibility `tls_sni`;
- `authority`;
- `matched_by = router | authority | host | sni | none`.
Diagnostic identity strings are control-character stripped and length-bounded before persistence. Existing secret redaction and error-detail limits remain in force.
In per-vhost file mode, a resolved record uses the existing configured vhost sink. Invalid internal vhost identifiers fall back to `_unmatched` instead of being sanitized into a new filename. In combined-file mode, sink behavior is unchanged.
## Protocol integration
TLS reads SNI from ClientHello before completing the handshake. A later handshake failure/timeout carries that SNI into the shared resolver. If ClientHello itself cannot be parsed, attribution remains unavailable and the record is unmatched.
HTTP/2 retains the TLS SNI for post-handshake connection/drain diagnostics. Request paths that have an HTTP request object can additionally provide authority/Host.
HTTP/3 obtains SNI from Quinn/Rustls handshake data after a successful QUIC handshake and uses it for later request, stream and connection protocol failures. Failures that happen before usable handshake identity exists remain unmatched.
No protocol path infers a vhost solely from a shared listener or local port.
## Stored Logs explorer changes
The `/logs` workspace now:
- highlights the selected log row;
- expands Record details inline directly below that row;
- removes the separate details panel below the table;
- keeps the table header sticky while the log list scrolls;
- lets each administrator choose visible Stored Logs columns and persists the choice in existing account preferences;
- offers protocol columns for transport, match source, SNI and authority;
- keeps the configured-file inventory collapsed by default;
- canonicalizes fields through the active structured-log field mapping so `client_ip` remains visible when the JSON output key was renamed;
- searches record values case-insensitively and includes `reason`, `error`, `sni`, `authority` and other stored values;
- refreshes keyword results with a short debounce while typing;
- retains the existing bounded source/byte/line/page limits and reports truncation when an `all` query contains more sources than can be scanned safely.
## Security invariants
The browser still accepts only opaque configured source IDs; it never accepts arbitrary filesystem paths. File reads remain regular-file-only and retain Unix `O_NOFOLLOW` protection.
SNI, Host and authority are lookup inputs only. They cannot select a filesystem path directly. Only a configured vhost resolved through the existing routing model can select a per-vhost log sink.
## Regression coverage
Source tests cover:
- exact and wildcard hostname precedence using the shared resolver;
- router priority over authority/Host/SNI;
- Host with port and case-insensitive normalization;
- unknown and missing SNI returning no vhost;
- mapped `client_ip` normalization in Stored Logs;
- keyword matching against protocol diagnostic values;
- safe per-vhost filename fallback;
- bounded protocol identity fields;
- protocol errors remaining outside Live Logs;
- preference validation for selectable Stored Logs columns.
A Rust-enabled release host must still run `cargo fmt --check`, Clippy with warnings denied, all-target/all-feature tests and the complete `dev.sh` release build before the package is considered compiler-verified.
-71
View File
@@ -1,71 +0,0 @@
# RustProxy 0.26.2 implementation note
## Scope
Version 0.26.2 is a UI/query follow-up to the Stored Logs explorer. It does not change SQLite schema 8 or the saved proxy configuration format.
The release:
- combines Stored Logs and Live Logs under one top-bar Logs menu;
- replaces the fixed Stored Logs filter matrix with an optional source selector, one bounded field-query input and a page-size selector;
- discovers selectable table columns from parsed record fields;
- parses RustProxy RAW error-log lines into basic fields;
- removes the configured-file inventory while retaining the source selector as an optional index-like scope;
- moves column configuration to the upper-right table toolbar;
- uses arrow symbols for backend sort direction.
## Stored Logs query syntax
The `q` parameter remains bounded and now accepts clauses separated by whitespace or explicit `AND`.
Supported forms include:
```text
req: GET AND !country: Poland
status:>=500 AND path:/api/*
error:"connection reset"
```
`req`/`request` aliases `method`, `ip`/`client` aliases `client_ip`, and `source` aliases the internal source ID. A leading `!` negates a clause. Quoted text keeps spaces together. `*` is a wildcard. Numeric fields accept `>`, `>=`, `<` and `<=`; status values also accept forms such as `5xx`.
All clauses are ANDed. `OR` is deliberately rejected rather than being interpreted ambiguously. Existing `source`, `event`, `status_class`, `client_ip` and `vhost` query parameters remain accepted by the API for compatibility. The dashboard emits `source` only when one specific log is selected; `All configured files` leaves the source parameter unset.
## Parsed records and dynamic columns
JSON/JSONL lines are normalized as before, including recovery of canonical fields from configured JSON mappings. The query response also returns a bounded list of discovered leaf field names. The browser uses those names to build the column picker dynamically.
The default concise columns remain time, event, status, client IP, vhost, record summary and source. Protocol-specific fields such as `matched_by` and `sni` are no longer hard-coded columns; they appear automatically when present in returned JSON and can be selected like any other discovered field.
Stored-log column preferences remain account-scoped. Validation now accepts up to 64 bounded, control-free field names so discovered JSON fields can be persisted without widening any filesystem or proxy configuration authority.
## RAW error-log parsing
The built-in RAW error format is parsed into:
- `timestamp`;
- `level`;
- `target`;
- message text before the first recognized `key=value` field;
- subsequent detected `key=value` fields.
Space-separated text following a `key=value` token is attached to that field until another valid field token begins. Because RAW is intentionally not a fully self-describing serialization, the original line is retained in `raw` and remains available in the expanded record view.
## UI details
Selecting a row shows parsed field/value pairs inline and offers the normalized raw JSON in a collapsible section. Column configuration sits in the table toolbar instead of the filter row.
The previous Configured files block remains removed. Stored Logs keeps an optional Source selector as an index-like scope: `All configured files` is the default, and choosing one configured log limits the query to that source. The server continues to derive every readable path from active RustProxy logging configuration; the browser submits only a source ID, never a filesystem path.
Stored Logs and Live Logs remain technically independent. The combined Logs top-bar control changes navigation only; Live Logs still reads the bounded in-memory request history, while Stored Logs reads bounded tails of configured files.
## Security and bounds
Existing file-reader protections remain unchanged: regular-file checks, Unix `O_NOFOLLOW`, configured-source derivation, blocking-worker I/O, bounded scan bytes/lines/source count, bounded result offsets and page size.
Search parsing is bounded to 512 input bytes and at most 32 clauses. Field names and individual values are bounded before matching. Dynamic column discovery is capped at 64 leaf fields.
## Regression coverage
Source tests cover field aliases, negation, quoted values, status/numeric matching, wildcard matching and RAW error parsing in addition to the retained 0.26.1 stored-log normalization tests.
A Rust-enabled release host must still run `cargo fmt --check`, Clippy with warnings denied, all-target/all-feature tests and the complete `dev.sh` release build before the package is considered compiler-verified.
-29
View File
@@ -1,29 +0,0 @@
# RustProxy 0.26.3 implementation note
## Scope
Version 0.26.3 is an administration-interface polish release. It keeps SQLite schema 8 and the saved proxy configuration format unchanged from 0.26.2.
## Appearance
The account-scoped interface scale now accepts 70-125% and is applied to the whole authenticated administration body, not only the workspace. This includes navigation, dialogs, overlays, toasts and the session draft dock.
Chart presentation expands to eight presets: Default, Soft, Technical, High contrast, Neon, Monochrome, Warm and Cool. Presets share CSS chart variables so line colors, multi-series palette, chart surfaces, grid treatment, stroke geometry and contrast change together across Overview diagnostics, vhost/stream charts and Traffic charts. Chart styles do not alter samples or runtime metrics.
The Appearance page also provides a reset action that restores theme, language, scale, time format and chart style to documented defaults while retaining unrelated administrator preferences.
## Draft dock
Web Server, Vhost, Redirect and Stream drafts remain session-only because editor state may contain secrets. The dock is restyled to use the existing RustProxy panel vocabulary: square corners, opaque panel surfaces, thin separators and compact rows. It does not use rounded glass-card styling.
## Settings navigation
Settings now includes a search field. It filters category tabs using the translated tab label and the text of each settings page. Filtering only changes tab visibility and never reconstructs forms, preserving dirty/unsaved values. Existing sticky desktop category navigation and unsaved-category markers remain active.
## Compatibility
No database migration is required from 0.26.2. Existing administrator preference JSON remains compatible through serde defaults; the only validation changes are the lower 70% scale bound and the additional chart-style enum values.
## Verification
Rust-independent source, language, documentation, JavaScript and packaging checks are expected to run in the release workspace. Rust compiler/type/runtime verification remains blocked when Cargo is unavailable and must be repeated on a Rust-enabled release host before describing the release as compiler-verified.
-30
View File
@@ -1,30 +0,0 @@
# RustProxy 0.26.4 implementation note
## Scope
0.26.4 is a compatibility-preserving administration and backend-lifecycle maintenance release. SQLite remains schema 8 and the saved proxy configuration document is unchanged from 0.26.3.
## Administration UI
- The 404-host editor now registers with the existing session-only draft manager. Dirty forms are captured with their host/listener editor nodes and read closures, can be minimized into the draft dock, and restore the exact unfinished form state.
- The shared Enabled presentation used by vhosts is exposed as one administration helper and reused by 404 hosts, Redirects and Streams. State text, hint text and enabled/disabled visual treatment therefore stay consistent across these editors.
- Polish Button style labels/help are translated instead of falling back to English values.
- Search/filter controls in compact toolbars use the active button minimum height so they do not visually exceed adjacent actions. Ordinary form controls keep their existing sizing.
- Appearance -> Overview cards uses a responsive tile grid instead of one checkbox row per metric card. A tile toggles visibility while the existing up/down controls retain ordering behavior and account-scoped persistence.
## Backend client rotation
Request-count recycling remains request-driven. Age-based recycling is now also checked by the existing backend health/maintenance loop. This removes the dependency on a new request arriving after `pool_recycle_seconds` and allows idle backend clients to advance generation/rotation counters on schedule.
Maintenance-driven rotation starts the new generation with zero requests; request-driven rotation continues to count the request that triggered the swap in the new generation. The rebuild remains guarded by the existing generation/rotation atomics, so concurrent request and maintenance checks cannot double-rotate the same generation.
## Compatibility
- SQLite schema: 8 (unchanged).
- Saved proxy configuration format: unchanged.
- Administration preferences: existing fields only; no migration required.
- Upgrade from 0.26.3: no data migration required.
## Verification
See [TEST_REPORT.md](TEST_REPORT.md). This workspace can run the Rust-independent source, documentation, browser-JavaScript, packaging and Go-agent gates. Rust compiler gates must still be run on a release host with Cargo before calling the archive compiler-verified.
-26
View File
@@ -1,26 +0,0 @@
# RustProxy 0.26.5 implementation note
## Scope
0.26.5 is a compatibility-preserving administration UI patch. SQLite remains schema 8 and the saved proxy configuration document is unchanged from 0.26.4.
## Scaled form controls
Interface-scale preferences already publish field sizing variables. Form inputs, selects and textareas now consume those variables consistently, while compact toolbar filters derive one shared height from the active button size and scaled field height. This keeps native select boxes visually aligned with neighboring search inputs without allowing toolbar fields to grow taller than the action controls beside them.
The change applies to the shared administration stylesheet, including Stored Logs, Backends, vhost-style lists, Docker, Live Logs and Settings filtering surfaces.
## Stream listener shortcut
The **Streams -> Add TCP/UDP listener** shortcut no longer opens a blank Stream editor. It switches the administration workspace to **Settings -> Listeners**, preserving normal Settings URL/tab synchronization.
## Compatibility
- SQLite schema: 8 (unchanged).
- Saved proxy configuration format: unchanged.
- Administration preferences: unchanged; no migration required.
- Upgrade from 0.26.4: no data migration required.
## Verification
See [TEST_REPORT.md](TEST_REPORT.md). Rust-independent source, documentation, browser-JavaScript, packaging and Go-agent gates can run in this workspace. Rust compiler gates still require a release host with Cargo.
-46
View File
@@ -1,46 +0,0 @@
# RustProxy 0.26.6 implementation note
## Scope
0.26.6 is a compatibility-preserving access-policy and administration workflow patch on top of 0.26.5. SQLite remains schema 8 and the saved proxy configuration document is unchanged.
## Backend table preferences
The backend diagnostics table continues to store visible columns, sort key and descending/ascending direction in the account-scoped administrator preferences. The shared preference application path now reapplies those values to the backend table whenever preferences are loaded or updated, instead of relying only on initial page activation.
## Certificate creation workflow
After `/api/acme/issue` accepts an issuance request, the UI remembers that job ID. The form is reset only when polling reports that same issue job as `ready`; a failed job cancels the pending reset so non-secret fields remain available for correction. Transient provider secrets continue to be cleared after submission for security. The submitted job remains visible in the ACME status UI.
## NPM migration helper
`scripts/migration/migrate_npm.py` imports sibling modules `scripts/migration/migrate_npm_core.py` and `scripts/migration/migrate_npm_api.py`. They use the Python standard library and require no third-party package installation, but all three files must stay together. The current documented container command mounts the dedicated `scripts/migration` directory.
## ACL policy composition
IP ACLs and country access are evaluated together inside each policy scope. Explicit IP denies and blocked countries take precedence. In **Allow only** country mode, either a listed country or an explicit IP allow can satisfy that scope, so a `default=deny` policy can express private/internal CIDRs **or** selected countries. An unresolved country fails the country branch but may still be admitted by an explicit IP allow.
Profile, vhost and location policy scopes remain cumulative: satisfying one scope never bypasses another scope or authentication. Country **Block** mode continues to reject listed countries while the IP ACL controls all other clients.
The reusable ACL editor exposes this as two guided modes without changing the saved configuration schema:
- **Block by default** — each selected country **OR** allowed IP/CIDR is an access exception. For example, `CN` plus `203.0.113.7` allows clients from China and also that single address from any other resolvable country.
- **Allow by default** — each selected country **OR** denied IP/CIDR is a block condition.
- **Custom** — retains manual default action, country mode and mixed IP allow/deny actions for advanced policies.
A live policy preview shows the effective default and OR branches before save. The editor also accepts a single IPv4/IPv6 address and normalizes it to a host rule; the backend accepts the same address form directly, so API-authored profiles do not require `/32` or `/128` either.
## LXC runtime diagnostics
Linux diagnostics classify the runtime as LXC, Docker, Podman, systemd-nspawn, WSL, VM or Host for the Process platform label. Classification uses namespace/container metadata already exposed by procfs/sysfs/systemd and does not shell out. LXC detection takes precedence over DMI virtualization so an LXC guest running on a virtualized host is still shown as `Linux / LXC`.
Only LXC changes metric sourcing. `system_uptime_seconds` is replaced with the age of PID 1 calculated from `/proc/1/stat` and `CLOCK_BOOTTIME`, avoiding the host uptime returned by ordinary Linux system APIs. The 1/5/15 load values are read from the container-visible `/proc/loadavg`, allowing LXCFS-backed installations to expose their virtualized container load instead of the host load. Other environments retain the existing `systemstat` uptime/load path.
## Compatibility
- SQLite schema: 8, unchanged.
- Saved proxy configuration format: unchanged from 0.26.5.
- No data migration is required for upgrade from 0.26.5.
- Existing ACL/country configuration is reinterpreted only where allow-only countries previously conflicted with `default=deny` IP rules.
Verification status and environment limitations are recorded in [TEST_REPORT](TEST_REPORT.md).
-53
View File
@@ -1,53 +0,0 @@
# RustProxy 0.26.7 implementation note
## Scope
0.26.7 is a compatibility-preserving cache observability, backend lifecycle and container documentation patch on top of 0.26.6. SQLite remains schema 8 and the saved proxy configuration document is unchanged.
## HOT/COLD cache occupancy
The authenticated `/cache` page now renders explicit occupancy meters for both cache tiers. HOT and COLD object cards compare the current entry count with `max_entries` / `cold_max_entries`; HOT memory compares charged HOT bytes with `max_bytes`; COLD disk compares persisted bytes with `cold_max_bytes`. The cards also show the configured limit and percentage so the progress bars remain interpretable without estimating from the visual fill alone.
The HOT byte meter intentionally uses allocated/charged HOT bytes, not only logical body length. This matches the budget enforced by cache admission. COLD uses persisted storage bytes.
## Backend rotation continuity
Age-based recycling continues to run from the backend maintenance loop and request-count recycling remains request-driven. The isolated client rotation logic already advanced generation and rotation counters correctly.
The release fixes a separate observability/lifecycle problem: rebuilding an otherwise compatible active routing snapshot created a new upstream client object, which restarted generation age, request count and rotation totals at zero. Pool rebuilds now retain the previous compiled backend when both ordinary and WebSocket upstream clients have exactly the same client factory: backend definition, address policy, keepalive settings, protocol mode and connect timeout. Health observations retain their existing compatibility rules independently.
A real backend-policy change still creates a fresh client. This avoids retaining stale DNS/listener safety policy, TLS identity, keepalive or timeout behavior while keeping long-running rotation diagnostics continuous through unrelated snapshot publications.
## Container certificate tooling and documentation
The OCI runtime image now creates a dedicated `/opt/rustproxy-certbot` Python environment and pins Certbot plus the supported Cloudflare, OVH and RFC2136 DNS plugins to the same release. The image therefore supports every certificate provider exposed by RustProxy without an extra image-extension step. Container documentation was refreshed to remove the obsolete statement that DNS plugins are absent and to use the current release tag in examples.
Current cache, vhost/backend and installation documentation was reviewed alongside the release notes so the dashboard limits, rotation semantics and container prerequisites describe the implemented behavior.
## Compatibility
- SQLite schema: 8, unchanged.
- Saved configuration format: unchanged.
- No migration is required from 0.26.6.
- Backend rotation counters intentionally restart when the backend client policy itself changes.
## Cache Age and downstream compression follow-up
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
new freshness calculation. `X-Cache-Resident-Age` remains the monotonic residency clock
across revalidation and tier movement. Existing 0.26.6 custom `Age` rules remain accepted
for configuration compatibility. Response-side Age rules are isolated to the final wire
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,
SSE, upgrades and already encoded responses. Cookie-bearing requests now have one bounded
exception: a response can be compressed when it is already a shared-cache HIT accepted by
cache policy or a local static Web Server response. This aligns the static-cookie cache
exception with downstream compression without enabling compression for arbitrary dynamic
cookie responses. The bundled compression tester no longer sends `Cache-Control: no-cache`
by default because that header forces validator revalidation and can make cache `Age` appear
to stay at zero; `--revalidate` retains the old diagnostic behavior explicitly.
-27
View File
@@ -1,27 +0,0 @@
# RustProxy 0.27.0 implementation note
## Scope
0.27.0 changes downstream compression defaults/negotiation, makes Docker publications persistent across transient service loss, adds scoped compression overrides, and fixes cache occupancy meter rendering. SQLite remains schema 8. Saved configuration changes are additive/defaulted: vhosts and Web Services gain scoped compression/codec overrides, while Docker assignments use one nullable compression switch. Legacy pre-release Docker codec fields remain accepted for configuration compatibility but are ignored. Fresh installations also default the `Server` header policy to `custom: RustProxy`, while a missing legacy field still deserializes with the previous preserve behavior.
## Compression
Global compression defaults to zstd, Brotli and gzip enabled, with deflate disabled and the existing 1024-byte minimum. RustProxy parses `Accept-Encoding` weights before entering tower-http. Client quality values are authoritative; equal qualities use server preference `zstd -> br -> gzip -> deflate`.
Responses carrying `Cookie`, `Authorization`, `Set-Cookie`, `Cache-Control: private` or `no-store` are no longer rejected merely because of those headers. Compression remains disabled for existing `Content-Encoding`, `no-transform`, request ranges/`Content-Range`, SSE, upgrades, HEAD and non-body statuses. Existing encoded bodies are never recompressed.
Ordinary vhosts and Web Server services expose inherit/enable/disable dynamic-compression policy plus individual zstd/Brotli/gzip/deflate overrides. Global Response codec switches remain the hard availability ceiling and the minimum size remains global. Docker vhosts intentionally expose one Compression checkbox only; when enabled, the generated vhost uses the globally enabled codec set and deterministic global priority. Legacy Docker per-codec values are ignored and the dashboard clears them to null when an assignment is saved.
## Persistent Docker publications
A Docker assignment is desired configuration, not merely a reflection of the latest inventory. Once published, its hostname remains owned until the administrator unpublishes it or disables/removes the agent. When a successful Docker inventory no longer contains the assigned service, RustProxy compiles an unavailable fallback route with no backend and returns `503 Service Unavailable` plus `Retry-After: 1` instead of withdrawing the hostname and falling through to 404.
Agent transport failures retain the previous successful inventory when one exists, allowing known working backends to continue routing during a temporary control-plane outage. If no inventory has ever succeeded, published assignments are still materialized as 503 routes. When the same service key becomes routable again, normal reconciliation rebuilds its backend pool automatically; no manual pool action is required.
## Cache occupancy meters
The `/cache` JavaScript already calculates occupancy percentages from configured object/byte limits and writes those values to native `<progress>` elements. 0.27.0 adds explicit WebKit/Chromium and Firefox progress-track/value styling so the filled portion is visible instead of relying only on `accent-color`.
## Compatibility and verification
No SQLite migration is required. Older vhost, Docker assignment and Web Service documents load because all new fields are defaulted; legacy Docker codec fields are tolerated but no longer affect generated vhosts. Existing stored `server_header` settings are preserved; documents predating that field keep the historical preserve behavior. Rust compiler/runtime verification remains mandatory on the release host; Rust-independent checks in `docs/TEST_REPORT.md` do not substitute for `cargo fmt`, `cargo check`, Clippy, tests or live protocol acceptance.
-41
View File
@@ -1,41 +0,0 @@
# RustProxy 0.27.1 implementation note
## Scope
0.27.1 improves first-run administration, makes ACME/Certbot setup independent of one distribution packaging layout, and adds per-user list sorting to the main routing views. SQLite remains schema 8; the saved administrator-preference document gains only defaulted fields.
## First administrator
The initial login/setup screen now shows a directly runnable token command for the default data path:
```bash
rust-proxy --data-dir ./data admin setup-token
```
A standard service-installation example is shown below it:
```bash
rust-proxy --data-dir /var/lib/rustproxy admin setup-token
```
This removes the previous `DATA_DIR` placeholder from the primary command while still documenting the installed-service path.
## Managed certificate runtime
RustProxy now prefers a private Python environment at `<data-dir>/acme-python`. On startup, and again on demand before an ACME job, it validates that environment and initializes it when missing. The managed environment pins Certbot 5.8.0 plus the Cloudflare, OVH and RFC2136 plugins.
Initialization first uses the available `python3`/`python` stdlib `venv`. If a distribution separates `venv` but provides pip, RustProxy installs `virtualenv` only into a temporary directory below its own data directory and uses it to create the managed environment; it does not modify distribution-owned site-packages. The managed environment receives its own pip packages.
If managed setup cannot be completed, RustProxy tests a distribution `certbot` from `PATH` and uses it as the fallback when runnable. If Python is unavailable, or neither managed nor system Certbot can be used, the Certificates panel shows an unavailable state and a specific diagnostic. Manual PEM import and use of existing certificate files remain independent of the ACME runtime. This makes minimal systems, including appliances that intentionally omit Python/pip, fail visibly instead of silently.
## List sorting
The administrator preference document now stores sort column and direction separately for ordinary vhosts, Docker vhosts, 404 hosts and streams. Each page exposes only columns meaningful to that feature and reuses the backend-table column/direction selection pattern. Preferences are account-scoped and reapplied when the administrator session/preferences are loaded.
## Compatibility
No SQLite migration is required. Schema 8 remains current. Existing saved configuration continues to load; missing sort preferences receive defaults. Historical distribution Certbot packages remain useful as an offline/runtime fallback, while the application-owned environment is the preferred path.
## Verification limitation
This workspace does not provide Cargo/Rust, so compiler/type/Clippy/unit-test gates must still be run on a Rust-enabled release host. Rust-independent source, JavaScript, documentation, packaging and Go-agent checks are tracked in [TEST_REPORT](TEST_REPORT.md).
-34
View File
@@ -1,34 +0,0 @@
# RustProxy 0.27.2 implementation note
## Scope
0.27.2 is a focused ACME runtime repair. It keeps SQLite schema 8 and the saved configuration format unchanged.
## Managed Certbot relocation bug
0.27.1 creates the private environment under `<data-dir>/acme-python.installing`, installs Certbot and its DNS plugins there, validates it, and then atomically renames the directory to `<data-dir>/acme-python`.
Python virtual-environment console scripts created by pip contain an absolute interpreter path in their shebang. After the directory rename, `bin/certbot` can still point at the removed `acme-python.installing/bin/python`. Executing that console script can therefore fail with a local `OSError`; the private worker intentionally reduced such errors to `acme.local_io_or_json_error`, which hid the relocation detail from the UI.
## Repair
Managed-environment validation now runs Certbot as:
```text
<data-dir>/acme-python/bin/python -I -m certbot --version
```
ACME issuance and renewal use the same final interpreter with `-m certbot`. The provider worker receives the managed interpreter path from RustProxy and no longer depends on the relocated pip console-script shebang.
The existing system/distribution Certbot fallback continues to execute its discovered `certbot` program normally.
## Upgrade behavior
No ACME profile, account, certificate or credential migration is required. An `acme-python` environment created by 0.27.1 can be reused as-is because 0.27.2 bypasses the stale console-script shebang and imports Certbot from the environment's installed Python modules.
## 0.27.3 correction
The module-based workaround described above was invalid for current Certbot packages: Certbot 5.8.0 installs a `certbot` console entry point but does not expose a runnable `certbot.__main__`, so `python -m certbot` fails even when installation and plugin imports are valid.
0.27.3 no longer relocates a freshly built managed environment after pip creates its console scripts. The environment is created/rebuilt directly at `<data-dir>/acme-python`, validation executes `<data-dir>/acme-python/bin/certbot --version`, and issuance/renewal use that same final console entry point. An older environment with a stale relocated shebang fails validation and is rebuilt at the final path.
-74
View File
@@ -1,74 +0,0 @@
# RustProxy 0.27.3 implementation note
## Scope
0.27.3 aligns HTTP/3 `HEAD` cache handling with HTTP/2, reduces long-lived cache/rate-limit memory, excludes the rebuildable managed Python runtime from backups, promotes package repositories as the normal installation path, and includes maintenance fixes for restore ownership, startup listener overrides, strict WebSocket 101 framing, resilient update checks, scaled sidebar layout and automatic GeoIP maintenance. SQLite schema 8 and the COLD cache on-disk format remain unchanged; existing saved configurations remain compatible because the new `geoip_updates` object is defaulted when absent.
## HTTP/3 HEAD cache parity
HTTP semantics do not make `HEAD` meaningfully different between HTTP/2 and HTTP/3. RustProxy already allows `GET` and `HEAD` in cache policy, but the forwarding path actively probed an initially unknown request-body stream only for `GET`.
The HTTP/3 receive body starts with an unknown end-of-stream state until it is polled. A body-less `HEAD` could therefore reach cache policy as `body_empty=false` and be bypassed with `request_body`. HTTP/2 happened to expose end-of-stream immediately for the same curl request.
0.27.3 runs the existing bounded `empty_for_cache` probe for both `GET` and `HEAD`. A genuinely body-bearing request is still bypassed, while a body-less H3 `HEAD` follows the same cache lookup path as H2.
## COLD cache RAM
The COLD tier stores bodies on disk, but its old in-memory index retained a complete response `HeaderMap`, origin validators, a duplicate 32-byte cache key and a 64-character SHA-256 string for every object. With many small COLD objects, these metadata allocations could become a material RSS component even though body bytes were no longer resident.
0.27.3 keeps the fields needed for matching, invalidation, dashboard rows, LRU and freshness in the COLD index, but removes the persistent header/validator copies. On a real COLD read RustProxy reads and validates the existing metadata JSON, materializes headers/validators for that request, verifies the body digest and then promotes/serves the object as before. The in-memory digest is stored as 32 raw bytes and the map-owned cache key is no longer duplicated inside `ColdEntry`.
This trades one metadata-file read on a COLD hit for substantially lower long-lived metadata RAM. HOT behavior, COLD disk format, invalidation fields and response semantics are unchanged.
## Rate-limit/IP retention
The previous limiter keyed every client bucket by `(revision, vhost String, IP)`, duplicating the vhost allocation for each IP. Expired buckets were normally retained until the global 65,536-entry bound caused cleanup.
0.27.3 groups client-IP buckets below a single vhost key, stores the active revision once and removes buckets whose fixed window has expired. Cleanup runs opportunistically during requests and from the existing periodic runtime-maintenance loop, so idle traffic no longer leaves expired IP state resident indefinitely. Sparse client/vhost hash tables are shrunk after reclamation and the allocator trim follows the periodic sweep. Removing an expired bucket is behavior-equivalent to the existing next-request reset: the next request starts a new fixed window with count zero. The global client-bucket bound remains 65,536.
## Compression RAM assessment
Downstream compression remains streaming through `tower_http::compression::Compression`. It does not collect the complete response body before encoding, and `CompressionLevel::Fastest` remains selected. The long-lived cache does not keep encoder state. Compression memory therefore scales primarily with the number and codec type of concurrently active compressed responses rather than the number of cached objects or historical requests.
No compression algorithm or negotiation behavior is changed in 0.27.3 because the existing implementation is already bounded by streaming response lifetime. Brotli and Zstandard can have larger per-stream codec state than gzip/deflate, so concurrency remains the main compression-RAM factor under high load.
## Backup scope
`acme-python` is a managed/rebuildable Python virtual environment, not authoritative application state. Backups now skip both `acme-python` and the underscore spelling `acme_python`, while certificate/configuration/secret state remains covered by the existing backup logic.
## Installation paths
The documented normal installation/update paths are:
- DEB/RPM packages: `https://rustproxy.linuxiarz.pl/`
- OPNsense plugin: `https://rustproxy-opn.linuxiarz.pl/`
The Debian 13+ source bootstrap remains available as an optional source/development path.
## Managed ACME bootstrap diagnostics
The managed ACME runtime now distinguishes private-environment creation, pip preparation, Certbot package installation and final plugin validation failures instead of collapsing all of them into one generic setup error. The selected stage is also emitted as a warning when no distribution Certbot fallback is available.
On distributions enforcing PEP 668, the application-local `virtualenv` bootstrap first uses normal `pip --target`; if the distribution rejects that operation, RustProxy retries with `--break-system-packages` while still writing exclusively into the private bootstrap directory under the RustProxy data tree. This does not install or modify packages in the distribution Python environment.
## Restore ownership and production data directory
The production CLI default is `/var/lib/rustproxy`; `dev.sh` explicitly sets `RUST_PROXY_DATA_DIR` to repository-local `./data`. During restore, RustProxy records the UID/GID of an existing target data directory and recursively applies that ownership to the validated staging tree before the atomic rename. This prevents a root-run restore from converting service data to `root:root`; symlinks are ownership-adjusted with `lchown` and are not followed.
## GeoIP background maintenance
`Config.geoip_updates` defaults to enabled with the sapics latest/checksum release URLs, 256 MiB maximum file size and a 24-hour interval. The worker starts after successful listener binding, attempts immediately, compares publisher SHA-256 files with local files and downloads only stale assets. Database payloads are streamed to private staging files with a transfer limit and incremental SHA-256, the staged pair is parsed before publication, and the in-memory GeoIP database is atomically replaced without a proxy restart. Failures are fail-soft and retry later. The worker and managed manual updater share `.geoip-update.lock`; that runtime lock is excluded from backups.
## Listener, WebSocket and control-plane startup fixes
Explicit CLI/systemd HTTP/HTTPS binds now replace the corresponding canonical saved `http`/`https` bind rather than appending a second listener that can leave `:80`/`:443` active. Strict WebSocket response validation accepts `101` with no `Content-Length` or exactly `Content-Length: 0`, while rejecting positive lengths and any `Transfer-Encoding`. The release-version checker starts only after all sockets bind and logs polling failures as warnings, keeping network/DNS availability outside the proxy startup success path.
## Scaled administration sidebar
The desktop shell/sidebar height compensates for CSS `zoom` via `100vh / --ui-scale`. At scales below 100%, the flex sidebar therefore still spans the visual viewport and its bottom runtime information remains at the bottom edge. Mobile rules continue to use automatic height.
## Source-tool separation
0.27.3 also separates application-coupled scripts from auxiliary tooling without changing proxy behavior. `scripts/runtime`, `scripts/build`, `scripts/deploy` and `scripts/migration` contain code directly tied to application/build/deployment workflows. Compiler-independent checks, mocks, audit probes and the retired Python backup helper moved to `tools/`. OPNsense and RouterOS helpers moved to `integrations/`.
The shell entry points, `build.rs`, embedded ACME source path, source upgrader, deterministic source archive and release checks were updated together so the relocation is part of the same 0.27.3 development release.
-39
View File
@@ -1,39 +0,0 @@
# RustProxy 0.28.0 implementation note
## Scope
0.28.0 extends the 0.27.3 maintenance release with operator-controlled GeoIP refresh, clearer listener status, cache `Via` metadata, narrower cache invalidation and a vhost Security header-rule layout repair. SQLite schema 8 and COLD cache format 3 are unchanged.
## GeoIP manual refresh and hot reload
`POST /api/geoip/update` is an authenticated/CSRF-protected control-plane action used by **Settings -> GeoIP updates -> Download now**. It invokes the same updater function as the background worker. The updater consumes direct source URLs, with optional publisher SHA-256 URLs per file, streams payloads into a private staging directory under the configured size limit, validates the selected database format, publishes only content whose local SHA-256 changed, and replaces the in-memory database only after validation succeeds. The UI action works when scheduled updates are disabled.
CSV mode uses separate IPv4 and IPv6 range files. MMDB mode uses one native MaxMind DB for both families and recognizes standard `country.iso_code` / `registered_country.iso_code` records plus a top-level `country_code` compatibility record or a root two-letter country string. The combined sapics `user-country.mmdb` is the default MMDB source. When the publisher checksum URL is empty, no remote checksum is required; structural validation and local content hashing remain mandatory. An updater-disabled MMDB configuration may leave the URL empty and manage `<data-dir>/geoip/country.mmdb` locally; the manual action then validates/reloads that local file.
The automatic worker, manual UI action and managed upgrade path serialize through `.geoip-update.lock`. Publishing uses the application's existing `ArcSwap<Database>`: a request that already owns the prior `Arc` remains memory-safe, while subsequent lookups load the new database. The request path uses this slot for country access rules, country rate limiting and optional country logging; administration and Prometheus status read the same slot. No process restart is needed.
## Listener status UX
Settings no longer compresses active endpoints into one `HTTP ... | HTTPS ...` sentence. Each active endpoint is rendered as a separate compact card with protocol and address plus badges for the protocols that are actually enabled on the running listener: `H1` is always present, TLS listeners add `H2` when ALPN HTTP/2 is enabled and `H3/UDP` when QUIC is enabled, while cleartext listeners add `H2C` when prior-knowledge HTTP/2 is enabled. Saved-listener restart semantics are unchanged.
## Cache Via and invalidation
`cache.via_header` is an optional string. Its default is `RustProxy`, producing `Via: RustProxy`; `null` disables RustProxy's Via value. Existing upstream Via fields are preserved and RustProxy appends its value. Validation bounds the configured field value to 256 valid HTTP-header bytes.
Cache compatibility hashing now normalizes request-only bypass fields and response-only cache diagnostics (`X-Cache`, hit counters, `Cache-Status`, resident age and `Via`). These changes apply on subsequent requests without discarding HOT/COLD objects. Representation/origin-affecting configuration remains in the compatibility identity and continues to invalidate incompatible objects.
## Vhost Security header rules
The request/response header-rule editor now uses the same structured header editor layout as location rules. Delete is contained in the rule heading and aligned to the right, while Operation, Name and Value remain in the field grid below it.
## Compatibility
- SQLite schema: **8**, unchanged.
- COLD cache format: **3**, unchanged.
- Existing saved configurations remain valid because `Settings` uses serde defaults; an absent `cache.via_header` becomes `RustProxy`.
- Legacy 0.27.3 release/checksum base URLs are accepted during configuration migration. The old default checksum base migrates to optional/no publisher checksum; custom legacy bases are translated into per-file URLs.
### Startup ACME runtime reporting
The human-readable startup banner no longer prints the initial `checking/preparing` ACME Python state, because that value can race the asynchronous runtime probe. When the probe finishes, RustProxy emits an `INFO` event named `ACME runtime check complete` containing the final status, mode, Python/pip/Certbot availability and managed-environment path.
-46
View File
@@ -1,46 +0,0 @@
# RustProxy 0.28.1 implementation note
## Scope
0.28.1 adds first-class support for certificate/key pairs that are managed outside the RustProxy data directory, with Certbot's `/etc/letsencrypt/live/...` layout as the primary operator case. It retains the managed filename mode under `<data-dir>/certs`, all 0.28.0 GeoIP/listener/cache/UI changes, SQLite schema 8 and COLD cache format 3.
## Certificate file modes
Manual certificate entries use the existing `cert_file` and `key_file` fields. Their addressing mode is derived without adding a database/configuration schema field:
- two simple filenames, for example `app.pem` and `app.key`, are resolved inside `<data-dir>/certs`;
- two absolute paths, for example `/etc/letsencrypt/live/example.com/fullchain.pem` and `/etc/letsencrypt/live/example.com/privkey.pem`, are read in place;
- mixed relative/absolute pairs are rejected.
Managed filenames retain the existing traversal protection and `O_NOFOLLOW` open behavior. Absolute external paths are bounded to 4096 bytes. Each external path is canonicalized at load/reload time, which intentionally resolves Certbot `live/` symlinks to the current generation under `archive/`; the resolved final file is then opened with `O_NOFOLLOW`, must be a regular file and remains subject to the 128 KiB PEM bound. Managed private keys remain owner-only. External private keys may grant read-only group access (for example `0640`) so the unprivileged `rustproxy` service can use a restricted TLS group, but they may not be executable, group-writable or world-accessible; every parent directory must also be traversable by the service identity.
The certificate/key pair is still parsed by rustls and must match. Config publication remains atomic: invalid or unreadable replacement material leaves the previous immutable TLS snapshot active.
## Renewal wrapper workflow
For an externally managed certificate, renew the files using Certbot/acme.sh/another issuer and then call:
```text
POST /api/certificates/{certificate_id}/reload
```
The endpoint re-reads configured files and republishes the complete immutable TLS/routing snapshot without changing the saved configuration revision. New HTTP/1.1, HTTP/2 and HTTP/3 handshakes use the refreshed certificate; established TLS sessions are not renegotiated. A selected administration certificate remains startup-fixed and reports that process restart is required.
Scoped Certificate API credentials can call the targeted reload endpoint. `POST /api/certificates/deploy` remains a PEM-body deployment API and deliberately refuses to overwrite an ID that is configured with absolute external file paths; this prevents a renewal owner from silently switching storage models.
## Backups
RustProxy full backups contain managed certificate material under `<data-dir>/certs`. Absolute external certificate/key files are not copied into the backup. Their configured paths remain in SQLite/configuration, so the files or mounts must exist separately on the restored host before TLS can be compiled successfully.
## Administration UI
Certificates -> Generate/Add -> Existing server files now has an explicit file-location selector:
- **RustProxy certificate directory (`<data-dir>/certs`)** accepts filenames only;
- **External files (absolute paths)** accepts complete server paths and shows a Certbot example.
English and Polish provider hints also explain that ACME modes generate/manage their own material, Import copies browser-selected PEM files into RustProxy storage, and Existing files references server-side material. Installed-certificate cards mark absolute-path entries as external.
## Compatibility
No migration is required from 0.28.0. Existing filename-only certificate entries keep their prior meaning. Existing deploy/upload/ACME flows continue to create RustProxy-managed filenames and never place PEM key bytes in configuration responses.
-45
View File
@@ -1,45 +0,0 @@
# RustProxy 0.28.2 implementation note
## External certificate path hardening
0.28.2 keeps the 0.28.1 direct-file workflow for Certbot/acme.sh certificates but changes validation from an exact-mode expectation to a security-policy check. POSIX ACLs are supported: a private key may be owner-readable or group/ACL-readable by the `rustproxy` service identity. RustProxy rejects a private key that is world-readable and rejects group/world write access on every external certificate/key file. Execute bits alone are not treated as key disclosure.
For an absolute external path RustProxy validates the configured parent chain before resolving the final file, resolves the final Certbot-style `live/*.pem` symlink, validates the resolved parent chain again, and opens the final regular file with `O_NOFOLLOW`. On Unix, parent directories must be owned by root or the running RustProxy service user and must not be group/world-writable. Root-owned sticky ancestors such as `/tmp` remain accepted for controlled test/custom trees.
This closes the writable-parent/path-redirection gap while preserving Certbot renewal semantics: after `certbot renew`, a targeted reload follows the current `live/ -> archive/` generation and publishes a new immutable TLS snapshot only after the pair validates.
## ACL example
The packaged systemd unit keeps RustProxy writable only in its data/log directories and explicitly marks `/etc/letsencrypt` and `/etc/ssl` read-only when those paths exist:
```ini
ReadWritePaths=/var/lib/rustproxy /var/log/rustproxy
ReadOnlyPaths=-/etc/letsencrypt -/etc/ssl
```
`ReadOnlyPaths` does not grant Unix access. For traversal-only access to protected directories and read access to the actual PEM files, use narrow POSIX ACLs. Example:
```bash
setfacl -m u:rustproxy:--x /etc/ssl/
setfacl -m u:rustproxy:--x /etc/letsencrypt/ /etc/letsencrypt/live/ /etc/letsencrypt/archive/
setfacl -m u:rustproxy:--x /etc/letsencrypt/live/example.com/ /etc/letsencrypt/archive/example.com/
setfacl -m u:rustproxy:r-- /etc/letsencrypt/archive/example.com/fullchain*.pem /etc/letsencrypt/archive/example.com/privkey*.pem
setfacl -m d:u:rustproxy:r-- /etc/letsencrypt/archive/example.com/
```
A default ACL on the archive lineage is useful, but a renewer may recreate a private key as `0600` and mask inherited named-user access. A renewal hook should therefore reapply `u:rustproxy:r--` to the current resolved `fullchain.pem` and `privkey.pem` targets before calling the reload API. Do not grant `w` to the `rustproxy` identity on the Certbot tree.
## Least-privilege Certificate API
SQLite schema 9 adds `certificate_api_grants`. New Certificate API credentials require 1-32 explicit certificate IDs and are reload-only unless **Also allow deploy** is enabled. Token authentication is accepted only for targeted `POST /api/certificates/{certificate_id}/reload`; the global `POST /api/certificates/reload` remains administrator-session only. Deploy tokens must have both explicit deploy permission and an allowlisted request certificate ID.
Credentials created before 0.28.2 migrate to reload-only compatibility grants covering all certificate IDs (`*`). Rotate those credentials in Settings -> Certificate API to replace the broad compatibility grant with explicit IDs.
Normal administrator session + CSRF access remains unchanged. Certificate API tokens still cannot read configuration or call unrelated administration APIs, and non-loopback token use still requires HTTPS.
## Compatibility
- SQLite schema: 9 (additive migration from schema 8).
- Saved proxy configuration: unchanged from 0.28.1.
- COLD cache format: 3, unchanged.
- External certificate paths/backups: same semantics as 0.28.1; external PEM bytes are not copied into RustProxy backups.
-41
View File
@@ -1,41 +0,0 @@
# RustProxy 0.29.0 implementation note
RustProxy 0.29.0 adds narrowly scoped redirect, health-check, cache and stored-log controls without changing SQLite schema 9 or Cold Cache format 3. Existing 0.28.2 configuration remains compatible because all new configuration fields are defaulted.
## Canonical www/non-www redirect
A vhost can enable `www_redirect`. The canonical exact DNS name is chosen in the UI from `frontend.hosts` and persisted as optional `www_redirect_target`; it defaults to the first hostname for older configurations. RustProxy derives its direct companion (`example.com` <-> `www.example.com`), claims that host on the same listeners and returns `308 Permanent Redirect` to the canonical name while preserving the request path/query. Wildcard/IP first hosts are rejected for this option. If `force_ssl` is also enabled, cleartext companion requests redirect directly to the HTTPS canonical URL.
Certificate assignment includes both routing names, but certificate validity is not synthesized: operators must use a certificate whose SANs cover the canonical and companion names.
## Custom location redirects
Custom locations have a backend-less `redirect` action with status `301`, `302`, `303`, `307` or `308`, an absolute HTTP(S) target, optional redirect rewrite regex and optional query preservation. The rewrite regex is separate from the location match regex, so a configuration equivalent to an Nginx `location` followed by `rewrite ^/(.*)$ .../$1 permanent` can capture the complete request path independently of the location selector.
Capture expansion is allowed in the target path/query but not in the scheme/authority. This prevents client-controlled captures from turning an approved redirect rule into arbitrary-host redirection.
## Health-check Host selection
HTTP backend probes already send an HTTP/1.1 Host header. In 0.29.0 an empty `health_check.host` automatically resolves to the first exact DNS hostname of the vhost. This makes IP-address backends with name-based virtual hosting work without repeating the public hostname in the health settings. An explicitly configured health-check Host remains authoritative; if the vhost has no exact DNS hostname, the existing backend-host fallback remains.
## Per-vhost forced cache
`cache.unsafe_force` is available only in a vhost override and is disabled by default. Matching GET/HEAD request targets may bypass request-side query, Cookie, Authorization and client cache-control exclusions. Protocol-safety exclusions remain active. Cookie and Authorization are added to variant identity for forced objects.
Response policy remains authoritative: responses with `Set-Cookie`, `private`, `no-store`, `Vary: *`, partial framing and the other normal non-cacheable conditions are not stored. This option is intentionally labelled dangerous and must be enabled per vhost with explicit target prefixes; it is not a global switch.
## Rotated gzip logs
Stored Logs discovers logrotate-style `.gz` siblings of configured combined access/error logs and `.jsonl.*.gz` rotations in the configured per-vhost directory. Only regular non-symlink files derived from configured locations are eligible. Compressed input and decompressed output are bounded, and only the existing bounded tail is parsed/searched.
## Local page status and URL-query regression coverage
The existing static/maintenance page model can serve its HTML body with an explicit status such as `503 Service Unavailable`; 0.29.0 adds regression coverage for that operator workflow.
Valid nested URL query parameters are preserved by upstream URI construction. Literal or percent-encoded backslashes remain rejected by the ambiguous-path guard. A quoted curl URL must therefore use normal `?`, `=` and `&` separators without backslashes.
## Verification scope
The release source includes Rust unit/integration regressions for canonical redirects, custom redirect capture/query behavior, automatic health-check Host selection and explicit override precedence, forced-cache request policy and target matching, gzip Stored Logs, maintenance-page 503 and nested URL query preservation. Rust-independent release/source/documentation checks are also retained. See [TEST_REPORT.md](TEST_REPORT.md) for the verification actually performed in the packaging environment.
Multiple hostnames on the same vhost (for example `suba.local` and `subb.local`) receive identical proxy behavior without implicit redirects.
-127
View File
@@ -1,127 +0,0 @@
# RustProxy 0.29.1 implementation note
RustProxy 0.29.1 adds explicit Nginx-style internal error routing without parsing Nginx configuration, plus multipart byte ranges and optional PHP `PATH_INFO` support. SQLite remains schema 9 and Cold Cache remains format 3.
## Internal error fallback
A vhost main source and each Custom location can define:
```json
"error_fallback": {
"target": "redirect-check",
"statuses": [404]
}
```
`target` is the stable `id` of an enabled Custom location in the same vhost with `"internal": true`. Internal locations are retained in the compiled route table but never participate in normal public URI matching. The supported intercepted status set is deliberately bounded to `403`, `404`, `500`, `502`, `503` and `504`; omitting `statuses` defaults to `404`.
The dispatch is internal, not a `3xx` client redirect. RustProxy rebuilds the fallback request with the original HTTP method, URI, query string, HTTP version, request headers and the existing transport/request context. The target location then executes through the normal vhost/location path, including ACL/authentication, header rules, cache policy, compression and response processing. Vhost rate limiting is not charged a second time for the same client request.
For requests with a body, RustProxy can replay a body only when its exact size is known and no larger than 8 MiB. Such a request is buffered once so the same bytes can be sent to the fallback target. Unknown-size or larger streaming bodies continue to the original target, but an error fallback cannot replay them; the original error response is retained.
Configuration validation rejects missing/public fallback targets and recursive fallback cycles. A runtime depth limit of 8 internal dispatches remains as a second guard against loops after hot-reload/state inconsistencies.
Configured upstream response statuses are intercepted before normal response finalization and cache publication. RustProxy-generated reverse-proxy failures also participate where applicable: missing backend (`500`), unavailable/capacity (`503`), connect failure (`503`), invalid upstream response (`502`) and response timeout (`504`). A configured stale cached response still wins before transport-error fallback.
## Example: static files -> application -> redirector
The following vhost fragment expresses the same request flow as an Nginx `try_files`/`error_page` chain, without an HTTP redirect between stages:
```json
{
"source": {
"type": "web_server",
"web_service_id": "www-linuxiarz"
},
"error_fallback": {
"target": "to8000",
"statuses": [404]
},
"backends": [
{
"id": "app",
"url": "http://127.0.0.1:8000",
"enabled": true,
"allow_private": true,
"address_allowlist": ["127.0.0.1/32"]
},
{
"id": "redirector",
"url": "http://127.0.0.1:9000",
"enabled": true,
"allow_private": true,
"address_allowlist": ["127.0.0.1/32"]
}
],
"routes": [
{
"id": "to8000",
"internal": true,
"backend_id": "app",
"source": {"type": "reverse_proxy"},
"error_fallback": {
"target": "redirect-check",
"statuses": [404]
}
},
{
"id": "redirect-check",
"internal": true,
"backend_id": "redirector",
"source": {"type": "reverse_proxy"},
"cache": false
}
]
}
```
The effective flow is:
```text
public request
-> Web Server
2xx/3xx -> return normally
404 -> @to8000 (internal)
-> backend app
non-404 -> return normally
404 -> @redirect-check (internal)
-> redirector backend
```
Normal public Custom locations can still route `/stats`, `/user-info`, `/health`, RBL files or other paths directly before the main source is used. An internal location's `path_prefix` is not used for public matching; its `id` is the fallback name.
## Multipart byte ranges
The Web Server now accepts comma-separated byte ranges such as:
```http
Range: bytes=0-99,200-299
```
One satisfiable range retains the existing single-part `206` response. Multiple satisfiable ranges produce a streaming `multipart/byteranges` response with per-part `Content-Type` and `Content-Range`. At most 16 ranges are accepted. Malformed or entirely unsatisfiable range sets return `416`. Multipart responses use the identity file representation rather than `.br`/`.gz` sidecars so the multipart MIME framing and `Content-Encoding` semantics cannot conflict.
## PHP PATH_INFO
Web Service PHP configuration gains the default-off field:
```json
"php": {
"enabled": true,
"path_info": true
}
```
When enabled, RustProxy may resolve a request such as `/index.php/foo/bar` by finding the longest existing configured PHP script prefix under the Web Service document root. FastCGI then receives, conceptually:
```text
SCRIPT_FILENAME=/document/root/index.php
SCRIPT_NAME=/index.php
PATH_INFO=/foo/bar
REQUEST_URI=/index.php/foo/bar
```
The split uses the same document-root confinement and PHP file checks as normal script resolution. It is disabled by default because it changes URL-to-script resolution semantics.
## Compatibility
No SQLite migration is required. Existing vhosts, locations and Web Services deserialize unchanged because `internal`, `error_fallback` and PHP `path_info` use backward-compatible defaults. Existing single-range responses are retained.
+1 -1
View File
@@ -42,7 +42,7 @@ The editor warns before applying the rename.
The main vhost `source` may define `error_fallback`, and each Custom location may do the same. A fallback references the `id` of an enabled `internal` Custom location in the same vhost. Supported statuses are `403`, `404`, `500`, `502`, `503` and `504`; omitted status lists default to `404`. Internal targets are not reachable by ordinary path matching.
Configured upstream/Web Server failures are intercepted before final response/cache publication. The original request method, URI/query and context are internally dispatched to the target, which then follows normal vhost/location processing. Validation rejects fallback cycles and runtime dispatch is depth-limited. See [V0.29.1_IMPLEMENTATION.md](V0.29.1_IMPLEMENTATION.md) for body replay bounds and an Nginx-style static -> app -> redirector example.
Configured upstream/Web Server failures are intercepted before final response/cache publication. The original request method, URI/query and context are internally dispatched to the target, which then follows normal vhost/location processing. Validation rejects fallback cycles and runtime dispatch is depth-limited. See [Custom Locations](CUSTOM_LOCATIONS.md) for the active internal-fallback behavior.
## Per-backend health monitoring (0.29.7)
+4 -216
View File
@@ -177,7 +177,10 @@ pub(super) static READER_DOCUMENTS: &[(&str, &str, &str)] = &[
(
"docs/PHP_FPM_ISOLATION.md",
"guides",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/PHP_FPM_ISOLATION.md")),
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/PHP_FPM_ISOLATION.md"
)),
),
(
"docs/PROMETHEUS.md",
@@ -244,13 +247,6 @@ pub(super) static READER_DOCUMENTS: &[(&str, &str, &str)] = &[
"guides",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/STREAMS.md")),
),
reader_doc!("docs/IMPLEMENTATION_0.31.7.md", "technical"),
reader_doc!("docs/IMPLEMENTATION_0.31.6.md", "technical"),
reader_doc!("docs/IMPLEMENTATION_0.31.5.md", "technical"),
reader_doc!("docs/IMPLEMENTATION_0.31.4.md", "technical"),
reader_doc!("docs/IMPLEMENTATION_0.31.3.md", "technical"),
reader_doc!("docs/IMPLEMENTATION_0.31.1.md", "technical"),
reader_doc!("docs/IMPLEMENTATION_0.31.0.md", "technical"),
(
"docs/TEST_REPORT.md",
"guides",
@@ -271,214 +267,6 @@ pub(super) static READER_DOCUMENTS: &[(&str, &str, &str)] = &[
"technical",
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/docs/TRANSPORT.md")),
),
(
"docs/V0.21.0_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.21.0_AUDIT.md"
)),
),
(
"docs/V0.22.0_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.22.0_AUDIT.md"
)),
),
(
"docs/V0.22.1_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.22.1_AUDIT.md"
)),
),
(
"docs/V0.22.2_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.22.2_AUDIT.md"
)),
),
(
"docs/V0.22.3_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.22.3_AUDIT.md"
)),
),
(
"docs/V0.23.0_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.23.0_AUDIT.md"
)),
),
(
"docs/V0.23.1_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.23.1_AUDIT.md"
)),
),
(
"docs/V0.23.2_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.23.2_AUDIT.md"
)),
),
(
"docs/V0.23.3_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.23.3_AUDIT.md"
)),
),
(
"docs/V0.24.0_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.24.0_AUDIT.md"
)),
),
(
"docs/V0.24.1_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.24.1_AUDIT.md"
)),
),
(
"docs/V0.24.3_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.24.3_AUDIT.md"
)),
),
(
"docs/V0.24.4_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.24.4_AUDIT.md"
)),
),
(
"docs/V0.24.5_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.24.5_AUDIT.md"
)),
),
(
"docs/V0.24.6_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.24.6_AUDIT.md"
)),
),
(
"docs/V0.24.7_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.24.7_AUDIT.md"
)),
),
(
"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"
)),
),
reader_doc!("docs/V0.29.1_IMPLEMENTATION.md", "technical"),
reader_doc!("docs/V0.29.0_IMPLEMENTATION.md", "technical"),
reader_doc!("docs/V0.28.2_IMPLEMENTATION.md", "technical"),
reader_doc!("docs/V0.28.1_IMPLEMENTATION.md", "technical"),
reader_doc!("docs/V0.28.0_IMPLEMENTATION.md", "technical"),
reader_doc!("docs/V0.27.3_IMPLEMENTATION.md", "technical"),
reader_doc!("docs/V0.27.2_IMPLEMENTATION.md", "technical"),
reader_doc!("docs/V0.27.1_IMPLEMENTATION.md", "historical"),
reader_doc!("docs/V0.27.0_IMPLEMENTATION.md", "technical"),
reader_doc!("docs/V0.26.7_IMPLEMENTATION.md", "technical"),
reader_doc!("docs/V0.26.6_IMPLEMENTATION.md", "technical"),
reader_doc!("docs/V0.26.5_IMPLEMENTATION.md", "technical"),
reader_doc!("docs/V0.26.4_IMPLEMENTATION.md", "technical"),
reader_doc!("docs/V0.26.3_IMPLEMENTATION.md", "technical"),
reader_doc!("docs/V0.26.2_IMPLEMENTATION.md", "technical"),
reader_doc!("docs/V0.26.1_IMPLEMENTATION.md", "technical"),
(
"docs/V0.25.5_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.25.5_AUDIT.md"
)),
),
(
"docs/V0.25.4_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.25.4_AUDIT.md"
)),
),
(
"docs/V0.25.3_AUDIT.md",
"audits",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/docs/V0.25.3_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",
+2 -2
View File
@@ -67,8 +67,8 @@
assert!(documents.iter().any(|document| document["path"] == "docs/README.md"));
assert!(documents.iter().any(|document| document["path"] == "docs/WEB_SERVER.md"));
assert!(documents.iter().any(|document| document["path"] == "docs/PHP_FPM.md"));
assert!(documents.iter().any(|document| document["path"] == "docs/V0.25.0_AUDIT.md"));
assert!(documents.iter().any(|document| document["path"] == "docs/V0.25.0_SECURITY_PREPROD_AUDIT.md"));
assert!(documents.iter().any(|document| document["path"] == "docs/SECURITY_AUDIT_0.31.0.md"));
assert!(documents.iter().any(|document| document["path"] == "docs/TEST_REPORT.md"));
store.close().await;
}
#[tokio::test]
+5 -5
View File
@@ -73,7 +73,7 @@ for marker in [
]:
assert marker in update_check_source, marker
assert 'pub last_error:' not in update_check_source, "update-check errors must not change dashboard/API state"
for name in ["README.md", "ROADMAP.md", "AGENTS.md", "CHANGELOG.md", "UPDATE_CHECK.toml", "Dockerfile", ".dockerignore", "docs/TEST_REPORT.md", "docs/V0.24.8_AUDIT.md", "docs/V0.24.7_AUDIT.md", "docs/V0.24.6_AUDIT.md", "docs/V0.24.5_AUDIT.md", "docs/V0.24.1_AUDIT.md", "docs/V0.24.0_AUDIT.md", "docs/V0.23.2_AUDIT.md", "docs/V0.23.1_AUDIT.md", "docs/V0.22.3_AUDIT.md", "docs/LIVE_LOGS.md", "docs/README.md", "docs/API.md", "docs/ARCHITECTURE.md", "docs/WEB_SERVER.md", "docs/PHP_FPM.md", "docs/VHOSTS.md", "docs/STREAMS.md", "docs/REDIRECTS.md", "docs/CONTAINER.md", "docs/NPM_MIGRATION.md", "docs/DOCKER_DISCOVERY.md", "docker-agent/main.go", "docker-agent/main_test.go", "docker-agent/rustproxy-docker-agent.service", "docker-agent/docker-agent.env.example", "docker-agent/install.sh", "src/docker.rs", "src/admin/web/docker.js", "scripts/migration/migrate_npm.py", "docs/CUSTOM_LOCATIONS.md", "nagios/README.md", "nagios/check_rustproxy.py", "nagios/check_rustproxy_api", "nagios/check_rustproxy_backends", "nagios/check_rustproxy_latency", "nagios/check_rustproxy_queues", "src/admin/web/error_401.html", "src/admin/web/error_404.html", "src/admin/web/streams.js", "src/admin/web/redirects.js", "src/admin/web/host404.js", "docs/NOT_FOUND_HOSTS.md", "grafana/rustproxy-overview.json", "grafana/README.md"]:
for name in ["README.md", "ROADMAP.md", "AGENTS.md", "CHANGELOG.md", "UPDATE_CHECK.toml", "Dockerfile", ".dockerignore", "docs/TEST_REPORT.md", "docs/LIVE_LOGS.md", "docs/README.md", "docs/API.md", "docs/ARCHITECTURE.md", "docs/WEB_SERVER.md", "docs/PHP_FPM.md", "docs/VHOSTS.md", "docs/STREAMS.md", "docs/REDIRECTS.md", "docs/CONTAINER.md", "docs/NPM_MIGRATION.md", "docs/DOCKER_DISCOVERY.md", "docker-agent/main.go", "docker-agent/main_test.go", "docker-agent/rustproxy-docker-agent.service", "docker-agent/docker-agent.env.example", "docker-agent/install.sh", "src/docker.rs", "src/admin/web/docker.js", "scripts/migration/migrate_npm.py", "docs/CUSTOM_LOCATIONS.md", "nagios/README.md", "nagios/check_rustproxy.py", "nagios/check_rustproxy_api", "nagios/check_rustproxy_backends", "nagios/check_rustproxy_latency", "nagios/check_rustproxy_queues", "src/admin/web/error_401.html", "src/admin/web/error_404.html", "src/admin/web/streams.js", "src/admin/web/redirects.js", "src/admin/web/host404.js", "docs/NOT_FOUND_HOSTS.md", "grafana/rustproxy-overview.json", "grafana/README.md"]:
assert (root / name).is_file(), name
npm_doc = (root / "docs/NPM_MIGRATION.md").read_text(encoding="utf-8")
for marker in ["scripts/migration/migrate_npm_core.py", "scripts/migration/migrate_npm_api.py", '-v "$PWD/scripts/migration:/rustproxy-migration:ro"', "python /rustproxy-migration/migrate_npm.py"]:
@@ -1243,7 +1243,7 @@ assert 'rfc8441_requires_an_h2_capable_listener_and_h2c_is_explicit' in transpor
assert 'h2c' in integrations_0221 and 'H2C' in integrations_0221
transport_doc = (root / "docs/TRANSPORT.md").read_text()
assert 'prior knowledge' in transport_doc and 'Upgrade: h2c' in transport_doc and 'stream 1' in transport_doc
assert (root / "docs/V0.24.1_AUDIT.md").is_file() and (root / "docs/V0.24.0_AUDIT.md").is_file() and (root / "docs/V0.23.2_AUDIT.md").is_file() and (root / "docs/V0.23.1_AUDIT.md").is_file() and (root / "docs/V0.23.0_AUDIT.md").is_file() and (root / "docs/V0.22.3_AUDIT.md").is_file()
# Historical V0.22-0.24 audits are not required for active transport tests.
# 0.22.3 replay-safe retry and ordinary H2 upstream contracts.
balance_0222 = rust_source(root, "src/balance.rs")
@@ -1541,7 +1541,7 @@ for marker_0291 in ['rust_proxy_vhost_error_fallback_triggered_total', 'rust_pro
assert marker_0291 not in prometheus_0291, marker_0291
assert 'unavailable: host.transient_unavailable && host.source.is_reverse_proxy()' in rust_source(root, "src/config/routing.rs")
assert 'filter(|_| needs_proxy_pool)' in rust_source(root, "src/config/routing.rs")
assert (root / 'docs/V0.29.1_IMPLEMENTATION.md').is_file()
assert (root / 'docs/CUSTOM_LOCATIONS.md').is_file()
assert '"ErrorFallback"' in openapi and '"path_info"' in openapi and '"internal"' in openapi
# Retained 0.29.0 redirect/health/cache/gzip-log contracts.
@@ -1566,7 +1566,7 @@ assert 'redirect_location' in rust_source(root, "src/config/snapshot.rs") and 'l
assert 'pub struct UnsafeForce' in cache_entry_0290 and 'unsafe_force: UnsafeForce' in cache_entry_0290
assert 'forced_request_policy' in cache_request_0290 and 'cache_miss_forced' in rust_source(root, "src/proxy/mod.rs")
assert 'compressed: bool' in file_logs_0290 and 'GzDecoder' in file_logs_io_0290
for path_0290 in ['src/vhost/redirect.rs', 'docs/V0.29.0_IMPLEMENTATION.md']:
for path_0290 in ['src/vhost/redirect.rs']:
assert (root / path_0290).is_file(), path_0290
assert 'static_vhost_page_can_serve_html_with_503_status' in rust_source(root, "src/pages.rs")
assert 'upstream_uri_preserves_nested_url_query_without_rejecting_valid_separators' in rust_source(root, "src/proxy/mod.rs")
@@ -1631,7 +1631,7 @@ for marker_0282 in [
]:
assert marker_0282 in html or marker_0282 in runtime_web_0280 or marker_0282 in (root / 'langs/en_US.json').read_text(), marker_0282
assert 'Zewnętrzne ścieżki plików' in (root / 'langs/pl_pl.json').read_text()
assert 'setfacl -m u:rustproxy:--x /etc/ssl/' in (root / 'docs/V0.28.2_IMPLEMENTATION.md').read_text()
assert 'setfacl -m u:rustproxy:--x /etc/ssl/' in (root / 'docs/INSTALL.md').read_text()
print("PASS: 0.31.7 manifest/package, shared system-statistics/runtime metadata, Settings logging, retained H2, external-packaging/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-v9 migrations.")
print("NOT VERIFIED: Rust parsing, types, compilation, linking or runtime behavior. Run ./dev.sh test.")
+4 -21
View File
@@ -14,25 +14,6 @@ required = [
DOCS / "README.md",
DOCS / "WEB_SERVER.md",
DOCS / "TODO.md",
DOCS / "V0.29.1_IMPLEMENTATION.md",
DOCS / "V0.29.0_IMPLEMENTATION.md",
DOCS / "V0.28.2_IMPLEMENTATION.md",
DOCS / "V0.28.1_IMPLEMENTATION.md",
DOCS / "V0.27.3_IMPLEMENTATION.md",
DOCS / "V0.27.2_IMPLEMENTATION.md",
DOCS / "V0.27.1_IMPLEMENTATION.md",
DOCS / "V0.27.0_IMPLEMENTATION.md",
DOCS / "V0.26.7_IMPLEMENTATION.md",
DOCS / "V0.25.5_AUDIT.md",
DOCS / "V0.25.4_AUDIT.md",
DOCS / "V0.25.3_AUDIT.md",
DOCS / "V0.25.2_AUDIT.md",
DOCS / "V0.25.1_AUDIT.md",
DOCS / "V0.25.0_AUDIT.md",
DOCS / "IMPLEMENTATION_0.31.5.md",
DOCS / "IMPLEMENTATION_0.31.7.md",
DOCS / "IMPLEMENTATION_0.31.3.md",
DOCS / "IMPLEMENTATION_0.31.1.md",
DOCS / "TEST_REPORT.md",
DOCS / "CHANGELOG_TECHNICAL_ARCHIVE.md",
DOCS / "ROADMAP_HISTORY.md",
@@ -41,10 +22,10 @@ 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.5_AUDIT.md", "V0.25.4_AUDIT.md", "V0.25.3_AUDIT.md", "V0.25.2_AUDIT.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", "SECURITY_AUDIT_0.31.0.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")):
if document.name == "README.md":
continue
assert document.name in index, f"documentation index does not classify {document.name}"
@@ -63,6 +44,8 @@ assert 'docs-content.json' not in documentation_rs and 'documentation_bundle' no
for document in sorted(DOCS.glob("*.md")):
marker = f'"{document.relative_to(ROOT).as_posix()}"'
assert marker in documentation_rs, f"Reader does not embed {document.relative_to(ROOT)}"
for path in set(re.findall(r'"(docs/[A-Za-z0-9_.-]+\.md)"', documentation_rs)):
assert (ROOT / path).is_file(), f"Reader embeds missing document: {path}"
assert '"/docs/content.json"' in admin
readme = (ROOT / "README.md").read_text(encoding="utf-8")
+14 -1
View File
@@ -3,6 +3,7 @@
from __future__ import annotations
import argparse
import hashlib
import re
from pathlib import Path
import sys
@@ -73,9 +74,21 @@ 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/backup/snapshot.rs", "src/upstream.rs", "src/tls.rs", "src/config/internal_routing.rs", "src/proxy/websocket.rs", "src/proxy/error_fallback.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/vhost/error_fallback.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/rates.js", "src/admin/web/vhosts/health.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/session-resume.js", "src/admin/web/app/preferences.js", "src/admin/web/app/config-history.js", "src/admin/web/app/runtime.js", "src/admin/web/app/settings.js", "src/admin/web/management.js", "src/admin/web/drafts.js", "src/pages.rs", "tools/dev/mock_server.py", "tools/dev/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.5_AUDIT.md", "docs/V0.25.4_AUDIT.md", "docs/V0.25.3_AUDIT.md", "docs/V0.25.2_AUDIT.md", "docs/CHANGELOG_TECHNICAL_ARCHIVE.md", "docs/ROADMAP_HISTORY.md", "tools/checks/docs_check.py", "tools/checks/web_server_contracts.py", "tools/checks/rust_source.py", "tools/checks/rust_module_contracts.py", "tools/checks/source_module_contracts.py", "scripts/runtime/acme_provider.py", "src/basic_auth.rs", "scripts/deploy/deploy.py", "scripts/deploy/deploy_core.py", "scripts/deploy/deploy_runtime.py", "scripts/migration/migrate_npm.py", "scripts/migration/migrate_npm_core.py", "scripts/migration/migrate_npm_api.py", "docs/NPM_MIGRATION.md", "docs/STREAMS.md", "install.sh", "install-debian13.sh", "update.sh", "build.rs", "scripts/build/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", "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/config/internal_routing.rs", "src/proxy/websocket.rs", "src/proxy/error_fallback.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/vhost/error_fallback.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/rates.js", "src/admin/web/vhosts/health.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/session-resume.js", "src/admin/web/app/preferences.js", "src/admin/web/app/config-history.js", "src/admin/web/app/runtime.js", "src/admin/web/app/settings.js", "src/admin/web/management.js", "src/admin/web/drafts.js", "src/pages.rs", "tools/dev/mock_server.py", "tools/dev/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/CHANGELOG_TECHNICAL_ARCHIVE.md", "docs/ROADMAP_HISTORY.md", "tools/checks/docs_check.py", "tools/checks/web_server_contracts.py", "tools/checks/rust_source.py", "tools/checks/rust_module_contracts.py", "tools/checks/source_module_contracts.py", "scripts/runtime/acme_provider.py", "src/basic_auth.rs", "scripts/deploy/deploy.py", "scripts/deploy/deploy_core.py", "scripts/deploy/deploy_runtime.py", "scripts/migration/migrate_npm.py", "scripts/migration/migrate_npm_core.py", "scripts/migration/migrate_npm_api.py", "docs/NPM_MIGRATION.md", "docs/STREAMS.md", "install.sh", "install-debian13.sh", "update.sh", "build.rs", "scripts/build/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", "grafana/rustproxy-overview.json"]:
if not (root / path).is_file():
raise ValueError(f"Incomplete RustProxy 0.31.7 source tree: missing {path}")
# The Reader uses compile-time include_str! for its bundled Markdown. A
# dangling registration is a real compiler error, unlike an omitted old
# release audit. Match both reader_doc! and the expanded include_str! form.
registry = root / "src/admin/documentation/documents.rs"
if not registry.is_file():
raise ValueError("Incomplete RustProxy 0.31.7 source tree: missing src/admin/documentation/documents.rs")
embedded_docs = set(re.findall(
r'"(docs/[A-Za-z0-9_.-]+\.md)"', registry.read_text(encoding="utf-8")
))
for doc_path in sorted(embedded_docs):
if not (root / doc_path).is_file():
raise ValueError(f"Missing embedded Reader document: {doc_path}; restore the file or remove its Reader registration")
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.31.7 entry point; extract the complete package, not individual files.")
+2 -8
View File
@@ -654,14 +654,8 @@ def source_contract_smoke() -> None:
transport_0223 = (ROOT / "docs/TRANSPORT.md").read_text(encoding="utf-8")
assert 'Upgrade: h2c' in transport_0223 and 'stream 1' in transport_0223
assert 'legacy HTTP/1.1 Upgrade-to-h2c is not implemented' not in transport_0223
audit_0233 = (ROOT / "docs/V0.24.0_AUDIT.md").read_text(encoding="utf-8")
audit_0232 = (ROOT / "docs/V0.23.2_AUDIT.md").read_text(encoding="utf-8")
audit_0223 = (ROOT / "docs/V0.22.3_AUDIT.md").read_text(encoding="utf-8")
for marker in ["legacy websocket", "origin", "http/1.1", "sec-websocket-accept", "ilo4"]:
assert marker.lower() in audit_0233.lower(), marker
for marker in ["600 samples", "308", "HTTPS", "<data-dir>/certs", "--livelogs"]:
assert marker.lower() in audit_0232.lower(), marker
assert 'external certificate ownership model' in audit_0223.lower() and 'release gate' in audit_0223.lower()
# Obsolete specification audit files were intentionally removed; the
# source/transport assertions above remain the executable regressions.
update_check = rust_source(ROOT, "src/update_check.rs")
update_defaults = tomllib.loads((ROOT / "UPDATE_CHECK.toml").read_text(encoding="utf-8"))
+47
View File
@@ -0,0 +1,47 @@
#!/usr/bin/env python3
"""Verify removed history is optional, but live embedded documents remain required."""
from __future__ import annotations
from pathlib import Path
import shutil
import sys
from tempfile import TemporaryDirectory
from preflight import check
ROOT = Path(__file__).resolve().parents[2]
# A compact 0.31.7 source tree may omit historical version-labelled documents.
# It must still pass the same preflight used by every ./dev.sh command.
assert check(ROOT) == []
with TemporaryDirectory(prefix="rustproxy-pruned-source-") as tmp:
stripped = Path(tmp) / "RustProxy"
shutil.copytree(
ROOT, stripped,
ignore=shutil.ignore_patterns(".git", "target", "data", ".upgrade-backup", "__pycache__"),
)
# This functional document is in the Reader registry, but intentionally not
# in the legacy hand-maintained required-file list. It must still be caught.
reader_doc = stripped / "docs/REFERENCES.md"
assert reader_doc.is_file(), "Missing test fixture: docs/REFERENCES.md"
reader_doc.unlink()
try:
check(stripped)
except ValueError as error:
assert "Missing embedded Reader document: docs/REFERENCES.md" in str(error), error
else:
raise AssertionError("Preflight accepted missing compile-time embedded document")
shutil.copy2(ROOT / "docs/REFERENCES.md", reader_doc)
required_module = stripped / "src/cache/mod.rs"
required_module.unlink()
try:
check(stripped)
except ValueError as error:
assert "missing src/cache/mod.rs" in str(error), error
else:
raise AssertionError("Preflight accepted missing runtime source")
print("PASS: pruned historical documents are optional; embedded Reader files and runtime sources are mandatory.")
-4
View File
@@ -208,10 +208,6 @@ 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.5_AUDIT.md").is_file()
assert (ROOT / "docs/V0.25.4_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()
docs_js = (ROOT / "src/admin/web/docs.js").read_text()