Files
smallwebwaf/docs/alerts.md
T
clawbot a9dc59546b
check / check (push) Waiting to run
New defaults for the request and byte limits (closes #161)
The request limits and the hour and day byte limits now sit about twice
above what 40 people busy with heavy web apps make together on one
address:

- requests: 6000 a minute, 100,000 an hour, 1,000,000 a day;
- bytes: 20 GiB an hour, 200 GiB a day.

The largest response drops from 5 GiB to 1 GiB. The minute byte limit
of 4 GiB is sized to hold two of the largest responses with their
requests. SPEC.md gives the new reasoning; README.md and the reference
documents give the new figures.

Model: opus-5-5
2026-10-27 19:28:09 +01:00

5.0 KiB

Alerts

The alert events, the JSON object the webhook gets, and the messages Slack and ntfy get. "Alerting" in 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:

{
    "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 6000",
    "detail": {
        "ban_expires": "2026-10-06T13:00:00.123Z",
        "cause": "limit",
        "notes": {
            "asn": "",
            "as_name": "",
            "country": "",
            "kind": "requests",
            "limit": 6000,
            "window": "minute",
            "count": 6001,
            "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": 8210,
            "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, file_error and summary.
  • netblock: empty for source_failure, file_error and summary; otherwise one of:
    • the ban's netblock;
    • the netblock an anomaly counted, empty in the asn and total scopes;
    • 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 6000 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);
    • 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;
    • for a summary, the hour it covers, the count of alerts held back, and that count by event in events;
    • 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.