Trap paths, and the error burst banning a client refused too often (closes #115)
check / check (push) Waiting to run
check / check (push) Waiting to run
SWWAF_TRAP_PATHS: a request whose path, as a path rule sees it, is one of them is a clear sign of attack, banned as a ban rule's match is; the ban's notes give its trap_path. Checked after the rate limits, before the rule files. SWWAF_ERROR_BURST_THRESHOLD (default 30, or off): more refusals in a minute after a block or ban rule or a trap path, or for a missing or wrong token, ban the client as a broken limit does. Counted in clients.json's minute_refusals; limit_hit error_burst, notes kind refusals. A token refusal is now the offence token_refused, and smallwebwaf_offences_total counts every kind the history does. Judgement call: the threshold is not lowered by a client's limit percentage. Model: opus-5-5
This commit was merged in pull request #118.
This commit is contained in:
@@ -679,8 +679,8 @@ func (f *bansFile) check(data []byte) error {
|
||||
}
|
||||
|
||||
// check refuses a client without its address, which would count nobody's
|
||||
// requests, or with requests or bytes in a window but no start, which
|
||||
// would drop them and give the client a fresh allowance.
|
||||
// requests, or with requests, bytes or refusals in a window but no start,
|
||||
// which would drop them and give the client a fresh allowance.
|
||||
func (f *clientsFile) check([]byte) error {
|
||||
for i, client := range f.Clients {
|
||||
switch {
|
||||
@@ -698,6 +698,8 @@ func (f *clientsFile) check([]byte) error {
|
||||
return missing(i, "hour_bytes.start")
|
||||
case countsWithoutStart(client.DayBytes):
|
||||
return missing(i, "day_bytes.start")
|
||||
case countsWithoutStart(client.MinuteRefusals):
|
||||
return missing(i, "minute_refusals.start")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -898,8 +900,8 @@ func missingFromCounter(counter anomaly.Counter) string {
|
||||
}
|
||||
}
|
||||
|
||||
// countsWithoutStart reports whether b holds requests, or bytes, but no
|
||||
// start, which places them in time.
|
||||
// countsWithoutStart reports whether b holds requests, bytes or refusals
|
||||
// but no start, which places them in time.
|
||||
func countsWithoutStart(b ratelimit.Buckets) bool {
|
||||
return b.Start.IsZero() && (b.Current != 0 || b.Previous != 0)
|
||||
}
|
||||
|
||||
@@ -639,6 +639,15 @@ func TestEntryWithoutAFieldItNeedsStopsTheStart(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientWithRefusalsInTheMinuteWithoutTheirStartStopsTheStart(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
wantRefused(t, clientsJSON,
|
||||
`{"version": 1, "clients": [{"client": "203.0.113.9/32", `+
|
||||
`"minute_refusals": {"current": 2}}]}`,
|
||||
`: entry 1 has no "minute_refusals.start"`)
|
||||
}
|
||||
|
||||
func TestReputationJSONEntryWithoutAFieldItNeedsStopsTheStart(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user