Files
smallwebwaf/docs/alerts.md
T
sneak dfb44c5546
check / check (push) Waiting to run
README rewritten from scratch so a newcomer can follow it (closes #154, closes #155)
The old README opened with project history and one list of a hundred
settings. The new one leads with what smallwebwaf is, what it protects
against, how to build and deploy it, how it sits in front of the app,
what happens to a request, the settings most deployments touch, how to
check it works and how to develop it. The reference material, every
setting, bans, rule files, the Core Rule Set, the admin endpoints, the
request log, alerts, metrics, state files, lookups, reputation sources,
the image and gitea, moves to files under docs/, each linked from the
README. Every statement was checked against the code on next.

Model: fable-5-1
2026-10-22 21:02:38 +00:00

150 lines
5.9 KiB
Markdown

# 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`:
- `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`.
- `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.
One alert per source.
- `source_failure`: one of:
- GeoJS failing;
- a list that cannot be fetched;
- a DNS blocklist or AbuseIPDB failing or refusing a query;
- the day's AbuseIPDB checks used up.
- `file_error`: one of:
- 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`.
## The JSON object
This is the alert for a ban for a broken rate limit, shown indented; it is sent
on one line:
```json
{
"instance": "fsn1app1/gitea",
"time": "2026-10-06T12:00:00.123461Z",
"event": "ban",
"client": "203.0.113.9",
"netblock": "203.0.113.9/32",
"asn": "",
"as_name": "",
"country": "",
"reason": "requests per minute over the limit of 1000",
"detail": {
"ban_expires": "2026-10-06T13:00:00.123Z",
"cause": "limit",
"notes": {
"asn": "",
"as_name": "",
"country": "",
"kind": "requests",
"limit": 1000,
"window": "minute",
"count": 1001,
"request": {
"time": "2026-10-06T12:00:00.123456789Z",
"method": "GET",
"host": "app.example",
"path": "/owner/repo/commits/branch/main?page=812",
"status": 403,
"user_agent": "scraper/1.0"
},
"requests": 5210,
"refused": 0,
"earlier_bans": {
"limit": 0,
"attack": 0,
"admin": 0,
"crowdsec": 0
}
}
},
"suppressed_repeats": 0
}
```
- `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
`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);
- 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
alert.
## Slack and ntfy
Slack and ntfy get a message with a title and a text. The title is the instance
and the event, such as `fsn1app1/gitea: ban`. The text is the `reason`, then one
line for each of these that the alert has:
- the client, the netblock and the country;
- the file, the source, the error and the mode from `detail`;
- the suppressed repeats, if any.
Slack gets the title in bold and the text below it, with `&`, `<` and `>`
escaped. ntfy gets the text as the message, with these headers:
- `Title`: the title;
- `Authorization`: the token, as `Bearer <token>`, while
`SWWAF_ALERT_NTFY_TOKEN` is set;
- `Priority` and `Tags`: by event, as listed below.
The priority and tag by event:
- `ban`: priority `default`, tag `no_entry`.
- `permanent_ban`: priority `high`, tag `no_entry`.
- `waf_block`: priority `default`, tag `shield`.
- `anomaly`: priority `high`, tag `chart_with_upwards_trend`.
- `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)).