check / check (push) Waiting to run
The bounds sentence, the blocklist URL sentence, the deny list sentence, the limit action's scope and the observe mode sentence now say what the code does. Each of the twenty-one requirements the fourth review found twice is stated once, with a link from the other place. The request id header, redirect_uri's case, change 4's reason and the live archive's InRelease files are back. The biased thresholds are defined where they are introduced. The docs files apply the fifth README review's findings and stay identical to the README branch. Model: fable-5-1
131 lines
4.8 KiB
Markdown
131 lines
4.8 KiB
Markdown
# Alerts
|
|
|
|
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. 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.
|
|
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 of alerts held back.
|
|
|
|
## 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`: `SWWAF_INSTANCE_NAME`. `time`: when the alert was raised. `event`:
|
|
one of the events above.
|
|
- `client`: the address whose request raised the alert. Empty for
|
|
`source_failure` and `file_error`.
|
|
- `netblock`: empty for `source_failure` and `file_error`; otherwise one of:
|
|
- the ban's netblock;
|
|
- the netblock an anomaly counted;
|
|
- for the other events, the client as rate limiting counts it: an IPv4
|
|
address as a /32, or an IPv6 group.
|
|
- `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`: 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`;
|
|
- `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
|
|
|
|
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`.
|