Use this checklist before publishing a Fluxheim release, changing dependency versions, changing TLS/cache/proxy behavior, or building an image for other people to run.
- Confirm the Rust version in
rust-toolchain.toml,Cargo.toml,README.md, and theContainerfileall agree. - Run the release metadata preflight:
scripts/validate-release-metadata.sh- Check that the pinned Rust version is still the current stable release before release work starts.
- Re-check the latest
cargo-denyandcargo-auditversions:
cargo info cargo-deny
cargo info cargo-audit
cargo info cargo-sbomInstall or update the tools with locked dependency resolution:
cargo install --locked cargo-deny
cargo install --locked cargo-audit
cargo install --locked cargo-sbom --version 0.10.0- Run
cargo updateonly as a deliberate dependency maintenance step. - Before tagging, verify compatible dependency updates are exhausted. This gate
intentionally ignores
pingora*packages because supported Fluxheim profiles no longer compile Pingora; separate Pingora dependency and boundary policy gates verify that this remains true.
scripts/check_latest_crates.sh- During the
1.6.xnative-runtime line, capture the runtime baseline evidence before tagging.checkmode records locked dependency trees, proves the per-profile Pingora dependency surface remains empty, and includes native web TLS proof profiles;releasemode also records the default release-binary size and local performance baseline:
scripts/capture-runtime-baseline.sh check
scripts/capture-runtime-baseline.sh release
scripts/capture-runtime-performance-baseline.sh release
scripts/validate-pingora-dependency-policy.sh check
scripts/validate-native-web-tls.sh check
scripts/validate-native-runtime-cutover.sh
scripts/validate-runtime-fixtures.sh check- Review every new dependency for maintenance status and SPDX license metadata.
- Review every new build script, procedural macro,
*-syscrate, vendored native source, native tool invocation, Cargo alias, and CI workflow edit as build-host code execution. - Keep
deny.tomlstrict: unknown registries, git sources, and unknown licenses stay denied. - Keep
.cargo/audit.tomlexceptions narrow, versioned, and documented with a removal condition. - For unmaintained transitive dependencies inherited from TLS backends or other
optional integrations, prefer upstream fixes over local forks. Track the
package, advisory ID, upstream source, Fluxheim reachability, and removal
condition in
SECURITY.mdand release notes. Do not promote an ignored advisory from warning to accepted risk without a written reason. - Run the release wrapper:
scripts/release_checks.shThe wrapper runs formatting, clippy, tests, selected feature builds, example
config validation, cargo deny check, cargo audit, and localhost smoke tests.
It may include incubator-module smoke checks during development. The 0.5.x
preview scope is basic static/proxy/TLS behavior; the 1.0 gateway scope is
defined in the versioning plan and must cover representative multi-site gateway
configs before a stable tag.
For the stable-release gate without incubator module checks, use:
scripts/stable_release_gate.sh releaseRun the core localhost smoke directly when changing stable static/proxy behavior:
scripts/smoke_1_0_core.shThis smoke requires openssl for a temporary self-signed certificate. It covers
HTTP static hosting, HTTP proxying, static certificate storage validation, HTTPS
static hosting, and HTTPS proxying.
Confirm GitHub CodeQL default setup is enabled for main. Do not also enable an
advanced CodeQL workflow for the same repository; GitHub rejects advanced SARIF
uploads when default setup is active.
Confirm the Rust CI workflow still runs the core feature matrix in both check
and release modes, plus the scripts/smoke_1_0_core.sh localhost smoke.
- Static certificate chains and private keys are supported. Bought certificates remain a first-class deployment mode.
- In the default rustls build, validate the default downstream certificate and at least one vhost-specific SNI certificate per TLS listener.
- The core smoke generates temporary static certificates and proves static, proxied, and SNI-selected vhosts over a TLS listener.
- ACME config and renewal queue planning are implemented, but account/order and challenge runtime work is not release-ready yet. Do not document automated ACME issuance as operational until that runtime is implemented and tested.
- Validate production-like TLS storage before startup:
fluxheim --config path/to/fluxheim.toml --check-tls-storageOn Unix, private keys should be owner-only (0600) and ACME storage directories
should be owner-only (0700).
For a 0.5.x preview or 1.0.x release, confirm the stable core binaries
compile. This matrix intentionally excludes post-1.0 modules such as load
balancing, metrics, admin, ACME runtime, WAF, PHP/CGI, Cloudflare automation,
legacy HTTP, and WASM.
scripts/validate-1-0-core.sh releaseFor faster local iteration before a release tag, run the same matrix as checks instead of release builds:
scripts/validate-1-0-core.sh checkValidate the representative split-config fixture set before a 1.0.x release:
scripts/validate-1-0-fixtures.shPassing memory-safe Rust builds is not enough for a proxy. Before every stable
release, run this gate against the stable modules included in that release and
record the results in the release notes. For 0.5.x, the target is the
documented basic-sites preview. For 1.0.x, the target is the gateway core
needed for representative multi-site configs. For later minors, include every
module promoted to stable in that release.
Local checks that can be run from this repository:
- Run the local gate wrapper:
scripts/stable_release_gate.sh releaseFor faster iteration before release week, run the same local gate in check mode:
scripts/stable_release_gate.sh checkThe stable gate includes the mapped OWASP Top 10 2025 baseline in quick
check mode. Run the deeper representative-test mode when the release touches
request handling, admin, PHP, TLS, logging, cache, dependency, or
error-handling code:
scripts/validate-owasp-top10-2025.sh runThe stable gate includes the promoted cache and observability smoke tests for
the 1.2 line. In release mode it also requires a local container image
smoke before tagging: the root Containerfile plus representative Debian and
Alpine variant builds. This catches workspace, packaging, and image build
context mistakes before an immutable tag is pushed.
For udp-proxy beta changes, include the optional UDP smoke before tagging:
FLUXHEIM_GATE_UDP=1 scripts/stable_release_gate.sh checkUse FLUXHEIM_UDP_SMOKE_ITERATIONS=<count> with that gate for a longer local
soak when UDP forwarding, rate limits, or passive health behavior changed.
If a release builder cannot run Podman, do not skip this silently. Run the image gate on another builder and attach the evidence before tagging. The emergency-only bypass is:
FLUXHEIM_SKIP_IMAGE_GATE=1 scripts/stable_release_gate.sh releaseOptional gates below still cover slower or environment-specific checks such as TLS backend matrices, load testing, fuzz target compilation, and full Podman variant image smoke tests.
For release-candidate validation, run the deeper local gate. It enables the TLS backend matrix, OpenSSL FIPS-capable validation, rustls/AWS-LC FIPS-capable validation, local TLS scan, local load smoke, raw request-framing smoke, real Wasm sandbox execution, and fuzz target compile check:
scripts/stable_release_deep_gate.sh releaseIf the local release builder can run the rest of the deep gate but is not an AWS-LC-supported rustls FIPS builder, disable only that gate and attach rustls evidence from the supported builder separately:
FLUXHEIM_GATE_FIPS_RUSTLS=0 scripts/stable_release_deep_gate.sh releaseEnable optional local matrices when the release includes those deliverables:
FLUXHEIM_GATE_TLS_BACKENDS=1 scripts/stable_release_gate.sh release
FLUXHEIM_GATE_FIPS_OPENSSL=1 scripts/stable_release_gate.sh release
FLUXHEIM_GATE_FIPS_RUSTLS=1 scripts/stable_release_gate.sh release
FLUXHEIM_GATE_OWASP_RUN=1 scripts/stable_release_gate.sh release
FLUXHEIM_GATE_TLS_SCAN=1 scripts/stable_release_gate.sh release
FLUXHEIM_GATE_LOAD=1 scripts/stable_release_gate.sh release
FLUXHEIM_GATE_WASM=1 scripts/stable_release_gate.sh release
FLUXHEIM_GATE_FRAMING=1 scripts/stable_release_gate.sh release
FLUXHEIM_GATE_FUZZ_CHECK=1 scripts/stable_release_gate.sh release
FLUXHEIM_GATE_IMAGE_VARIANTS="debian alpine wolfi suse-micro" scripts/stable_release_gate.sh releaseFor release builders that are expected to have a working OpenSSL FIPS provider, make absence a hard failure:
FLUXHEIM_REQUIRE_FIPS_PROVIDER=1 \
FLUXHEIM_GATE_FIPS_OPENSSL=1 \
scripts/stable_release_gate.sh release- Dependency and license policy:
cargo deny check
cargo audit
scripts/generate-sbom.sh
scripts/reproducible_build_check.sh- Static analysis and regression suite:
cargo fmt --all --check
cargo clippy --all-targets -- -D warnings
cargo test
scripts/validate-1-0-core.sh release
scripts/smoke_1_0_core.sh-
Direct unsafe policy. Fluxheim's crate roots use
#![forbid(unsafe_code)]; this is enforced by the normal compile, clippy, and feature-matrix gates. Do not add directunsafeblocks or raw FFI to Fluxheim source. Prefer safe wrapper crates for platform APIs and safe task/waker primitives in tests. -
Panic and overflow policy. Release artifacts build with
panic = "abort"andoverflow-checks = true, and crate roots deny productionunwrap(),expect(), andpanic!()through clippy. Keep operational errors onResultor explicit fallback responses. Test-only assertions may continue using panicking helpers. -
Native dependency policy. FIPS-capable profiles can intentionally pull native cryptographic modules such as OpenSSL providers or
aws-lc-fips-sys. Record the provider/module certificate, compiler, platform, and Security Policy in release evidence, and run sanitizer builds where supported by that native dependency and target platform. -
Secret handling policy. Admin bearer tokens are read into zeroing buffers, hashed, and compared through a vetted constant-time equality primitive. Keep new long-lived credentials in
sanitizationsecret containers or existing audited zeroing buffer types, and usesanitization::ctfor any equality check involving authentication tokens, signing secrets, or derived verifiers. Disk cache encryption keys and OpenBao tokens are long-lived credentials and must follow this policy. Do not classify normal cache object bodies as secrets unless a future module explicitly stores private user data. -
Third-party unsafe inventory. Before a stable release, run
cargo-geigeras an informational dependency review and record unexpected changes in the release notes. Do not treat every dependency-level unsafe finding as an automatic blocker: the networking, TLS, OS, and runtime stack may contain audited unsafe internals. Treat new unsafe in direct dependencies as a review trigger alongsidecargo denyandcargo audit. -
Supply-chain evidence. Stable release gates must generate SPDX and CycloneDX SBOMs with
cargo-sbom, record their checksums, and run a local reproducible-build check that compiles the release binary twice from separate target directories and compares the resulting executable hash. This does not prove cross-machine bit-for-bit reproducibility; it proves the published builder can reproduce its own release artifact from the tagged source and pinned lockfile. Cross-distro/container reproducibility remains a future hardening goal. -
Human dependency review. Track the
cargo-vetadoption path in Rust Supply-Chain Security. Do not makecargo-veta blocking release gate until the initial exemption set and trusted audit imports have been reviewed. -
Request framing and smuggling regression tests. The unit suite must keep coverage for ambiguous
Content-LengthandTransfer-Encoding, invalidContent-Length, oversized headers, oversized body streams, unsafeHostvalues, and redirect target construction. Later stable modules must add their own malicious-input regression tests before release.
Run the raw-socket framing smoke before tagging a stable proxy release:
scripts/smoke_request_framing.shThis smoke bypasses browser/client normalization and verifies malformed request framing is rejected on the wire.
- Header scrubbing checks. The 1.0 smoke must continue proving that the default response policy does not expose version banners and strips common upstream implementation headers from proxied responses. Later stable modules must prove equivalent secret/banner scrubbing for their own outputs.
- Local load test with a release binary and a representative 1.0 config:
cargo build --release
hey -z 30s -c 64 -host static.test http://127.0.0.1:18080/Watch CPU, memory, open file descriptors, and logs during this test. The server should reject or shed load cleanly rather than panic.
Fluxheim also includes a local wrapper for this check:
scripts/load_smoke_1_0.shThe default duration is intentionally modest. Tune it for release validation:
FLUXHEIM_LOAD_DURATION=60s FLUXHEIM_LOAD_CONCURRENCY=128 scripts/load_smoke_1_0.sh- TLS policy check. The current Fluxheim TLS listener configuration uses Fluxheim-owned rustls/OpenSSL policy helpers for ALPN, protocol versions, curves, and cipher lists. Release scans remain an enforcement gate: a stable release must not ship if a selected TLS backend negotiates deprecated protocol versions or weak cipher suites.
- Local TLS smoke. The localhost smoke already proves a static certificate over
a TLS listener. For a deeper local scan, use a temporary copy of the latest
stable
testssl.shrelease against the release binary. Re-check the latest stable tag before each release; as of 2026-05-06, GitHub listsv3.2.3as the latest stable release:
curl -sSfL -o /tmp/testssl.sh https://raw.githubusercontent.com/testssl/testssl.sh/v3.2.3/testssl.sh
chmod +x /tmp/testssl.sh
/tmp/testssl.sh --fast --parallel https://127.0.0.1:18443/Fluxheim also includes a local wrapper that starts a temporary TLS listener, downloads the pinned stable scanner, and stores the report:
scripts/tls_scan_local.shDo not vendor this script into the repository. Re-download it for release validation and record the commit/version it reports.
Checks that should be run by the maintainer from a deployment-like environment:
- Run an authenticated or allowlisted OWASP ZAP/Burp active scan against a staging deployment that has no real secrets, users, or customer content.
- Run
testssl.shor a public TLS scanner against the real public hostname after DNS, certificates, and firewall rules are final. - Run a larger
hey,wrk,k6, or Gatling load test from a separate host. Local loopback hides network and socket pressure. - Run a slow-client test from a separate host to confirm header/body timeouts
and connection limits are effective. If using a tool such as
slowhttptest, point it only at infrastructure you own. - Confirm upstream handoff uses one of the supported safe deployment patterns: private network, loopback/service network, TLS upstream verification, or mTLS where the backend supports it.
- Confirm trusted-proxy settings are not enabled for public traffic unless the actual ingress proxy IP ranges are pinned and reviewed.
Fuzzing gate:
- Before tagging a stable release, add or run fuzz targets for custom parser and
policy code rather than Pingora internals. For
1.0.x, cover Host normalization, redirect URL construction, header mutation policy, static path resolution, cache key generation, and cache-header parsing. Later stable modules must add fuzz targets for their own parsers and security policy boundaries. - Fuzzing is release-blocking if it finds a panic, path escape, open redirect, request-boundary ambiguity, or unbounded memory growth.
Initial fuzz targets live under fuzz/ and can be run with cargo-fuzz:
cargo install --locked cargo-fuzz
scripts/validate-fuzz-targets.sh
cargo fuzz run host_normalization -- -max_total_time=60
cargo fuzz run cache_headers -- -max_total_time=60
cargo fuzz run header_policy -- -max_total_time=60Increase the runtime substantially before a stable tag. Keep generated corpora and artifacts out of git unless a minimized regression case should be promoted into a normal unit test.
Property-testing gate:
cargo testincludes property-based checks for Host normalization, dynamic request-header templates, and ACME retry backoff. These tests are intended to protect invariants that are easy to miss with hand-picked examples.- Add a property test when a parser, normalizer, path resolver, or security policy accepts attacker-controlled input and the expected behavior can be stated as an invariant.
- Property-test failures are release-blocking if they show a panic, invalid header generation, path escape, open redirect, arithmetic overflow, or acceptance of a malformed security-sensitive value.
Formal-verification note:
- Kani or another Rust verifier is not part of the mandatory
1.0release gate. Before enabling it in CI, add a small harness for one bounded parser and prove that the toolchain is reproducible on the supported release builders. - Candidate future harnesses are header-template parsing, Host normalization, ACME date/backoff arithmetic, and static path containment checks.
Run this matrix for normal development and for pre-release validation when
post-1.0 modules changed. Passing these commands does not make the modules part
of the 1.0 stable scope.
cargo build --release --no-default-features --features proxy,load-balancer
cargo build --release --no-default-features --features proxy,metrics
cargo build --release --no-default-features --features proxy,tls-rustls,acme
cargo build --release --no-default-features --features profile-full
cargo build --release --no-default-features --features profile-cache-edge
cargo build --release --no-default-features --features profile-proxy-edge
cargo build --release --no-default-features --features profile-load-balancer-edge
cargo build --release --no-default-features --features profile-observability
scripts/validate-fips-openssl.sh check
scripts/validate-fips-rustls.sh check
scripts/smoke_peer_fill_cache.sh
scripts/smoke_observability_local.shTLS backend validation is split into its own helper so release builders can check the supported rustls and OpenSSL backend families explicitly:
scripts/validate-tls-backends.sh releaseThe rustls/AWS-LC FIPS validation helper requires the aws-lc-fips-sys build
toolchain, including CMake, Go, and a C compiler. Skip it on release builders
that are not intended to produce rustls/AWS-LC FIPS candidate evidence.
For release evidence, use an AWS-LC-supported FIPS builder. Rolling
distribution compilers can be too new for aws-lc-fips-sys; the helper fails
early for known newer GCC/Clang families unless the investigation-only
FLUXHEIM_ALLOW_EXPERIMENTAL_AWS_LC_FIPS_TOOLCHAIN=1 override is set.
If local OpenSSL FIPS evidence works but rustls/AWS-LC evidence must be
collected elsewhere, run scripts/release_evidence.sh VERSION --skip-fips-rustls locally and attach the rustls/AWS-LC evidence captured from
the supported builder separately. Use --skip-fips-openssl for the opposite
case, or --skip-fips only when no FIPS evidence is relevant to that release.
For a clean container check on common stable tooling, run the helper inside a Debian Bookworm Rust image with CMake, Go, Clang/libclang, and pkg-config:
podman run --rm -v "$PWD:/work:Z" -w /work docker.io/library/rust:1-bookworm \
bash -c 'set -e; export PATH=/usr/local/cargo/bin:$PATH; apt-get update; apt-get install -y --no-install-recommends cmake golang-go clang libclang-dev pkg-config perl ca-certificates; CARGO_TARGET_DIR=/tmp/fluxheim-target scripts/validate-fips-rustls.sh release'For hardware-specific local binaries, use target-cpu=native only for the
machine that will run the binary. Do not publish those binaries as portable
artifacts:
RUSTFLAGS="-C target-cpu=native" cargo build --releaseRun the Podman smoke before publishing a container image:
FLUXHEIM_RELEASE_PODMAN=1 scripts/release_checks.shBefore publishing multi-OS container images, run the variant smoke matrix:
scripts/podman_smoke_variants.shOr include it in the release wrapper:
FLUXHEIM_RELEASE_PODMAN=1 FLUXHEIM_RELEASE_PODMAN_VARIANTS=1 scripts/release_checks.shIf Codex or another sandboxed tool cannot reach the rootless socket, export the socket explicitly:
CONTAINER_HOST="unix://$XDG_RUNTIME_DIR/podman/podman.sock" scripts/podman_smoke.shThe smoke builds the image, validates the packaged config, and checks that the runtime process does not run as root.
Fluxheim publishes variant images from the explicit Containerfiles under
containers/: wolfi, alpine, suse-micro, and debian. Each OS variant
is published for the full/default, cache, proxy, PHP, and, starting with the
1.5 line, load-balancer image profiles. Pre-1.5 tags can include the
load-balancer profile only through manual workflow dispatch. GitHub Container
Registry publishing uses the repository GITHUB_TOKEN; Docker Hub publishing
requires DOCKERHUB_USERNAME and DOCKERHUB_TOKEN repository secrets.
The published default images should keep FLUXHEIM_RUNTIME_UID=65532 and
FLUXHEIM_RUNTIME_GID=65532. Root-runtime images are supported through build
args, but should be tagged deliberately and not replace the non-root defaults.
- Confirm
git statuscontains only intentional release changes. - Update
CHANGELOG.mdbefore tagging. - Prepare release notes from
docs/release-notes-template.md. - Confirm the repository still carries the
EUPL-1.2license. - Confirm reviewed advisory exceptions still match current
cargo auditoutput. - Capture a local release-gate report for the tag:
scripts/capture_release_gate_report.sh release- For release candidates, capture the deep gate with scanner, load, framing, fuzz-target, and TLS-backend checks enabled:
FLUXHEIM_CAPTURE_DEEP=1 scripts/capture_release_gate_report.sh release- Attach or summarize the stable release security and stability gate results in the release notes.