Files
caddy-waf/caddyfile.example
fab 050f9df76e fix: bound response body buffering (GHSA-gfj3-cmff-q8wh), bump to v0.3.4 (#119)
responseRecorder accumulated the entire upstream response body in an in-memory
bytes.Buffer before releasing a single byte to the client, with no 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
(CWE-400, CVSS 3.1 7.5).

The response body is now buffered only when a Phase 4 rule can use it, and only
up to max_response_body_size (new, default 10 MiB); past the budget, or on an
upstream flush, the recorder releases what it holds and streams the rest.
A released response is not evaluated by Phase 4 and is logged at warn instead.

Measured on a 512 MiB response: heap allocated during ServeHTTP drops from
1535 MiB to 0 MiB, with all 512 MiB still delivered.

Also fixes a Phase 4 block swallowing the custom_response body, and makes
max_request_body_size settable from the Caddyfile.

Reported by @EQSTLab.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 11:09:31 +02:00

99 lines
3.5 KiB
Plaintext

# Example Caddyfile for the caddy-waf module (http.handlers.waf).
# Every directive below is recognised by the Caddyfile parser in config.go.
{
auto_https off
admin localhost:2019
}
# ---------------------------------------------------------------------------
# Example 1: minimal setup
# ---------------------------------------------------------------------------
example.com {
route {
waf {
rule_file rules.json
ip_blacklist_file ip_blacklist.txt
dns_blacklist_file dns_blacklist.txt
metrics_endpoint /waf_metrics
anomaly_threshold 10
log_severity info
log_path /var/log/caddy/waf.log
}
respond "Hello, World — protected by Caddy WAF" 200
}
}
# ---------------------------------------------------------------------------
# Example 2: API gateway with rate limiting, GeoIP, ASN block, and
# custom responses
# ---------------------------------------------------------------------------
api.example.com {
route {
waf {
metrics_endpoint /waf_metrics
anomaly_threshold 15
# One or more rule files (the directive may repeat).
rule_file rules/sql-injection.json
rule_file rules/xss.json
rule_file rules/lfi.json
# Per-IP rate limiting on /api/* and /admin/*.
rate_limit {
requests 100
window 10s
cleanup_interval 5m
paths ^/api/.* ^/admin/.*
match_all_paths false
}
# Country and ASN gating (require GeoLite2 MMDB files).
block_countries GeoLite2-Country.mmdb RU CN KP
block_asns GeoLite2-ASN.mmdb 14618 16509
# Tor exit-node blocking; refreshed every 24h, retries every hour
# on transient failures.
tor {
enabled true
tor_ip_blacklist_file tor_blacklist.txt
update_interval 24h
retry_on_failure true
retry_interval 1h
}
# IP and DNS blacklists.
ip_blacklist_file ip_blacklist.txt
dns_blacklist_file dns_blacklist.txt
# Custom responses for blocked requests.
# form 1: status content-type "inline body…"
# form 2: status content-type /path/to/body.file
custom_response 403 application/json error.json
custom_response 429 text/plain "Slow down."
# Body size ceilings, in bytes.
# max_request_body_size caps request body reads (io.LimitReader).
# max_response_body_size caps how much of the response body is held
# in memory for Phase 4 inspection; past
# this the response is streamed through
# un-inspected instead of buffered.
# Both default to 10 MiB (10485760).
max_request_body_size 10485760
max_response_body_size 10485760
# Logging.
log_severity info
log_json
log_path /var/log/caddy/waf.log
log_buffer 2000
redact_sensitive_data
}
reverse_proxy localhost:8080
}
}