Releases: wolfsoftwaresystemsltd/wolfproxy
Release list
WolfProxy v0.4.16
v0.4.16: survive backends with short keep-alives — fixes intermittent 502s through the node proxy (wabil)
pool_idle_timeout drops from 30s to 1s (WolfStack's actix backend
closes idle connections after 2s, so 2-30s-old pooled sockets were
already dead and 502'd with IncompleteMessage/SendRequest), plus one
same-backend retry on a fresh connection when a pooled socket turns
out stale — the request body is fully buffered so the replay is safe
and invisible. HeaderTimeout joins the routine-noise log filter.
Verifying this release
Each binary is signed via cosign keyless OIDC and ships with a SLSA build provenance attestation.
Verify the cosign signature:
cosign verify-blob \
--bundle wolfproxy-x86_64.cosign.bundle \
--certificate-identity-regexp 'https://github.com/wolfsoftwaresystemsltd/wolfproxy/\.github/workflows/release\.yml@.*' \
--certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \
wolfproxy-x86_64
Verify the build provenance:
gh attestation verify wolfproxy-x86_64 --repo wolfsoftwaresystemsltd/wolfproxy
Verify the SHA-256 checksum:
sha256sum -c SHA256SUMS
Artifacts
wolfproxy-x86_64/wolfproxy-aarch64— static musl binaries (Linux x86_64 and ARM64).wolfproxy-<arch>.cosign.bundle— cosign signature bundle (cert + signature + Rekor entry).SHA256SUMS— checksums for both binaries.
WolfProxy v0.4.15
v0.4.15: scaffold a default nginx tree when none exists
klasSponsor installed wolfproxy WITHOUT nginx, so /etc/nginx had no
config files or site structure at all — wolfproxy came up with zero
sites and behaved oddly.
ensure_nginx_scaffold() now lays down a minimal standard nginx tree when
the config dir has no usable configuration (no root.conf, nothing in
sites-enabled/, no *.conf in conf.d/): creates conf.d/, sites-available/,
sites-enabled/, a default web root (/var/www/html) with a landing page,
and a default catch-all HTTP site on :80 (sites-available/default +
sites-enabled/default). Runs at startup before the config load.
NEVER overwrites: it bails the moment any existing config is detected,
and every individual write is existence-guarded, so an install that
already has nginx files is untouched (Golden Rule). Best-effort and
non-fatal — a write failure is logged and wolfproxy still starts.
Tests: scaffolds_a_loadable_default_site_when_empty (round-trips the
default site through the real load_nginx_config and asserts a usable
HTTP:80 server + idempotency) and never_touches_an_existing_config.
Also smoke-tested the live binary against an empty config dir. clippy-clean.
Verifying this release
Each binary is signed via cosign keyless OIDC and ships with a SLSA build provenance attestation.
Verify the cosign signature:
cosign verify-blob \
--bundle wolfproxy-x86_64.cosign.bundle \
--certificate-identity-regexp 'https://github.com/wolfsoftwaresystemsltd/wolfproxy/\.github/workflows/release\.yml@.*' \
--certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \
wolfproxy-x86_64
Verify the build provenance:
gh attestation verify wolfproxy-x86_64 --repo wolfsoftwaresystemsltd/wolfproxy
Verify the SHA-256 checksum:
sha256sum -c SHA256SUMS
Artifacts
wolfproxy-x86_64/wolfproxy-aarch64— static musl binaries (Linux x86_64 and ARM64).wolfproxy-<arch>.cosign.bundle— cosign signature bundle (cert + signature + Rekor entry).SHA256SUMS— checksums for both binaries.
WolfProxy v0.4.14
v0.4.14: name the process holding a port on bind failure
klasSponsor hit 'Failed to bind HTTPS to 0.0.0.0:80: Address in use'
with the unhelpful blanket hint 'try running with sudo or use ports >
1024' — but sudo can't free a port another process already holds.
bind_failure_hint() now splits by error kind:
- AddrInUse: identify the holder via /proc and name it, e.g.
'port 80 is already held by nginx (pid 1234). Stop that process or
move this site to a free port.' Plus a nudge that SSL on port 80 is
almost certainly a misconfig (HTTP=80, HTTPS=443) — which is what
klasSponsor's config did to land :80 in the HTTPS listener set. - PermissionDenied: keep the run-as-root / port>1024 advice (the only
case where that hint is actually correct).
identify_port_holder() parses /proc/net/tcp{,6} for LISTEN sockets on
the port, collects their inodes, then walks /proc//fd to find the
owning PID(s) and reads /proc//comm. Pure /proc — no ss/lsof
dependency, so it works on minimal images. Best-effort: falls back to
the ss hint if the holder can't be resolved. Runs only on bind failure
(startup, rare). Verified end-to-end against a live listener. clippy-clean.
Verifying this release
Each binary is signed via cosign keyless OIDC and ships with a SLSA build provenance attestation.
Verify the cosign signature:
cosign verify-blob \
--bundle wolfproxy-x86_64.cosign.bundle \
--certificate-identity-regexp 'https://github.com/wolfsoftwaresystemsltd/wolfproxy/\.github/workflows/release\.yml@.*' \
--certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \
wolfproxy-x86_64
Verify the build provenance:
gh attestation verify wolfproxy-x86_64 --repo wolfsoftwaresystemsltd/wolfproxy
Verify the SHA-256 checksum:
sha256sum -c SHA256SUMS
Artifacts
wolfproxy-x86_64/wolfproxy-aarch64— static musl binaries (Linux x86_64 and ARM64).wolfproxy-<arch>.cosign.bundle— cosign signature bundle (cert + signature + Rekor entry).SHA256SUMS— checksums for both binaries.
WolfProxy v0.4.13
v0.4.13: resolve relative includes against the config root, like nginx
wabil reported includes that "were working and now aren't." The cause is a
long-standing divergence from nginx: WolfProxy resolved a relative include
path only against the including file's directory, whereas nginx resolves it
against the config-root prefix (e.g. /etc/nginx). So the standard pattern of a
server block in sites-enabled/ doing include snippets/ssl.conf; — written
relative to /etc/nginx — resolved to /etc/nginx/sites-enabled/snippets/ssl.conf
(missing) and the directives were silently dropped.
Fix: thread the config-root prefix through the parser and resolve every
relative include against BOTH the including file's dir (historical behaviour)
and the prefix (nginx behaviour), at the top level and inside server blocks.
Matches are merged and de-duplicated by canonical path, so a config that
already worked keeps working and a file reachable both ways is never loaded
twice. Purely additive — it can only resolve includes that previously matched
nothing, so it can't break an existing install.
- include_candidate_paths() builds the [file-dir, prefix] candidate list.
- parse_nginx_config_in / parse_nginx_content_in carry the prefix recursively;
load_nginx_config + load_nginx_sites pass nginx_dir as the prefix. - expand_server_includes() takes the prefix and de-dups the same way.
- 2 tests: a prefix-relative server-block include now resolves; a top-level
include reachable via identical base+prefix roots loads exactly once.
Verifying this release
Each binary is signed via cosign keyless OIDC and ships with a SLSA build provenance attestation.
Verify the cosign signature:
cosign verify-blob \
--bundle wolfproxy-x86_64.cosign.bundle \
--certificate-identity-regexp 'https://github.com/wolfsoftwaresystemsltd/wolfproxy/\.github/workflows/release\.yml@.*' \
--certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \
wolfproxy-x86_64
Verify the build provenance:
gh attestation verify wolfproxy-x86_64 --repo wolfsoftwaresystemsltd/wolfproxy
Verify the SHA-256 checksum:
sha256sum -c SHA256SUMS
Artifacts
wolfproxy-x86_64/wolfproxy-aarch64— static musl binaries (Linux x86_64 and ARM64).wolfproxy-<arch>.cosign.bundle— cosign signature bundle (cert + signature + Rekor entry).SHA256SUMS— checksums for both binaries.
WolfProxy v0.4.12
v0.4.12: support globbed includes inside server blocks
An include directive inside a server { } block was fed line-by-line to
the flat-directive parser, which can't parse nested location { } blocks,
and globbed paths were rejected outright with "only supported for top-level
includes". So include sites-enabled/subfolders/*.conf; inside a server —
where the files hold location blocks — silently dropped every directive
(wabil report: "configuration OK — 28 server block(s)" but his locations
never loaded).
Fix: expand server-block includes textually BEFORE parsing the server body
(expand_server_includes), mirroring nginx's textual-include semantics — the
included content (location/if blocks, flat directives, further includes) is
then parsed in the server context. * globs work exactly as at the top
level (zero-match = warning, not error); relative paths resolve against the
including file's dir; recursion is depth-capped (16).
A file that wrongly contains a full server { } block is skipped with a
warning rather than folded into the outer server (nginx can't nest servers;
such a file belongs in a top-level include). The old in-place include branch
and the now-unused base_dir params were removed.
3 tests: globbed include with location blocks; recursive + literal includes;
and the nested-server skip guard. Runtime-verified with wolfproxy --test
on wabil's exact shape. One independent review pass (its critical/warning
findings — double-processing corruption path and double-warning — fixed).
Verifying this release
Each binary is signed via cosign keyless OIDC and ships with a SLSA build provenance attestation.
Verify the cosign signature:
cosign verify-blob \
--bundle wolfproxy-x86_64.cosign.bundle \
--certificate-identity-regexp 'https://github.com/wolfsoftwaresystemsltd/wolfproxy/\.github/workflows/release\.yml@.*' \
--certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \
wolfproxy-x86_64
Verify the build provenance:
gh attestation verify wolfproxy-x86_64 --repo wolfsoftwaresystemsltd/wolfproxy
Verify the SHA-256 checksum:
sha256sum -c SHA256SUMS
Artifacts
wolfproxy-x86_64/wolfproxy-aarch64— static musl binaries (Linux x86_64 and ARM64).wolfproxy-<arch>.cosign.bundle— cosign signature bundle (cert + signature + Rekor entry).SHA256SUMS— checksums for both binaries.
WolfProxy v0.4.11
v0.4.11: surface include failures + clippy-clean with CI gate
WolfProxy silently swallowed include problems: a glob that matched no
files (e.g. Debian's extensionless sites-enabled entries vs *.conf) or
an explicit include of a missing file just produced an empty config,
and wolfproxy --test printed "configuration OK" regardless. Real nginx
errors loudly here, so the operator had no signal why their sites weren't
served (wabil report).
- NginxConfig gains include_warnings: Vec, populated for a
zero-match glob, a missing explicit include, or an invalid glob.
merge_configs propagates them up nested include chains. wolfproxy --testnow prints each include warning (still exit 0: a
missing include never takes the proxy down, and failing would make
WolfStack's reload-gate refuse to reload a node with a pre-existing
broken include). The server-block count remains the primary signal.- The live loader logs the same warnings via warn! on startup/reload.
- Server-context include failures (snippets) now warn instead of being
dropped silently.
Also clears all clippy warnings at root (manual_strip -> strip_prefix/
strip_suffix, map_entry, while_let -> for, let_and_return,
unnecessary_map_or; one documented allow for the 8-arg request handler)
and adds a CI lint gate (cargo clippy --all-targets -- -D warnings +
tests) that blocks the release build.
Verifying this release
Each binary is signed via cosign keyless OIDC and ships with a SLSA build provenance attestation.
Verify the cosign signature:
cosign verify-blob \
--bundle wolfproxy-x86_64.cosign.bundle \
--certificate-identity-regexp 'https://github.com/wolfsoftwaresystemsltd/wolfproxy/\.github/workflows/release\.yml@.*' \
--certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \
wolfproxy-x86_64
Verify the build provenance:
gh attestation verify wolfproxy-x86_64 --repo wolfsoftwaresystemsltd/wolfproxy
Verify the SHA-256 checksum:
sha256sum -c SHA256SUMS
Artifacts
wolfproxy-x86_64/wolfproxy-aarch64— static musl binaries (Linux x86_64 and ARM64).wolfproxy-<arch>.cosign.bundle— cosign signature bundle (cert + signature + Rekor entry).SHA256SUMS— checksums for both binaries.
WolfProxy v0.4.10
v0.4.10: load config the nginx way — read root.conf main config + process its includes
WolfProxy previously only auto-scanned <config_dir>/sites-enabled/* and
conf.d/*.conf and never read a top-level main config, so an nginx-style
root.conf full of include directives was ignored (wabil report).
New load_nginx_config(): if <config_dir>/root.conf exists, parse it as the
main config and let its include directives pull in everything else —
relative paths and * globs resolved against each file's directory, exactly
like nginx, recursively. Both the live loader and 'wolfproxy --test' use it.
Falls back to the existing sites-enabled/ + conf.d/ auto-scan when there is
no root.conf, so existing installs are unchanged — root.conf is a
non-standard filename, so detecting it by presence only ever opts a user in.
Test covers both: root.conf include-glob loads a subfolder server, and the
no-root.conf auto-scan fallback still loads conf.d servers.
Verifying this release
Each binary is signed via cosign keyless OIDC and ships with a SLSA build provenance attestation.
Verify the cosign signature:
cosign verify-blob \
--bundle wolfproxy-x86_64.cosign.bundle \
--certificate-identity-regexp 'https://github.com/wolfsoftwaresystemsltd/wolfproxy/\.github/workflows/release\.yml@.*' \
--certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \
wolfproxy-x86_64
Verify the build provenance:
gh attestation verify wolfproxy-x86_64 --repo wolfsoftwaresystemsltd/wolfproxy
Verify the SHA-256 checksum:
sha256sum -c SHA256SUMS
Artifacts
wolfproxy-x86_64/wolfproxy-aarch64— static musl binaries (Linux x86_64 and ARM64).wolfproxy-<arch>.cosign.bundle— cosign signature bundle (cert + signature + Rekor entry).SHA256SUMS— checksums for both binaries.
WolfProxy v0.4.9
v0.4.9: WebSocket / upgrade proxying — consoles work through the proxy
handle_proxy did http_client.request().await and returned the response,
and stripped the Upgrade header, so WebSocket upgrades never completed —
every console (WolfStack + Proxmox, both wss) hung behind the proxy (wabil
2026-06-12). The plain-HTTP listener uses axum::serve (upgrades work
natively); only the HTTPS manual loop was broken (it used serve_connection).
Fix:
- TLS listener -> serve_connection_with_upgrades.
- New is_ws_upgrade(); handle_proxy gains a WebSocket branch that forwards
the handshake to one backend KEEPING Upgrade/Connection/Sec-WebSocket-*
(stripping the other hop-by-hop headers), captures the client upgrade
before sending upstream, and on a 101 spawns a detached copy_bidirectional
tunnel between the two upgraded sockets (handshake bounded by a 30s
timeout; copy unbounded for the session). - Reuses the existing hyper-util legacy client (it manages upgrades), so
v0.4.8's hyper-rustls connector handles wss upstreams too.
Verified end-to-end (netns-isolated): normal HTTPS GET unregressed, plus
wss->plain-ws and wss->wss(TLS) upstreams both 101 + correct accept +
working bidirectional tunnel. Confirmed the 5-min connection cap does NOT
kill upgraded tunnels (the conn future resolves at upgrade; timer never fires).
Verifying this release
Each binary is signed via cosign keyless OIDC and ships with a SLSA build provenance attestation.
Verify the cosign signature:
cosign verify-blob \
--bundle wolfproxy-x86_64.cosign.bundle \
--certificate-identity-regexp 'https://github.com/wolfsoftwaresystemsltd/wolfproxy/\.github/workflows/release\.yml@.*' \
--certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \
wolfproxy-x86_64
Verify the build provenance:
gh attestation verify wolfproxy-x86_64 --repo wolfsoftwaresystemsltd/wolfproxy
Verify the SHA-256 checksum:
sha256sum -c SHA256SUMS
Artifacts
wolfproxy-x86_64/wolfproxy-aarch64— static musl binaries (Linux x86_64 and ARM64).wolfproxy-<arch>.cosign.bundle— cosign signature bundle (cert + signature + Rekor entry).SHA256SUMS— checksums for both binaries.
WolfProxy v0.4.8
v0.4.8: upstream HTTPS — proxy_pass https:// backends actually work
The upstream client was a plain hyper HttpConnector with no TLS path at
all: an https:// proxy_pass parsed fine, then the client sent a
cleartext HTTP hello to the TLS port and the backend reset the
connection (502, ConnectionReset). Every https upstream — WolfStack
:8553, Proxmox :8006, PBS :8007 — was categorically broken while http
upstreams worked, and no config directive could fix it.
The client now wraps the same tuned connector (keepalive, connect
timeout, bounded pool) in hyper-rustls with nginx-parity defaults for
the fleet this proxies: certificate verification off (proxy_ssl_verify
off equivalent — these upstreams all serve self-signed certs; the TLS
session itself is still fully verified against the presented cert) and
SNI sent automatically from the URI host. https_or_http keeps plain
http:// upstreams on the same client.
Verified live: a self-signed local TLS upstream through wolfproxy
returned 502/ConnectionReset on v0.4.7 and returns 200 with the
upstream body on this build; the http control path is unchanged.
Verifying this release
Each binary is signed via cosign keyless OIDC and ships with a SLSA build provenance attestation.
Verify the cosign signature:
cosign verify-blob \
--bundle wolfproxy-x86_64.cosign.bundle \
--certificate-identity-regexp 'https://github.com/wolfsoftwaresystemsltd/wolfproxy/\.github/workflows/release\.yml@.*' \
--certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \
wolfproxy-x86_64
Verify the build provenance:
gh attestation verify wolfproxy-x86_64 --repo wolfsoftwaresystemsltd/wolfproxy
Verify the SHA-256 checksum:
sha256sum -c SHA256SUMS
Artifacts
wolfproxy-x86_64/wolfproxy-aarch64— static musl binaries (Linux x86_64 and ARM64).wolfproxy-<arch>.cosign.bundle— cosign signature bundle (cert + signature + Rekor entry).SHA256SUMS— checksums for both binaries.
WolfProxy v0.4.7
v0.4.7: parse CLI args — --version/--test/--help no longer start a server
WolfProxy's main() did no argument parsing, so wolfproxy --version
(WolfStack's install detection) and wolfproxy --test (WolfStack's config
validation) fell straight through to the normal server start and bound
:80/:443. Run while the systemd instance was down, the probe bound :80 and
left an orphaned listener OUTSIDE systemd's cgroup — systemctl can't reap what
it never tracked, so the next systemctl start failed with "Address in use"
and the proxy could neither restart nor apply rules (klasSponsor's hemulen
node, 2026-06-05: ss showed a stray wolfproxy pid on :80 while the unit was
inactive/dead).
Now parse argv before the banner and before binding anything:
--version/-V print version, exit 0
--help/-h usage, exit 0
--test/-t validate wolfproxy.toml + nginx sites, exit 0/1; never binds
--config/-c config path (default ./wolfproxy.toml — unchanged under systemd,
which sets WorkingDirectory=/opt/wolfproxy)
run_config_test() loads config exactly as the serve path does, without binding.
The no-arg systemd start is byte-for-byte unchanged. Runtime-verified:
--version=0, --test valid=0, --test missing=1, --bogus=2, and none leaves a
listener on :80. Build clean, zero warnings.
Verifying this release
Each binary is signed via cosign keyless OIDC and ships with a SLSA build provenance attestation.
Verify the cosign signature:
cosign verify-blob \
--bundle wolfproxy-x86_64.cosign.bundle \
--certificate-identity-regexp 'https://github.com/wolfsoftwaresystemsltd/wolfproxy/\.github/workflows/release\.yml@.*' \
--certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \
wolfproxy-x86_64
Verify the build provenance:
gh attestation verify wolfproxy-x86_64 --repo wolfsoftwaresystemsltd/wolfproxy
Verify the SHA-256 checksum:
sha256sum -c SHA256SUMS
Artifacts
wolfproxy-x86_64/wolfproxy-aarch64— static musl binaries (Linux x86_64 and ARM64).wolfproxy-<arch>.cosign.bundle— cosign signature bundle (cert + signature + Rekor entry).SHA256SUMS— checksums for both binaries.