check / check (push) Failing after 1m19s
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 or ban now logs heuristic, the name of the heuristic that made it, and heuristic_figures, the figures it acted on. Both are in docs/request-log.md, and SPEC.md names each heuristic at its step. Model: opus-5-5
55 lines
1.5 KiB
Go
55 lines
1.5 KiB
Go
package servewaf
|
|
|
|
// The heuristic refusal_limit. It reads SWWAF_ERROR_BURST_THRESHOLD and
|
|
// SWWAF_BAN_RESPONSE.
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"time"
|
|
|
|
"sneak.berlin/go/smallwebwaf/internal/requestlog"
|
|
)
|
|
|
|
// refusalLimit counts the request once it has been answered, if servewaf
|
|
// refused it after a rule file match, a trap path or a Core Rule Set match, and
|
|
// in observe mode if enforce mode would have: more than
|
|
// SWWAF_ERROR_BURST_THRESHOLD such refusals of the client within a minute break
|
|
// a limit, which bans the client. A client in SWWAF_ALLOW_NETS, which the
|
|
// heuristics skip, is not counted, and nothing is while the threshold is off.
|
|
// Its figures are the window, the count and the limit.
|
|
type refusalLimit struct{}
|
|
|
|
func (refusalLimit) name() string { return "refusal_limit" }
|
|
|
|
func (refusalLimit) check(_ context.Context, rq *request, now time.Time) *verdict {
|
|
cfg := rq.h.config
|
|
if cfg.ErrorBurstThreshold == 0 {
|
|
return nil
|
|
}
|
|
|
|
if !rq.attack && !rq.ruleBlocked && !rq.wafBlocked {
|
|
return nil
|
|
}
|
|
|
|
hit, over := rq.h.limiter.CountRefusal(rq.h.clientGroup(rq.client), now,
|
|
cfg.ErrorBurstThreshold)
|
|
if !over {
|
|
return nil
|
|
}
|
|
|
|
// What the client was sent, or in observe mode would have been.
|
|
status := rq.out.status
|
|
|
|
switch rq.line.WouldAction {
|
|
case requestlog.ActionRuleBlocked, requestlog.ActionWAFBlocked:
|
|
status = http.StatusForbidden
|
|
case requestlog.ActionBanned:
|
|
status = cfg.BanResponse
|
|
}
|
|
|
|
rq.banForLimit(now, hit, status)
|
|
|
|
return &verdict{action: requestlog.ActionBanned, figures: limitFigures(hit)}
|
|
}
|