servewaf answers rproxy's question about each request and takes its report (closes #170)
check / check (push) Failing after 14m16s

`smallwebwaf servewaf` runs today's checks on the visitor's request each
question carries, answers rproxy, and passes nothing to an app. rproxy reports
every request servewaf answered. A report counts an allowed request's bytes and
writes servewaf's second log line. The table holds a copy of what a report
needs, never the request, so a report cannot reach a request being handled.

Judgement call: a question's body is read before any check; over 64 KiB gets 400.
Judgement call: a report's bytes and durations have ceilings, so they cannot overflow.
Judgement call: a `close` disposition for SWWAF_BAN_RESPONSE=close.

Model: opus-5-5
This commit was merged in pull request #180.
This commit is contained in:
2026-11-05 00:32:08 +01:00
parent 718a2c94b2
commit 2eeea136e8
28 changed files with 2919 additions and 143 deletions
+11 -4
View File
@@ -150,13 +150,16 @@ type Params struct {
// Alert is one alert, as the webhook is sent it and alerts.json holds it,
// with the fields of "The JSON object" in docs/alerts.md. ASN, ASName and
// Country are, for a ban, the client's as the ban's notes give them.
// Country are, for a ban, the client's as the ban's notes give them. Host is
// the site of the request that raised the alert, while one servewaf serves
// many sites, and "" otherwise.
//
//nolint:tagliatelle // docs/alerts.md names the alert's fields in snake_case
type Alert struct {
Instance string `json:"instance"`
Time time.Time `json:"time"`
Event string `json:"event"`
Host string `json:"host,omitempty"`
Client netip.Addr `json:"client"`
Netblock netip.Prefix `json:"netblock"`
ASN string `json:"asn"`
@@ -899,12 +902,16 @@ func title(alert *Alert) string {
}
// text returns the text of alert in Slack and ntfy: its reason, then a
// line for each of its client, netblock and country, the file, source,
// error and mode its detail gives, and its suppressed repeats, that it
// has.
// line for each of its host, client, netblock and country, the file,
// source, error and mode its detail gives, and its suppressed repeats, that
// it has.
func text(alert *Alert) string {
lines := []string{alert.Reason}
if alert.Host != "" {
lines = append(lines, "host: "+alert.Host)
}
if alert.Client.IsValid() {
lines = append(lines, "client: "+alert.Client.String())
}
+3 -1
View File
@@ -1284,9 +1284,11 @@ func anAlertForEachEvent() []eventMessage {
Event: alerts.EventWAFBlock, Client: netip.MustParseAddr("192.0.2.1"),
Netblock: netip.MustParsePrefix("192.0.2.1/32"),
Reason: "refused by the Core Rule Set",
Host: "app.example",
},
"fsn1app1/gitea: waf_block", "default shield",
"refused by the Core Rule Set\nclient: 192.0.2.1\nnetblock: 192.0.2.1/32",
"refused by the Core Rule Set\nhost: app.example\nclient: 192.0.2.1\n" +
"netblock: 192.0.2.1/32",
},
{
alerts.Alert{