servewaf: each decision heuristic is its own module (closes #195)
check / check (push) Successful in 3m12s

Each check servewaf runs on a request is a type in its own file behind
one interface, heuristic, and is registered in one of four ordered lists
in heuristics.go. Order and behaviour are unchanged.

Every refusal now logs heuristic, the name of the heuristic that made
it, and heuristic_figures, the figures it acted on. A ban by
refusal_limit or byte_limits after the answer logs report_heuristic and
report_heuristic_figures instead, so a line can name both. All four are
in docs/request-log.md, and SPEC.md names each heuristic at its step.

Model: opus-5-5
This commit is contained in:
2026-11-06 19:53:13 +00:00
parent 21cac70b50
commit 14e8ee7413
36 changed files with 1108 additions and 397 deletions
+50
View File
@@ -118,6 +118,56 @@ func TestEachBlocklistActionForAListedAddressAndAListedNetblock(t *testing.T) {
}
}
func TestReputationRefusalNamesItsHeuristicAndWhatListedTheClient(t *testing.T) {
t.Parallel()
for _, tc := range []struct {
heuristic string
env map[string]string
load func(*testing.T, *servewaf.Server)
figures string
}{
{
"blocklists",
map[string]string{blocklistURLs: dropURL, blocklistAction: actionDeny},
func(t *testing.T, server *servewaf.Server) {
t.Helper()
loadLists(t, server, map[string][]string{dropURL: {client}})
},
`{"lists": ["` + dropURL + `"]}`,
},
{
"dnsbl",
map[string]string{
dnsblZones: dnsblZone, dnsblResolver: noResolver,
reputationAction: actionDeny,
},
func(_ *testing.T, server *servewaf.Server) {
loadVerdicts(server, map[string][]string{client: {dnsblZone}})
},
`{"zones": ["` + dnsblZone + `"]}`,
},
{
"abuseipdb",
map[string]string{abuseIPDBKey: accountKey, reputationAction: actionDeny},
func(_ *testing.T, server *servewaf.Server) {
loadScores(server, map[string]int64{client: 80})
},
`{"score": 80, "min_score": 75}`,
},
} {
t.Run(tc.heuristic, func(t *testing.T) {
t.Parallel()
s, server, _ := startWithLookups(t, tc.env)
tc.load(t, server)
line := s.get(client, http.StatusForbidden, requestlog.ActionDenied)
wantHeuristic(t, line, tc.heuristic, tc.figures)
})
}
}
func TestBlocklistsComeAfterTheCountryListsAndSkipAllowNets(t *testing.T) {
t.Parallel()