This commit is contained in:
Mateusz Gruszczyński committed 2026-10-10 23:41:54 +02:00
1 parent 24af7478b8
commit 0facfe6f5c
44 files changed
+897 -320

No files matched your search

+1 -1
View File
@@ -2,7 +2,7 @@
Read README.md, ROADMAP.md and docs/TEST_REPORT.md before making changes.
The project name is RustProxy; the Cargo package/binary remains `rust-proxy` for
compatibility. Current source version: 0.31.1.
compatibility. Current source version: 0.31.2.
## Delivery
+5
View File
@@ -3,6 +3,11 @@
> Operator-facing release history. Historical engineering notes are kept in
> [`docs/CHANGELOG_TECHNICAL_ARCHIVE.md`](docs/CHANGELOG_TECHNICAL_ARCHIVE.md).
## 0.31.2
- Refresh core runtime, security, TLS certificate generation, database, Swagger UI and monitoring libraries for current stable versions. Keep persisted data, cache format, configuration, panel behavior, HTTP/3 and proxy routing compatible.
- Move the build toolchain baseline to Rust 1.99. This dependency refresh must pass compilation, runtime and integration checks before production deployment.
## 0.31.1
- Make mobile and reverse-proxied panel reloads reliable by loading the dashboard JavaScript as one ordered resource, waiting for its initialization before fetching settings, and showing a recoverable error if essential resources are unavailable. Preserve the loading indicator and preferences.
Generated
+322 -106
View File
@@ -23,13 +23,28 @@ version = "2.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3"
[[package]]
name = "alloc-no-stdlib"
version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2fb6cfd47bf496ff64095c20eaba0c201404ee38714d4142fcfa1dc334fcc7a"
[[package]]
name = "alloc-stdlib"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e76a019e91224d279006ff972f1e984179a6e9feb050adba6ce8274aef23195"
dependencies = [
"alloc-no-stdlib",
"alloc-no-stdlib 2.0.4",
]
[[package]]
name = "alloc-stdlib"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b5c1865780388bfa186411ab5f247819487fc4864c6e9c3106611fa347586e1"
dependencies = [
"alloc-no-stdlib 3.0.0",
]
[[package]]
@@ -105,16 +120,55 @@ dependencies = [
[[package]]
name = "argon2"
version = "0.5.3"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072"
checksum = "134c52ddac6d63c576bef8168db10c83c49c26444ecbc68060fef078925a901c"
dependencies = [
"base64ct",
"blake2",
"cpufeatures 0.2.17",
"cpufeatures 0.3.1",
"password-hash",
]
[[package]]
name = "asn1-rs"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7f43a50ac4fdca5df8e885c21b835997f0a1cdee65494a6847694a98652d9d8"
dependencies = [
"asn1-rs-derive",
"asn1-rs-impl",
"displaydoc",
"nom",
"num-traits",
"rusticata-macros",
"thiserror 2.0.21",
"time",
]
[[package]]
name = "asn1-rs-derive"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.119",
"synstructure 0.13.2",
]
[[package]]
name = "asn1-rs-impl"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.119",
]
[[package]]
name = "async-compression"
version = "0.4.48"
@@ -155,7 +209,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90"
dependencies = [
"axum-core",
"base64",
"base64 0.22.1",
"bytes",
"form_urlencoded",
"futures-util",
@@ -207,6 +261,12 @@ version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "base64"
version = "0.23.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5"
[[package]]
name = "base64ct"
version = "1.8.3"
@@ -231,6 +291,15 @@ dependencies = [
"syn 2.0.119",
]
[[package]]
name = "bit-vec"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b71798fca2c1fe1086445a7258a4bc81e6e49dcd24c8d0dd9a1e57395b603f51"
dependencies = [
"serde",
]
[[package]]
name = "bitflags"
version = "2.13.2"
@@ -239,11 +308,11 @@ checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06"
[[package]]
name = "blake2"
version = "0.10.6"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe"
checksum = "5b5d4d889834ee8ecfc0f8426ad30faf7cdcb10f741a8e6d7224d95325479f6f"
dependencies = [
"digest",
"digest 0.11.3",
]
[[package]]
@@ -255,15 +324,35 @@ dependencies = [
"generic-array",
]
[[package]]
name = "block-buffer"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa"
dependencies = [
"hybrid-array",
]
[[package]]
name = "brotli"
version = "8.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5cc91aac060a7a1e25823bdccbfb6af1875b88f17c6daac97894eed8207166b3"
dependencies = [
"alloc-no-stdlib",
"alloc-stdlib",
"brotli-decompressor",
"alloc-no-stdlib 2.0.4",
"alloc-stdlib 0.2.4",
"brotli-decompressor 5.0.3",
]
[[package]]
name = "brotli"
version = "9.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8b851b75c23ca7873623d612fe49bd1989aeb03d08fb9432187eb253d3d4c6b"
dependencies = [
"alloc-no-stdlib 3.0.0",
"alloc-stdlib 0.3.0",
"brotli-decompressor 6.0.1",
]
[[package]]
@@ -272,8 +361,18 @@ version = "5.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a32acac15fe1967bc3986b2a6347dffc965602354ea6f450ad07e8bfd253583"
dependencies = [
"alloc-no-stdlib",
"alloc-stdlib",
"alloc-no-stdlib 2.0.4",
"alloc-stdlib 0.2.4",
]
[[package]]
name = "brotli-decompressor"
version = "6.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "941cd9bd4ddab83cb46fa5a2d428f1c857b24ac78cb876cf7beb710840934bd7"
dependencies = [
"alloc-no-stdlib 3.0.0",
"alloc-stdlib 0.3.0",
]
[[package]]
@@ -389,6 +488,12 @@ version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c133bc6a41be0d194c306b5506d15e6feeea7b1d6604bd3f8310dfb2ca96486"
[[package]]
name = "cmov"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a"
[[package]]
name = "colorchoice"
version = "1.0.5"
@@ -401,7 +506,7 @@ version = "0.4.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef16c47ba2797aa6a909cc37d39911f3a6743811fe7408ac0b0cc0276b656e9"
dependencies = [
"brotli",
"brotli 8.0.4",
"compression-core",
"flate2",
"memchr",
@@ -482,12 +587,44 @@ dependencies = [
"typenum",
]
[[package]]
name = "crypto-common"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453"
dependencies = [
"hybrid-array",
]
[[package]]
name = "ctutils"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03bb0e1cc970d482d121d9a1744999169b69a07470b3d644a7894e53fcaf4574"
dependencies = [
"cmov",
]
[[package]]
name = "data-encoding"
version = "2.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4583a4551df46e2792f82ceeac45e850d2e2d5debba0b91f102385cda5b11f06"
[[package]]
name = "der-parser"
version = "10.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6"
dependencies = [
"asn1-rs",
"displaydoc",
"nom",
"num-bigint",
"num-traits",
"rusticata-macros",
]
[[package]]
name = "deranged"
version = "0.5.8"
@@ -500,9 +637,19 @@ version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
"crypto-common",
"subtle",
"block-buffer 0.10.4",
"crypto-common 0.1.7",
]
[[package]]
name = "digest"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2"
dependencies = [
"block-buffer 0.12.1",
"crypto-common 0.2.2",
"ctutils",
]
[[package]]
@@ -582,9 +729,9 @@ dependencies = [
[[package]]
name = "flume"
version = "0.11.1"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095"
checksum = "5e139bc46ca777eb5efaf62df0ab8cc5fd400866427e56c68b22e414e53bd3be"
dependencies = [
"futures-core",
"futures-sink",
@@ -599,9 +746,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "foldhash"
version = "0.1.5"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
[[package]]
name = "form_urlencoded"
@@ -781,9 +928,9 @@ dependencies = [
[[package]]
name = "hashbrown"
version = "0.15.5"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
dependencies = [
"allocator-api2",
"equivalent",
@@ -798,11 +945,11 @@ checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
[[package]]
name = "hashlink"
version = "0.10.0"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1"
checksum = "824e001ac4f3012dd16a264bec811403a67ca9deb6c102fc5049b32c4574b35f"
dependencies = [
"hashbrown 0.15.5",
"hashbrown 0.16.1",
]
[[package]]
@@ -819,9 +966,9 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "hpx-h3"
version = "2.5.19"
version = "2.5.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4b773a1c31de56cc61c87de6d1db73ed838c1d23d8fad454b302a38ae178ad7"
checksum = "f137abcec3be4197580d1429894c2c33dc438a6d830500dd11bf0a1b958472ff"
dependencies = [
"bytes",
"fastrand",
@@ -879,10 +1026,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
[[package]]
name = "hyper"
version = "1.11.1"
name = "hybrid-array"
version = "0.4.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "27b501faa50e7a26c3d3560ca625132f4078a17771f4810baf70475ae48cbe43"
checksum = "27f864f10dfb56725ce5ce5472bc52252c8f93a4ab86327122cebf62c5f59a17"
dependencies = [
"typenum",
]
[[package]]
name = "hyper"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c3e324da4c95177d6291d4c8730197c0d1822f8a9766814a4a44fa5ab797c9c"
dependencies = [
"atomic-waker",
"bytes",
@@ -1288,12 +1444,31 @@ dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "num-bigint"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367"
dependencies = [
"num-integer",
"num-traits",
]
[[package]]
name = "num-conv"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441"
[[package]]
name = "num-integer"
version = "0.1.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b"
dependencies = [
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.19"
@@ -1322,6 +1497,15 @@ dependencies = [
"objc2-core-foundation",
]
[[package]]
name = "oid-registry"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12f40cff3dde1b6087cc5d5f5d4d65712f34016a03ed60e9c08dcc392736b5b7"
dependencies = [
"asn1-rs",
]
[[package]]
name = "once_cell"
version = "1.21.4"
@@ -1365,22 +1549,21 @@ dependencies = [
[[package]]
name = "password-hash"
version = "0.5.0"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166"
checksum = "aab41826031698d6ffcd9cff78ef56ef998e39dc7e5067cdfebe373842d4723b"
dependencies = [
"base64ct",
"rand_core 0.6.4",
"subtle",
"getrandom 0.4.3",
"phc",
]
[[package]]
name = "pem"
version = "3.0.6"
version = "4.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be"
checksum = "d354a98a3d1251555de99e8fdd8afda05573c31b82f59063a7b0a29b5527f120"
dependencies = [
"base64",
"base64 0.23.1",
"serde_core",
]
@@ -1390,6 +1573,17 @@ version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
[[package]]
name = "phc"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44dc769b75f93afdddd8c7fa12d685292ddeff1e66f7f0f3a234cf1818afe892"
dependencies = [
"base64ct",
"ctutils",
"getrandom 0.4.3",
]
[[package]]
name = "pin-project-lite"
version = "0.2.17"
@@ -1534,24 +1728,13 @@ version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
[[package]]
name = "rand"
version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e058c7de0b26af77780c769414d6257830bb240f3c38477dbc2c16e5f54d6d4c"
dependencies = [
"libc",
"rand_chacha 0.3.1",
"rand_core 0.6.4",
]
[[package]]
name = "rand"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41"
dependencies = [
"rand_chacha 0.9.0",
"rand_chacha",
"rand_core 0.9.5",
]
@@ -1566,16 +1749,6 @@ dependencies = [
"rand_core 0.10.1",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core 0.6.4",
]
[[package]]
name = "rand_chacha"
version = "0.9.0"
@@ -1586,15 +1759,6 @@ dependencies = [
"rand_core 0.9.5",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom 0.2.17",
]
[[package]]
name = "rand_core"
version = "0.9.5"
@@ -1621,14 +1785,15 @@ dependencies = [
[[package]]
name = "rcgen"
version = "0.13.2"
version = "0.14.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75e669e5202259b5314d1ea5397316ad400819437857b90861765f24c4cf80a2"
checksum = "8774e05a7d0de114588e6a28fe7e71694b82614ed569d86d8b389dfbc98b8ad8"
dependencies = [
"pem",
"ring",
"rustls-pki-types",
"time",
"x509-parser",
"yasna",
]
@@ -1716,14 +1881,14 @@ dependencies = [
[[package]]
name = "rust-proxy"
version = "0.31.1"
version = "0.31.2"
dependencies = [
"anyhow",
"arc-swap",
"argon2",
"axum",
"base64",
"brotli",
"base64 0.22.1",
"brotli 9.0.0",
"bytes",
"clap",
"flate2",
@@ -1741,7 +1906,7 @@ dependencies = [
"libc",
"metrics-process",
"quinn",
"rand 0.8.8",
"rand 0.10.3",
"rcgen",
"regex",
"ring",
@@ -1776,6 +1941,15 @@ version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d"
[[package]]
name = "rusticata-macros"
version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632"
dependencies = [
"nom",
]
[[package]]
name = "rustix"
version = "1.1.5"
@@ -1925,7 +2099,7 @@ checksum = "a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8"
dependencies = [
"cfg-if",
"cpufeatures 0.2.17",
"digest",
"digest 0.10.7",
]
[[package]]
@@ -1936,7 +2110,7 @@ checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
dependencies = [
"cfg-if",
"cpufeatures 0.2.17",
"digest",
"digest 0.10.7",
]
[[package]]
@@ -2009,9 +2183,9 @@ dependencies = [
[[package]]
name = "sqlx"
version = "0.8.6"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc"
checksum = "378620ccc25c62c89d8be1c819e76a88d59bdcc3304733330788948e619bfd71"
dependencies = [
"sqlx-core",
"sqlx-macros",
@@ -2020,12 +2194,13 @@ dependencies = [
[[package]]
name = "sqlx-core"
version = "0.8.6"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6"
checksum = "05b44e85bf579a8eeb4ceaa77a3a523baf2bf0e9bac7e40f405d537b5d2d5ccb"
dependencies = [
"base64",
"base64 0.22.1",
"bytes",
"cfg-if",
"crc",
"crossbeam-queue",
"either",
@@ -2034,12 +2209,11 @@ dependencies = [
"futures-intrusive",
"futures-io",
"futures-util",
"hashbrown 0.15.5",
"hashbrown 0.16.1",
"hashlink",
"indexmap",
"log",
"memchr",
"once_cell",
"percent-encoding",
"serde",
"sha2",
@@ -2053,9 +2227,9 @@ dependencies = [
[[package]]
name = "sqlx-macros"
version = "0.8.6"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2d452988ccaacfbf5e0bdbc348fb91d7c8af5bee192173ac3636b5fb6e6715d"
checksum = "bd2b84f2bc39a5705ef27ec785a11c934a41bbd4a24941e257927cddc26b60bf"
dependencies = [
"proc-macro2",
"quote",
@@ -2066,15 +2240,15 @@ dependencies = [
[[package]]
name = "sqlx-macros-core"
version = "0.8.6"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b"
checksum = "fb8d96de5fdc85a5c4ec813432b523ec637e80ba98f046555f75f7908ddac7c3"
dependencies = [
"cfg-if",
"dotenvy",
"either",
"heck",
"hex",
"once_cell",
"proc-macro2",
"quote",
"serde",
@@ -2089,12 +2263,13 @@ dependencies = [
[[package]]
name = "sqlx-sqlite"
version = "0.8.6"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea"
checksum = "488e99c397a62007e4229aec669a179816339afc6d2620ca6fa420dbee2e982c"
dependencies = [
"atoi",
"flume",
"form_urlencoded",
"futures-channel",
"futures-core",
"futures-executor",
@@ -2104,7 +2279,6 @@ dependencies = [
"log",
"percent-encoding",
"serde",
"serde_urlencoded",
"sqlx-core",
"thiserror 2.0.21",
"tracing",
@@ -2157,6 +2331,17 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
[[package]]
name = "synstructure"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.119",
]
[[package]]
name = "synstructure"
version = "0.14.0"
@@ -2170,9 +2355,9 @@ dependencies = [
[[package]]
name = "sysinfo"
version = "0.38.4"
version = "0.39.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92ab6a2f8bfe508deb3c6406578252e491d299cbbf3bc0529ecc3313aee4a52f"
checksum = "d2071df9448915b71c4fe6d25deaf1c22f12bd234f01540b77312bb8e41361e6"
dependencies = [
"libc",
"memchr",
@@ -2271,6 +2456,7 @@ dependencies = [
"powerfmt",
"serde_core",
"time-core",
"time-macros",
]
[[package]]
@@ -2279,6 +2465,16 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109"
[[package]]
name = "time-macros"
version = "0.2.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85"
dependencies = [
"num-conv",
"time-core",
]
[[package]]
name = "tinystr"
version = "0.8.4"
@@ -2297,9 +2493,9 @@ checksum = "fd3ca314f692efd6c868f8408f53fe444634a845f96c028b97d35f6a1f79f0ee"
[[package]]
name = "tokio"
version = "1.53.1"
version = "1.53.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed"
checksum = "e95f91fcc7a621e8b030f6aa23c71fe9838ae2fb4d8118b75602a328f5144044"
dependencies = [
"bytes",
"libc",
@@ -2387,9 +2583,9 @@ dependencies = [
[[package]]
name = "tower-http"
version = "0.6.11"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840"
checksum = "08a05a66a4fdd61cbbe0a1d755ffe0ca6aba159dd4820936a0ff8a8278245b9c"
dependencies = [
"async-compression",
"bitflags",
@@ -2397,6 +2593,7 @@ dependencies = [
"futures-core",
"http",
"http-body",
"percent-encoding",
"pin-project-lite",
"tokio",
"tokio-util",
@@ -2562,9 +2759,9 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "want"
version = "0.3.1"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
checksum = "ec4cdd0dd910afe868b7ef477227d8d538b46b3075031afee8a9f2acb0a2ed0b"
dependencies = [
"try-lock",
]
@@ -2875,11 +3072,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc"
[[package]]
name = "yasna"
version = "0.5.2"
name = "x509-parser"
version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd"
checksum = "d43b0f71ce057da06bc0851b23ee24f3f86190b07203dd8f567d0b706a185202"
dependencies = [
"asn1-rs",
"data-encoding",
"der-parser",
"lazy_static",
"nom",
"oid-registry",
"ring",
"rusticata-macros",
"thiserror 2.0.21",
"time",
]
[[package]]
name = "yasna"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5f6765e852b9b4dc8e2a76843e4d64d1cea8e79bcde0b6901aea8e7c7f08282"
dependencies = [
"bit-vec",
"time",
]
@@ -2903,7 +3119,7 @@ dependencies = [
"proc-macro2",
"quote",
"syn 3.0.6",
"synstructure",
"synstructure 0.14.0",
]
[[package]]
@@ -2944,7 +3160,7 @@ dependencies = [
"proc-macro2",
"quote",
"syn 3.0.6",
"synstructure",
"synstructure 0.14.0",
]
[[package]]
+13 -13
View File
@@ -1,13 +1,13 @@
[package]
name = "rust-proxy"
version = "0.31.1"
version = "0.31.2"
edition = "2021"
rust-version = "1.88"
rust-version = "1.99"
description = "RustProxy - independent reverse proxy with local administration"
publish = false
[dependencies]
argon2 = { version = "0.5.3", features = ["std"] }
argon2 = "0.6"
sha2 = "0.10"
ipnet = "2.11"
rpassword = "7.4"
@@ -15,7 +15,7 @@ zeroize = "1.8"
anyhow = "1.0"
libc = "0.2"
arc-swap = "1.7"
axum = { version = "0.8", default-features = false, features = ["http1", "http2", "json", "query", "tokio", "ws"] }
axum = { version = "0.8.9", default-features = false, features = ["http1", "http2", "json", "query", "tokio", "ws"] }
bytes = "1.10"
clap = { version = "4.5", features = ["derive", "env"] }
futures-util = "0.3"
@@ -23,15 +23,15 @@ fs2 = "0.4"
http = "1.3"
http-body = "1.0"
http-body-util = "0.1"
hyper = { version = "1.6", features = ["client", "server", "http1", "http2"] }
hyper = { version = "1.12.0", features = ["client", "server", "http1", "http2"] }
hyper-util = { version = "0.1", features = ["client-legacy", "server-auto", "http1", "http2", "tokio"] }
rand = "0.8"
rand = "0.10"
regex = "1.11"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
sqlx = { version = "0.8", default-features = false, features = ["runtime-tokio", "sqlite"] }
sqlx = { version = "0.9", default-features = false, features = ["runtime-tokio", "sqlite"] }
subtle = "2.6"
tokio = { version = "1.44", features = ["macros", "rt-multi-thread", "net", "signal", "time", "sync", "fs", "io-util", "process"] }
tokio = { version = "1.53.2", features = ["macros", "rt-multi-thread", "net", "signal", "time", "sync", "fs", "io-util", "process"] }
tokio-util = { version = "0.7", features = ["rt", "io"] }
tower = { version = "0.5", features = ["util"] }
tracing = "0.1"
@@ -46,19 +46,19 @@ tokio-rustls = { version = "0.26", default-features = false, features = ["ring",
hyper-rustls = { version = "0.27", default-features = false, features = ["ring", "http1", "http2", "tls12"] }
rustls-pemfile = "2.2"
webpki-roots = "1.0"
rcgen = { version = "0.13.2", default-features = false, features = ["crypto", "pem", "ring"] }
rcgen = { version = "0.14.10", default-features = false, features = ["crypto", "pem", "ring"] }
httpdate = "1.0"
tower-http = { version = "0.6.8", default-features = false, features = ["compression-gzip", "compression-br", "compression-zstd", "compression-deflate"] }
tower-http = { version = "0.7.1", default-features = false, features = ["compression-gzip", "compression-br", "compression-zstd", "compression-deflate"] }
flate2 = "1.1"
brotli = "8"
brotli = "9"
zstd = "0.14"
h3 = { package = "hpx-h3", version = "=2.5.19", features = ["quinn"] }
h3 = { package = "hpx-h3", version = "=2.5.20", features = ["quinn"] }
quinn = { version = "0.11.12", default-features = false, features = ["runtime-tokio", "rustls-ring"] }
metrics-process = { version = "=2.4.3", default-features = false, features = ["dummy"] }
sysinfo = { version = "=0.38.4", default-features = false, features = ["network", "system"] }
sysinfo = { version = "0.39", default-features = false, features = ["network", "system"] }
[target.'cfg(any(target_os = "linux", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", target_os = "dragonfly", target_os = "macos", target_os = "windows"))'.dependencies]
systemstat = "=0.2.7"
+1 -1
View File
@@ -1,5 +1,5 @@
# syntax=docker/dockerfile:1
FROM rust:1.88-trixie AS build
FROM rust:1.99-trixie AS build
WORKDIR /build
COPY . .
RUN if [ ! -f Cargo.lock ]; then cargo generate-lockfile; fi \
+14 -3
View File
@@ -1,11 +1,11 @@
# RustProxy 0.31.1
# RustProxy 0.31.2
**0.31.0 panel/cache additions:** browser-negotiated Zstd/Brotli/gzip for HTML, JS and CSS (Settings → Administration); optional HTTP BAN/PURGE restricted to explicit IP/CIDR allowlist (Settings → Cache, disabled by default); System information Refresh button beside the API badge. See [Settings](docs/SETTINGS.md) and [Cache](docs/CACHE.md).
Independent Rust reverse proxy and static Web Server with a dashboard, SQLite configuration,
streaming HTTP/TLS/WebSocket forwarding, raw TCP/UDP stream hosts, IP-bound vhosts and backend pools.
**0.31.1 release source.**
**0.31.2 dependency-refresh candidate source (Cargo verification required).**
## Layer-4 streams
@@ -13,7 +13,18 @@ Raw TCP/UDP forwarding has its own **Streams** page in the primary navigation, a
Named HTTP/HTTPS listener bind addresses use the same chip-style editor as vhost hostnames, including multi-address listeners.
## What changed in 0.31.1
## What changed in 0.31.2
- Refresh Rust dependencies to current stable API families, including SQLx 0.9, Rand 0.10, Argon2 0.6, rcgen 0.14, Tower HTTP 0.7, Brotli 9 and Sysinfo 0.39. Retain SQLite schema 9, serialized settings, TLS/QUIC behavior and administration contracts.
- Raise the minimum compiler version to Rust 1.99 for current dependency requirements, including Docker and RouterOS build images.
- **Release gate open:** the updated dependency graph must be resolved with Cargo; the bundled lockfile is a prior-resolution snapshot and must be refreshed and tested before release. The source package is not production-verified.
- **Complete the dependency upgrade on a Rust 1.99+ build host** with `bash scripts/resolve_dependencies_0312.sh` (registry and the pinned Swagger UI asset must be reachable, or supply an audited local archive). This calls `cargo update`, verifies the refreshed lockfile and runs formatting, clippy, build and tests. Review the lockfile diff and fix any Rust compiler errors before publishing.
## Previously in 0.31.1
- Improve mobile panel startup via an ordered JavaScript resource, versioned URLs and retry of temporary config failures.
## Earlier panel improvements
- Improved mobile panel loading behind reverse proxies: one ordered JavaScript resource, versioned assets and retry of temporary configuration errors.
+6
View File
@@ -1,5 +1,11 @@
# RustProxy roadmap
## 0.31.2 - Dependency updates
- [x] Prepare stable dependency-family and code API migrations, preserving existing wire/configuration/SQLite formats.
- [ ] Resolve and commit an actual Cargo.lock using Rust 1.99; run format, clippy, tests and release builds.
- [ ] Confirm HTTP/3/QUIC, TLS, SQLite migration/restore, auth, compression, panel and cache BAN/PURGE in real integration tests.
## 0.31.1 - Mobile panel loading stability
- [x] Load dashboard modules as one ordered JavaScript resource and wait for document/module readiness before session initialization.
+39 -37
View File
@@ -1,18 +1,18 @@
61bf40a0572889dc8b0dda9eae4e93e44b0de9364b3c798106c85700ffb37c23 .dockerignore
4140a2956264b44f259e6a37fc00ba02097bbe68b09296e04d3d243c1287129b .env.example
9e6356475bf587091bab72246ec9e325b498f7e48a6220e9a18baa50d0a385a6 .gitignore
a414a328c21b7c4f81e5448a452258d8069ce40243a41e4e06b8950386494b96 AGENTS.md
15a02c3476a51deade17d25b56c45068ebe50e5f127680f387c98777a8854c5b CHANGELOG.md
e95030916a57faaa3fceb509f4aeec167b789022703f2e0cc09afaa0a071c89b Cargo.lock
a1aba61dca0fa20436ce0651baf6c59fe2602d2e9b4ee48b6900ba0351d7c208 Cargo.toml
75b008f237b2226c62a7721b7901018ce83bfc2726f047a83a17c66fcb799f3b Dockerfile
3a0ee0e90ee5a75cfbc6b5497546861ae56b4fc5f343a5d83f722cdddbd4b8e1 README.md
d1914a8afe65d73620bafb6cb4d9b13206ebde5674f79da77fab5cec6bbe81c7 ROADMAP.md
047139f3c7c40778001602e77ef69302e3bef8f495d48d780953c97c401fa2d4 AGENTS.md
9b42590541917e8d92fa26a5831b9dd43503f179337f8ed6c3a4d7d1de84d3b7 CHANGELOG.md
50238f33805987e7301f9ed06a8fd51b74b0e01330dbbae42c3a2fd8bd0d8e86 Cargo.lock
c6cc53538b89420b82526bc766d73a5fc93a82017f8fdb4bf767335dd613c90a Cargo.toml
25c7f3c8162cbf5cc156439774118a81dae9fdc52b611ec7e40c230fc99a772c Dockerfile
f3ad7c8b4b450bcb32a7c68f90e868726154b3f1ea88c7d9ba5173850334d24e README.md
e2187e12e611638f85bae7808947b46a5de3c12db234ec217767ac8aefd1919a ROADMAP.md
da8311e3db945290666324a2f24bf4521b365272d7ac3f862ded1be993eea646 UPDATE_CHECK.toml
f645f218dd062bf6cb54a8ee634290562cad364a4cccf09f16e3c8303582af8c build.rs
ea8d091843f86f4c66a5c76b20268b2feb397070851b6e05c5bc7d7a8d9157eb deploy/README.md
4834c278430ddf3bf842995735195368f1ac2c3b5c1d749df4f9238caf1e2b25 deploy/rustproxy.service
90c5b561079142ff094330066a6a551d7b0e1705012613341ec3defb7b3dc199 dev.sh
31d018c9ef676d397bafa66c5099a57bc41ee56bbb272df59bc521574d920c82 dev.sh
5f51efe621f45512a466fa7f2656867c6502a135ca040f4bfeec06a6919b39b6 docker-agent/README.md
7fee7d51a7deb67e73e53a0bdff3aa4630aea9bab9b23a0f9b8001b2169892b1 docker-agent/certificate.go
5e4f565c3c9de4dba897af2e801ea646a83395e72eae4717a6d502dff2fcec10 docker-agent/docker-agent.env.example
@@ -28,7 +28,7 @@ e52b31d0abf65ae3034f6cb367cca113633014ce8d1d799ad4dd5624d2c7b8bb docker-agent/n
875faa3969df9409d79f050a84b4183496f557edda3d34aaec19fe556cf8c471 docs/ACME_PROVIDERS.md
ec15163f5f51c9212003b7edd1fc380321311c2496fbaa9b45c838281b9af475 docs/ACME_RFC2136.md
8ded74f422426606238fa9876b49277a7bc56ddd96062a836cdd6ddbf78bd335 docs/API.md
f054206a09bed81018708608cdeecbfd09018ee7cec9d3abc038f8a37d47b815 docs/API_ACCESS.md
6ed12684b970fb7173fc9d1ad1da8ecce784eb38b29b7e6e631309fb25446807 docs/API_ACCESS.md
4c5288c9ed427cfb919bd023ed9a321dcf6d64bc4cad3e1768393356c5d835ba docs/ARCHITECTURE.md
e9e2327684a4ae2d1ca4a0b3fb67d0ff83b5a150d38d4de4cfad2c5d3102eab6 docs/CACHE.md
9113f3abbfe81bafc4855d8680df8aa2a381ecc358cc3e1a0f78b6b756421117 docs/CACHE_DIAGNOSTICS.md
@@ -65,7 +65,7 @@ a2bb837cdd3559d58158880c1f832badf64035376a3623050bb4e40c2bca104c docs/ROADMAP_H
007537ee971463072b2482dfa4fdcf1955f837ced0b711d4eef3b6267d16d4f6 docs/SECURITY_AUDIT_0.31.0.md
2cd9dadc49a5474e4ab06d5c01fa38c27cab436578cb9c9f327a37b3406d2e13 docs/SETTINGS.md
3611defd206cde107e75cac006159856d12cd8f15df7ae2eb9ec3592ed2b4abd docs/STREAMS.md
14ef0d7935c0ff9a1557ca3a8cac97274be7e2b8e0fd68ea6cce87372989865d docs/TEST_REPORT.md
8ce2502e64a159a23eadebafdc9f0d65d384e8099eb63368748ee7e0269e299d docs/TEST_REPORT.md
cfb452d8496839e832b5fbacdd4e52525b0ecc3112fbb62340c1e580bd5c1717 docs/TODO.md
751c9b13b47d0959a8575d6e82bdecba988b950a374ba0b084fbba08b4b65ebe docs/TRANSFORM.md
660687fc961f71aae2bdf71595aa47a336fa782f4e78ceee8c1d1e63e46393ee docs/TRANSPORT.md
@@ -131,7 +131,7 @@ fde541cd76bc3fa2b6765822bf04baf9f9ea3e4b6e185e76ff467270ddbd945e install-debian
2d4c859aae5ba357a6d30024c8487ae8f59f01b192711f13f9c2f8fee0f48b50 integrations/opnsense-acme/README.md
c73b814a42bb4ff703441dbd177ee625055eeb115f9b5bb0c2150aede573a159 integrations/opnsense-acme/opnsense-rustproxy-cert-deploy.sh
45be151cd85fc2ef2f1fdc3003ace63381af262e73af8a685ab48cb697cc0de1 integrations/routeros-oci-build/.env.routeros.example
cdb15e23f04b4a330b7ab8869eb38da72b3c27729fa7fcf164e3d9e8e893a651 integrations/routeros-oci-build/Dockerfile.routeros
11aa102de1089ecc375f20c4bd5420fcfd3b2ef23fe0c8bdf04953fcfaf40ef0 integrations/routeros-oci-build/Dockerfile.routeros
7c7a3e51f43b43e1c962a4b0bb591f99c1f06977bf77f23719f7f867dfe7eb8e integrations/routeros-oci-build/Dockerfile.routeros.dockerignore
cb01fdef286f38b442dd9252c23c4f8e44eb72e9ecdd99adb926e8e9f6318bb1 integrations/routeros-oci-build/README.md
96563a83e87cfb33e238c2a970ce36b1a1b51e91318d09b123c3755002ba2646 integrations/routeros-oci-build/build-routeros-oci.sh
@@ -156,7 +156,7 @@ ee50f38c037765fab742e888d4b9fc4d874c31c785c6064d4eca3d2c4d24d944 nagios/check_r
6e036f736a2b40feb78eec17bf34871771524190f283591d1c89e48e6d2271e6 nagios/check_rustproxy_queues
7458b2d74495e6fb1c1edeea4d5a793705476431dadb60627187111f91441e13 rust-toolchain.toml
2145ad17ae7a823756371ceab6bdbeb332211df93193ffb4e486f7a80c4f29ae scripts/README.md
2044e6d979712457d6ebc3483efa029cc6de22416ee85898d950300015bd815d scripts/build/swagger_assets.py
a1f55872201b5178f8af5e2beee6b3a68e37026831e41426ee62a8c15c1cf333 scripts/build/swagger_assets.py
e4d15aa6aa864d04b9b30aca33826b833632af02ca643f8b23e219a5ea1298dd scripts/deploy/deploy.py
cc268d9d3dd125c9cbff2522fe3137a561fb4fcbd5ee461d70ef2e3a8241cdb1 scripts/deploy/deploy_core.py
8c7da13e95bcddd2bd1b4f1b22ade1c5ac1ecf54448601daa8416905d4239751 scripts/deploy/deploy_runtime.py
@@ -164,6 +164,7 @@ a641bd35400c56f3b68b0a92e97b602d06090f03972ca8f01fa744e90dd97403 scripts/deploy
2cc658faaa610b21b48942c59b955525539ad4503fd60f2a49c7e626a22faaff scripts/migration/migrate_npm.py
a459d95bbb0076f33ade8b273b813308167f9d8d45242b309a2d18320ca0093c scripts/migration/migrate_npm_api.py
31ccad3ec3c880119f3b7002f0c6652f8b43634edad8ab3763cb0e4497d0bb1f scripts/migration/migrate_npm_core.py
308ceeca51ddfb43db8be8286d5286aff1a44979f84af87d9346fb4176499c0c scripts/resolve_dependencies_0312.sh
20124173452b4514bad8850c1637569a9a191882478451e549bd16b432605fc1 scripts/runtime/acme_provider.py
e69813fb1d3a4cdcfb7243964cc323fc21a603087bf2e84188fa42b9d3cee4b5 scripts/runtime/acme_rfc2136.py
22e880e98c7b9d700c7a4c02b9b1d543e5dbd1f4ecdca42813a1083165b32a19 scripts/version.py
@@ -188,14 +189,14 @@ bc9eb32f0d2785bac122d5eff44742cd24306c503daa065564383d46416b2211 src/admin/acce
d7aa68a78a4cb5a61342d92c1f3209af230d0f9ca3fea1115f67078372e6f37c src/admin/file_logs_io.rs
899e87a73bfe46c4078a46f9e1eb1c17b384472e8a30d2bab5486872b43fc809 src/admin/logging_api.rs
58802528128f53e493f7339063433a747b605007a8842b5efc670132d897d6f7 src/admin/mod.rs
2f7e9ff687147cbf751f88828479e970727594d7c21726b1470e8c59ea40289e src/admin/openapi.json
8c58aec76463fc512ee87a8cceff7b905488c6f70185c055c496d235badea6d7 src/admin/openapi.json
0d1a5324e8ad32f5ab0861b722663f50f066e256bc5fd5dcced6a63f2bd399d1 src/admin/operations.rs
2b0ec6df0ed9e1bdc5c9e4ff1758b59dd14abc150f98365ac3edcf4672908e48 src/admin/panel_assets.rs
75bbedd54fdabb2b489642a642ee7c6533f69cdacb85e251763a56ba11174d5c src/admin/panel_content.rs
7c113cd9d8642a808c73ebee24e695f7a318995e39dc2b81e747d2b2246e277f src/admin/preferences.rs
d9e90166d2199152585a5208feedfc1dd1395dc32ddbd2a094ae4a1aee45e115 src/admin/preferences_columns.rs
9baf3c0ec4c543bda51605930ac57d6425c5e03e06d29ff755a7ce3ff611aeaf src/admin/public.rs
5135f743f628103deda9f50629065ca343f7a5bf872dcd3cc0033ac2971e361b src/admin/public/tests.rs
c9f952bec99dfe1d5a60998a38bd177f82f54de567230413e0c322ee561eba7c src/admin/public/tests.rs
6125419b099cf4d330767bc07424d52a2b400f273d31084c058c43a72a6427b7 src/admin/refresh_preferences.rs
f99655cf19b04b1eb7fb49d12bfd2ba1ef2c095b81e80ef46cfddfacefc91936 src/admin/router.rs
8708427263cd4caf8454b9115e89acb09172483a090c6b0bd43811dd9e52c7f6 src/admin/session.rs
@@ -207,7 +208,7 @@ cadd7dca29b9aad8c77c7bc23dd249ea37a15adfa4d636a8ef00273a91ce35dc src/admin/tail
5df98246c46135a176b4b8b705c92fd328719e82eb7342f088256abb976d5ae7 src/admin/tests/part_01.rs
6f8c1e30babb2cc4cb7accf4bfa28338651a469b16341f6e7eaec881dd9abf2b src/admin/tests/part_02.rs
34cf08cafb63cf7a8a6336e5c81fc99abfce8669662e387a3702c3a532be8056 src/admin/tests/part_03.rs
a1e14bfad1ba42045421fb761e0cc490cc30530409b22416db7e727548c06b93 src/admin/tests/part_04.rs
2137a53991c9337988545c4472b4ffcb9a111cac9c460ebbec7e51c0be8f5630 src/admin/tests/part_04.rs
5f0062abc42a3b81c79088dc0824ab4c81a22c6ea765e77d2527d07d13bffd58 src/admin/tests/part_05.rs
311c4ad16b3ad5b295a339241afd4ca8ea0d3c7f54f609c27518b4de1b4a4b71 src/admin/tests/part_06.rs
5fa006413fd93529ac9aaa4ffb28bf6b0b88d0a55a0bf0c49576db4a06fe9fc3 src/admin/tests/part_07.rs
@@ -255,28 +256,28 @@ a6e7bbff4fd2d50ecf759ed28161a672d7b483af2b35c9f912746a7a06a9f476 src/admin/web/
f2689283cfe43d6aae911b0df778a69335089be230fbfc6de5dcce1801c38427 src/admin/web/vhosts/render.js
91e3417fba7e8f59023cd64813befccc8341741cd4aa53dfa1ec68a03c7f4632 src/admin/web/web-server-profiles.js
de3326d4fae254eb0b8c190f02e762f4094dcba86bfdcb24c5fb472676ebcc4b src/admin/web/web-server.js
98c00d99822f9be54fc58afb6d4b577901712effd2c5eddce014312681864625 src/auth/files.rs
76b0d92e4360e06ae97f87c0f6ddde4962c44efdeec9727e5b9a720598302891 src/auth/files.rs
b2f948bcc3dbd24952c7b1b02d41d06757f49f0c38a78caf51f8f8a687d8480d src/auth/files/tests.rs
97b5a060f24a102bbaa2f406426e985236c0d719b8038db210c25b34fcd8e923 src/auth/mod.rs
105cb934956457a68adc14f2c64569d9d1d40b8da0a7908a9a59fc2153a939c9 src/auth/mod.rs
c96a87e82ff0c1c9ce3d0cd5135903bb53af78d1c4fb6ebfe5fea503bf5644ea src/auth/store.rs
745226cabb007819370fc6aa680995a03a8a13fa057dacfab0fcc21d0c43bbe4 src/auth/tests.rs
09c3a622a7d2169e49e3369ea5f0f816eab2f717eaf1675c625d518c8f34d367 src/backup.rs
ebce56a154c57bf20d6618de161359c7c49392d7d24e8d23428b7d78a7a060c1 src/backup/archive.rs
76aa2afe9e7302be39b3512f30d0448d9134300f8ae930d64ada40cf3ad4a8a3 src/backup/archive.rs
732cd4f9ed38ae69f8bbae61b1db8baaa6f86b07ca21c41f24a206a30f0a96fc src/backup/ownership.rs
076350506215faec3cfe7d9de4e60baf29672cc22a41eabe601dcc510c257799 src/backup/snapshot.rs
a00571b409d305af86ab2a57a25434b5ab58706772df440d017fc1fec097f7b4 src/backup/tests.rs
b2ce83a48736c082e6b820e4e363cca7a5018f3ae2a1ce5adfd6af1a03d62598 src/backup/validation.rs
f5afae218f3a6f91bc2f1f5a90094630ce5b7ca574de61807b5a317b13937ec2 src/backup/validation.rs
426a946b2c9e244f09f6cbc5f9664b8ad0737529a9bb5a3d549d27196a1f8043 src/balance.rs
3d4b3986f54bed99212063b6d135d40a202d556a3e4d253e24c74be4cb9e8ebc src/balance/health.rs
a1e5d43f5417d71f3974812cb539c0932f206e2290090917f067494f3587fc72 src/balance/lease_runtime.rs
a57f352aad3b5a9a6debb59625864808f0a0ff7b11db83b47d6825a5932ca16b src/balance/model.rs
7628aa331c9dbb02650392afeb65838cc23e32939c002d5d0488a8f3b8c11bd4 src/balance/pool_core.rs
46d739f14b510b35aaccf18fb9f21593c92d8b8d3ea47828271574df5ed008e5 src/balance/model.rs
2a6b20ffc2b326cb2380beb0805b52181306d3b9cc57330d67930000535c03f9 src/balance/pool_core.rs
477132dd381e23f1be197c32ff58a0925f41d3e6aa63fe77c10973cefb2b76f6 src/balance/pool_selection.rs
519cb798040ac9a65d9ebe43ae11ef729bd78c5c4a5c756490458add4b6fdc24 src/balance/pool_view.rs
7d94d1bd4eb698e0588b822d93e56e3dc5594c46edac4aa9713f148893ebe6d5 src/balance/tests.rs
81141aedaec495183b88a4f6362a71f56ee14cbfa83d71fce51d8abed8df3f5d src/balance/tests/part_01.rs
490b38a8b1472dbd9fcb823ec0ed0e92498fb9528b982bcee340c8579e89ae36 src/balance/tests/part_02.rs
ccb3e1988d6ae5825450e894eea896d53ae9fac150d0175ea5e742413e55105a src/basic_auth.rs
5049d2bd01a9c866f3bad2288d8d594df96bba6045dfe15cd22b4173562b7388 src/basic_auth.rs
9a8858b7ff461a4d961ba701ca3ed6eaec15c3f7f05eef8e0e30edc63617605a src/basic_auth/tests.rs
7def03ace07dc38a4806277d75b6ce673d33ddaaf1c4855fa0a3372be1cf9489 src/cache/cold.rs
a9eeb93519aae2de7a1282296ced91d2eb8307cc966f8fbf6085af0fd21b0ad2 src/cache/cold/cleanup.rs
@@ -309,7 +310,7 @@ d426d341a7678f8ec70406d86052c97d0f49804bbcd8402951103d61a735b9da src/cache/poli
7e2bcfbb2b57732cdc704d9df7a9298fb80e735e37984431bc936630e41e7f8a src/cache/range.rs
7ad96b3baae36ec49c90d03b380e749944fb3d54abb045c90db3a35f6b89b1c1 src/cache/tests.rs
13dab7b7a587cfc146826a92ab94ec9e04f7e500b7a84e678d77226829844923 src/cache/tests/cold_tier_tests.rs
b4f609ee900c556b613136bbd3fce11c242a415d277ac3d25bdcb72275b66832 src/cache/tests/cold_tier_tests/part_01.rs
9cd6a8407888c8f9cbf7652774186d1ca588d2d78308750e8aa1316b4e3e5cb9 src/cache/tests/cold_tier_tests/part_01.rs
f43aa6c231f2c85ee3390dddab74aab2eea2a3f874acb35f145c23866266cb52 src/cache/tests/cold_tier_tests/part_02.rs
22cb9bf20a0d25bee30c1e2213dfe8278ad7d0d5d294275d47d4a6ff9ac646ab src/cache/tests/dashboard_tests.rs
d7a02eb7bad37e6a5524600aabb5e60ccf50e26c7a487e2806891c9d8acd48f3 src/cache/tests/http_invalidation_tests.rs
@@ -411,9 +412,9 @@ c438066616527a3108f2ad53e3698ae9b2a80961714394cdd0a450c92f99d992 src/proxy/head
cc682f0d5754e0b806a4eb5950e4cc00611aca5ce4668414fddef1d692489805 src/proxy/tests/authority_tests.rs
d134b0eb617f82f9971b3f3461379ea37c57bd0079bf54748d0c434b2ef1b475 src/proxy/tests/http_invalidation_tests.rs
e80c7fe44abb7607c64ea02d56542753dc25290b3bcf4c4d45b75f2db6ca90d0 src/proxy/types.rs
26afb4dcda99a54abdd85f7950ecac6d608eca78067323f695a43382d21fa2fb src/proxy/upstream_helpers.rs
55c44a5d0c6588beea67439d62e3c9f57041e4d07cb1f8cf293efd856ceb8eff src/proxy/upstream_helpers.rs
69f775f6746b4c6d729dc10961dbef54168846f4ce9cf7fe453ab5a2b879c9b8 src/proxy/websocket.rs
016a60b2471edd3df9bbe9f92a9cebea11047178b83ff0e3b2351c12814a4939 src/proxy/websocket/handshake.rs
d866e7e72dee6ff54c2418c34e9899cbcd7e4e027d9cbe2b625044dc9062e4ee src/proxy/websocket/handshake.rs
7e9c4d4c207badabf90630223964bb2ad641f06d4149843cab751f192b8b4adf src/proxy/websocket/tests.rs
a44421714aab37f7ceda19e3ec2512c645d2a3ce5dcb3da5bc7a0d87a442d6a8 src/proxy/websocket/tunnel.rs
15fabd38576b61f650c039acdbf7ac5a5bd24330062475b94315285b2dc258b6 src/proxy/websocket/validation.rs
@@ -428,7 +429,7 @@ d0939e1b0829c25356a0b67ecb3a178c74936dd07baa15fee24eb32544db6cad src/runtime.rs
e90df3fff8a25465425b099ae358d19085809e56d948ba47e01e53f8b93d5dd0 src/runtime/tests.rs
0046525317ffbd5d3b34845656b0f58d9d31f38b3d8acd71e90d94bd7cdfb8ab src/runtime/types.rs
88bc546fdda20d3f1f8f5e7d7872ede1fa334b8bb5102c729bd72f56e3fb94b9 src/runtime/updates.rs
b4c17b304166592e63c81884076589fbaf725c3f57043f8924c46167b061ac5b src/secret_store.rs
b5062082d7fc7c9b08533283e1f0a841f85a42917b6ebf135760b04023884d9a src/secret_store.rs
6dca8da072a3bd610704a391bb6e25c9042971f4ebc60edb47665ce1d74cbc58 src/secret_store/tests.rs
6fa1d8c352bce7a16ff0208bc4cdbe80404715df0aff4ed26910a5df57cbbcd4 src/security.rs
0e5fc2aff856bf6fd959cba12b5ddd1e3b3cbeb0fbaadde906c811cdfafdaae7 src/security/tests.rs
@@ -451,12 +452,12 @@ d9123c623e2a97d09e41edaa29993b4b2c8cba39a3abdf2b48a205aa9a9c2939 src/system_sta
7467d83066bb6986d8c9103597f7a7617ac677579ed46d573c573ff6ca57bbd9 src/tinyauth.rs
0e1d87554db55e724f30c41ec4a5c80bdb8fc9c8cba8700ac509864d4abf8871 src/tinyauth/tests.rs
1084cccd9de28acd6cb0f0813de7c0953910f51726f73f4acdb2e3ada4593976 src/tls.rs
5919db3296523dd0b7a8e0cccbe44266500a6a5183a7a08cc195d7f9847886dc src/tls/certificates.rs
4972a436f8878dec5f9eb8ff17eb82670b0bfd9e94e821c516575479f5fc1919 src/tls/certificates.rs
436575144aa352498a4696f12c2258bef3a898f3f50e6fbc6d67c3b93afa099f src/tls/external_paths.rs
3e6947dde167f82a5efc407ccf9bd4c9f5b26cc8633d1418a10284bc1a1503d4 src/tls/server_config.rs
893d1077fc0d9a08598596a1d3f4bde7019357fac27ebe13f52c11a4859a1d25 src/tls/tests.rs
e0ab65406911d4b95cb26594df65779a88d9011d5b11adb22c1a28831cda3826 src/tls/tests/acme.rs
cfab6b61f0d121a15d6f78943091c770cb5a2a023d68d66be3c0228f9c8adf9c src/tls/tests/external_paths.rs
31c0f51a3a0bab433f438c3ba1b1cdaf3c63fae6086e7d99b3464a537f75a5e1 src/tls/tests.rs
2af2164ecdf2b7fd9eef7c6e358dec053dfac9aedaa501dbb75df742d18638b2 src/tls/tests/acme.rs
93b359e9defd62506729bc90e56c96783e2f14dd477dd67f337ca83149da7168 src/tls/tests/external_paths.rs
10c4a187e3aeb0c72a8c8968ea0cc8bfae4e3cb2f3d834ebd3da1056c4171ec5 src/tls/types.rs
c2f802d95da4332fb8480e83f0c7502334457b3408141c1c4516c9ba320a0463 src/traffic.rs
36301bcbd20b557dd3d58e77a9175f3581947f577c0bb0b972899cec6786b03f src/traffic/tests.rs
@@ -509,7 +510,7 @@ c5cb561b0f276e8082f904b9446fa5885f6b3c0608f25ea2ef425061e9f7f6b4 src/web_server
518856a221161430cb0ab3e411fa81cab07e1a6e52edd470f026d25c5307943e src/web_server/serve.rs
63d71a336187779e9cbc1cc6afa065c5fa9472835a8c2cc15dbf4527279d0c90 src/web_server/tests.rs
9426ad19fd6234e944d6e122a995d2cf8ebe4177d27e42fd157f64d05abd1e23 src/web_server/tests/part_01.rs
6f0b68d732fa0d1385744ef7ac1b36d558f9532f9ef1dea44d3dcc47d149840a src/web_server/tests/part_02.rs
5591a0b994d4392265799f881790d15abd8b6263b5475a84766455d66572bfcf src/web_server/tests/part_02.rs
8f9a0e2b0a69a1f2b8cf7505a654606150da4a53bc8c09d1d6c4b8751185b134 src/web_server/tests/part_03.rs
b25b6e0082b5153272119c7faba9d6f2ccdd288ce5c066526431c56954c66c4c src/web_server/tests/part_04.rs
f00743af2cf4a5952caef7a1dcf68e7817b5599a252a9973e1c22045517c0b0e tests/fixtures/argon2id.json
@@ -522,23 +523,24 @@ b04362ad082a32c9ccc4cf38c46f44d71347adecf7aba3224b3af3ea4872e3d0 tools/README.m
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
daa718b6a3b519234cbb3614bd321f5b4b4ebd704bc9fb1c1dc18ba742759203 tools/checks/check_source.py
4bb77602254ff8ed1673b6d31518c77ae40c9b9c44e85ac436b93a300115ca18 tools/checks/check_source.py
0b583ee59dcccc35b15ef4e211d7e6546a8aaa7b0286ceab03c0b0dc084c1e07 tools/checks/dashboard_boot_0311_ui_test.mjs
5f798699502c7a18e7b45aa84bdcf7ad0ccdb77f11cf2003527c97f0391c7abe tools/checks/dashboard_startup_0310_ui_test.mjs
7428c63a80be49c57b6aad15738679175bad24c4271964cf01a67805ec22c0eb tools/checks/docs_check.py
3a5703cd3e0e511e0ea8da0c7802ccdc5cf53289f9b9169b9cf96ea5209b4a57 tools/checks/dependency_upgrade_0312.py
5d4fa0b66ae05241236551c07c900ad0c437bd10e34406b17a8a002188caebc3 tools/checks/docs_check.py
e33a9cc51d58bc92a637c0e6a1c88cbd24ba4605be55748212e296c3c35a4060 tools/checks/high_traffic_memory_model.py
4f7821e7e25b09ce251d25a0b3fb4d8ce8396c36a5ed971c2637aeb325425909 tools/checks/langs_check.py
f8f87ba891338b565249d02c3cf5f02cec44e606b4205de99fcbea1eb2c24d57 tools/checks/memory_contracts.py
5b7901747d879e12a39ed82b6efc513bcdddd15d508b5828210d2c83398fb6a4 tools/checks/panel_cache_http_0310_ui_test.mjs
274019cdcdccff5a5c7f76107daf096d111df1834b7280ccc3da9c564b65a04e tools/checks/preflight.py
8c5c64b681de7f72f01b414f2c77d1f9a44c13b47972d8252a0d392fd71c6ab4 tools/checks/preflight.py
3485681d369b5f486dfc2d14660f19b4098df670bafd9a296cc9dabe68003a51 tools/checks/refresh_0310_ui_test.mjs
c8b9216936300dac4ebb582a70efe2286124f0d14e501a6c22fc10cc2caaddbb tools/checks/release_smoke.py
db468bc0ad68704e70a15d7dda894d1913db1e2ce94e04a84b20c7dadfaa80a6 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
5c1d428cbda5f08b8d6847732672e45e9da96b545337334e473986e119197de2 tools/checks/source_module_contracts.py
b78240c3ccead8c023609fc71560be7ce2f2aa5d31c78059a24cfe4b0412d9c5 tools/checks/system_info_0301_ui_test.mjs
49c1fe96340d97c326943826dd8b3331b884c19f4a9631beaa820f01a12108e0 tools/checks/system_info_0301_ui_test.mjs
543a488951ea6ec8fe06e04f168ccc4d4f2af3cfc281c9a728ab7bd87377fa98 tools/checks/system_info_no_subprocess_contracts.py
614ed0c775e32ec5bfb4ee75aaa90206a10da0f43659747a7ec76fd93e33767a tools/checks/version_contracts.py
2020e171be1a044f7ca2ab83af7604a014c36fe4ff04791d0620ccbf1bf8e000 tools/checks/vhost_origin_errors_0297_ui_test.mjs
@@ -547,7 +549,7 @@ b78240c3ccead8c023609fc71560be7ce2f2aa5d31c78059a24cfe4b0412d9c5 tools/checks/s
0398892be30fe416a00578915c588f360fd93244e3af77ece834e560e547d1c8 tools/checks/web_server_contracts.py
35f76060e585a96c623ef83bd5f771969f5be16a0e1ae626a3092d7458b84e84 tools/checks/web_service_ui_test.mjs
adaa1ed9e5958739df40190055c780c80d0a8e270ec8dc105ed3fa2dbb8fb74f tools/checks/www_redirect_ui_test.mjs
c8bf1c3ef876b4f87840414f5de19c183309550572437cca7ba1fe02df496462 tools/dev/memory_load.py
05382366fd801a17f8d7c1166fd4e64b8aa70e465cb69121519b239ebf8ed55f tools/dev/memory_load.py
d1418f8e574a7390fd45a2aa879bdf5720b773162d65e295ab0350506aa5220a tools/dev/mock_auth.py
2d3ff92300b09ce9cecbbaaa9309b4d33160fa4b2291c9952b784e8cfb599ab5 tools/dev/mock_server.py
538b5f6cfa738355a729940ee3d92e7ab3dc25ca69ae330ddaa08f831775b8ba tools/dev/test_compression.py
+1
View File
@@ -101,6 +101,7 @@ if [[ "$mode" == "test" ]]; then
python3 tools/checks/security_contracts.py
python3 tools/checks/admin_surface_audit_0310.py
python3 tools/checks/security_dependency_contracts.py
python3 tools/checks/dependency_upgrade_0312.py
if command -v node >/dev/null 2>&1; then
node tools/checks/web_service_ui_test.mjs
node tools/checks/backend_cache_0297_ui_test.mjs
+4 -4
View File
@@ -132,7 +132,7 @@ promise to erase browser/OS screenshots or data intentionally downloaded by an a
## First build / offline assets (fixed in 0.7.2)
No `SWAGGER_UI_DOWNLOAD_URL` environment variable or `.cargo/config.toml` is required.
The default is Swagger UI **5.32.11**, selected by `scripts/build/swagger_assets.py` and
The default is Swagger UI **5.33.1**, selected by `scripts/build/swagger_assets.py` and
called from this application's `build.rs`. The former `utoipa-swagger-ui` adapter
is removed: there is no separate dependency build silently choosing its older
historical browser default. Direct Cargo builds using `--manifest-path` use the
@@ -160,10 +160,10 @@ release binaries**, served behind the existing admin guard. No runtime download,
CDN, public asset endpoint or alternate authentication mechanism was introduced.
An offline or GitHub-restricted build can use a reviewed local copy of the same
GitHub **tag archive**, with its original `swagger-ui-5.32.11/` directory structure:
GitHub **tag archive**, with its original `swagger-ui-5.33.1/` directory structure:
```bash
export SWAGGER_UI_DOWNLOAD_URL=file:///srv/build-assets/swagger-ui-5.32.11.zip
export SWAGGER_UI_DOWNLOAD_URL=file:///srv/build-assets/swagger-ui-5.33.1.zip
# Optional: require the archive digest independently verified by your build team.
# export SWAGGER_UI_ARCHIVE_SHA256=<64-hex-digit-verified-digest>
bash dev.sh check
@@ -237,4 +237,4 @@ unchanged. No configuration schema migration is needed for existing header rules
- Swagger UI configuration: https://swagger.io/docs/open-source-tools/swagger-ui/usage/configuration/
- Cargo configuration discovery: https://doc.rust-lang.org/cargo/reference/config.html
- Cargo build scripts: https://doc.rust-lang.org/cargo/reference/build-scripts.html
- Pinned browser release: https://github.com/swagger-api/swagger-ui/tree/v5.32.11
- Pinned browser release: https://github.com/swagger-api/swagger-ui/tree/v5.33.1
+13 -1
View File
@@ -1,4 +1,16 @@
# Verification report - RustProxy 0.31.1
# Verification report - RustProxy 0.31.2
## 0.31.2 — stable library upgrade candidate (2026-10-10)
- **Source basis:** shipped RustProxy 0.31.1 archive, without local Git history; no confirmation of current remote `master`.
- **Changed dependency branches:** Rust 1.99 (manifest and Docker builders), SQLx 0.9, Argon2 0.6, Rand 0.10, rcgen 0.14, Tower HTTP 0.7, Brotli 9 and sysinfo 0.39; HTTP/3 hpx-h3 is pinned to reviewed 2.5.20. Existing SemVer-compatible crates remain range-defined for a full `cargo update`. Embedded Swagger UI is pinned to upstream 5.33.1.
- **Code adaptations:** secure RNG API, Argon2 PHC generation/verification and TLS certificate signing key representation. No intended changes to JSON schema, SQLite schema 9, cache format, credential storage, TLS policy, or proxy traffic processing.
- **Executed locally:** `./dev.sh test` passed all offline code/API/SQLite/upgrade/security/translation/UI/Node/documentation checks; then exited 127 because Rust/Cargo is absent. `cd docker-agent && go test ./...` passed. `python3 tools/checks/dependency_upgrade_0312.py` reports PENDING for the old lockfile. Python compilation and Bash syntax checks passed. This does not prove a Rust build.
- **CRITICAL:** This sandbox has no Rust/Cargo/rustfmt and cannot reach the crates registry. The existing `Cargo.lock` carries the previous dependency graph: only the root package version has been updated, with all third-party resolved entries left untouched. It is **NOT the resolved lockfile for 0.31.2** and `--locked` is expected to reject it until `cargo update` runs. Do not treat this source package as a built or production-accepted release. Do not fake checksums or mark compilation/tests as passing.
- **Required on a Rust 1.99 build host:** run `cargo update`, review and commit the newly resolved `Cargo.lock`, `cargo metadata --locked`, `cargo fmt --all -- --check`, `cargo clippy --locked --all-targets -- -D warnings`, `./dev.sh check`, `./dev.sh test`, `./dev.sh release`, plus `cargo audit` when installed. Fix all compilation/API errors revealed by real Cargo before approving a release.
- **Runtime regression gate:** real H1/H2/h2c/H3 QUIC, WebSocket, TLS and certificates, SQLite upgrade/backup/restore with preexisting data, sessions/Basic Auth/API security, SSE and panel on Safari behind reverse proxy, cache HOT/COLD and BAN/PURGE, and zstd/br/gzip negotiation. Compare load/RSS and avoid overwriting production data.
# Historical verification records (0.31.1 and earlier)
## 0.31.1 — mobile/reverse-proxy dashboard startup (2026-10-10)
Executable → Regular
+94 -4
View File
@@ -27,6 +27,9 @@ Environment:
RUSTPROXY_ALLOW_REMOTE_ADMIN Set to 1 only to permit plain-HTTP non-loopback admin on first install
RUSTPROXY_PREFIX Managed install prefix (default /opt/rustproxy)
RUSTPROXY_BUILD_JOBS Cargo jobs for low-memory builds (default 1)
The bootstrap installs/updates the builder Rust toolchain only when required
by Cargo.toml (and respects rust-toolchain.toml, if present).
USAGE
exit 0
;;
@@ -101,11 +104,94 @@ run_builder() {
runuser -u "$builder" -- env HOME="$builder_home" PATH="$builder_home/.cargo/bin:/usr/local/bin:/usr/bin:/bin" "$@"
}
if ! run_builder bash -lc 'command -v cargo >/dev/null 2>&1'; then
run_builder bash -lc "wget -qO- --https-only --secure-protocol=TLSv1_2 https://sh.rustup.rs | sh -s -- -y --profile minimal"
run_builder git clone --depth 1 "$REPO_URL" "$work/RustProxy"
# The builder, not root, owns the toolchain. Never update a system-wide
# apt-managed compiler or change the user's default rustup toolchain.
# rustup is needed even when an older system-installed cargo exists.
if ! run_builder bash -c 'command -v rustup >/dev/null 2>&1'; then
echo "Installing rustup for build user: $builder"
run_builder env RUSTUP_INIT_SKIP_PATH_CHECK=yes bash -c \
'set -o pipefail; wget -qO- --https-only --secure-protocol=TLSv1_2 https://sh.rustup.rs | sh -s -- -y --profile minimal --no-modify-path --default-toolchain none'
fi
run_builder git clone --depth 1 "$REPO_URL" "$work/RustProxy"
# Read the actual requirements from the checked-out sources, not a hardcoded
# Rust version. The project may inherit rust-version from [workspace.package].
# The channel from rust-toolchain.toml (or legacy rust-toolchain) takes precedence.
rust_settings=$(python3 - "$work/RustProxy" <<'PY_RUST'
import pathlib
import re
import sys
import tomllib
repo = pathlib.Path(sys.argv[1])
with (repo / "Cargo.toml").open("rb") as f:
manifest = tomllib.load(f)
minimum = manifest.get("package", {}).get("rust-version")
if isinstance(minimum, dict) and minimum.get("workspace"):
minimum = manifest.get("workspace", {}).get("package", {}).get("rust-version")
if not isinstance(minimum, str) or not re.fullmatch(r"[0-9]+\.[0-9]+(?:\.[0-9]+)?", minimum):
sys.exit("Cargo.toml must declare a valid package rust-version for safe automatic toolchain updates")
channel = "stable"
toolchain_toml = repo / "rust-toolchain.toml"
toolchain_legacy = repo / "rust-toolchain"
if toolchain_toml.exists():
with toolchain_toml.open("rb") as f:
channel = tomllib.load(f).get("toolchain", {}).get("channel", "")
elif toolchain_legacy.exists():
channel = toolchain_legacy.read_text(encoding="utf-8").strip()
if not isinstance(channel, str) or not re.fullmatch(
r"(?:stable|beta|nightly|[0-9]+\.[0-9]+(?:\.[0-9]+)?(?:-[A-Za-z0-9_\-]+)?)", channel
):
sys.exit(f"Unsupported rust-toolchain channel: {channel!r}")
print(f"{minimum}|{channel}")
PY_RUST
)
required_rust=${rust_settings%%|*}
build_toolchain=${rust_settings#*|}
# Compare numerically (1.100 > 1.99); do not use lexicographic ordering.
rust_version_at_least() {
python3 - "$1" "$2" <<'PY_VERSION'
import sys
def version(value):
parts = value.split(".")
if len(parts) not in (2, 3) or not all(part.isdecimal() for part in parts):
sys.exit(2)
return tuple(map(int, (parts + ["0"] * (3 - len(parts)))))
sys.exit(0 if version(sys.argv[1]) >= version(sys.argv[2]) else 1)
PY_VERSION
}
# rustup run is a non-updating check: it fails when the channel is missing.
installed_rust=$(run_builder rustup run "$build_toolchain" rustc --version 2>/dev/null | awk '$1 == "rustc" { print $2; exit }' || true)
if [[ -z "$installed_rust" ]] || ! rust_version_at_least "$installed_rust" "$required_rust"; then
echo "Rust toolchain '$build_toolchain' needs installation/update (installed: ${installed_rust:-none}; required: >= $required_rust)."
case "$build_toolchain" in
stable|beta|nightly)
run_builder rustup update "$build_toolchain"
;;
*)
# A fixed toolchain must be respected. Do not silently override a pin
# that is older than the manifest's minimum supported Rust version.
if [[ -n "$installed_rust" ]]; then
echo "Pinned toolchain '$build_toolchain' is older than rust-version $required_rust; fix rust-toolchain.toml in the repository." >&2
exit 1
fi
run_builder rustup toolchain install "$build_toolchain" --profile minimal
;;
esac
installed_rust=$(run_builder rustup run "$build_toolchain" rustc --version | awk '$1 == "rustc" { print $2; exit }')
fi
if ! rust_version_at_least "$installed_rust" "$required_rust"; then
echo "Toolchain '$build_toolchain' provides Rust $installed_rust, but Cargo.toml requires $required_rust. Aborting before installation/update." >&2
exit 1
fi
run_builder rustup run "$build_toolchain" cargo --version
echo "Building with Rust $installed_rust ($build_toolchain); required >= $required_rust."
# Keep source builds reliable on small Debian VPSes. The repository release
# profile may use LTO/codegen settings that can make rustc exceed RAM limits.
@@ -119,7 +205,11 @@ if [[ "$ALLOW_REMOTE_ADMIN" != "0" && "$ALLOW_REMOTE_ADMIN" != "1" ]]; then
exit 1
fi
run_builder bash -lc "cd '$work/RustProxy' && CARGO_BUILD_JOBS='$BUILD_JOBS' CARGO_PROFILE_RELEASE_LTO=false CARGO_PROFILE_RELEASE_CODEGEN_UNITS=8 cargo build --release --locked"
run_builder env RUSTUP_TOOLCHAIN="$build_toolchain" \
CARGO_BUILD_JOBS="$BUILD_JOBS" \
CARGO_PROFILE_RELEASE_LTO=false \
CARGO_PROFILE_RELEASE_CODEGEN_UNITS=8 \
bash -c 'cd "$1" && exec cargo build --release --locked' _ "$work/RustProxy"
cd "$work/RustProxy"
if [[ "$MODE" == install ]]; then
@@ -1,5 +1,5 @@
# syntax=docker/dockerfile:1
FROM rust:1.88-trixie AS build
FROM rust:1.99-trixie AS build
WORKDIR /build
COPY . .
+2 -2
View File
@@ -22,7 +22,7 @@ from urllib.request import HTTPRedirectHandler, Request, build_opener, url2pathn
import zipfile
import zlib
VERSION = "5.32.11"
VERSION = "5.33.1"
DOWNLOAD_URL = f"https://github.com/swagger-api/swagger-ui/archive/refs/tags/v{VERSION}.zip"
PREFIX = f"swagger-ui-{VERSION}/"
MAX_ARCHIVE = 64 * 1024 * 1024
@@ -32,7 +32,7 @@ ASSETS = {
}
LICENSE_MEMBERS = ("LICENSE", "NOTICE", "dist/swagger-ui-bundle.js.LICENSE.txt")
HELP = ("Install Python 3.10+ and allow HTTPS access to github.com/codeload.github.com, "
"or set SWAGGER_UI_DOWNLOAD_URL=file:///absolute/path/swagger-ui-5.32.11.zip "
"or set SWAGGER_UI_DOWNLOAD_URL=file:///absolute/path/swagger-ui-5.33.1.zip "
"to a reviewed copy of the same tag archive. See docs/API_ACCESS.md.")
+29
View File
@@ -0,0 +1,29 @@
#!/usr/bin/env bash
# Run on a clean Rust 1.99+ build machine with registry and Swagger asset access.
set -euo pipefail
ROOT="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$ROOT"
if ! command -v cargo >/dev/null 2>&1 || ! command -v rustc >/dev/null 2>&1; then
printf 'Rust 1.99+ and Cargo are required; no lockfile was regenerated.\n' >&2
exit 127
fi
python3 tools/checks/preflight.py
if [[ "$(python3 scripts/version.py)" != '0.31.2' ]]; then
printf 'Expected RustProxy 0.31.2 sources.\n' >&2
exit 1
fi
printf 'Toolchain: '; rustc --version
cargo update
python3 tools/checks/dependency_upgrade_0312.py --require-lock
cargo metadata --locked --format-version 1 --no-deps >/dev/null
cargo fmt --all -- --check
cargo clippy --locked --all-targets -- -D warnings
./dev.sh check
./dev.sh test
./dev.sh release
if cargo audit --version >/dev/null 2>&1; then
cargo audit
else
printf 'NOTICE: cargo-audit is unavailable; install it and run cargo audit before release.\n' >&2
fi
printf 'Source and locked Rust build complete. Still required: live H1/H2/H3, auth/TLS, SQLite, cache, UI and performance acceptance.\n'
+1 -1
View File
@@ -2,7 +2,7 @@
"openapi": "3.0.3",
"info": {
"title": "RustProxy Administration API",
"version": "0.31.1",
"version": "0.31.2",
"description": "Administration API for the active panel origin. Sign in through the panel; the same-origin HttpOnly cookie authenticates all private operations. Mutations also require the exact Origin, X-RustProxy-Request: 1 and a session-bound X-CSRF-Token. Loopback HTTP is the default; Settings can enable HTTPS before using a non-loopback administration bind. This document contains no live configuration or credentials. Object schemas marked extensible are summaries: full configuration validation is authoritative in Rust."
},
"servers": [
+1
View File
@@ -1,5 +1,6 @@
use super::*;
use flate2::read::GzDecoder;
use http::header;
use http_body_util::BodyExt;
use std::io::Read;
+2 -2
View File
@@ -284,7 +284,7 @@
let first_body = json!({
"certificate_id": "opnsense-cert",
"cert_pem": first.cert.pem(),
"key_pem": first.key_pair.serialize_pem(),
"key_pem": first.signing_key.serialize_pem(),
})
.to_string();
@@ -326,7 +326,7 @@
let second_body = json!({
"certificate_id": "opnsense-cert",
"cert_pem": second.cert.pem(),
"key_pem": second.key_pair.serialize_pem(),
"key_pem": second.signing_key.serialize_pem(),
})
.to_string();
let response = router.oneshot(deploy(second_body)).await.unwrap();
+9 -7
View File
@@ -46,14 +46,16 @@
interception.append(toggle, codesLabel, hint); parent.append(interception);
const errors = errorsEditor(parent, values.errors);
parent.append(label("p", "pagesSafetyHint", "fine"));
return { read: () => {
const selectedTokens = enabled.checked ? statuses.value.split(/[\s,]+/).filter(Boolean) : [];
const selected = selectedTokens.map(Number);
if (enabled.checked && (!selected.length || selectedTokens.some(token => !/^\d{3}$/.test(token)) || selected.length > codes.length || new Set(selected).size !== selected.length || selected.some(code => !codes.includes(code)))) {
throw new Error(t("vhostInterceptOriginInvalid"));
return {
read: () => {
const selectedTokens = enabled.checked ? statuses.value.split(/[\s,]+/).filter(Boolean) : [];
const selected = selectedTokens.map(Number);
if (enabled.checked && (!selected.length || selectedTokens.some(token => !/^\d{3}$/.test(token)) || selected.length > codes.length || new Set(selected).size !== selected.length || selected.some(code => !codes.includes(code)))) {
throw new Error(t("vhostInterceptOriginInvalid"));
}
return { response: response.read(), fallback: fallback.read(), errors: errors.read(), intercept_statuses: selected };
}
return { response: response.read(), fallback: fallback.read(), errors: errors.read(), intercept_statuses: selected };
} };
};
}
let global = null;
function render() { if (!envelope || settingsDirty.has("pages-form")) return; const values = envelope.config.pages || {}, parent = $("global-pages-fields"); parent.replaceChildren(); const ip = pageEditor(parent, "pageDirectIp", values.direct_ip), unknown = pageEditor(parent, "pageUnknownHost", values.unknown_host), errors = errorsEditor(parent, values.errors); global = { read: () => ({ direct_ip: ip.read(), unknown_host: unknown.read(), errors: errors.read() }) }; }
+2 -2
View File
@@ -214,7 +214,7 @@ curl --fail --show-error --request POST \
$("admin-certificate").replaceChildren(generated, ...(envelope.config.tls?.certificates || []).map(cert => { const o = document.createElement("option"); o.value = cert.id; o.textContent = cert.id; return o; })); $("admin-certificate").value = current;
$("admin-hosts").value = (admin.allowed_hosts || []).join("\n");
$("admin-session-days").value = Number(admin.session_days || 30);
for (const codec of ["zstd", "br", "gzip"]) $( `admin-compression-${codec}` ).checked = admin.panel_compression?.[codec] !== false;
for (const codec of ["zstd", "br", "gzip"]) $(`admin-compression-${codec}`).checked = admin.panel_compression?.[codec] !== false;
}
const listenLocked = !!adminStatus?.listen_locked;
$("admin-listen").disabled = listenLocked; $("admin-listen-lock-note").hidden = !listenLocked;
@@ -300,7 +300,7 @@ curl --fail --show-error --request POST \
}, "prometheus");
bindSettingsForm("administration-form", next => {
const locked = !!latestStatus?.administration?.listen_locked;
next.administration = { listen: locked ? null : $("admin-listen").value.trim(), tls: $("admin-tls").checked, certificate_id: $("admin-tls").checked ? ($("admin-certificate").value || null) : null, allowed_hosts: $("admin-hosts").value.split(/[\n,]+/).map(v => v.trim()).filter(Boolean), session_days: Number($("admin-session-days").value), panel_compression: Object.fromEntries(["zstd", "br", "gzip"].map(codec => [codec, $( `admin-compression-${codec}` ).checked])) };
next.administration = { listen: locked ? null : $("admin-listen").value.trim(), tls: $("admin-tls").checked, certificate_id: $("admin-tls").checked ? ($("admin-certificate").value || null) : null, allowed_hosts: $("admin-hosts").value.split(/[\n,]+/).map(v => v.trim()).filter(Boolean), session_days: Number($("admin-session-days").value), panel_compression: Object.fromEntries(["zstd", "br", "gzip"].map(codec => [codec, $(`admin-compression-${codec}`).checked])) };
}, "administration");
bindSettingsForm("tls-policy-form", next => {
if (!$("tls-policy-12").checked && !$("tls-policy-13").checked) throw fieldError($("tls-policy-12"), t("tlsPolicyVersionRequired"));
+93 -25
View File
@@ -252,15 +252,21 @@ html.app-startup-failed .app-startup-recovery {
}
@keyframes app-startup-spin {
to { transform: rotate(360deg) }
to {
transform: rotate(360deg)
}
}
@keyframes app-startup-reveal {
to { visibility: visible }
to {
visibility: visible
}
}
@media (prefers-reduced-motion: reduce) {
.app-startup-spinner { animation: none }
.app-startup-spinner {
animation: none
}
}
body {
@@ -5416,7 +5422,9 @@ dialog .window-heading {
}
@media (max-width: 650px) {
.vh-www-pair-directions { grid-template-columns: minmax(0, 1fr) }
.vh-www-pair-directions {
grid-template-columns: minmax(0, 1fr)
}
}
.vh-www-choice {
@@ -7701,9 +7709,17 @@ body[data-density="compact"] .page-footer {
}
/* The legend keys must use the same theme/preset palette as the SVG strokes. */
.stat-chart .stat-chart-legend .legend-h1 i { background: var(--traffic-h1); }
.stat-chart .stat-chart-legend .legend-h2 i { background: var(--traffic-h2); }
.stat-chart .stat-chart-legend .legend-h3 i { background: var(--traffic-h3); }
.stat-chart .stat-chart-legend .legend-h1 i {
background: var(--traffic-h1);
}
.stat-chart .stat-chart-legend .legend-h2 i {
background: var(--traffic-h2);
}
.stat-chart .stat-chart-legend .legend-h3 i {
background: var(--traffic-h3);
}
.stat-chart .stat-chart-legend b {
font-family: var(--mono);
@@ -9946,6 +9962,7 @@ html[data-chart-style] :is(#workspace, dialog) svg polyline {
width: 100%
}
}
.listener-active-summary {
margin-top: 10px;
padding: 10px 12px;
@@ -10002,7 +10019,7 @@ html[data-chart-style] :is(#workspace, dialog) svg polyline {
margin-top: 10px
}
.location-header-editor .vh-header-rule-head > button {
.location-header-editor .vh-header-rule-head>button {
margin-left: auto;
flex: 0 0 auto
}
@@ -10013,7 +10030,6 @@ html[data-chart-style] :is(#workspace, dialog) svg polyline {
}
}
/* 0.29.7: PHP-FPM local/profile toggle rows with accessible info popovers. */
.php-switch-row {
display: flex;
align-items: center;
@@ -10023,6 +10039,7 @@ html[data-chart-style] :is(#workspace, dialog) svg polyline {
min-height: 30px;
margin: 4px 0;
}
.php-switch-row .php-switch-label {
display: inline-flex !important;
flex: 0 1 auto;
@@ -10032,14 +10049,26 @@ html[data-chart-style] :is(#workspace, dialog) svg polyline {
margin: 0 !important;
line-height: 1.5;
}
.php-switch-row .php-switch-label input[type="checkbox"] {
position: static;
flex: 0 0 var(--ui-check-size);
margin: 0;
}
.php-switch-row .php-switch-label span { min-width: 0; }
.php-switch-row .php-field-info { flex: 0 0 auto; }
.php-switch-row .php-field-info .metric-info-popover { right: auto; left: 0; }
.php-switch-row .php-switch-label span {
min-width: 0;
}
.php-switch-row .php-field-info {
flex: 0 0 auto;
}
.php-switch-row .php-field-info .metric-info-popover {
right: auto;
left: 0;
}
.php-reference pre {
overflow-x: auto;
white-space: pre;
@@ -10049,6 +10078,7 @@ html[data-chart-style] :is(#workspace, dialog) svg polyline {
background: var(--panel2);
font: 12px/1.6 var(--mono);
}
.php-profile-help-divider {
width: 100%;
height: 0;
@@ -10056,12 +10086,27 @@ html[data-chart-style] :is(#workspace, dialog) svg polyline {
border: 0;
border-top: 1px solid var(--line);
}
.php-origin-errors { grid-column: 1 / -1; display: grid; gap: 8px; padding: 10px 0; border-top: 1px solid var(--line); }
.php-origin-errors > label { max-width: 430px; }
.php-origin-errors > .check { margin: 0 !important; }
.php-origin-errors .fine { margin: 0; }
/* Keep the Overview refresh action on the same row as the page title. */
.php-origin-errors {
grid-column: 1 / -1;
display: grid;
gap: 8px;
padding: 10px 0;
border-top: 1px solid var(--line);
}
.php-origin-errors>label {
max-width: 430px;
}
.php-origin-errors>.check {
margin: 0 !important;
}
.php-origin-errors .fine {
margin: 0;
}
.page-intro-copy,
.page-intro-title-row {
min-width: 0;
@@ -10090,30 +10135,47 @@ html[data-chart-style] :is(#workspace, dialog) svg polyline {
font-size: clamp(18px, 5vw, 24px);
letter-spacing: -.6px
}
.page-intro-title-row .overview-refresh {
padding: 7px 9px;
font-size: 11px
}
}
/* Keep the system API indicator next to its action, aligned as one unit. */
.system-info-heading-actions { display: flex; align-items: center; gap: 8px; margin-left: auto; flex-shrink: 0; }
.system-info-heading-actions .pill { white-space: nowrap; }
.system-info-heading-actions {
display: flex;
align-items: center;
gap: 8px;
margin-left: auto;
flex-shrink: 0;
}
.system-info-heading-actions .pill {
white-space: nowrap;
}
/* Contextual, keyboard-accessible curl examples next to the cache allowlist. */
.cache-maintenance-help {
display: flex;
align-items: center;
gap: 8px;
position: relative;
}
.cache-maintenance-help .subheading { margin: 0; }
.cache-maintenance-info { position: static; flex-shrink: 0; }
.cache-maintenance-help .subheading {
margin: 0;
}
.cache-maintenance-info {
position: static;
flex-shrink: 0;
}
.cache-maintenance-info .metric-info-popover {
max-height: min(60vh, 420px);
overflow-y: auto;
width: min(440px, calc(100vw - 56px));
}
.cache-maintenance-info .metric-info-popover code {
display: block;
white-space: pre-wrap;
@@ -10122,5 +10184,11 @@ html[data-chart-style] :is(#workspace, dialog) svg polyline {
font-size: 11px;
line-height: 1.55;
}
.cache-maintenance-info .metric-info-popover .fine + code { margin-top: 6px; }
.cache-maintenance-info .metric-info-popover code + .fine { margin-top: 8px; }
.cache-maintenance-info .metric-info-popover .fine+code {
margin-top: 6px;
}
.cache-maintenance-info .metric-info-popover code+.fine {
margin-top: 8px;
}
+4 -2
View File
@@ -1,7 +1,7 @@
//! Local filesystem authority. The process owner can initialize or recover the sole admin.
use anyhow::{ensure, Context, Result};
use fs2::FileExt;
use rand::{rngs::OsRng, RngCore};
use rand::{rngs::SysRng, TryRng};
use std::{
fs::{self, File, OpenOptions},
io::Write,
@@ -86,7 +86,9 @@ pub fn load_or_create_setup_token(dir: &Path) -> Result<String> {
return load_setup_token(dir);
}
let mut bytes = [0u8; 32];
OsRng.fill_bytes(&mut bytes);
SysRng
.try_fill_bytes(&mut bytes)
.expect("secure operating system random source unavailable");
let token: String = bytes.iter().map(|b| format!("{b:02x}")).collect();
let mut file = match private_options().create_new(true).open(&path) {
Ok(file) => file,
+9 -9
View File
@@ -5,7 +5,7 @@ mod store;
use crate::{db::Store, runtime::ApiError};
use anyhow::{ensure, Context, Result};
use argon2::{
password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString},
password_hash::{phc::PasswordHash, PasswordHasher, PasswordVerifier},
Algorithm, Argon2, Params, Version,
};
pub use files::{
@@ -13,7 +13,7 @@ pub use files::{
prepare_directory, try_lock_directory,
};
use http::{header::COOKIE, HeaderMap, StatusCode};
use rand::{rngs::OsRng, RngCore};
use rand::{rngs::SysRng, TryRng};
use sha2::{Digest, Sha256};
use std::{
path::PathBuf,
@@ -56,7 +56,9 @@ pub fn now() -> i64 {
}
pub fn random_secret() -> String {
let mut bytes = [0_u8; 32];
OsRng.fill_bytes(&mut bytes);
SysRng
.try_fill_bytes(&mut bytes)
.expect("secure operating system random source unavailable");
bytes.iter().map(|b| format!("{b:02x}")).collect()
}
pub fn secret_hash(secret: &str) -> String {
@@ -118,17 +120,15 @@ pub fn validate_password(value: &str) -> Result<()> {
}
pub(crate) fn hash_sync(password: &str) -> Result<String> {
let params = Params::new(19_456, 2, 1, None).context("Argon2 parameters")?;
let salt = SaltString::generate(&mut OsRng);
Ok(Argon2::new(Algorithm::Argon2id, Version::V0x13, params)
.hash_password(password.as_bytes(), &salt)?
.hash_password(password.as_bytes())?
.to_string())
}
fn verify_sync(password: &str, encoded: &str) -> bool {
PasswordHash::new(encoded).ok().is_some_and(|hash| {
Argon2::default()
.verify_password(password.as_bytes(), &hash)
PasswordHash::new(encoded).is_ok()
&& Argon2::default()
.verify_password(password.as_bytes(), encoded)
.is_ok()
})
}
pub async fn hash_password_offline(password: String) -> Result<String> {
validate_password(&password)?;
+4 -4
View File
@@ -1,7 +1,7 @@
use anyhow::{bail, ensure, Context, Result};
use argon2::{Algorithm, Argon2, Params, Version};
use base64::Engine as _;
use rand::{rngs::OsRng, RngCore};
use rand::{rngs::SysRng, TryRng};
use ring::aead::{Aad, LessSafeKey, Nonce, UnboundKey, AES_256_GCM};
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
@@ -231,8 +231,8 @@ fn derive_key(password: &str, salt: &[u8]) -> Result<Zeroizing<[u8; 32]>> {
fn new_crypto(password: &str) -> Result<CryptoContext> {
let mut salt = [0u8; 16];
let mut nonce_prefix = [0u8; 4];
OsRng.fill_bytes(&mut salt);
OsRng.fill_bytes(&mut nonce_prefix);
SysRng.try_fill_bytes(&mut salt).expect("secure operating system random source unavailable");
SysRng.try_fill_bytes(&mut nonce_prefix).expect("secure operating system random source unavailable");
let header = CryptoHeader {
format_version: FORMAT_VERSION,
kdf: "Argon2id".into(),
@@ -395,7 +395,7 @@ fn decrypt_archive(input: &Path, output: &Path, password: &str) -> Result<Zeroiz
fn seal_master_key(master: &[u8], backup_key: &[u8]) -> Result<Vec<u8>> {
ensure!(master.len() == 32, "invalid secret store master key");
let mut nonce_bytes = [0u8; 12];
OsRng.fill_bytes(&mut nonce_bytes);
SysRng.try_fill_bytes(&mut nonce_bytes).expect("secure operating system random source unavailable");
let cipher = cipher(backup_key)?;
let mut encrypted = Zeroizing::new(master.to_vec());
cipher
+1 -1
View File
@@ -426,6 +426,6 @@ pub async fn restore(archive: &Path, data_dir: &Path, password: &str) -> Result<
pub fn generated_password() -> String {
let mut bytes = [0u8; 32];
OsRng.fill_bytes(&mut bytes);
SysRng.try_fill_bytes(&mut bytes).expect("secure operating system random source unavailable");
base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(bytes)
}
+5 -2
View File
@@ -3,7 +3,7 @@ use crate::{config::CompiledBackend, vhost::PoolMember};
use anyhow::{ensure, Result};
use axum::body::Body;
use http::{HeaderMap, HeaderValue, Request};
use rand::{distributions::Alphanumeric, Rng};
use rand::{distr::Alphanumeric, RngExt};
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use sha2::{Digest, Sha256};
@@ -218,7 +218,10 @@ impl Default for Settings {
impl Settings {
/// Resolve active health per member; a disabled monitor never probes.
pub fn health_for(&self, member: &PoolMember) -> HealthCheck {
let mut health = member.health_check.clone().unwrap_or_else(|| self.health_check.clone());
let mut health = member
.health_check
.clone()
.unwrap_or_else(|| self.health_check.clone());
if health.host.is_none() && health.mode == HealthMode::Http {
health.host = self.health_check.host.clone();
}
+10 -8
View File
@@ -24,8 +24,8 @@ impl Pool {
state: Arc::new(BackendState::new(settings.health_for(&definition).mode)),
definition,
backend,
token: rand::thread_rng()
.sample_iter(&Alphanumeric)
token: rand::rng()
.sample_iter(Alphanumeric)
.take(32)
.map(char::from)
.collect(),
@@ -53,14 +53,16 @@ impl Pool {
};
let same_origin = serde_json::to_vec(&member.definition.backend()).ok()
== serde_json::to_vec(&old.definition.backend()).ok();
let same_health =
serde_json::to_vec(&(
self.settings.health_for(&member.definition),
self.settings.passive_for(&member.definition),
)).ok() == serde_json::to_vec(&(
let same_health = serde_json::to_vec(&(
self.settings.health_for(&member.definition),
self.settings.passive_for(&member.definition),
))
.ok()
== serde_json::to_vec(&(
previous.settings.health_for(&old.definition),
previous.settings.passive_for(&old.definition),
)).ok();
))
.ok();
let same_clients = member.backend.client.reusable_with(&old.backend.client)
&& member
.backend
+7 -6
View File
@@ -2,7 +2,7 @@
//! ACLs run first; credentials are never cached with content or written to logs.
use anyhow::{ensure, Result};
use argon2::{
password_hash::{PasswordHash, PasswordVerifier},
password_hash::{phc::PasswordHash, PasswordVerifier},
Argon2,
};
use axum::response::{IntoResponse, Response};
@@ -88,8 +88,10 @@ impl Settings {
);
let hash = PasswordHash::new(&user.password_hash)
.map_err(|_| anyhow::anyhow!("basic.invalid_hash"))?;
// Match the former 16-character minimum for the PHC-encoded salt.
// phc 0.6 Salt::len() now measures decoded bytes instead.
ensure!(
hash.salt.is_some_and(|s| s.as_str().len() >= 16)
hash.salt.is_some_and(|s| s.to_salt_string().len() >= 16)
&& hash.hash.is_some_and(|h| h.len() == 32),
"basic.invalid_hash"
);
@@ -221,11 +223,10 @@ impl Compiled {
.map_err(|_| denied(StatusCode::SERVICE_UNAVAILABLE, None))?;
let verified = tokio::task::spawn_blocking(move || {
let _permit = permit;
PasswordHash::new(&encoded).ok().is_some_and(|hash| {
Argon2::default()
.verify_password(password.as_bytes(), &hash)
PasswordHash::new(&encoded).is_ok()
&& Argon2::default()
.verify_password(password.as_bytes(), encoded.as_str())
.is_ok()
})
})
.await
.unwrap_or(false);
+22 -16
View File
@@ -256,23 +256,29 @@ async fn cold_restart_preserves_transformed_validators_stale_metadata_and_no_tra
restart_shutdown.clone(),
);
assert_eq!(restarted.view().cold_entries, 2);
{
// COLD entries intentionally keep validators and headers on disk to bound RAM.
// Inspect them through the same verified metadata loader used on COLD hits.
let (key, transformed) = {
let cold = restarted.cold.lock().unwrap();
let transformed = cold
.entries
.values()
.find(|entry| entry.path == "/transformed.json")
.expect("transformed cold entry");
assert_eq!(transformed.stale_while_revalidate, 30);
assert_eq!(transformed.stale_if_error, 90);
assert_eq!(transformed.origin_etag.as_ref().unwrap(), "\"origin-v1\"");
assert_eq!(
transformed.origin_last_modified.as_ref().unwrap(),
"Mon, 26 Jan 2026 23:11:40 GMT"
);
assert_eq!(transformed.headers["etag"], "\"rustproxy-representation\"");
assert!(transformed.transformed);
}
cold.entries
.iter()
.find(|(_, entry)| entry.path == "/transformed.json")
.map(|(key, entry)| (*key, entry.clone()))
.expect("transformed cold entry")
};
assert_eq!(transformed.stale_while_revalidate, 30);
assert_eq!(transformed.stale_if_error, 90);
assert!(transformed.has_validator);
assert!(transformed.transformed);
let metadata = read_cold_metadata(&key, &transformed)
.await
.expect("persisted transformed metadata");
assert_eq!(metadata.origin_etag.as_ref().unwrap(), "\"origin-v1\"");
assert_eq!(
metadata.origin_last_modified.as_ref().unwrap(),
"Mon, 26 Jan 2026 23:11:40 GMT"
);
assert_eq!(metadata.headers["etag"], "\"rustproxy-representation\"");
let transformed_hit = restarted
.get_tiered(&transformed_ticket, &settings)
+24 -9
View File
@@ -1,10 +1,11 @@
fn request_id_hash(sequence: u64) -> String {
use rand::RngCore;
use rand::TryRng;
use sha2::{Digest, Sha256};
let mut nonce = [0u8; 32];
let mut rng = rand::rngs::OsRng;
rng.fill_bytes(&mut nonce);
let mut rng = rand::rngs::SysRng;
rng.try_fill_bytes(&mut nonce)
.expect("secure operating system random source unavailable");
let mut hash = Sha256::new();
hash.update(sequence.to_be_bytes());
hash.update(nonce);
@@ -13,9 +14,18 @@ fn request_id_hash(sequence: u64) -> String {
fn should_replace_error_page(response: &Response, minimal_welcome: bool) -> bool {
!minimal_welcome
&& response.extensions().get::<ErrorFallbackResponse>().is_none()
&& response.extensions().get::<crate::pages::OriginProvidedResponse>().is_none()
&& response.extensions().get::<crate::pages::PageServed>().is_none()
&& response
.extensions()
.get::<ErrorFallbackResponse>()
.is_none()
&& response
.extensions()
.get::<crate::pages::OriginProvidedResponse>()
.is_none()
&& response
.extensions()
.get::<crate::pages::PageServed>()
.is_none()
&& response
.extensions()
.get::<crate::tinyauth::AuthenticationFailure>()
@@ -298,14 +308,19 @@ async fn observe(State(state): State<Arc<App>>, mut request: Request, next: Next
let global_compression = &snapshot.envelope.config.compression;
let mut compression = global_compression.clone();
if let Some(frontend) = frontend_context.as_ref() {
compression = frontend.compression.resolve(global_compression, &compression);
compression = frontend
.compression
.resolve(global_compression, &compression);
}
if let Some(route) = effective_route_context {
if let crate::config::CompiledSource::WebServer(service) = &route.source {
compression = service.compression.resolve(global_compression, &compression);
compression = service
.compression
.resolve(global_compression, &compression);
}
}
response = crate::compression::encode(response, &original_headers, &method, &compression).await;
response =
crate::compression::encode(response, &original_headers, &method, &compression).await;
if let Some(encoding) = response
.extensions()
.get::<crate::compression::AppliedEncoding>()
+3 -3
View File
@@ -13,7 +13,7 @@ use http_body::{Body as HttpBody, Frame, SizeHint};
use http_body_util::BodyExt;
use hyper::upgrade::{OnUpgrade, Upgraded};
use hyper_util::rt::TokioIo;
use rand::{rngs::OsRng, RngCore};
use rand::{rngs::SysRng, TryRng};
use serde::Serialize;
use sha1::{Digest, Sha1};
use std::{
@@ -186,7 +186,7 @@ pub fn validate_h3(request: &Request) -> Result<Handshake> {
"WebSocket version must be 13"
);
let mut nonce = [0u8; 16];
OsRng.fill_bytes(&mut nonce);
SysRng.try_fill_bytes(&mut nonce).expect("secure operating system random source unavailable");
let key = STANDARD.encode(nonce);
Ok(Handshake {
accept: accept_for_key(&key),
@@ -229,7 +229,7 @@ pub fn validate_h2(request: &Request) -> Result<Handshake> {
"WebSocket version must be 13"
);
let mut nonce = [0u8; 16];
OsRng.fill_bytes(&mut nonce);
SysRng.try_fill_bytes(&mut nonce).expect("secure operating system random source unavailable");
let key = STANDARD.encode(nonce);
Ok(Handshake {
accept: accept_for_key(&key),
+35 -11
View File
@@ -1,7 +1,7 @@
//! Central encrypted secret storage for provider/ACME credentials.
//! Plaintext exists only for the duration of an explicit read/write operation.
use anyhow::{ensure, Context, Result};
use rand::{rngs::OsRng, RngCore};
use rand::{rngs::SysRng, TryRng};
use ring::aead::{Aad, LessSafeKey, Nonce, UnboundKey, AES_256_GCM};
use serde::Serialize;
use sqlx::Row;
@@ -59,7 +59,10 @@ fn read_key(path: &Path) -> Result<Zeroizing<[u8; KEY_BYTES]>> {
metadata.is_file() && !metadata.file_type().is_symlink(),
"secret store master key must be a regular file"
);
ensure!(metadata.len() == KEY_BYTES as u64, "invalid secret store master key size");
ensure!(
metadata.len() == KEY_BYTES as u64,
"invalid secret store master key size"
);
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
@@ -79,7 +82,10 @@ fn read_key(path: &Path) -> Result<Zeroizing<[u8; KEY_BYTES]>> {
let mut file = options.open(path)?;
file.read_exact(bytes.as_mut())?;
let mut extra = [0u8; 1];
ensure!(file.read(&mut extra)? == 0, "invalid secret store master key size");
ensure!(
file.read(&mut extra)? == 0,
"invalid secret store master key size"
);
Ok(bytes)
}
@@ -92,7 +98,9 @@ pub fn initialize(data_dir: &Path) -> Result<()> {
return Ok(());
}
let mut key = Zeroizing::new([0u8; KEY_BYTES]);
OsRng.fill_bytes(key.as_mut());
SysRng
.try_fill_bytes(key.as_mut())
.expect("secure operating system random source unavailable");
let mut options = OpenOptions::new();
options.write(true).create_new(true);
#[cfg(unix)]
@@ -158,13 +166,18 @@ pub async fn put(
metadata: serde_json::Value,
) -> Result<()> {
validate_identity(secret_id, provider)?;
ensure!(!plaintext.is_empty() && plaintext.len() <= MAX_SECRET_BYTES, "invalid secret size");
ensure!(
!plaintext.is_empty() && plaintext.len() <= MAX_SECRET_BYTES,
"invalid secret size"
);
let metadata = serde_json::to_string(&metadata)?;
ensure!(metadata.len() <= 4096, "secret metadata is too large");
initialize(data_dir)?;
let cipher = key(data_dir)?;
let mut nonce = [0u8; NONCE_BYTES];
OsRng.fill_bytes(&mut nonce);
SysRng
.try_fill_bytes(&mut nonce)
.expect("secure operating system random source unavailable");
let mut ciphertext = Zeroizing::new(plaintext.to_vec());
cipher
.seal_in_place_append_tag(
@@ -212,12 +225,16 @@ pub async fn get(
}
validate_identity(secret_id, &provider)?;
let version: i64 = row.try_get("encryption_version")?;
ensure!(version == ENCRYPTION_VERSION, "unsupported secret encryption version");
ensure!(
version == ENCRYPTION_VERSION,
"unsupported secret encryption version"
);
let nonce: Vec<u8> = row.try_get("nonce")?;
ensure!(nonce.len() == NONCE_BYTES, "invalid secret nonce");
let mut ciphertext = Zeroizing::new(row.try_get::<Vec<u8>, _>("ciphertext")?);
ensure!(
ciphertext.len() >= AES_256_GCM.tag_len() && ciphertext.len() <= MAX_SECRET_BYTES + AES_256_GCM.tag_len(),
ciphertext.len() >= AES_256_GCM.tag_len()
&& ciphertext.len() <= MAX_SECRET_BYTES + AES_256_GCM.tag_len(),
"invalid encrypted secret size"
);
let mut nonce_bytes = [0u8; NONCE_BYTES];
@@ -287,7 +304,10 @@ pub async fn migrate_acme_credentials(
return Ok(0);
}
let root_meta = fs::symlink_metadata(&root)?;
ensure!(root_meta.is_dir() && !root_meta.file_type().is_symlink(), "invalid ACME directory");
ensure!(
root_meta.is_dir() && !root_meta.file_type().is_symlink(),
"invalid ACME directory"
);
let mut migrated = 0usize;
for entry in fs::read_dir(&root)? {
let entry = entry?;
@@ -301,7 +321,10 @@ pub async fn migrate_acme_credentials(
continue;
}
let profile: serde_json::Value = serde_json::from_slice(&fs::read(&profile_path)?)?;
let provider = profile.get("provider").and_then(serde_json::Value::as_str).unwrap_or("");
let provider = profile
.get("provider")
.and_then(serde_json::Value::as_str)
.unwrap_or("");
let certificate_id = profile
.get("certificate_id")
.and_then(serde_json::Value::as_str)
@@ -341,7 +364,8 @@ pub async fn migrate_acme_credentials(
)
.await?;
plaintext.zeroize();
fs::remove_file(&credential_path).context("legacy ACME credential was encrypted but could not be removed")?;
fs::remove_file(&credential_path)
.context("legacy ACME credential was encrypted but could not be removed")?;
migrated += 1;
}
Ok(migrated)
+2 -2
View File
@@ -80,8 +80,8 @@ fn generate_locked(directory: &Path, name: &str, hosts: Vec<String>) -> Result<(
}
})
.collect::<Result<Vec<_>>>()?;
let rcgen::CertifiedKey { cert, key_pair } = rcgen::generate_simple_self_signed(hosts)?;
let key = Zeroizing::new(key_pair.serialize_pem());
let rcgen::CertifiedKey { cert, signing_key } = rcgen::generate_simple_self_signed(hosts)?;
let key = Zeroizing::new(signing_key.serialize_pem());
store_pair(directory, name, cert.pem().as_bytes(), key.as_bytes())
}
pub fn initialize_default(directory: &Path) -> Result<()> {
+5 -5
View File
@@ -77,7 +77,7 @@ mod tests {
dir.path(),
"shared-cert",
generated.cert.pem().as_bytes(),
generated.key_pair.serialize_pem().as_bytes(),
generated.signing_key.serialize_pem().as_bytes(),
)
.unwrap();
assert!(cert_file.starts_with("external-shared-cert-"));
@@ -100,7 +100,7 @@ mod tests {
let generated =
rcgen::generate_simple_self_signed(vec!["reconcile.example.test".into()]).unwrap();
let certificate = generated.cert.pem();
let key = generated.key_pair.serialize_pem();
let key = generated.signing_key.serialize_pem();
let (cert_file, key_file, _) = stage_external_pair(
dir.path(),
"reconcile-cert",
@@ -281,7 +281,7 @@ mod tests {
let b = rcgen::generate_simple_self_signed(vec!["b.localhost".into()]).unwrap();
assert!(parse_pair(
a.cert.pem().as_bytes(),
b.key_pair.serialize_pem().as_bytes()
b.signing_key.serialize_pem().as_bytes()
)
.is_err());
let dir = tempfile::tempdir().unwrap();
@@ -338,7 +338,7 @@ mod tests {
assert!(upload(
dir.path(),
pair.cert.pem().as_bytes(),
pair.key_pair.serialize_pem().as_bytes(),
pair.signing_key.serialize_pem().as_bytes(),
hosts,
)
.is_ok());
@@ -353,7 +353,7 @@ mod tests {
])
.unwrap();
let pem = pair.cert.pem();
let key = pair.key_pair.serialize_pem();
let key = pair.signing_key.serialize_pem();
assert!(upload(
dir.path(),
pem.as_bytes(),
+2 -2
View File
@@ -10,7 +10,7 @@ mod acme_tests {
dir.path(),
"acme-fixture",
cert.cert.pem().as_bytes(),
cert.key_pair.serialize_pem().as_bytes(),
cert.signing_key.serialize_pem().as_bytes(),
)
.unwrap();
let requested = vec!["example.org".into(), "*.example.org".into()];
@@ -62,7 +62,7 @@ mod acme_tests {
dir.path(),
"acme-many",
cert.cert.pem().as_bytes(),
cert.key_pair.serialize_pem().as_bytes(),
cert.signing_key.serialize_pem().as_bytes(),
)
.unwrap();
assert!(validate_acme(
+2 -2
View File
@@ -15,7 +15,7 @@ fn external_absolute_paths_follow_certbot_style_symlinks_on_each_reload() {
let first = rcgen::generate_simple_self_signed(vec!["example.test".into()]).unwrap();
fs::write(archive.join("fullchain1.pem"), first.cert.pem()).unwrap();
fs::write(archive.join("privkey1.pem"), first.key_pair.serialize_pem()).unwrap();
fs::write(archive.join("privkey1.pem"), first.signing_key.serialize_pem()).unwrap();
fs::set_permissions(archive.join("privkey1.pem"), fs::Permissions::from_mode(0o650)).unwrap();
symlink("../../archive/example.test/fullchain1.pem", live.join("fullchain.pem")).unwrap();
symlink("../../archive/example.test/privkey1.pem", live.join("privkey.pem")).unwrap();
@@ -47,7 +47,7 @@ fn external_absolute_paths_follow_certbot_style_symlinks_on_each_reload() {
])
.unwrap();
fs::write(archive.join("fullchain2.pem"), second.cert.pem()).unwrap();
fs::write(archive.join("privkey2.pem"), second.key_pair.serialize_pem()).unwrap();
fs::write(archive.join("privkey2.pem"), second.signing_key.serialize_pem()).unwrap();
fs::set_permissions(archive.join("privkey2.pem"), fs::Permissions::from_mode(0o640)).unwrap();
fs::remove_file(live.join("fullchain.pem")).unwrap();
fs::remove_file(live.join("privkey.pem")).unwrap();
+1 -1
View File
@@ -40,7 +40,7 @@ async fn php_fpm_gzip_response_finishes_after_fastcgi_eof() {
let tmp = TempDir::new().unwrap();
std::fs::write(tmp.path().join("index.php"), b"<?php echo 'placeholder';").unwrap();
let address = fastcgi_fixture(expected).await;
let configured = Service {
let mut configured = Service {
id: "php-compression-regression".into(),
name: "PHP compression regression".into(),
document_root: tmp.path().to_path_buf(),
+12 -12
View File
@@ -24,22 +24,22 @@ def read_docker_agent_source() -> str:
preflight(root)
manifest = tomllib.loads((root / "Cargo.toml").read_text())
assert manifest["package"]["version"] == "0.31.1"
assert manifest["package"]["version"] == "0.31.2"
assert manifest["dependencies"]["quinn"]["version"] == "0.11.12"
h3dep = manifest["dependencies"]["h3"]
assert h3dep["package"] == "hpx-h3" and h3dep["version"] == "=2.5.19" and "quinn" in h3dep["features"]
assert h3dep["package"] == "hpx-h3" and h3dep["version"] == "=2.5.20" and "quinn" in h3dep["features"]
assert "h3-quinn" not in manifest["dependencies"]
lock_path = root / "Cargo.lock"
if lock_path.exists():
lock = lock_path.read_text()
assert 'name = "rust-proxy"\nversion = "0.31.1"' in lock
assert 'name = "rust-proxy"\nversion = "0.31.2"' in lock
assert 'name = "systemstat"' in lock and 'name = "metrics-process"' in lock and 'name = "sysinfo"' in lock, "Cargo.lock must be resolver-generated after adding cross-platform statistics libraries"
for release_doc, marker in {
"README.md": "# RustProxy 0.31.1",
"CHANGELOG.md": "## 0.31.1",
"ROADMAP.md": "## 0.31.1",
"AGENTS.md": "Current source version: 0.31.1.",
"docs/TEST_REPORT.md": "# Verification report - RustProxy 0.31.1",
"README.md": "# RustProxy 0.31.2",
"CHANGELOG.md": "## 0.31.2",
"ROADMAP.md": "## 0.31.2",
"AGENTS.md": "Current source version: 0.31.2.",
"docs/TEST_REPORT.md": "# Verification report - RustProxy 0.31.2",
}.items():
assert marker in (root / release_doc).read_text(), f"stale release identity in {release_doc}"
assert tomllib.loads((root / "rust-toolchain.toml").read_text())["toolchain"]["channel"] == "stable"
@@ -285,7 +285,7 @@ assert 'Opt-in health decisions based on ordinary user traffic. Defaults to disa
assert '"force_ssl"' in openapi and '"forward_client_ip"' in openapi
assert '"backend_header"' in openapi and '"backend_timing_header"' in openapi
assert '"StreamMetrics"' in openapi and '"streams"' in openapi
for marker in ['"version": "0.31.1"', '"/api/traffic"', '"capture_user_agent"', '"capture_country"', '"hot_auto_trim_enabled"', '"hot_auto_trim_after_seconds"', '"hot_auto_trim_target_percent"', '"/api/config/{section}"', '"ConfigSectionUpdate"', '"cold_on_idle"', '"cold_on_age"', '"cold_max_hot_seconds"', '"cold_on_lru"', '"cold_on_shutdown"', '"cold_gc_interval_seconds"', '"cold_gc_max_delete"', '"collapse_enabled"', '"collapse_wait_timeout_ms"', '"collapse_timeout_fallback"', '"stale_if_error_timeout"', '"stale_if_error_connection"', '"stale_if_error_dns"', '"stale_if_error_tls"', '"stale_if_error_statuses"', '"proactive_refresh_enabled"', '"proactive_refresh_before_expiry_seconds"', '"proactive_refresh_percent"', '"proactive_refresh_max_inflight"', '"TransformSettings"', '"TransformOverrides"', '"/api/cache/cold/cleanup"', '"/api/cache/invalidate"', '"/api/cache/bans"', '"/api/cache/bans/{id}"', '"CacheInvalidationRequest"', '"CacheInvalidationResponse"', '"ColdCleanupJob"', '"match_type"', '"regex_insensitive"', '"rewrite"', '"response"', '"covered_hosts"', '"default": true']:
for marker in ['"version": "0.31.2"', '"/api/traffic"', '"capture_user_agent"', '"capture_country"', '"hot_auto_trim_enabled"', '"hot_auto_trim_after_seconds"', '"hot_auto_trim_target_percent"', '"/api/config/{section}"', '"ConfigSectionUpdate"', '"cold_on_idle"', '"cold_on_age"', '"cold_max_hot_seconds"', '"cold_on_lru"', '"cold_on_shutdown"', '"cold_gc_interval_seconds"', '"cold_gc_max_delete"', '"collapse_enabled"', '"collapse_wait_timeout_ms"', '"collapse_timeout_fallback"', '"stale_if_error_timeout"', '"stale_if_error_connection"', '"stale_if_error_dns"', '"stale_if_error_tls"', '"stale_if_error_statuses"', '"proactive_refresh_enabled"', '"proactive_refresh_before_expiry_seconds"', '"proactive_refresh_percent"', '"proactive_refresh_max_inflight"', '"TransformSettings"', '"TransformOverrides"', '"/api/cache/cold/cleanup"', '"/api/cache/invalidate"', '"/api/cache/bans"', '"/api/cache/bans/{id}"', '"CacheInvalidationRequest"', '"CacheInvalidationResponse"', '"ColdCleanupJob"', '"match_type"', '"regex_insensitive"', '"rewrite"', '"response"', '"covered_hosts"', '"default": true']:
assert marker in openapi, marker
for marker in ['"NotFoundHost"', '"not_found_hosts"', '"PrometheusSettings"', '"allow_trusted_network_without_token"', '"include_docker_vhosts"', '"include_streams"', '"include_logging"']:
assert marker in openapi, marker
@@ -1362,7 +1362,7 @@ metrics_dep = manifest["dependencies"]["metrics-process"]
assert metrics_dep["version"] == "=2.4.3" and metrics_dep["default-features"] is False
assert metrics_dep["features"] == ["dummy"]
sysinfo_dep = manifest["dependencies"]["sysinfo"]
assert sysinfo_dep["version"] == "=0.38.4" and sysinfo_dep["default-features"] is False
assert sysinfo_dep["version"] == "0.39" and sysinfo_dep["default-features"] is False
assert set(sysinfo_dep["features"]) == {"network", "system"}
for marker_0241 in [
"systemstat::{Platform, System}", "metrics_process::collector::collect",
@@ -1396,7 +1396,7 @@ assert '"error_log" => {' not in admin_0241 and 'request("/api/config/error_log"
assert '"error_log"' in openapi and 'logging, error_log, forwarding' not in openapi
assert 'logging_section_persists_error_log_settings_atomically' in admin_0241 and 'error_log_is_not_a_standalone_configuration_section' in admin_0241
assert 'location.pathname === "/logging"' in settings_js_0241
assert '"RuntimeInformation"' in openapi and '"version": "0.31.1"' in openapi
assert '"RuntimeInformation"' in openapi and '"version": "0.31.2"' in openapi
# 0.24.1 compiler and hostname-cardinality regressions.
tls_0241 = rust_source(root, "src/tls.rs")
@@ -1635,5 +1635,5 @@ for marker_0282 in [
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()
print("PASS: 0.31.1 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("PASS: 0.31.2 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.")
+81
View File
@@ -0,0 +1,81 @@
#!/usr/bin/env python3
"""Check the 0.31.2 dependency/source migration; optionally require a resolved lock.
This is not a substitute for rustc, cargo metadata or runtime tests.
"""
from __future__ import annotations
import argparse
from pathlib import Path
import re
import tomllib
ROOT = Path(__file__).resolve().parents[2]
parser = argparse.ArgumentParser()
parser.add_argument('--require-lock', action='store_true', help='Require the post-upgrade Cargo.lock produced by cargo update')
args = parser.parse_args()
manifest = tomllib.loads((ROOT / 'Cargo.toml').read_text())
assert manifest['package']['version'] == '0.31.2'
assert manifest['package']['rust-version'] == '1.99'
deps = manifest['dependencies']
expected_requirements = {
'argon2': '0.6', 'rand': '0.10', 'sqlx': '0.9', 'rcgen': '0.14.10',
'tower-http': '0.7.1', 'brotli': '9', 'sysinfo': '0.39',
'h3': '=2.5.20', 'tokio': '1.53.2', 'hyper': '1.12.0',
}
for name, version in expected_requirements.items():
entry = deps[name]
found = entry if isinstance(entry, str) else entry['version']
assert found == version, f'{name} requirement changed: {found!r} != {version!r}'
assert deps['h3']['package'] == 'hpx-h3'
assert deps['h3']['features'] == ['quinn']
assert deps['sqlx']['default-features'] is False
assert deps['sqlx']['features'] == ['runtime-tokio', 'sqlite']
assert set(deps['sysinfo']['features']) == {'network','system'}
assert set(deps['rcgen']['features']) == {'crypto', 'pem', 'ring'}
assert 'tls12' in deps['rustls']['features'] and 'ring' in deps['rustls']['features']
assert 'tls12' in deps['tokio-rustls']['features']
for target in ('Dockerfile', 'integrations/routeros-oci-build/Dockerfile.routeros'):
assert 'FROM rust:1.99-trixie AS build' in (ROOT / target).read_text(), target
files = [p for p in (ROOT / 'src').rglob('*.rs')]
sources = '\n'.join(p.read_text() for p in files)
for old in ('rngs::OsRng', 'rand::thread_rng()', 'SaltString::generate(',
'distributions::Alphanumeric', '.key_pair.serialize_pem()',
'hash_password(password.as_bytes(), &salt)'):
assert old not in sources, f'obsolete dependency API: {old}'
for new in ('SysRng.try_fill_bytes(', 'phc::PasswordHash',
'hash_password(password.as_bytes())', 'verify_password(password.as_bytes()',
'signing_key.serialize_pem()', 'rand::rng()'):
assert new in sources, f'missing dependency migration: {new}'
assert 'VERSION = "5.33.1"' in (ROOT / 'scripts/build/swagger_assets.py').read_text()
lock = tomllib.loads((ROOT / 'Cargo.lock').read_text())
installed: dict[str,set[str]] = {}
for item in lock['package']:
installed.setdefault(item['name'], set()).add(item['version'])
latest_required = {
'argon2': '0.6.0', 'rand': '0.10.3', 'sqlx': '0.9.0',
'rcgen': '0.14.10', 'tower-http': '0.7.1', 'brotli': '9.0.0',
'sysinfo': '0.39.6', 'hpx-h3': '2.5.20', 'hyper': '1.12.0',
'tokio': '1.53.2',
}
def compatible_update(package: str, installed_version: str, minimum: str) -> bool:
candidate = tuple(int(x) for x in installed_version.split('.')[:3])
baseline = tuple(int(x) for x in minimum.split('.')[:3])
if package == 'hpx-h3':
return candidate == baseline # Intentionally exact-pinned for the QUIC integration.
if candidate[0] != baseline[0]:
return False
if baseline[0] == 0 and candidate[1] != baseline[1]:
return False
return candidate >= baseline
missing = {name: version for name,version in latest_required.items()
if not any(compatible_update(name, candidate, version)
for candidate in installed.get(name, set()))}
if missing:
details = ', '.join(f'{name} {version}' for name,version in missing.items())
if args.require_lock:
raise SystemExit(f'FAIL: Cargo.lock not resolved to current deps: {details}. Run cargo update first.')
print(f'PENDING: lockfile still requires cargo update ({details})')
else:
print('PASS: pinned key packages found in post-upgrade lock; cargo --locked verification is still required')
print('PASS: 0.31.2 source dependency APIs, manifest, TLS features, toolchain images and Swagger pin (static only)')
+2 -2
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
"""Rust-independent documentation/Reader consistency checks for the 0.31.1 release."""
"""Rust-independent documentation/Reader consistency checks for the 0.31.2 release."""
from pathlib import Path
import re
from rust_source import rust_source
@@ -65,7 +65,7 @@ assert '"/docs/content.json"' in admin
readme = (ROOT / "README.md").read_text(encoding="utf-8")
roadmap = (ROOT / "ROADMAP.md").read_text(encoding="utf-8")
changelog = (ROOT / "CHANGELOG.md").read_text(encoding="utf-8")
assert readme.startswith("# RustProxy 0.31.1")
assert readme.startswith("# RustProxy 0.31.2")
assert "docs/WEB_SERVER.md" in readme and "docs/README.md" in readme
assert readme.count("## What changed in ") == 1, "README duplicates historical release notes; use CHANGELOG/archive"
assert "## Release history" in readme and "docs/ROADMAP_HISTORY.md" in readme
+2 -2
View File
@@ -75,10 +75,10 @@ def check(root: Path, repair_known: bool = False) -> list[str]:
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/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/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"]:
if not (root / path).is_file():
raise ValueError(f"Incomplete RustProxy 0.31.1 source tree: missing {path}")
raise ValueError(f"Incomplete RustProxy 0.31.2 source tree: missing {path}")
text = (root / "src/lib.rs").read_text(encoding="utf-8")
if '#[path = "auth/mod.rs"]' not in text:
raise ValueError("src/lib.rs is not the 0.31.1 entry point; extract the complete package, not individual files.")
raise ValueError("src/lib.rs is not the 0.31.2 entry point; extract the complete package, not individual files.")
return messages
+7 -7
View File
@@ -135,7 +135,7 @@ def upgrade_smoke(tmp: Path) -> None:
assert backup.is_dir()
assert (backup / "src/obsolete.rs").is_file()
assert not (target / "src/obsolete.rs").exists()
assert 'version = "0.31.1"' in (target / "Cargo.toml").read_text(encoding="utf-8")
assert 'version = "0.31.2"' in (target / "Cargo.toml").read_text(encoding="utf-8")
assert (target / "data/keep.txt").read_text(encoding="utf-8") == "preserve\n"
assert (target / ".env").read_text(encoding="utf-8") == "RUST_PROXY_DEMO=0\n"
assert (target / "langs/site.json").is_file()
@@ -146,7 +146,7 @@ def upgrade_smoke(tmp: Path) -> None:
assert (target / "docker-agent/certificate.go").is_file()
assert (target / "docker-agent/server.go").is_file()
manifest = json.loads((backup / "upgrade.json").read_text(encoding="utf-8"))
assert manifest["to_version"] == "0.31.1"
assert manifest["to_version"] == "0.31.2"
assert manifest["removed_obsolete_runtime_state"] is True
assert not legacy.exists()
assert not (backup / "legacy-data").exists()
@@ -582,7 +582,7 @@ def source_contract_smoke() -> None:
assert metrics_process["default-features"] is False
assert metrics_process["features"] == ["dummy"]
sysinfo_network = manifest["dependencies"]["sysinfo"]
assert sysinfo_network["version"] == "=0.38.4"
assert sysinfo_network["version"] == "0.39"
assert sysinfo_network["default-features"] is False
assert set(sysinfo_network["features"]) == {"network", "system"}
target_key = 'cfg(any(target_os = "linux", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", target_os = "dragonfly", target_os = "macos", target_os = "windows"))'
@@ -617,7 +617,7 @@ def source_contract_smoke() -> None:
assert marker in admin, marker
anonymous_block = admin.split("fn anonymous_allowed", 1)[1].split("const CERTIFICATE_API_KEY_HEADER", 1)[0]
assert '"/api/status"' not in anonymous_block
assert '"RuntimeInformation"' in openapi_0241 and '"version": "0.31.1"' in openapi_0241
assert '"RuntimeInformation"' in openapi_0241 and '"version": "0.31.2"' in openapi_0241
assert 'id="panel-logging"' not in html
for marker in ['id="settings-tab-logging"', 'id="settings-page-logging"', 'id="logging-form"', 'id="error-log-form"']:
assert marker in html, marker
@@ -1108,7 +1108,7 @@ def source_contract_smoke() -> None:
assert '.managed-update' in main and "flush_hot_to_cold_for_update" in main
openapi = (ROOT / "src/admin/openapi.json").read_text(encoding="utf-8")
assert '"/api/config/history/restore"' in openapi
for marker in ['"version": "0.31.1"', '"memory_preset"', '"cold_preset"', '"memory_budget_bytes"', '"max_inflight"', '"preset"', '"session_days"', '"session_lifetime_seconds"', '"accept_local_listener_risk"', '"backend_header"', '"backend_timing_header"', '"cold_on_age"', '"match_type"', '"regex_insensitive"', '"NotFoundHost"', '"not_found_hosts"', '"PrometheusSettings"', '"allow_trusted_network_without_token"', '"include_docker_vhosts"', '"include_streams"', '"include_logging"']:
for marker in ['"version": "0.31.2"', '"memory_preset"', '"cold_preset"', '"memory_budget_bytes"', '"max_inflight"', '"preset"', '"session_days"', '"session_lifetime_seconds"', '"accept_local_listener_risk"', '"backend_header"', '"backend_timing_header"', '"cold_on_age"', '"match_type"', '"regex_insensitive"', '"NotFoundHost"', '"not_found_hosts"', '"PrometheusSettings"', '"allow_trusted_network_without_token"', '"include_docker_vhosts"', '"include_streams"', '"include_logging"']:
assert marker in openapi, marker
assert '"software_update"' in openapi and '"update_available"' in openapi
for marker in ['"/api/certificates/deploy"', '"CertificateDeploy"', '"CertificateDeployResult"', '"/api/certificates/reload"']:
@@ -1177,7 +1177,7 @@ def container_contract_smoke() -> None:
assert "LogsDirectory=rustproxy" in generated_unit and "/var/log/rustproxy" in generated_unit
assert "Environment=MALLOC_ARENA_MAX=2" in generated_unit
for marker in [
"FROM rust:1.88-trixie AS build",
"FROM rust:1.99-trixie AS build",
"cargo generate-lockfile",
"cargo build --locked --release",
"FROM debian:trixie-slim AS runtime",
@@ -1313,7 +1313,7 @@ def main() -> None:
source_contract_smoke()
container_contract_smoke()
grafana_contract_smoke()
print("PASS: 0.31.1 SQLite/schema-9/upgrade, local-response 404 hosts, Prometheus/Grafana diagnostics, external Debian-packaging separation, cache/dashboard/logging/Nagios, retained admin/H3/stream/TLS contracts and reproducible source archives.")
print("PASS: 0.31.2 SQLite/schema-9/upgrade, local-response 404 hosts, Prometheus/Grafana diagnostics, external Debian-packaging separation, cache/dashboard/logging/Nagios, retained admin/H3/stream/TLS contracts and reproducible source archives.")
if __name__ == "__main__":
+3 -3
View File
@@ -43,7 +43,7 @@ const code = js.slice(js.indexOf(' const runtime = latestStatus?.runtime || {
const elements = new Map();
const dollar = id => { if (!elements.has(id)) elements.set(id, { textContent: '' }); return elements.get(id); };
const status = {
version: '0.31.1', uptime_seconds: 4201,
version: '0.31.2', uptime_seconds: 4201,
runtime: {
platform: 'Linux', operating_system: 'Debian GNU/Linux 13', kernel_version: '6.12.0', architecture: 'x86_64',
statistics_source: 'systemstat+sysinfo', data_directory: '/var/lib/rustproxy', database_path: '/var/lib/rustproxy/config.sqlite3',
@@ -63,7 +63,7 @@ const context = {
vm.runInNewContext(code, context);
assert.equal(dollar('runtime-operating-system').textContent, 'Debian GNU/Linux 13');
assert.equal(dollar('runtime-kernel-version').textContent, '6.12.0');
assert.equal(dollar('runtime-version').textContent, '0.31.1');
assert.equal(dollar('runtime-version').textContent, '0.31.2');
assert.equal(dollar('runtime-logical-cpus').textContent, '8');
assert.equal(dollar('runtime-cpu-usage').textContent, '17.2%');
assert.equal(dollar('runtime-python').textContent, 'Available · 3.13.5');
@@ -124,4 +124,4 @@ for (const key of ['python_version','pip_version','certbot_version']) {
assert.ok(runtimeSchema.properties.certificate_runtime.properties[key]);
assert.equal(runtimeSchema.properties.certificate_runtime.properties[key].nullable, true);
}
console.log('PASS: 0.31.1 legend palette, About cleanup, system tab/navigation, read-only API schema and simulated status rendering.');
console.log('PASS: 0.31.2 legend palette, About cleanup, system tab/navigation, read-only API schema and simulated status rendering.');
+1 -1
View File
@@ -54,7 +54,7 @@ def run_load(
port = parsed.port or (443 if parsed.scheme == "https" else 80)
tls = ssl._create_unverified_context() if insecure else ssl.create_default_context()
connection_type = http.client.HTTPSConnection if parsed.scheme == "https" else http.client.HTTPConnection
headers = {"user-agent": "RustProxy-Memory-Stress/0.31.1", "accept": "*/*"}
headers = {"user-agent": "RustProxy-Memory-Stress/0.31.2", "accept": "*/*"}
if host_header:
headers["host"] = host_header