Full rewrite of README.md, MODULE.md, caddyfile.example, and docs/ to match the actual code in caddywaf.go, handler.go, config.go, rules.go, ratelimiter.go, blacklist.go, geoip.go, request.go, and types.go. Notable corrections: - docs/configuration.md now lists every directive recognised by the Caddyfile parser (directiveHandlers in config.go) and separates JSON-only fields (MaxRequestBodySize, GeoIPFailOpen, Tor.CustomTORExitNodeURL). - docs/rules.md documents the JSON tag mismatch on Rule.Action: the struct tag is "mode" while the bundled rule files commonly use "action". - docs/ratelimit.md corrects the match_all_paths semantics to match ratelimiter.go (true rate-limits all paths; false + non-empty paths rate-limits only matching paths; false + empty paths is a no-op). - docs/dynamicupdates.md adds a reload matrix distinguishing what fsnotify reloads from what requires caddy reload. - docs/metrics.md aligns the JSON schema with handleMetricsRequest and notes that all counters are process-local. - docs/prometheus.md uses Gauge.set instead of Counter.inc to match the process-local monotonic counter semantics. - caddyfile.example no longer references inexistent directives (country_block, custom_response block form). Also adds the missing CHANGELOG entry for v0.3.0 (duplicate response headers fix and CIDR support in IP blacklist) and removes emoji from all user-facing documentation.
5.2 KiB
Testing
The repository ships several testing layers:
| Layer | Tool | Scope |
|---|---|---|
| Go unit tests | go test ./... (or make test) |
Per-package logic in *_test.go. |
| Go integration tests | go test ./... -tags=it (or make it) |
Tests guarded by the it build tag. |
| Lint | golangci-lint run (or make lint) |
Style and static analysis configured in .golangci.yml. |
| Live attack suite | test.py (or make test-integration) |
Sends a curated payload set to a live WAF and checks the returned status codes. |
| Traffic generator | caddytest.py |
High-volume, configurable traffic generation; see caddytest.md. |
| Benchmark | benchmark.py |
Throughput/latency measurement against a running WAF. |
This page focuses on the live attack suite; the others are documented inline in their files.
test.py — live attack suite
test.py exercises the WAF against a long list of attack payloads using curl. Each test case is a tuple of (description, url, expected_code, headers, body); the script issues the request and compares the HTTP status returned by the WAF against the expected one.
Configuration constants
Defined at the top of test.py:
| Constant | Default | Meaning |
|---|---|---|
TARGET_URL |
http://localhost:8080 |
Base URL of the running WAF. |
TIMEOUT |
8 |
curl connect/total timeout in seconds. |
OUTPUT_FILE |
waf_test_results.log |
Per-case log file (appended to). |
DEFAULT_USER_AGENT |
WAF-Test-Script/1.0 |
UA sent when a test case does not specify one. |
To run against a different target, edit TARGET_URL directly (the script does not currently expose it as a CLI flag).
Running
python3 test.py
CLI flags:
| Flag | Description |
|---|---|
--user-agent, -ua |
Override DEFAULT_USER_AGENT for this run. |
Output
The script:
- Prints a colour-coded line per test (green
[+]for pass, red[!]for fail orcurlerror). - Appends every test result (pass and fail) to
waf_test_results.log, including the URL, headers, body, expected code, and observed code. - Prints a summary at the end with the total number of cases, the number passed, the number failed, and a final verdict.
Test categories
test.py covers (non-exhaustive):
- SQL injection (boolean, UNION, time-based, header- and cookie-borne)
- XSS (script tags, attribute injections, encoded payloads)
- Path traversal and LFI (encoded variants, header- and cookie-borne)
- RCE / command injection
- Header injection, host-header tampering
- Insecure deserialization (Java, PHP, Python)
- SSRF (cloud-metadata, internal IPs)
- XXE (inline and parameter entities)
- HTTP request smuggling and response splitting
- IDOR, mass assignment, NoSQL injection, XPath, LDAP, XML
- File upload payloads, JWT manipulation, GraphQL abuse
- Clickjacking and CSRF probes
- A set of valid baseline requests that should pass
Workflow
# 1. Start the WAF
./caddy run --config Caddyfile &
# 2. Run the suite
python3 test.py
# 3. Inspect failures
grep '\[FAIL\]\|\[ERROR\]' waf_test_results.log
# 4. Iterate on rules, repeat
Inside Docker
The Makefile target test-integration runs test.py inside a python:3.9-slim container against the host's WAF:
make test-integration
This requires Docker and a WAF reachable at http://localhost:8080 from inside the container — typically meaning the WAF runs on the host.
Tuning workflow
A pragmatic tuning loop:
- Start with the bundled
rules.json(or a curated subset underrules/). - Run
test.pyto confirm the baseline behaviour. - Use
caddytest.pywith--behavior burst_calmand--compositeto mix legitimate and malicious traffic and watch the false-positive rate. - Watch the WAF metrics endpoint (
/waf_metrics) for the rule IDs with the highest hit counts. False positives usually concentrate on a small number of rules. - Adjust the offending rules: tighten the regex, scope the
targets, lower thescore, or move them tomode: logfor observation. - Reload — the file watcher applies the new rule set without restarting Caddy (see dynamicupdates.md).
Go test suite
make test # go test -v ./...
make it # go test -v ./... -tags=it (integration)
make lint # golangci-lint run
make lintfix # golangci-lint run --fix
Test files cover: blacklist loading, configuration parsing, GeoIP, rate limiter, request value extraction, response handling, rule loading, Tor fetcher, the middleware lifecycle, and integration scenarios.
CI
The repository ships three GitHub Actions workflows in .github/workflows/:
test.yml— runsgo test ./...and the lint suite on push and pull request.build-run-validate.yml— builds Caddy with the WAF, starts it, and runs validation requests.release.yml— builds binaries and creates a GitHub release on a tag.
A green badge from these workflows on a commit is a reasonable signal that the change at minimum compiles, passes unit tests, and survives a smoke test of the bundled Caddyfile.