github.com/fabriziosalmi/caddy-waf
v0.4.1
#2839 most downloaded on Go modules
fabriziosalmi/caddy-waf
What this package is like to depend on
Last release 3 days ago
21 Aug 2026
Release timing varies
gaps range from 8 days to 3 months
Most releases are documented
notes for 19 of 28 stable releases
Nothing withdrawn
no release was ever pulled
2 years old
246 releases · first in 2025
35 releases in the last 12 months
see the full history below
Release timeline
246 releases · Jan 2025 to Aug 2026Releases
latest 60 of 246-
v0.4.121 Aug 2026Release notes
Open source →What's Changed
- fix: response-target rule in an early phase panics the whole request (#144) by @fabriziosalmi in #146
- test: make TestTorConfig_Provision hermetic (no external CDN fetch) by @fabriziosalmi in #147
- chore: release v0.4.1 by @fabriziosalmi in #148
Full Changelog: v0.4.0...v0.4.1
Release notes
Open source →Fixed
- A response-target rule in an early phase no longer panics the request. A rule listing
RESPONSE_HEADERS(orRESPONSE_HEADERS:<name>) in phase 1 or 2 — as several OWASP CRS rules do, e.g.950010— reachedw.Header()on the nilhttp.ResponseWriterthathandlePhasepasses before the response exists. The panic was recovered into an HTTP 500, so every request behind the WAF failed. Response-header extraction is now nil-safe (mirroring the existing response-body guard): an out-of-phase response target degrades to a skipped target instead of crashing. Reported on OPNsense with OWASP rules (#144, #146).
Changed
- Bumped version constant
wafVersiontov0.4.1.
Internal
TestTorConfig_Provisionis now hermetic — it serves the Tor exit-node list from a localhttptest.Serverinstead of a live external CDN, removing the last third-party network dependency from CI (#147).
-
v0.4.018 Aug 2026Release notes
Open source →What's Changed
- feat: input normalization for rule matching (v0.4.0) — close encoding evasion by @fabriziosalmi in #142
Full Changelog: v0.3.11...v0.4.0
Release notes
Open source →Security
Rule matching now inspects requests the way the application will decode them, closing encoding evasion. Confirmed empirically before the fix: a percent-encoded attack in a raw request target slipped past literal rule patterns.
rules/sql-injection.jsonblockedid=1 UNION SELECTbut notid=1 %55NION%20SELECT, nor%75nion, nor the same payload in anapplication/x-www-form-urlencodedbody. Because the application decodes the request before acting on it, the WAF must match the decoded form. This affected the raw targetsARGS,URI,URLandBODY— the targets used by the bulk of the bundled and modular rules (SQLi, XSS, RCE, SSTI, SSRF).The fix is additive dual-match: each target is matched against the raw value first, then against a normalized copy, and the rule fires if either matches. Testing the raw value first is a mathematical guarantee that no rule which matches today can stop matching — the change only adds coverage, never removes it. Unencoded traffic runs no extra regex (the normalized pass is skipped when normalization does not change the value).
Design followed ModSecurity/OWASP-CRS/Coraza prior art, including its guardrails:
- Single-pass decoding, never recursive.
%2555decodes to the literal%55, matching what a single-decoding backend sees; decoding twice would manufacture false positives and diverge from reality. - Context-aware
+: a space in query/body context, literal in the path portion ofURI/URL, which are split on the first?. - Lenient decoder: a malformed escape (
%,%zz, a truncated%a) is left literal, never dropped — Go'surl.QueryUnescapeblanks the whole value on one bad byte, which would itself be a bypass.
Added
- Optional per-rule
transformationsfield (ModSecurity/CRS-style pipeline):["urlDecodeUni","removeNulls","replaceComments","htmlEntityDecode",…]. Absent means the per-target default chain (urlDecode,removeNulls,compressWhitespace); an explicit[]means match the raw value only. Names are case-insensitive, accept at:prefix, and an unknown name fails at load time rather than silently doing nothing. - ModSecurity/CRS target aliases so SecLang-derived rule files resolve:
REQUEST_HEADERS→HEADERS,REQUEST_COOKIES→COOKIES,QUERY_STRING→ARGS,REQUEST_URI→URI,REQUEST_BODY→BODY,REQUEST_FILENAME→PATH. Previously these fell through to "unknown extraction target" and were skipped, so 135 bundled rules (88 usingREQUEST_COOKIES, 47REQUEST_HEADERS) silently lost cookie/header coverage and one rule was fully inert. transform.gowith the transformation registry and lenient single-pass decoders;normalization_test.goandtransform_test.gocovering the closed evasions, the zero-regression property, per-rule transformations, the aliases, load-time validation, and — as an explicit test — the documented limit that double-encoding is not decoded twice.
Honest limits
Single-pass decoding does not catch double-encoding (correct against a single-decoding backend), and
%uXXXX/ overlong-UTF-8 are not decoded by the default pipeline. Cookie and header targets are not normalized by default; a rule that needs it can settransformations. See Input normalization.Migration
This changes what every rule targeting
ARGS/URI/URL/BODYsees. Because matching is additive (raw tested first), no existing rule stops firing and no config change is required. Rule authors who want the raw, un-normalized value only can set"transformations": []on a rule. Custom rule files using ModSecurity target names now gain coverage they were silently missing.Changed
- Bumped version constant
wafVersiontov0.4.0.
-
v0.3.1118 Aug 2026Release notes
Open source →What's Changed
- docs: document the published container images by @fabriziosalmi in #134
- build(deps): bump github.com/fsnotify/fsnotify from 1.9.0 to 1.10.1 in the go-minor-patch group across 1 directory by @dependabot[bot] in #135
- build(deps): bump the actions group across 1 directory with 14 updates by @dependabot[bot] in #136
- docs: stop presenting add-package as an install path for caddy-waf by @fabriziosalmi in #139
- fix: metrics endpoint raced on shared counters and corrupted its response by @fabriziosalmi in #140
- feat: whitelist_ip — exempt addresses from the IP-reputation checks by @fabriziosalmi in #141
Full Changelog: v0.3.10...v0.3.11
Release notes
Open source →Added
-
whitelist_ip— exempt addresses from the IP-reputation checks without switching the WAF off for them. Accepts bare IPs, CIDR ranges, or the tokenprivate_ranges, and is repeatable:whitelist_ip private_ranges whitelist_ip 203.0.113.4 198.51.100.0/24Requested in #137 by @nozonyan:
whitelist_countriesblocks anything it cannot geolocate, which includes every address on the local network, so enabling it locks you out of your own service from inside the LAN. The only workarounds weregeoip_fail_open— which also admits every unresolvable public address — or maintaining two site blocks with separate rule sets, with the risk of leaving the public one unprotected after a test.The exemption covers the checks that judge a client by where it comes from: the IP blacklist (including Tor exit nodes fed into it),
whitelist_countries/block_countries, andblock_asns. It deliberately does not cover the DNS blacklist (which judges the requested host, not the client), the rate limiter, or the regex rules in any phase. Exempting an address from geolocation is the fix; stopping inspection of its requests is not.private_rangesexpands to exactly the set Caddy uses for its own placeholder —192.168.0.0/16,172.16.0.0/12,10.0.0.0/8,127.0.0.1/8,fd00::/8,::1. Identical rather than "improved": a WAF and the server in front of it disagreeing about which addresses are private is how bypasses get built. An entry that does not parse fails startup rather than being skipped with a warning.
Security notes on the design
- The whitelist matches the peer address only, never
X-Forwarded-For. This is the deliberate opposite of the blacklist, which checks the peer address and every forwarded hop. When blocking, consulting extra addresses can only block more; when allowing, honouring a client-supplied header would let anyone sendX-Forwarded-For: 10.0.0.1and exempt themselves from the blacklist, the country filter and the ASN filter in a single header. Covered byTestWhitelistIgnoresForwardedHeaders. private_rangesis only safe when caddy-waf is the edge. Because the check is on the peer address, running behind another proxy makes the peer that proxy — typically a private or loopback address — which would exempt every request passing through it. The WAF now logs a warning at startup whenprivate_rangesis whitelisted, anddocs/configuration.mddocuments the trap.
Changed
- Bumped version constant
wafVersiontov0.3.11.
-
v0.3.11-0.20260728203530-3d2d926b4f0d28 Jul 2026 pre-releaseNothing published for this version
-
v0.3.1028 Jul 2026Release notes
Open source →What's Changed
- feat: publish container images to ghcr, and make docker build use the source by @fabriziosalmi in #133
Full Changelog: v0.3.9...v0.3.10
Release notes
Open source →Fixed
docker build .ignored your source tree. The Dockerfile rangit clone https://github.com/fabriziosalmi/caddy-waf.gitand built that, so the build context was never used: the image contained whatever happened to be onmainat build time, could not be pinned to a version, and a CI image build would have tested the wrong code. The build context is now the source.- The builder image was older than
go.modrequires.golang:1.24-alpineagainst a module declaringgo 1.25.1; it only worked becauseGOTOOLCHAIN=autosilently downloaded a newer toolchain mid-build. Nowgolang:1.26-alpine.
Added
- Published container images at
ghcr.io/fabriziosalmi/caddy-waf, built on release tags forlinux/amd64andlinux/arm64. Tagged by version as well aslatest, so a deployment can pin —latestalone would leave anyone who pulled before a security release with no way to name the image they wanted. .github/workflows/docker.ymlbuilds the image on pull requests without pushing, and assertscaddy list-modulesreportshttp.handlers.wafrather than trusting a green build. Nothing built this image before, which is how it came to clone the repository instead of using the context, and to pin a stale Go version..dockerignoreextended so the context excludesnode_modules,docs/, tests and helper scripts.ui/is deliberately kept:assets.goembeds it behind thewith_uibuild tag.
Changed
- Bumped version constant
wafVersiontov0.3.10.
-
v0.3.928 Jul 2026Release notes
Open source →What's Changed
- fix: ReloadRules deadlock, DNS blacklist bypass, IP blacklist data race by @fabriziosalmi in #132
Full Changelog: v0.3.8...v0.3.9
Release notes
Open source →Security
Two further defects in the same subsystem, one of them the sibling of a bug fixed in v0.3.7. Both were surfaced by an adversarial sweep that drives real requests through
ServeHTTPand asks whether the client is actually refused, rather than whether a helper returnstrue.-
ReloadRuleshad the identical self-deadlock fixed inReloadConfig. It tookm.muand then calledloadRules, which takesm.muagain; the goroutine blocked forever while owning the write lock, so every later request stalled on theRLockin the request path. This is the primary hot-reload branch:startFileWatcherroutes any changed path containing"rule"toReloadRules, which means editingrules.json— the casedocs/dynamicupdates.mddocuments — wedged the server. v0.3.7 fixed one branch of the watcher and missed the other. Found by the automated review on #130. -
The DNS blacklist was bypassable, and inert on non-default ports.
isDNSBlacklistedonly lowercased and trimmed theHostheader, soevil.example:8080andevil.example.both missed an entry forevil.example.r.Hostcarries the port whenever the site is served on anything other than 80/443 — which every example in this repository does — so those deployments had no DNS filtering at all; and a client may send an explicit:443even on the default port, making it a one-header bypass. Hosts are now normalised (lowercase, port stripped, trailing dot removed, IPv6 brackets removed).
Fixed
- Data race on the IP blacklist during hot reload.
ReloadConfigswappedm.ipBlacklistunderm.mu, butisIPBlacklistedread it without taking the lock, so the swap never synchronised with in-flight requests. The read is now underRLock, mirroringisDNSBlacklisted. Found by the automated review on #130. - Documentation described the pre-v0.3.8
X-Forwarded-Forbehaviour ("first XFF value if present, otherwiser.RemoteAddr"), which stopped being true when that bypass was closed.
Added
TestReloadRulesDoesNotDeadlock— the branch v0.3.7 missed, asserted on a deadline and followed by a reader that must get through.- The full suite now passes under
-race.
Changed
- Documented Go requirement corrected to 1.25.1.
go.moddeclares it becausecaddy/v2 v2.11.4andgo.step.sm/cryptorequire it and Go propagates the maximum; forcing1.25.0breaks the build. - Bumped version constant
wafVersiontov0.3.9.
-
v0.3.828 Jul 2026Release notes
Open source →What's Changed
- fix: a forged X-Forwarded-For bypassed the IP blacklist entirely by @fabriziosalmi in #131
Full Changelog: v0.3.7...v0.3.8
Release notes
Open source →Security
A single request header bypassed the IP blacklist entirely. Phase 1 consulted
X-Forwarded-Forinstead ofr.RemoteAddrwhenever the header was present:if xForwardedFor != "" { if m.isIPBlacklisted(firstIP) { block } // no else -- r.RemoteAddr was never checked } else { if m.isIPBlacklisted(r.RemoteAddr) { block } }Any blacklisted client could send
X-Forwarded-For: 8.8.8.8and skip the check. No tooling, no preconditions, no authentication — one arbitrary header. Demonstrated end to end: the same client is refused with403without the header and served200with it.The peer address is now checked first and unconditionally, since it is the only value a client cannot forge, and the forwarded chain is checked in addition rather than instead. Checking more addresses can only block more, never less. A client can therefore blacklist itself by forging a listed address, which is harmless. Deciding which forwarded values to trust requires a
trusted_proxiesoption and is tracked in #94.This was masked until v0.3.7: before that the blacklist was never populated at all (see v0.3.7), so nothing was bypassable because nothing was enforced. Fixing enforcement made this the live bypass, which is why it ships one release later.
Covered by
GHSA-w6gv-76q4-prqg, updated to reflect v0.3.8 as the patched version.Added
TestBlacklistedIPIsBlockedEndToEnd/a_forged_X-Forwarded-For_cannot_skip_the_check— a blacklisted peer sending a cleanX-Forwarded-Formust still be refused.
Changed
- Bumped version constant
wafVersiontov0.3.8.
-
v0.3.728 Jul 2026Release notes
Open source →What's Changed
- fix: the IP blacklist never blocked, and hot reload deadlocked the server by @fabriziosalmi in #130
Full Changelog: v0.3.6...v0.3.7
Release notes
Open source →Security
Two defects in the blacklist subsystem, both silent. Reported in substance by @doogienz in discussion #96 on 2026-05-21, with the log evidence that pinned it down.
-
The IP blacklist never blocked anything.
loadIPBlacklisttook the trie by value, and both callers —ProvisionandReloadConfig— dereferenced their pointer to satisfy that signature. EveryInserttherefore landed in a copy discarded on return: the trie the middleware consults stayed empty, while the loader still loggedIP blacklist loaded {"valid_entries": N}. Any deployment relying onip_blacklist_file, including the 223,770-entry list bundled with the project, had no IP filtering at all and no indication of it. Present since v0.0.7 (commitc905277, "switch to go-trie", 2025-10-10) — 15 releases. -
Hot-reloading a blacklist deadlocked the server.
ReloadConfigheldm.muand then calledloadRules, which takesm.muagain; on Go's non-reentrantRWMutexthe goroutine blocked forever while still owning the write lock. SinceisDNSBlacklistedtakesm.mu.RLock()on every request, all subsequent requests blocked forever — no crash, no log line. The file watcher callsReloadConfigwheneverip_blacklist_fileordns_blacklist_filechanges, and the documented Tor setup (docs/dynamicupdates.md) pointsip_blacklist_fileat the file the Tor fetcher rewrites everyupdate_interval(default 24h), so the configuration the docs recommend wedges the server within a day of starting, unattended.
Both are fixed: the trie is passed by pointer, and
ReloadConfigbuilds the new structures outside the lock, swaps them under it, and never holdsm.muacross a call that takes it again.Added
blacklist_enforcement_test.go— four regression tests: the trie is actually populated (IPv4, IPv6, CIDR, with and without a port), the reload path repopulates it,ReloadConfigcompletes and releases the lock under a deadline, and a fullServeHTTPpass confirms a blacklisted client is refused, never reaches the upstream, and thatX-Forwarded-Foris honoured.
Changed
- Bumped version constant
wafVersiontov0.3.7.
-
v0.3.628 Jul 2026Release notes
Open source →What's Changed
- docs: caddy-waf is registered in Caddy's package registry by @fabriziosalmi in #122
- docs: refresh README — registry status, security section, project layout by @fabriziosalmi in #123
- docs: publish the docs tree as a VitePress site on GitHub Pages by @fabriziosalmi in #124
- docs: give the site a restrained visual identity by @fabriziosalmi in #126
- docs: brand mark, social preview, and hero fixes for the docs site by @fabriziosalmi in #127
- docs: fix two review findings from #122, and guard the class of bug by @fabriziosalmi in #128
- deps: clear 25 of 30 Dependabot alerts, bump to v0.3.6 by @fabriziosalmi in #129
Full Changelog: v0.3.5...v0.3.6
Release notes
Open source →Security
Cleared the Dependabot backlog on the default branch: 25 of 30 open alerts — 7 critical, 5 high, 13 moderate. Every bump was verified by building and testing, not by trusting the suggestion.
Module From To Alerts closed golang.org/x/cryptov0.49.0 v0.52.0 7 critical, 2 high, 4 moderate github.com/caddyserver/caddy/v2v2.11.2 v2.11.4 2 high, 3 moderate google.golang.org/grpcv1.79.3 v1.82.1 1 high golang.org/x/netv0.52.0 v0.55.0 1 moderate github.com/quic-go/quic-gov0.59.0 v0.59.1 1 moderate go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttpv1.43.0 v1.44.0 1 moderate go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttpv0.19.0 v0.20.0 1 moderate No source changes were required.
go build,go vetand the full unit suite pass, andxcaddy buildagainst the bumped tree produces a working Caddy v2.11.4 binary that registershttp.handlers.waf.Not fixed, and why
Five alerts remain open. Leaving them undocumented would be worse than leaving them open.
github.com/google/cel-go(moderate, GHSA-gcjh-h69q-9w9g) — the suggested v0.29.0 does not compile against Caddy v2.11.4:interpreter.NewCallchanged from[]interpreter.Interpretableto[]interpreter.InterpretableV2, andcaddyhttp/celmatcher.gostill passes the former. Caddy's owngo.modpins v0.28.1. Taking the bump would trade a moderate transitive advisory for a build that does not exist. Blocked until Caddy updates.vite×3 (1 high, 2 moderate) andesbuild×1 (moderate) — introduced in v0.3.5 by thepackage-lock.jsonfor the VitePress docs site. All four are development-server issues (arbitrary origins reading dev-server responses;server.fs.denybypass on Windows; NTLMv2 disclosure via UNC paths on Windows; path traversal in optimized-deps.maphandling). CI only ever runsvitepress build, the published site is static HTML, andnpm audit --omit=devreports zero. The fix requires vite ≥ 6.4.3, which no stable VitePress pulls —latestis 1.6.4 and pins vite ^5.4.14; only the 2.0.0-alpha line moves to vite 6. Running an alpha documentation generator to silence dev-only advisories is the worse trade.
Changed
- Documentation references to the pinned Caddy version updated from v2.11.2 to v2.11.4.
- Bumped version constant
wafVersiontov0.3.6.
-
v0.3.6-0.20260728112428-b1658e5e31d728 Jul 2026 pre-releaseNothing published for this version
-
v0.3.6-0.20260728101137-bb8fa2de569228 Jul 2026 pre-releaseNothing published for this version
-
v0.3.528 Jul 2026Release notes
Open source →What's Changed
- docs: refresh SECURITY.md supported versions and reporting channel by @fabriziosalmi in #120
- fix: make module scannable by Caddy's package registry, bump to v0.3.5 by @fabriziosalmi in #121
Full Changelog: v0.3.4...v0.3.5
Release notes
Open source →Fixed
-
Caddy package registry could not scan this module.
caddy.RegisterModule(&Middleware{})parses as anast.UnaryExprwrapping the literal, and the static analyzer behind https://caddyserver.com/account/register-package accepts only a composite literal ornew(). Every registration attempt therefore failed with the opaque portal errorunable to scan modules in package github.com/fabriziosalmi/caddy-waf, which never names the offending line — leaving the module absent from https://caddyserver.com/download andcaddy add-package github.com/fabriziosalmi/caddy-wafreturning HTTP 400. Bothcaddy.RegisterModuleandModuleInfo.Newnow usenew(Middleware).The two forms are semantically identical (each allocates a zeroed
Middlewareand yields a pointer), so there is no behavioural change. The pointer is required regardless:CaddyModulehas a pointer receiver andMiddlewarecarries mutexes that must not be copied.Diagnosis courtesy of the Caddy community thread Unable to register module in the portal, where the underlying analyzer error is quoted as
unexpected argument to RegisterModule(): &ast.UnaryExpr{...} - expect either composite literal or new().
Added
TestRegisterModuleArgumentIsScannable— parses the package's own AST and asserts thecaddy.RegisterModuleargument stays a composite literal ornew(), so the registry constraint cannot silently regress on a future edit. Verified to fail against the v0.3.4 pattern and pass against the fix.
Changed
- Rewrote
CADDY_MODULE_REGISTRATION.md, which was stale (referenced v0.0.6 and Caddy v2.9.1) and speculated that the failures were server-side and "may resolve automatically". It now records the verified root cause and the maintenance notes. - Bumped version constant
wafVersiontov0.3.5.
Registered
With the scan fixed,
github.com/fabriziosalmi/caddy-wafwas claimed in Caddy's package registry on 2026-07-28 at 10:05:52 UTC, atv0.3.5. The build service now serves it (GET /api/download?p=github.com%2Ffabriziosalmi%2Fcaddy-wafreturns a binary instead of HTTP 400), socaddy add-package github.com/fabriziosalmi/caddy-wafworks and the module is selectable on https://caddyserver.com/download.README.md,docs/installation.mdanddocs/add-package-guide.mdupdated accordingly — they previously documented the install path as unavailable.Note the module documentation shown on caddyserver.com is extracted from the doc comment on the
Middlewarestruct intypes.go. -
v0.3.428 Jul 2026Release notes
Open source →What's Changed
- vendor: self-host CDN assets same-origin (kill third-party egress) by @fabriziosalmi in #98
- fix: bump installer Go to 1.25.x and sync stale version references by @fabriziosalmi in #99
- chore: add Dependabot for automated dependency & Actions updates by @fabriziosalmi in #100
- fix: bound response body buffering (GHSA-gfj3-cmff-q8wh), bump to v0.3.4 by @fabriziosalmi in #119
Full Changelog: v0.3.3...v0.3.4
Release notes
Open source →Security
-
Fixed unbounded response buffering (GHSA-gfj3-cmff-q8wh, CWE-400, CVSS 3.1 7.5 high, remote unauthenticated DoS). Up to and including v0.3.3,
responseRecorderaccumulated the entire upstream response body in an in-memorybytes.Bufferbefore releasing a single byte to the client, with no configurable or hard-coded ceiling. A single unauthenticated request for a large or streaming resource made the Caddy process's heap grow in step with the response size, so an attacker could OOM-kill the process and take down every site served by that instance. Reported by @EQSTLab.The response body is now buffered only when it can actually be used, and only up to a bound:
- No Phase 4 rules ⇒ no buffering.
ServeHTTPnow askshasResponseBodyRules()before capturing anything; with noRESPONSE_BODYrule loaded the recorder is a pass-through that forwards writes as they arrive. The bundledrules.jsonhas no Phase 4 rules, so the default configuration buffers nothing at all and no longer defeats HTTP streaming. - Hard ceiling of
max_response_body_size(new setting, default 10 MiB). When a response outgrows the budget, the recorder writes out what it holds and streams the remainder straight to the client, so peak memory is bounded by the limit rather than by the response size. - An upstream flush releases the buffer instead of stalling, so server-sent events and chunked streaming work through a WAF-protected route rather than being held until the budget fills.
- A released response cannot be blocked, since part of it is already on the wire. Phase 4 is skipped in that case and logged at
warn("Response body exceeded the WAF inspection limit; Phase 4 rules were not applied") rather than scoring a truncated body and reporting the response as vetted.
Measured on a 512 MiB response through a WAF-protected route: heap allocated during
ServeHTTPdrops from 1535 MiB to 0 MiB, with all 512 MiB still delivered to the client. - No Phase 4 rules ⇒ no buffering.
Added
max_response_body_size(Caddyfile directive and JSON field, default10485760) — ceiling on how much of the response body is retained for Phase 4 inspection. Validated as non-negative byValidate;0selects the default.max_request_body_sizeis now settable from the Caddyfile as well, not only from JSON.responseRecorderimplementshttp.Flusher.
Fixed
- A Phase 4 block no longer swallows the configured
custom_responsebody.ServeHTTPwrote the custom response into the recorder, whose buffer is discarded on the blocked path, so the client received an empty body; it now writes to the realResponseWriter.
Known limitation
- The status code of a Phase 3/4 block is still not applied:
responseRecorder.WriteHeaderforwards the status to the underlyingResponseWriteras soon as the upstream sets it, so by the time the response phases run the status line is already committed and a block surfaces as200with the custom body. This is pre-existing behaviour, unrelated to the advisory above, and is tracked separately.
Changed
- Bumped version constant
wafVersiontov0.3.4.
-
v0.3.329 Apr 2026Nothing published for this version
-
v0.3.226 Apr 2026Release notes
Open source →Security
Patched 3 critical and 10 high severity Dependabot alerts by upgrading the affected dependencies to their fixed versions:
github.com/caddyserver/caddy/v2v2.10.2 → v2.11.2 — fixes 4 high (FastCGI split_path Unicode case-folding bypass, MatchHost case-sensitivity bypass on >100 hosts, MatchPath %xx case normalization bypass, mTLS silent fail-open on missing CA file) and 2 medium (admin API CSRF on/load, file matcher glob sanitization).google.golang.org/grpcv1.78.0 → v1.79.3 — fixes 1 critical (authorization bypass via missing leading slash in:path).github.com/jackc/pgx/v5v5.8.0 → v5.9.2 — fixes 1 critical (memory-safety) and 1 low (SQL injection via dollar-quoted placeholder confusion).github.com/smallstep/certificatesv0.29.0 → v0.30.2 — fixes 1 critical (unauthenticated certificate issuance via SCEPUpdateReqMessageType=18) and 1 low (TPM EKU validation index-out-of-bounds panic).go.opentelemetry.io/otelv1.39.0 → v1.43.0 — fixes 1 high (multi-valuebaggageheader DoS amplification).go.opentelemetry.io/otel/sdkv1.39.0 → v1.43.0 — fixes 2 high (BSDkenvPATH hijacking; arbitrary code execution via PATH hijacking).github.com/go-jose/go-jose/v4v4.1.3 → v4.1.4 — fixes 1 high (JWE decryption panic).github.com/go-jose/go-jose/v3v3.0.4 → v3.0.5 — fixes 1 high (JWE decryption panic).github.com/slackhq/nebulav1.9.7 → v1.10.3 — fixes 1 high (blocklist bypass via ECDSA signature malleability).github.com/cloudflare/circlupgraded to v1.6.3 — fixes 1 low (incorrectsecp384r1CombinedMultcalculation).filippo.io/edwards25519upgraded to v1.2.0 — fixes 1 low (MultiScalarMultinvalid results when receiver is not the identity).
No source-code changes required; the WAF compiles and the full unit test suite passes against the upgraded dependency tree.
Changed
- Bumped version constant
wafVersiontov0.3.2.
-
v0.3.126 Apr 2026Release notes
Open source →Documentation
- Rewrote
README.md,MODULE.md,caddyfile.example, and the entiredocs/tree to be 1:1 accurate with the current source code. docs/configuration.mdnow lists every Caddyfile directive recognised byconfig.go, every JSON-only field on theMiddlewarestruct, the precise Phase 1 evaluation order, and the parser- vs.Provision-time defaults.docs/rules.mddocuments the JSON tag mismatch onRule.Action(struct tag ismode, while the bundled rule files commonly useaction), so authors know which key is actually parsed.docs/ratelimit.mdcorrects thematch_all_pathssemantics to matchratelimiter.go(true⇒ rate-limit every request;false+ non-emptypaths⇒ rate-limit only matching paths).docs/dynamicupdates.mdadds an explicit reload matrix showing which settings are reloaded byfsnotifyand which requirecaddy reload.docs/metrics.mddocuments the actual response schema returned byhandleMetricsRequestand clarifies that all counters are process-local and reset on restart.docs/prometheus.mdswitches the example exporter fromCounter.inc(absolute)toGauge.set(absolute)to match the WAF's monotonic process-local counter semantics.caddyfile.exampleno longer references non-existent directives (country_block,custom_response { … }block form).- Removed emoji from all user-facing documentation.
Changed
- Bumped version constant
wafVersiontov0.3.1.
- Rewrote
-
v0.3.022 Feb 2026Release notes
Open source →Fixed
- Resolved duplicate response headers when a custom block response was emitted.
- IP blacklist loader now accepts CIDR notation in addition to single IPs (
net.ParseCIDRis tried beforenet.ParseIP).
-
v0.2.1-0.20260117182623-c837101335a017 Jan 2026 pre-releaseNothing published for this version
-
v0.2.017 Jan 2026Release notes
Open source →Fixed
- Fixed potential panic in
isIPBlacklisted()when parsing malformed IP addresses - now usesnetip.ParseAddr()instead ofnetip.MustParseAddr(). - Fixed type assertion panic in
processRuleMatch()- now uses safegetLogID()helper function. - Fixed potential panic in
extractIP()andgetClientIP()when handling empty or malformed input.
Added
- Added 30-second HTTP client timeout in
tor.goto prevent hanging requests during Tor exit node list fetches. - Added comprehensive input validation in
Validate()method for negative threshold/limit values. - Added parameter validation in
NewRateLimiter()to ensure positive values.
Changed
- Updated installation documentation to clarify that
caddy add-packageis not available (module not registered in Caddy's package registry). - Reordered installation methods in documentation to recommend Quick Script and xcaddy as primary options.
- Updated
CADDY_MODULE_REGISTRATION.mdwith current registration status.
Documentation
- Added warnings about
caddy add-packagelimitations in README.md, installation.md, and add-package-guide.md.
- Fixed potential panic in
-
v0.1.606 Jan 2026 -
v0.1.6-0.20251229091634-cf45542c7e0729 Dec 2025 pre-releaseNothing published for this version
-
v0.1.508 Dec 2025Release notes
Open source →Fixed
- Fixed critical bug where POST request bodies were lost or truncated by using
io.MultiReaderto restore the full body stream (fixes #76).
- Fixed critical bug where POST request bodies were lost or truncated by using
-
v0.1.406 Dec 2025Release notes
Open source →Security
- Fixed Panic vulnerability in
quic-goby upgrading tov0.54.0(requires Caddy v2.10.x and Go 1.25). - Addressed Dependabot Alert #7.
Changed
- Upgraded Caddy dependency to
v2.10.2. - Upgraded Go requirement to
1.25. - Improved CI workflows to use Go 1.25 for build and release.
- Fixed Panic vulnerability in
-
v0.1.306 Dec 2025Release notes
Open source →Fixed
- Downgraded
quic-gotov0.48.2and Caddy tov2.9.1to temporarily resolve Go version conflicts (superseded by v0.1.4). - Fixed import grouping for
gcilinter compliance. - Fixed GitHub Actions release workflow.
- Downgraded
-
v0.1.206 Dec 2025Release notes
Open source →Added
- SOTA Engineering patterns (Zero-Copy headers, Wait-Free Ring Buffer, Circuit Breaker).
- ASN Blocking support.
- Configurable Request Body size limit.
- GeoIP Fail Open configuration.
-
v0.1.006 Dec 2025Nothing published for this version
-
v0.0.10-0.20251206213732-47e05e907e5e06 Dec 2025 pre-releaseNothing published for this version
-
v0.0.906 Dec 2025Nothing published for this version
-
v0.0.9-0.20251023131337-e98fd16392c623 Oct 2025 pre-releaseNothing published for this version
-
v0.0.820 Oct 2025Nothing published for this version
-
v0.0.715 Oct 2025Nothing published for this version
-
v0.0.7-0.20251012173338-1207bd7a6dd312 Oct 2025 pre-releaseNothing published for this version
-
v0.0.7-0.20251011215047-485c86fdbcbb11 Oct 2025 pre-releaseNothing published for this version
-
v0.0.7-0.20251011075419-8d5af6be5fda11 Oct 2025 pre-releaseNothing published for this version
-
v0.0.7-0.20250914121224-8685d03503a514 Sep 2025 pre-releaseNothing published for this version
-
v0.0.613 Jul 2025Nothing published for this version
-
v0.0.6-0.20250502053323-bae17679f1cf02 May 2025 pre-releaseNothing published for this version
-
v0.0.530 Apr 2025Nothing published for this version
-
v0.0.5-0.20250430091917-fe84fbb5c55630 Apr 2025 pre-releaseNothing published for this version
-
v0.0.5-0.20250313100929-bf367b5c53c213 Mar 2025 pre-releaseNothing published for this version
-
v0.0.409 Feb 2025Nothing published for this version
-
v0.0.309 Feb 2025Nothing published for this version
-
v0.0.0-20250209153245-4f31673cb54f09 Feb 2025 pre-releaseNothing published for this version
-
v0.0.0-20250202233915-6b94875c0aea02 Feb 2025 pre-releaseNothing published for this version
-
v0.0.0-20250202215558-39d3773524c002 Feb 2025 pre-releaseNothing published for this version
-
v0.0.0-20250202095321-7115c73c792b02 Feb 2025 pre-releaseNothing published for this version
-
v0.0.0-20250201234947-d730f0acdcb001 Feb 2025 pre-releaseNothing published for this version
-
v0.0.0-20250201234218-dec2e4dba58801 Feb 2025 pre-releaseNothing published for this version
-
v0.0.0-20250201000951-f01d59ca250f01 Feb 2025 pre-releaseNothing published for this version
-
v0.0.0-20250201000618-40d02334351301 Feb 2025 pre-releaseNothing published for this version
-
v0.0.0-20250129225620-9ec63810155c29 Jan 2025 pre-releaseNothing published for this version
-
v0.0.0-20250129221749-b39b0fb2e6f729 Jan 2025 pre-releaseNothing published for this version
-
v0.0.0-20250129214658-e1a013ebad2529 Jan 2025 pre-releaseNothing published for this version
-
v0.0.0-20250129191903-b7c7f4e4998929 Jan 2025 pre-releaseNothing published for this version
-
v0.0.0-20250129182107-3d83116dbc0b29 Jan 2025 pre-releaseNothing published for this version
-
v0.0.0-20250129181706-297fcab5bc6c29 Jan 2025 pre-releaseNothing published for this version
-
v0.0.0-20250129181315-44c4a30c07cd29 Jan 2025 pre-releaseNothing published for this version
-
v0.0.0-20250129154313-859a9f1ed99929 Jan 2025 pre-releaseNothing published for this version
-
v0.0.0-20250129143035-6974ef87022b29 Jan 2025 pre-releaseNothing published for this version
-
v0.0.0-20250129131750-8aca85cf7ddb29 Jan 2025 pre-releaseNothing published for this version