Files
smallwebwaf/internal/proxy/rules.go
T
clawbot 7f472c40e2
check / check (push) Waiting to run
Rule files, and bans for a clear sign of attack (closes #24)
Every *.rules file in SWWAF_RULES_DIR is read at start and on each
change, and each request is checked against the rules after the rate
limits: log notes a match, block refuses with 403, ban refuses and bans
the netblock for SWWAF_ATTACK_BAN_DURATION, made permanent by its next
request or clear sign of attack. path, query and uri are matched as the
request line sent them. bans.json gains each ban's cause, and ban notes
count earlier bans by cause. The image ships 00-default.rules.

Judgement call: a header sent twice is matched with its values joined
by ", ".
Judgement call: SWWAF_MAX_BAN_DURATION does not cap a ban for an attack.
Not in this unit: offences for rule matches, with the error burst.

Model: opus-5-5
2026-10-06 14:33:26 +00:00

41 lines
1.1 KiB
Go

package proxy
import (
"time"
"sneak.berlin/go/smallwebwaf/internal/requestlog"
"sneak.berlin/go/smallwebwaf/internal/rules"
)
// checkRules checks the request against the rules of the rule files at
// now, notes the ids of those it matches in the log line, and returns the
// action of the rule that refuses it, ActionRuleBlocked for a block rule
// and ActionBanned for a ban rule, or "" when none does. In enforce mode
// a ban rule bans the client's netblock for a clear sign of attack.
func (rq *request) checkRules(now time.Time) string {
matched := rq.h.rules.Match(rq.in)
for _, rule := range matched {
rq.line.RuleIDs = append(rq.line.RuleIDs, rule.ID)
rq.h.metrics.RuleMatched(rule.ID, rule.Action)
}
if len(matched) == 0 {
return ""
}
// Only the last rule matched can refuse the request.
switch last := matched[len(matched)-1]; last.Action {
case rules.ActionBlock:
return requestlog.ActionRuleBlocked
case rules.ActionBan:
if !rq.h.config.Observe {
rq.banForAttack(now, last)
}
return requestlog.ActionBanned
default:
return ""
}
}