Author SHA1 Message Date
sneak 1dbe17e594 README rewritten from scratch so a newcomer can follow it (closes #154, closes #155)
check / check (push) Canceled after 0s
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, what happens to a request, the
settings most deployments touch, how to check it works, the entrypoints,
the rationale and the design, as REPO_POLICIES.md requires. The reference
lists, every setting, the rule file format, the endpoints, the request
log's fields, the alert events, the metrics, the state files, the image
and the gitea notes, move to files under docs/, each linked from the
README, with SPEC.md as the one place for behaviour. Every statement was
checked against the code on next.

Model: fable-5-1
2026-10-22 22:29:53 +00:00
8 changed files with 150 additions and 123 deletions

No files matched your search

+39 -32
View File
@@ -30,9 +30,9 @@ reference documents under [`docs/`](docs/) are listed under "Documents" below.
of your own, or an entry in `SWWAF_TRAP_PATHS`, bans those the same way.
- Attacks carried in a request's URL or headers, such as SQL injection and path
traversal. The OWASP Core Rule Set is a published set of rules that recognise
common web attacks. Coraza, a Go engine that runs those rules, inspects each
request the earlier checks let through. A request the rules take for an attack
is refused.
common web attacks. Coraza, a Go engine, runs those rules (step 11 under "What
happens to a request" below). A request the rules take for an attack is
refused.
- Abuse from networks and countries you do not want. You can:
- refuse whole netblocks and countries;
- give the clients of chosen networks and countries a fraction of every
@@ -132,8 +132,9 @@ docker run --detach --name app \
With no setting, the address of every new visitor is sent to GeoJS, a web
service, to find its country and network. `SWWAF_LOOKUP_SOURCE=off` sends
nothing. `SWWAF_LOOKUP_SOURCE=file` looks addresses up in a database file on
your own host (see "Lookup of AS number and country" in [`SPEC.md`](SPEC.md)).
nothing. `SWWAF_LOOKUP_SOURCE=file`, with `SWWAF_LOOKUP_DB_PATH` naming a
database file on your own host, looks addresses up in that file (see "Lookup of
AS number and country" in [`SPEC.md`](SPEC.md)).
`deploy/example-app` is such an app. `make example-app` builds it and checks it
(see "Checking that it works" below). To run `smallwebwaf` outside the image
@@ -149,9 +150,10 @@ request that counts against its client in its history. It is one of:
- a broken limit;
- a refusal by a rule, by the Core Rule Set or for a missing or wrong token.
`smallwebwaf` handles each request in this order. The first step that refuses it
ends the handling. Nothing of a refused request reaches the app. "What happens
to a request" in [`SPEC.md`](SPEC.md) gives each step in full.
`smallwebwaf` handles each request in this order. A request one step refuses
skips the steps after it, apart from step 13. Nothing of a refused request
reaches the app. "What happens to a request" in [`SPEC.md`](SPEC.md) gives each
step in full.
1. `GET /_smallwebwaf/healthz` is answered `200` with `ok` at once, before any
check and without asking the app.
@@ -187,18 +189,18 @@ to a request" in [`SPEC.md`](SPEC.md) gives each step in full.
10. The rule files are checked. A `log` rule notes the match. A `block` rule
refuses the request. A `ban` rule refuses it and bans the client, as a clear
sign of attack.
11. The Core Rule Set inspects the request's method, URL and headers, and the
body too once `SWWAF_WAF_BODY_LIMIT` is set. A request it takes for an
attack is refused while `SWWAF_WAF_MODE` is `block`, the default, and let
through while it is `detect`. It bans no one by itself.
11. The Core Rule Set inspects the request's method, URL and headers, and, once
`SWWAF_WAF_BODY_LIMIT` is set, form, JSON and XML bodies. A request it takes
for an attack is refused while `SWWAF_WAF_MODE` is `block`, the default, and
let through while it is `detect`. It bans no one by itself.
12. A request under `/_smallwebwaf/` is answered here by `smallwebwaf`, after
its token is checked. Any other request is passed to the app. The app's
answer is streamed back within the size and time limits.
13. Once the answer has ended, the request's bytes are counted for the byte
limits. If a rule, a trap path, the Core Rule Set or a token check refused
it, the refusal is counted for the error burst. The client's history and the
anomaly counters are updated. More than `SWWAF_ERROR_BURST_THRESHOLD`
refusals of one client in a minute ban it.
13. Once the answer has ended, a request passed to the app has its bytes counted
for the byte limits. If a rule, a trap path, the Core Rule Set or a token
check refused the request, the refusal is counted for the error burst. The
client's history and the anomaly counters are updated. More than
`SWWAF_ERROR_BURST_THRESHOLD` refusals of one client in a minute ban it.
In `observe` mode (`SWWAF_MODE=observe`), `smallwebwaf` makes no ban of its own.
It refuses a request only for a size or time limit, or for a missing or wrong
@@ -209,27 +211,32 @@ configuration before enforcing it.
What a refused visitor sees:
- `SWWAF_BAN_RESPONSE`, by default `403` with the plain text `Forbidden`. It can
be `429` instead, or `close`, which closes the connection without an answer.
It answers:
be `429` instead, or `close`, which closes the connection, so that traefik
answers `502`. It answers:
- a ban or a broken rate limit;
- a `ban` rule or a trap path;
- `SWWAF_DENY_NETS` and the country lists;
- a reputation source whose action is `deny`.
- `403` for a `block` rule and the Core Rule Set.
- `413` or `408` when the client passes a size or time limit, `502` or `504`
when the app does.
- For a size or time limit:
- `431` for headers over their limit;
- `413` for a body over its limit;
- `408` for a client too slow to send its body;
- `502` or `504` when the app passes a limit or cannot be reached.
- `401` from an endpoint of `smallwebwaf`'s own, for a request without its
token.
## Settings you are likely to touch
Each setting is an environment variable of the app's container, such as
`docker run -e SWWAF_MODE=observe ...`, read at start.
[`docs/settings.md`](docs/settings.md) lists every setting, its default and the
values it takes. The ones most deployments touch:
- `SWWAF_MODE=observe`: log what would have been refused, refuse almost nothing,
ban no one. For trying a configuration.
- `SWWAF_ALLOW_NETS`: netblocks whose clients are refused only for a size or
time limit or a missing or wrong token, such as your monitoring.
- `SWWAF_ALLOW_NETS`: netblocks you trust, such as your monitoring. Their
clients are refused only for a size or time limit or a missing or wrong token.
- `SWWAF_RATE_LIMIT_EXEMPT_NETS`: netblocks exempt from rate limiting and the
byte limits, such as a machine that talks to the app all day.
- `SWWAF_TRAP_PATHS`: paths only scanners ask for, such as
@@ -237,7 +244,8 @@ values it takes. The ones most deployments touch:
the client.
- `SWWAF_WAF_DISABLED_RULES`: Core Rule Set rule ids to switch off when a rule
refuses your app's ordinary requests. The log line names the rules a request
matched.
matched. Keep the six default ids in a list you give (see
[`docs/settings.md`](docs/settings.md)).
- `SWWAF_REQUEST_MAX_BYTES`, `SWWAF_CLIENT_REQUEST_TIMEOUT` and
`SWWAF_UPSTREAM_REQUEST_TIMEOUT`: raise them for an app that takes uploads
over 100 MiB or slower than 60 seconds.
@@ -261,9 +269,8 @@ values it takes. The ones most deployments touch:
`SWWAF_MODE=observe` and ask for `/.env`. The request reaches the app. Its log
line has `"would_action":"banned"` and `"rule_ids":["env-file"]`. In `enforce`
mode the same request bans your address for seven days, and your next request
makes the ban permanent. Lift the ban by editing `bans.json` or through the
ban endpoint (see "Bans" in [`SPEC.md`](SPEC.md) and
[`docs/admin-endpoints.md`](docs/admin-endpoints.md)).
makes the ban permanent. Lift the ban by editing `bans.json` (see "Bans" in
[`SPEC.md`](SPEC.md)).
- With `SWWAF_METRICS_TOKEN` set,
`curl -H "Authorization: Bearer $TOKEN" https://<site>/_smallwebwaf/metrics`
answers with the metrics.
@@ -285,8 +292,8 @@ the `Makefile` targets are thin shims that call them. The scripts are POSIX sh.
The tests and the linter run in Docker, as phases of the `Dockerfile`, and never
on the host.
- `script/bootstrap`: installs what the other scripts need on the host.
`make bootstrap`. They need:
- `script/bootstrap`: installs on the host what the other scripts need, with
`make bootstrap`:
- `make`, `git` and `curl`;
- Go, for `gofmt`;
- node, yarn and prettier.
@@ -416,9 +423,9 @@ another port, the health check's timing, and the forwarded headers.
## TODO
- Everything `SPEC.md` describes is built.
- Two points `SPEC.md` defers until there is experience to go on: sharing bans
between the instances of several apps, and sending the log over RELP.
- Planned, as "Planned, not built" in [`SPEC.md`](SPEC.md) describes:
- sharing bans between the instances of several apps;
- sending the log over RELP.
## Documents
+8 -5
View File
@@ -78,11 +78,14 @@ on one line:
- `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
- `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`.
+38 -34
View File
@@ -21,10 +21,10 @@ checked again when gitea or the Core Rule Set changes version.
- a release attachment.
At the defaults, an upload larger than 100 MiB or taking more than 60
seconds is cut off, and no one is banned for it. "Size and time limits" in
`SPEC.md` gives the answers the client gets. A gitea that takes large
uploads needs `SWWAF_REQUEST_MAX_BYTES`, `SWWAF_CLIENT_REQUEST_TIMEOUT` and
`SWWAF_UPSTREAM_REQUEST_TIMEOUT` raised to fit.
seconds is cut off ("Size and time limits" in `SPEC.md`). A gitea that takes
large uploads needs `SWWAF_REQUEST_MAX_BYTES`,
`SWWAF_CLIENT_REQUEST_TIMEOUT` and `SWWAF_UPSTREAM_REQUEST_TIMEOUT` raised
to fit.
- Archive download and blame or history pages are what scrapers hammer. The
request limits do most of the work there.
@@ -63,15 +63,15 @@ toward). The request log names the rule in `waf_rule_ids`, which
### A query string that reads as an attack
Most often this is a search. Every query parameter is checked against the Core
Rule Set's lists of system files and commands, except the ten named under
"Attack detection" in `SPEC.md`. A release attachment uploaded through the API
as `docker-compose.yml` is refused the same way. Examples:
Most often this is a search. A search term is a query parameter, which the Core
Rule Set checks against its lists of system files and commands (see the fourth
change under "Attack detection" in `SPEC.md`). A release attachment uploaded
through the API as `docker-compose.yml` is refused the same way. Examples:
- a name on those lists, such as `package.json`, `.gitignore` or
`docker-compose.yml`;
- text that starts with a command name, or whose first word has one right after
a `/`, such as `python3`, `ssh key` or `feat/docker-support`;
- text that starts with a command name, such as `python3` or `ssh key`, or has
one right after a `/` in its first word, such as `feat/docker-support`;
- a shell command with its options or a system path (`ls -la`, `sed -i`,
`/bin/sh`), or a command in backticks;
- script code (`fetch(`, `${VAR}`, `process.env`), HTML (`<img src=`) or an SQL
@@ -81,15 +81,15 @@ as `docker-compose.yml` is refused the same way. Examples:
### A branch, tag or file name that starts with a command name
The Core Rule Set's list of commands (932260) holds names such as `docker-`,
`python3`, `ansible`, `base64` and `whoami`. A branch, tag or file name that
starts with one is refused. So is one that has such a name right after a `/`,
such as `feat/docker-support`, `fix/docker-build` or
`python3`, `ansible`, `base64` and `whoami`. A query parameter holding a branch,
tag or file name that starts with one is refused. So is one that has such a name
right after a `/`, such as `feat/docker-support`, `fix/docker-build` or
`renovate/docker-build-push-action-6.x`. gitea's own pages send the name in a
query parameter that 930120, 932160 and 932260 still check:
- `refSubUrl`, the branch or tag of a directory listing. It is sent when the
listing asks for its entries' last commits in a second request, which it does
whenever gitea takes more than a second to work them out;
- `refSubUrl`, the branch or tag of a directory listing. A listing sends it when
it asks for its entries' last commits in a second request. It does so whenever
gitea takes more than a second to work them out;
- `name`, when a branch is deleted or restored on the branches page;
- `tag`, when a release is started from a tag;
- `template`, the file of an issue template, when an issue is opened from it;
@@ -110,9 +110,9 @@ What a visitor sees, for a branch named `docker-build`:
### A file name in a query parameter that trips the code rules
The Core Rule Set's rules for Java, script, PowerShell and SQL find what they
look for in some file names. This holds in the parameters `smallwebwaf` leaves
out of 930120, 932160 and 932260 too (see the changes to the Core Rule Set under
"Attack detection" in `SPEC.md`). Examples:
look for in some file names. This holds too in the ten query parameters that
930120, 932160 and 932260 do not check (see the fourth change under "Attack
detection" in `SPEC.md`). Examples:
- `runtime` in OpenJDK's `src/java.base/share/classes/java/lang/Runtime.java`
(944110);
@@ -129,9 +129,9 @@ For such a file, these requests are refused:
- the API's call for the contents of files that names it in its query (`body`);
- the commit graph of that file alone (`file`);
- the hidden lines of its diff (`path`), which stay hidden;
- the sign-in page gitea sends a visitor to on the way to a page of the file
that needs signing in, such as its edit page (`redirect_to`). The visitor can
still sign in by opening the sign-in page itself.
- the sign-in page, for a visitor who opens a page of the file that needs
signing in, such as its edit page (`redirect_to`). The visitor can still sign
in by opening the sign-in page itself.
### A path
@@ -143,25 +143,30 @@ For such a file, these requests are refused:
`process.env.d.ts` does.
- A path with a directory named `...` (930100, 930110), such as `.../notes.txt`.
For such a file, the page of the file, its raw file, its history, its blame and
its edit page are refused. The directory that holds it lists it, and a clone
carries it.
For such a file, these are refused:
- the page of the file;
- its raw file;
- its history and its blame;
- its edit page.
The directory that holds it lists it, and a clone carries it.
### A container image push that reuses a layer
A push can reuse a layer of an image docker has pushed to that gitea before.
docker then asks gitea to take the layer from that image, naming it in the query
parameter `from`, which 932260 checks. The request is refused when the image's
owner starts with an entry on the list of commands. So is one whose name after
the owner does. Examples are `owner/python3-app`, `owner/docker-base`, and any
image of an owner named `ansible` or `docker-team`.
owner starts with an entry on the list of commands. It is refused too when the
image's name after the owner starts with one. Examples are `owner/python3-app`,
`owner/docker-base`, and any image of an owner named `ansible` or `docker-team`.
What happens next depends on docker's image store. A docker that keeps its
images in containerd's image store fails the push, and fails each new try the
same way. A new install of Docker Engine 29 does this. A docker that keeps them
in its own older image store uploads the layer instead. The push then succeeds
after one refusal for each layer it reuses, and each of those refusals counts
toward the error burst.
toward the error burst ("Attack detection" in `SPEC.md`).
### Artifact uploads from `actions/upload-artifact@v3`
@@ -169,9 +174,9 @@ This action sends the header `Content-Range`, which the Core Rule Set refuses
(920450). The action sends two files at a time, does not retry a `403`, and
sends no further file once one is refused. So an upload is refused once or
twice, however many files it holds, and the step fails. One upload does not ban
the runner. More than 30 such refusals from its address within a minute, as when
many jobs upload at once, ban it through the error burst.
`actions/upload-artifact@v4` does not send the header.
the runner. The error burst ("Attack detection" in `SPEC.md`) bans it after more
than 30 such refusals from its address in a minute, as when many jobs upload at
once. `actions/upload-artifact@v4` does not send the header.
## What body inspection refuses
@@ -193,8 +198,7 @@ Of the requests above, the script sends:
code rules", the API's list of its commits;
- for `Runtime.java` alone, the commit graph of that file, the hidden lines of
its diff, and the API's call for the contents of files that names it;
- the sign-in page gitea sends a visitor to on the way to the edit page of
`document.write.js`;
- the sign-in page for a visitor who opens the edit page of `document.write.js`;
- the pages of `java.lang.String.md`,
`META-INF/services/org.apache.commons.logging.LogFactory`, `process.env.d.ts`
and `.../notes.txt`;
+3 -2
View File
@@ -31,6 +31,7 @@ is pinned that way.
- nixpkgs: the release file `nixexprs.tar.xz` of
`nixos-26.05.11045.774debe7a0d1` on `releases.nixos.org`, named with its
SHA-256 hash.
- `runsvinit`: the archived repository, unchanged since 2015, at tag `v2.0.0`,
by commit hash, with a `go.mod` the build writes.
- `runsvinit`: the archived repository, unchanged since 2015, at its last commit
(`b4b2c78`, eight commits after `v2.0.0`), by commit hash, with a `go.mod` the
build writes.
- The Go image that builds `smallwebwaf`: by digest.
+3 -5
View File
@@ -1,10 +1,8 @@
# The metrics
`GET /_smallwebwaf/metrics` answers in the Prometheus text format. The
requirements are under "Metrics endpoint" in [`SPEC.md`](../SPEC.md); this file
lists the metrics. Every metric carries the label `instance`, set to
`SWWAF_INSTANCE_NAME`; Prometheus keeps it as `exported_instance` unless the
scrape sets `honor_labels: true`. No metric carries a client's address.
The metrics `GET /_smallwebwaf/metrics` answers with, as "Metrics endpoint" in
[`SPEC.md`](../SPEC.md) describes it. Prometheus keeps the `instance` label as
`exported_instance` unless the scrape sets `honor_labels: true`.
## Traffic
+23 -12
View File
@@ -12,9 +12,13 @@ sent to a syslog server. A field that does not apply to a request is left out.
## Every line
Every line has `type` (`request`; the process's own messages have `process`),
the standard web log fields, `request_id`, `peer_ip`, `client_group`, `asn`,
`as_name`, `country`, `action` and `duration_total`.
Every line has:
- `type`: `request` (the process's own messages have `process`);
- the standard web log fields;
- `request_id`, `peer_ip` and `client_group`;
- `asn`, `as_name` and `country`;
- `action` and `duration_total`.
## Standard web log fields
@@ -28,7 +32,7 @@ the standard web log fields, `request_id`, `peer_ip`, `client_group`, `asn`,
## Request detail
- `request_id`: the `X-Request-ID` a trusted proxy sent, or a new random one of
26 letters and digits. It goes to the app in `X-Request-ID`.
26 letters and digits.
- `peer_ip`: the TCP peer, normally traefik.
- `forwarded_for`: the `X-Forwarded-For` header as received.
- `client_group`: the client as rate limiting counts it, an IPv4 address as a
@@ -73,15 +77,22 @@ the standard web log fields, `request_id`, `peer_ip`, `client_group`, `asn`,
rate limits and the setting that lowered it, when one did. `bytes_percent` and
`bytes_percent_setting` do the same for the byte limits.
- `counts`: the client's totals as rate limiting counts them, this request
included. It is left out when rate limiting did not count the request. A count
can have a fraction, since each window is counted in two buckets with the
earlier one weighted:
included. It is left out when rate limiting did not count the request. It
holds:
- `minute`, `hour` and `day`: requests;
- `minute_bytes`, `hour_bytes` and `day_bytes`: bytes.
- `limit_hit`: the limit broken: `minute`, `hour` or `day` for a rate limit;
`minute_bytes`, `hour_bytes` or `day_bytes` for a byte limit; or
`error_burst`. A request whose bytes broke a limit was passed on whole, so its
`action` stays `forward`.
A count can have a fraction (see "Counting method" in
[`SPEC.md`](../SPEC.md)).
- `limit_hit`: the limit broken:
- `minute`, `hour` or `day` for a rate limit;
- `minute_bytes`, `hour_bytes` or `day_bytes` for a byte limit;
- `error_burst`.
A request whose bytes broke a limit was passed on whole, so its `action`
stays `forward`.
- `rule_ids`: the rule file rules that matched, in order, up to the one that
refused.
- `waf_score` and `waf_rule_ids`: the Core Rule Set's score and the rules that
@@ -91,7 +102,7 @@ the standard web log fields, `request_id`, `peer_ip`, `client_group`, `asn`,
- the CrowdSec decision list's URL;
- DNS blocklist zones;
- `abuseipdb`.
- `offence`: the kind of offence recorded, when one was.
- `offence`: `limit`, for a request that broke a limit.
- `ban_expires`: when the client's ban ends, for a request that made one or was
refused under one: a time, or `permanent`.
+17 -18
View File
@@ -2,8 +2,7 @@
Every setting, its default and the values it takes, in the groups of "Settings"
in [`SPEC.md`](../SPEC.md). That section gives the conventions, the settings
that take `off`, the `_FILE` form, and what each group does and why. A setting
set to a value its entry does not allow stops the start.
that take `off`, the `_FILE` form, and what each group does and why.
## Forms of values
@@ -27,8 +26,9 @@ set to a value its entry does not allow stops the start.
- `SWWAF_UPSTREAM_URL` (default `http://127.0.0.1:8081`): the app. An `http` or
`https` URL with a host and an optional port, and nothing more.
- `SWWAF_LISTEN_ADDR` (default `:8080`): where `smallwebwaf` listens, an
optional host and a port. The host stays empty.
- `SWWAF_LISTEN_ADDR` (default `:8080`): where `smallwebwaf` listens: `:` and a
port, such as `:9000`. Leave the host out (see "Ports" in
[`SPEC.md`](../SPEC.md)).
- `SWWAF_INSTANCE_NAME` (default: the container's host name): the `instance`
name in every log line, alert and metric, such as `fsn1app1/gitea`. Valid
UTF-8. No control character while `SWWAF_ALERT_NTFY_URL` is set. 1 to 48
@@ -42,8 +42,8 @@ set to a value its entry does not allow stops the start.
client's group, from 32 to 128.
- `SWWAF_MAX_TRACKED_CLIENTS` (default `20000`): the most clients kept, a whole
number above zero.
- `SWWAF_MAX_BANS` (default `5000`): the most bans `smallwebwaf` made that are
kept, a whole number above zero.
- `SWWAF_MAX_BANS` (default `5000`): how many of its own bans `smallwebwaf`
keeps, a whole number above zero.
## Tokens
@@ -90,8 +90,9 @@ set to a value its entry does not allow stops the start.
## Static lists
- `SWWAF_ALLOW_NETS` (default empty): netblocks whose clients are refused only
for a size or time limit or a missing or wrong token, such as your monitoring.
- `SWWAF_ALLOW_NETS` (default empty): netblocks you trust, such as your
monitoring. Their clients are refused only for a size or time limit or a
missing or wrong token.
- `SWWAF_RATE_LIMIT_EXEMPT_NETS` (default empty): netblocks whose clients are
exempt from rate limiting and the byte limits.
- `SWWAF_DENY_NETS` (default empty): netblocks whose clients are always refused.
@@ -150,7 +151,7 @@ set to a value its entry does not allow stops the start.
such as `cn,ru,kp`.
- `SWWAF_EXCLUSIVELY_ALLOWED_COUNTRIES` (default empty): when set, the only
countries whose clients get through, such as `us,de`. A country on both lists
is refused.
stops the start.
## Biased thresholds
@@ -164,8 +165,8 @@ set to a value its entry does not allow stops the start.
- `SWWAF_UNKNOWN_LIMIT_PERCENT` (default `100`): the percentage for a client the
lookup cannot place.
- `SWWAF_ASN_LIMIT_PERCENT_URL` (default unset): the `http` or `https` URL of a
text file of `AS<number>:<percent>` lines, one to a line. A URL that is also
in `SWWAF_BLOCKLIST_URLS` is refused.
text file with one AS number and percentage on each line, such as
`AS64496:50`. A URL that is also in `SWWAF_BLOCKLIST_URLS` is refused.
## Attack detection
@@ -260,8 +261,8 @@ set to a value its entry does not allow stops the start.
`ban,permanent_ban,waf_block,anomaly,reputation_hit,source_failure,file_error`):
the event types alerts are sent for, from those in [`alerts.md`](alerts.md)
but `summary`. Any other name is refused.
- `SWWAF_ALERT_COOLDOWN` (default `15m`): how long a repeat of an alert is held
back. A duration or `off`.
- `SWWAF_ALERT_COOLDOWN` (default `15m`): how long after an alert is sent its
repeats are held back. A duration or `off`.
- `SWWAF_ALERT_MAX_PER_HOUR` (default `60`): the most alerts sent in an hour. A
whole number above zero, or `off`.
@@ -294,10 +295,8 @@ as `REQUESTS_PER_MINUTE`:
## Settings given as files
Add `_FILE` to a setting's name to read its value from a file:
`SWWAF_METRICS_TOKEN_FILE` names the file that holds `SWWAF_METRICS_TOKEN`. To
keep a token from the app, make the file on the host, owned by uid 65532 with
mode `0400`:
To give a token as a file, as "Tokens" under "Deployment" in
[`SPEC.md`](../SPEC.md) describes, make the file on the host:
```sh
mkdir -p /srv/app/tokens
@@ -306,7 +305,7 @@ chown 65532:65532 /srv/app/tokens/metrics
chmod 0400 /srv/app/tokens/metrics
```
Mount its directory into the container read-only, and name the file:
Then mount its directory into the container read-only, and name the file:
```sh
docker run \
+19 -15
View File
@@ -15,8 +15,8 @@ those `smallwebwaf` made. Per entry:
- `lifted`: when an admin lifted it, if one did;
- `notes`: the notes below.
To ban a netblock, add an entry with its `netblock`, `start` and `expires`.
`reason`, `notes` and `cause` may be left out; the cause is then `admin`:
An entry an admin adds needs `netblock`, `start` and `expires`; `reason`,
`notes` and `cause` may be left out, and the cause is then `admin`:
```json
{
@@ -32,20 +32,23 @@ To ban a netblock, add an entry with its `netblock`, `start` and `expires`.
}
```
To lift a ban, add `"lifted"` with the time you lift it, such as
`"lifted": "2026-10-06T13:00:00Z"`. To keep a ban `smallwebwaf` made, set its
`cause` to `admin`. To forget a ban, delete its entry.
The notes:
"Bans" in [`SPEC.md`](../SPEC.md) says how an edit lifts, keeps or forgets a
ban. The notes:
- `asn`, `as_name` and `country`: the client's, added when the lookup answers
(empty when lookups are off);
- what was broken: the rule id and target that matched, or the trap path, or the
limit, its window and the count reached;
- the client's percentage of the limit, with the setting that gave it;
- the reputation sources the client is on;
- `request`: the request that caused the ban: time, method, host, path with its
query string, status and user agent, each text cut to 256 bytes;
- for a broken limit, `kind` (`requests`, `bytes` or `refusals`), `limit`,
`window` and `count`: what the limit was on, the limit, its window and the
count reached;
- `limit_percent` and `limit_percent_setting`: the client's percentage of that
limit and the setting that gave it, when a setting lowered it;
- for a clear sign of attack, `rule_id` and `target`, or `trap_path`;
- `reputation`: the sources the client was on, each with its `source` and, for
AbuseIPDB, its `score`;
- `request`: the request that caused the ban, each text cut to 256 bytes:
- `time`, `method` and `host`;
- `path`, with its query string;
- `status` and `user_agent`;
- `requests` and `refused`: the netblock's requests since it was first seen, and
how many this ban has refused so far, kept up to date while the ban lasts;
- `earlier_bans`: how many earlier bans the netblock had, by cause.
@@ -93,8 +96,9 @@ GeoJS answers, one per line:
`SWWAF_MAX_TRACKED_CLIENTS`. Written every 15 minutes, that is under 2 GiB of
disk writes a day.
- `bans.json`: about 1.2 KiB per ban, at most about 2.5 KiB, so about 6 MiB at
the default `SWWAF_MAX_BANS`. With a full file, a hundred new bans a day come
to about 600 MiB of writes.
the default `SWWAF_MAX_BANS`. Written every 15 minutes, a full file comes to
about 600 MiB of writes a day, and a hundred new bans a day add about 600 MiB
more.
- `lookups.json`: about 150 bytes per answer, under 1.5 GiB of writes a day when
full.
- `reputation.json` and `alerts.json`: usually a few MiB or less.