SPEC: the one place for behaviour and reasons; docs/ as lookup lists only
check / check (push) Waiting to run

docs/core-rule-set.md, docs/lookup.md, docs/reputation.md and docs/bans.md
restated the spec and are folded back into it and removed. The remaining
docs files hold only lists: settings with their defaults and value forms,
the request log's fields, the metrics, the endpoints, the state files and
their contents, the rule file format and default file, the alert events
and messages, the image's contents and pins, and the gitea notes. Every
behaviour each of them held moves to the spec, said once. The settings
groups have the same names in both files. Stale comments in
internal/state and script/example-app point at the right places.

Model: fable-5-1
This commit is contained in:
2026-10-22 21:51:39 +00:00
parent d712bb1496
commit 4ea0a975c8
17 changed files with 918 additions and 1273 deletions
+24 -46
View File
@@ -1,17 +1,17 @@
# Alerts
Each alert goes to each destination you name. `SWWAF_ALERT_WEBHOOK_URL` gets it
as one JSON object. `SWWAF_ALERT_SLACK_WEBHOOK_URL` and `SWWAF_ALERT_NTFY_URL`
get a message made from it. The settings are under "Alert destinations" in
[`settings.md`](settings.md). An alert is one of these events, and is sent when
its event is in `SWWAF_ALERT_EVENTS`:
The alert events, the JSON object the webhook gets, and the messages Slack and
ntfy get. "Alerting" in [`SPEC.md`](../SPEC.md) says when an alert is sent, held
back or dropped.
## Events
- `ban`: a ban `smallwebwaf` made.
- `permanent_ban`: a permanent ban it made, or an existing ban made permanent by
a request.
- `waf_block`: the Core Rule Set scored a request at or over the threshold. In
`block` mode the request was refused. In `detect` mode it was not, and the
alert's `detail` gives `mode`.
- `waf_block`: the Core Rule Set scored a request at or over the threshold. The
request was refused unless the alert's `detail` gives `mode`: `detect` or
`observe`.
- `anomaly`: a count over an anomaly threshold.
- `reputation_hit`: a request from a client on a blocklist, the CrowdSec
decision list or a DNS blocklist, or with an AbuseIPDB score that is a hit.
@@ -25,12 +25,7 @@ its event is in `SWWAF_ALERT_EVENTS`:
- a rule file or state file edit with an error;
- a state file that cannot be written;
- a replacement lookup database that cannot be read.
- `summary`: the hourly summary described under "Repeats and the hourly cap"
below.
The bans you make raise no alert. In `observe` mode, a request that would have
made a ban still raises the ban's alert, with `mode` set to `observe` in its
`detail`.
- `summary`: the hourly summary of alerts held back.
## The JSON object
@@ -81,24 +76,26 @@ on one line:
}
```
- `instance` is `SWWAF_INSTANCE_NAME`. `time` is when the alert was raised.
`event` is one of the events above.
- `client` is the address whose request raised the alert. `netblock` is the
ban's netblock, the netblock an anomaly counted, or the client's group. Both
are empty for `source_failure` and `file_error`. `asn`, `as_name` and
`country` are the client's as known when the alert was raised. They are empty
when GeoJS had not answered yet.
- `reason` is a short sentence, such as
- `instance`: `SWWAF_INSTANCE_NAME`. `time`: when the alert was raised. `event`:
one of the events above.
- `client`: the address whose request raised the alert. `netblock`: the ban's
netblock, the netblock an anomaly counted, or, for the other events, the
client as rate limiting counts it: an IPv4 address as a /32, or an IPv6 group.
Both are empty for `source_failure` and `file_error`.
- `asn`, `as_name` and `country`: the client's as known when the alert was
raised. They are empty when GeoJS had not answered yet.
- `reason`: a short sentence, such as
`requests per minute over the limit of 1000` or `listed by a blocklist`.
- `detail` is what is particular to the event:
- for a ban, its `cause`, `ban_expires` and `notes` (see
[`bans.md`](bans.md) for the notes);
- `detail`: what is particular to the event:
- for a ban, its `cause`, `ban_expires` and `notes` (the notes are listed
under `bans.json` in [`state-files.md`](state-files.md));
- for an anomaly, the `scope`, `window`, `kind`, `count` and `threshold`;
- for a reputation hit, the `source` and, for AbuseIPDB, the `score`;
- for a Core Rule Set match, the `rule_ids`, `score`, `method` and `path`;
- for a source failure, the `source` and `error`;
- for a file error, the `file` and `error`.
- `suppressed_repeats` is how many repeats the cooldown held back before this
- for a file error, the `file` and `error`;
- `mode`, when the request was not refused: `detect` or `observe`.
- `suppressed_repeats`: how many repeats the cooldown held back before this
alert.
## Slack and ntfy
@@ -128,22 +125,3 @@ The priority and tag by event:
- `reputation_hit`: priority `low`, tag `label`.
- `source_failure` and `file_error`: priority `high`, tag `warning`.
- `summary`: priority `default`, tag `bar_chart`.
## Repeats and the hourly cap
A repeat is an alert for the same event on the same netblock, source, file or
thing counted, less than `SWWAF_ALERT_COOLDOWN` after the last. It is held back
and counted, and the next alert sent gives the count. Past
`SWWAF_ALERT_MAX_PER_HOUR` alerts in an hour, the rest are held back and counted
by event. Once the hour ends, one alert with the event `summary` gives those
counts. It also counts the repeats that a cooldown held back and that no later
alert reported.
## Sending
Each destination has a queue of at most 1000 alerts, sent one at a time, so a
slow destination holds up nothing. A `2xx` answer takes an alert. A `4xx` other
than `408` and `429` refuses it, and it is dropped. Anything else is a failure:
the alert is sent again a second later, and twice as long after each further
failure, up to a minute. `alerts.json` keeps the queues and cooldowns across a
restart (see [`state-files.md`](state-files.md)).