Files
smallwebwaf/docs/settings.md
T
sneak d712bb1496
check / check (push) Waiting to run
SPEC: requirements and reasons only, reference material in docs/
docs/settings.md is now the one settings reference; the spec keeps the
conventions, the off list and each group's requirements and reasons, and
links to it for the per-setting entries. The endpoint list, the ban
notes, the state file contents, the rule file format and the alert
object likewise live in docs/ and are linked from the spec. The package
list from the README's Design section joins "Architecture". The nine
docs files are byte-identical to those on the README branch. Package
comments that named "Alert webhook schema", "Request log" and "Metrics
endpoint" for their field lists now name the docs files.

Model: fable-5-1
2026-10-22 21:08:26 +00:00

26 KiB

Settings

Every setting is an environment variable whose name starts with SWWAF_. Every setting has a default, so none has to be set. Settings are read once, at start. To change one, restart the container.

A setting that is set to a value it cannot take stops the start. The process writes one line, "type":"process" with "msg":"invalid setting", naming the variable and what is wrong with the value. It exits with status 1, and runit starts it again every second until the value is fixed. The entries below say which values each setting takes. Any other value stops the start. At start, the settings in effect are logged on one line. Tokens, keys and the paths of webhooks are shown as ********.

The forms of values:

  • A duration is Go's syntax, with d for days: 90s, 15m, 24h, 7d. Zero and negative durations are refused.
  • A size is a number of bytes with an optional K, M or G, which are powers of 1024: 1K is 1024 bytes, 512K, 100M, 5G.
  • A count is a whole number of requests, such as 1000.
  • off switches a setting off, where its entry below says it takes off.
  • A list is comma separated, with spaces around items ignored. An empty item is refused. A list set but empty is an empty list, and replaces the default.
  • A netblock is an address and a prefix length, such as 10.0.0.0/8. A bare address stands for itself alone.
  • A country is a two-letter code of ISO 3166-1, in either case: de and DE are the same. xk stands for Kosovo. A code that is not a country is refused, such as nk (North Korea is kp) or the withdrawn su.
  • An AS number is AS and the number, in either case, such as AS64496.
  • A percentage is a whole number from 0 to 100.
  • A path prefix starts with /, such as /assets/.

Where it listens and what it protects

  • SWWAF_LISTEN_ADDR (default :8080): where smallwebwaf listens, an optional host and a port. Keep the host empty: :9000, never 127.0.0.1:9000. The app must not listen on that port. The health check follows the setting, and traefik's label must name the port.
  • 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: no path, user or query.
  • SWWAF_INSTANCE_NAME (default: the container's host name, the first 12 characters of the container's id): the instance name in every log line, alert and metric. Set it to a name that survives deploys and tells instances apart, such as fsn1app1/gitea. It must be valid UTF-8. While SWWAF_ALERT_NTFY_URL is set, it may hold no control character. While SWWAF_LOG_REMOTE_URL is set and SWWAF_LOG_REMOTE_APP_NAME is not, it must be 1 to 48 printable ASCII characters without a space.
  • SWWAF_MODE (default enforce): enforce, or observe to ban no one and log what enforce would have done. Observe mode still refuses a request over a size or time limit, and one without the right token.
  • SWWAF_TRUSTED_PROXIES (default 10.0.0.0/8,172.16.0.0/12,192.168.0.0/16, the private address ranges): the netblocks whose X-Forwarded-For is believed, normally where traefik comes from. Set but empty, it trusts nothing: every TCP peer is then the client.
  • SWWAF_IPV6_GROUP_PREFIX (default 64): the length of the netblock that is one IPv6 client, from 32 to 128. After a change, each IPv6 client starts afresh. Existing bans keep refusing their netblocks until they end.
  • SWWAF_MAX_TRACKED_CLIENTS (default 20000): the most clients kept in memory and in clients.json, a whole number above zero. Past it, the least recently seen client is dropped first.

Time and size limits

Each request has limits on both sides: between the client and smallwebwaf, and between smallwebwaf and the app. Bodies stream through in both directions, except the part the Core Rule Set reads while SWWAF_WAF_BODY_LIMIT is set.

  • SWWAF_CLIENT_REQUEST_TIMEOUT (default 60s): how long a client may take to send its request line and headers, and then, from the end of the headers, its body. A duration or off.
  • SWWAF_CLIENT_REQUEST_HEADER_MAX_BYTES (default 32K): the largest request line and headers. A size over 4K. It cannot be off: Go's HTTP server always has such a limit.
  • SWWAF_CLIENT_IDLE_TIMEOUT (default 120s): how long a kept-open connection may wait for its next request before it is closed. A duration or off. The default is longer than the 90 seconds after which traefik closes an unused connection, so traefik closes first.
  • SWWAF_CLIENT_RESPONSE_TIMEOUT (default 30m): how long the answer may take to reach the client, from the end of the request to the last byte. A duration or off.
  • SWWAF_UPSTREAM_REQUEST_TIMEOUT (default 60s): how long connecting to the app and sending it the whole request may take. A duration or off.
  • SWWAF_UPSTREAM_RESPONSE_TIMEOUT (default 30m): how long the app may take to send its whole answer. A duration or off.
  • SWWAF_REQUEST_MAX_BYTES (default 100M): the largest request body. A size or off. A request that announces a larger body is refused before anything reaches the app.
  • SWWAF_RESPONSE_MAX_BYTES (default 5G): the largest response body. A size or off.

In front of a code forge, a clone is a response and a push is a request. At the defaults, an upload over 100 MiB or slower than 60 seconds is cut off. A forge that takes large uploads needs SWWAF_REQUEST_MAX_BYTES, SWWAF_CLIENT_REQUEST_TIMEOUT and SWWAF_UPSTREAM_REQUEST_TIMEOUT raised to fit.

What the client gets when a limit is passed is under "Size and time limits" in SPEC.md.

Netblock lists

  • SWWAF_ALLOW_NETS (default empty): netblocks whose clients skip every check, such as your monitoring or your own network. The size and time limits still apply to them. They are not looked up. They still need a token for the admin endpoints.
  • SWWAF_RATE_LIMIT_EXEMPT_NETS (default empty): netblocks whose clients are exempt from rate limiting and the byte limits, such as a machine that talks to the app all day. Everything else still applies to them.
  • SWWAF_DENY_NETS (default empty): netblocks whose clients are always refused. A client in both lists is let through.

Rate limiting and byte limits

  • SWWAF_RATE_LIMIT_PER_MINUTE (default 1000), SWWAF_RATE_LIMIT_PER_HOUR (default 10000) and SWWAF_RATE_LIMIT_PER_DAY (default 50000): the most requests a client may make in each window. A count or off. The defaults are several times what one busy person produces. A browser loading a heavy page makes a few hundred requests, and several people often share one address.
  • SWWAF_RATE_LIMIT_EXEMPT_PATHS (default empty): path prefixes exempt from rate limiting, such as /assets/. Their bytes are not counted either. The path is matched exactly as sent, without the query, and is not percent-decoded. /assets/ matches /assets/app.js. It does not match /Assets/app.js, /%61ssets/app.js or /static/assets/app.js. There are no wildcards. A path holding .., a backslash or an encoded slash (%2F) is never exempt, since the app may read it as a path outside the prefix.
  • SWWAF_BYTES_LIMIT_PER_MINUTE (default 10G), SWWAF_BYTES_LIMIT_PER_HOUR (default 20G) and SWWAF_BYTES_LIMIT_PER_DAY (default 50G): the most bytes a client may have counted in each window. A size or off. Bytes are counted once the answer has ended, so a byte limit never cuts an answer short. The client's next request is refused instead. Each default is above the largest request and answer together, so at the defaults no single download breaks a limit.
  • SWWAF_BYTES_COUNT (default both): which body bytes the byte limits count: response, request or both. For a WebSocket, what it carried each way counts once it closes.

Looking up the AS number and country

  • SWWAF_LOOKUP_SOURCE (default geojs): geojs, file or off. geojs asks the GeoJS web service, which is then told the address of every new visitor. file looks clients up in the IPinfo Lite database file at SWWAF_LOOKUP_DB_PATH, and sends nothing off the host. off looks up no client. With off, a setting that needs lookups stops the start:
    • a country list;
    • SWWAF_ADD_LOOKUP_HEADERS;
    • an entry in SWWAF_ASN_LIMIT_PERCENT, SWWAF_COUNTRY_LIMIT_PERCENT, SWWAF_ASN_BYTES_PERCENT or SWWAF_COUNTRY_BYTES_PERCENT;
    • SWWAF_UNKNOWN_LIMIT_PERCENT below 100;
    • SWWAF_ASN_LIMIT_PERCENT_URL;
    • an anomaly threshold per AS number.
  • SWWAF_LOOKUP_DB_PATH (default empty): the IPinfo Lite database file, in its .mmdb form, for SWWAF_LOOKUP_SOURCE=file. file without it stops the start, and so does setting it with any other source. A file that is missing or cannot be read stops the start too.
  • SWWAF_LOOKUP_TIMEOUT (default 1s): how long a request waits for its client's first answer from GeoJS when a setting needs it, and how long a request to GeoJS may take. A duration; it cannot be off.
  • SWWAF_ADD_LOOKUP_HEADERS (default false): true passes the app the client's AS number in X-Client-ASN and its country in X-Client-Country. A header whose value is unknown is left out. Whatever this setting says, any such header the client sent is removed.

See lookup.md for what each source sends where, and how long answers are kept.

Country lists

  • SWWAF_DENIED_COUNTRIES (default empty): countries whose clients are refused, 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 client whose country cannot be found is refused too. Otherwise every new client would be let in whenever GeoJS stops answering. A client on a private address has no country, so it is refused unless it is in SWWAF_ALLOW_NETS.

A country on both lists stops the start. What a refused request gets, and why the lists are empty by default, is under "Country lists" in SPEC.md.

Lower limits by AS number and country

Clients of the AS numbers and countries listed here get a percentage of every rate limit and byte limit. A lowered limit is rounded down. "Biased thresholds" in SPEC.md says why the lists are empty by default and why no budget is shared by a whole AS number or country.

  • SWWAF_ASN_LIMIT_PERCENT (default empty): a list of AS numbers, each with : and a percentage, such as AS14061:50,AS16276:50,AS45102:25. An AS number listed twice is refused.
  • SWWAF_COUNTRY_LIMIT_PERCENT (default empty): the same by country, such as cn:25,ru:50.
  • SWWAF_ASN_BYTES_PERCENT and SWWAF_COUNTRY_BYTES_PERCENT (default empty): the same for the byte limits alone. For the AS numbers and countries they list, they take the place of the two settings above.
  • SWWAF_UNKNOWN_LIMIT_PERCENT (default 100): the percentage of every limit for a client without a country:
    • one the lookup cannot place;
    • one on a private address;
    • one whose answer from GeoJS has not come in time.
  • SWWAF_ASN_LIMIT_PERCENT_URL (default unset): the http or https URL of a text file of such entries, one to a line, such as AS14061:50. One list can then serve several instances. It is fetched and kept as a blocklist is. Where it and SWWAF_ASN_LIMIT_PERCENT both give a percentage, the lower applies. A URL that is also in SWWAF_BLOCKLIST_URLS stops the start.

A client to which several percentages apply gets the lowest. The log line of each counted request, and the notes of each ban, say which setting lowered the limit.

Reputation sources

None is on by default. A reputation source judges a client by what others saw it do. The defaults judge it by what it does to your service.

  • SWWAF_BLOCKLIST_URLS (default empty): blocklists to fetch, such as https://www.spamhaus.org/drop/drop.txt. Each is an http or https URL without a user or a fragment. A URL listed twice is refused.
  • SWWAF_BLOCKLIST_REFRESH (default 24h): how long a list waits after its last fetch, or failed try, before it is fetched again. A duration of 1h or more; it cannot be off. The Spamhaus lists may be fetched no more than once an hour.
  • SWWAF_BLOCKLIST_ACTION (default deny): what is done with a client on a blocklist. deny refuses its requests. limit:<percent>, such as limit:25, gives it that percentage of every limit. log only notes the list in the log line and raises an alert.
  • SWWAF_DNSBL_ZONES (default empty): the DNS blocklists to ask about each client, such as dnsbl.dronebl.org. A DNS blocklist answers a DNS query for a name made from an address, and so says whether the address is on it. A zone is a DNS name of at most 189 characters. A Spamhaus zone carries its key, as in <key>.xbl.dq.spamhaus.net, and is shown with the key masked. A zone listed twice is refused, whatever its case or key. Do not name a zone meant for mail: such a zone holds the residential addresses ordinary visitors come from.
  • SWWAF_DNSBL_RESOLVER (default unset): the resolver to ask the zones through, such as 192.0.2.53 or [2001:db8::53]:5353. An IP address with an optional port, 53 when none is given. Unset, the host's resolver is used. Several zones refuse queries that come through a public resolver.
  • SWWAF_ABUSEIPDB_KEY (default unset): the key of your AbuseIPDB account. While it is set, a client that has committed an offence is checked. A control character in it is refused.
  • SWWAF_ABUSEIPDB_MIN_SCORE (default 75): the least abuse confidence score, from 0 to 100, that is a hit.
  • SWWAF_ABUSEIPDB_DAILY_BUDGET (default 900): the most checks made in a day, in UTC, a whole number above zero. AbuseIPDB's free accounts may make 1,000.
  • SWWAF_CROWDSEC_LAPI_URL (default unset): the local API of a CrowdSec engine you run, such as http://172.17.0.1:8080. An http or https URL without a user or a fragment. Its decision list is at v1/decisions under that URL, and is fetched every minute. Set without the key, it stops the start. So does a decision list URL that is also in SWWAF_BLOCKLIST_URLS, or that is SWWAF_ASN_LIMIT_PERCENT_URL.
  • SWWAF_CROWDSEC_LAPI_KEY (default unset): the key the engine gave for smallwebwaf, as cscli bouncers add smallwebwaf prints it. Set without the URL, it stops the start.
  • SWWAF_REPUTATION_ACTION (default limit:25): what is done with a client on a DNS blocklist, or one whose AbuseIPDB score is a hit. deny, limit:<percent> or log, as for SWWAF_BLOCKLIST_ACTION. Such a verdict is less certain than the Spamhaus DROP list, so by default the client gets a quarter of every limit.
  • SWWAF_REPUTATION_CACHE_TTL (default 24h): how long a zone's verdict or AbuseIPDB's score is used. A duration; it cannot be off.
  • SWWAF_REPUTATION_TIMEOUT (default 2s): how long a query to a zone or a check with AbuseIPDB may take. A duration; it cannot be off.

See reputation.md for how each source is asked and what it does with the answer.

Attack detection

  • SWWAF_RULES_DIR (default /etc/smallwebwaf/rules.d): the directory of the rule files (see rules.md). A directory that cannot be read stops the start. An empty one is no error.
  • SWWAF_RULES_ENABLED (default true): false reads no rule file.
  • SWWAF_TRAP_PATHS (default empty): paths the app never serves and only scanners ask for, such as /wp-login.php,/xmlrpc.php in front of a code forge. A request for one bans the client, as a ban rule does. Each starts with / and holds no ?. A trap path is matched whole, without the query, against the path as sent and once percent-decoded. /wp-login.php matches /wp-login.php?x=1 and /%77p-login.php. It does not match /wp-login.php/ or /blog/wp-login.php.
  • SWWAF_WAF_MODE (default block): what the Core Rule Set does with a request it takes for an attack. block refuses it with 403. detect lets it through, logged and alerted. off inspects no request.
  • SWWAF_WAF_PARANOIA_LEVEL (default 1): the Core Rule Set's paranoia level, from 1 to 4. Each level up runs more rules. Those find more attacks, and take more ordinary requests for attacks.
  • SWWAF_WAF_ANOMALY_THRESHOLD (default 5): the score at which a request is a match. Each rule that matches adds to the request's score, and a rule rated critical adds 5. So by default one such rule is enough. A whole number above zero, or off. With off no request is a match, and scores are still logged.
  • SWWAF_WAF_DISABLED_RULES (default 920340,920420,920440,920640,930130,930140): the ids of Core Rule Set rules to switch off. The log line names the rules a request matched, so a rule that refuses your app's ordinary requests can be found there. "Attack detection" in SPEC.md says what the six default ids refuse and why they are off. A list you give replaces the default, so name these six ids in it to keep them off. An id must be a whole number above zero. An id from 900000 to 900999 is refused: those are the rules that set the Core Rule Set up, and smallwebwaf's own.
  • SWWAF_WAF_EXEMPT_PATHS (default empty): path prefixes the Core Rule Set does not inspect, matched as SWWAF_RATE_LIMIT_EXEMPT_PATHS is.
  • SWWAF_WAF_BODY_LIMIT (default off): off reads no request body. A size, at most 1G, switches body inspection on. The Core Rule Set then reads a form data or multipart body up to that size, and the rest streams on to the app. It reads a JSON or XML body no larger than that size, since those cannot be read in part. Any other body, such as a git push, reaches the app uninspected. See core-rule-set.md.
  • SWWAF_ERROR_BURST_THRESHOLD (default 30): the most refusals of one client in a minute for a rule match, a trap path, a Core Rule Set match or a failed token check. One more bans the client, as a broken rate limit does. A count or off. The app's own 401 and 404 answers are not counted.

How long bans last and what they cover

  • SWWAF_BAN_RESPONSE (default 403): how a refused client is answered: 403, 429, or close to close the connection without an answer. A block rule and the Core Rule Set always answer 403.
  • SWWAF_LIMIT_BAN_DURATION (default 1h): the ban for a first broken rate limit, byte limit or error burst. A duration; it cannot be off.
  • SWWAF_LIMIT_BAN_REPEAT_WINDOW (default 24h): a client that breaks a limit again within this time after its ban ended is banned three times as long. A duration; it cannot be off.
  • SWWAF_MAX_BAN_DURATION (default 7d): a ban for a broken limit that would be longer is permanent instead. A duration; it cannot be off.
  • SWWAF_ATTACK_BAN_DURATION (default 7d): the ban for a first clear sign of attack. A duration; it cannot be off.
  • SWWAF_MAX_BANS (default 5000): the most bans smallwebwaf made that are kept, whether past, active or permanent. A whole number above zero. Bans whose cause is admin are kept besides and never dropped (see bans.md).
  • SWWAF_BAN_SCOPE_V4_PREFIX (default 32): the length of the netblock around an IPv4 client that a ban covers, from 0 to 32. 24 bans the surrounding /24. An IPv6 ban covers the client's group.

Where and how often the state files are written

  • SWWAF_STATE_DIR (default /var/lib/smallwebwaf): the directory of the state files, an absolute path. A directory that does not exist or cannot be written stops the start.
  • SWWAF_STATE_WRITE_DELAY (default 10s): how long bans.json waits to be written after a ban is made, lifted or made permanent. Every change in between goes into the same write. A duration; it cannot be off.
  • SWWAF_STATE_COUNTER_INTERVAL (default 15m): how often every state file is written. A duration; it cannot be off.

Logging

  • SWWAF_LOG_REQUEST_HEADERS (default accept,accept-language,accept-encoding,content-type,origin,range): the request headers to record in the log line, in either case. Authorization, Cookie and Set-Cookie are never logged, even if listed. Host and Transfer-Encoding are refused: Go's HTTP server takes them out of the request. The host is the field host.
  • SWWAF_LOG_LEVEL (default info): the least severe of smallwebwaf's own messages that are written: debug, info, warn or error. No message is at debug. It holds back no request log line. warn and error hold back the line of settings at start.
  • SWWAF_LOG_REMOTE_URL (default unset): a syslog server every line on stdout is also sent to, such as syslog+tls://logs.example:6514. The forms are syslog+udp://, syslog+tcp:// and syslog+tls://, each with a host and a port and nothing more.
  • SWWAF_LOG_REMOTE_TLS_CA_FILE (default unset): a file of PEM certificates that a syslog+tls server's certificate must chain to, instead of the host's own. A file that cannot be read, or holds no certificate, stops the start.
  • SWWAF_LOG_REMOTE_BUFFER (default 10000): the most lines held while they wait to be sent, a whole number above zero.
  • SWWAF_LOG_REMOTE_FACILITY (default local0): the syslog facility: kern, user, mail, daemon, auth, syslog, lpr, news, uucp, cron, authpriv, ftp, or local0 to local7.
  • SWWAF_LOG_REMOTE_APP_NAME (default: SWWAF_INSTANCE_NAME): the app name in each syslog record, 1 to 48 printable ASCII characters without a space.

Tokens and metrics

  • SWWAF_ADMIN_TOKEN (default unset): the token for the ban endpoints and /_smallwebwaf/clients/<ip>. A long random value of at least 32 characters. Unset, those endpoints are off. Give it as a file to keep it from the app (see "Settings given as files" below).
  • SWWAF_METRICS_TOKEN (default unset): the token a scraper sends for /_smallwebwaf/metrics, at least 32 characters. Unset, the metrics are off. It is separate from the admin token, so a scraper cannot manage bans.
  • SWWAF_METRICS_TOP_N (default 50): how many AS numbers and how many countries get series of their own in the metrics. The rest count as other. A whole number above zero.

Alert destinations

  • SWWAF_ALERT_WEBHOOK_URL (default unset): the webhook that receives each alert as a JSON object. An http or https URL without a user or a fragment. The settings logged at start show its path and query as ********, since many webhooks carry their secret there.
  • SWWAF_ALERT_WEBHOOK_HEADERS (default empty): headers sent with each post, such as Authorization:Bearer 0123456789abcdef. A list of name, : and value. A value cannot hold a comma.
  • SWWAF_ALERT_SLACK_WEBHOOK_URL (default unset): the Slack incoming webhook that receives each alert as a message. It is checked and masked as the webhook URL is.
  • SWWAF_ALERT_NTFY_URL (default unset): the ntfy topic that receives each alert, as the topic's full URL, such as https://ntfy.sh/my-alerts. It is checked and masked as the webhook URL is.
  • SWWAF_ALERT_NTFY_TOKEN (default unset): an ntfy access token, sent as Authorization: Bearer <token>. A control character in it is refused.
  • SWWAF_ALERT_EVENTS (default ban,permanent_ban,waf_block,anomaly,reputation_hit,source_failure,file_error): the events alerts are sent for. 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_MAX_PER_HOUR (default 60): the most alerts sent in an hour. The rest are rolled into one summary. A whole number above zero, or off.

See alerts.md for the events and what each destination gets.

Anomaly thresholds

These only alert. They refuse and ban nothing. Each scope has four thresholds: the most requests and the most bytes counted in a minute and in an hour. Requests are counts, bytes are sizes. Each is off by default. What is unusual depends on each service's normal traffic, which the metrics show. The four thresholds per client are:

  • SWWAF_ANOMALY_CLIENT_REQUESTS_PER_MINUTE;
  • SWWAF_ANOMALY_CLIENT_REQUESTS_PER_HOUR;
  • SWWAF_ANOMALY_CLIENT_BYTES_PER_MINUTE;
  • SWWAF_ANOMALY_CLIENT_BYTES_PER_HOUR.

The other scopes' names have another prefix before the same four endings, such as REQUESTS_PER_MINUTE:

  • SWWAF_ANOMALY_NET_...: per netblock around a client. Two settings give the netblock's length:
    • SWWAF_ANOMALY_NET_V4_PREFIX (default 24, from 0 to 32) for an IPv4 client;
    • SWWAF_ANOMALY_NET_V6_PREFIX (default 48, from 0 to 128) for an IPv6 client.
  • SWWAF_ANOMALY_ASN_...: per AS number. Needs lookups on.
  • SWWAF_ANOMALY_TOTAL_...: the whole service.
  • SWWAF_WATCH_...: each netblock in SWWAF_WATCH_NETS, as a whole. SWWAF_WATCH_NETS (default empty) is a list of name, = and netblock, such as office=203.0.113.0/24,scraper-x=198.51.100.0/22. An item without = or a name, or with a bad netblock, is refused. So is a name used twice.

Every request but the health check is counted, whatever was done with it, clients in SWWAF_ALLOW_NETS included. At most 20,000 counters are kept. alerts.json keeps them across a restart.

Settings given as files

Any setting may be given as a file holding its value. Add _FILE to the setting's name: SWWAF_METRICS_TOKEN_FILE names the file that holds SWWAF_METRICS_TOKEN. The file's contents are the value, less one newline at the end. They are checked as the value itself would be. Setting both forms stops the start, and so does naming a file that cannot be read. SWWAF_LOG_REMOTE_TLS_CA_FILE already holds a file name, so it has no _FILE form.

A token given as a file is out of the app's reach only while the smallwebwaf user alone can read the file. Make it on the host, owned by uid 65532 with mode 0400:

mkdir -p /srv/app/tokens
openssl rand -hex 32 > /srv/app/tokens/metrics
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:

docker run \
    -v /srv/app/tokens:/etc/smallwebwaf/tokens:ro \
    -e SWWAF_METRICS_TOKEN_FILE=/etc/smallwebwaf/tokens/metrics \
    ...

Fixed limits

A few limits are fixed rather than settings:

  • at most 100,000 GeoJS answers are kept, for 7 days each;
  • at most 100,000 DNS blocklist verdicts and 100,000 AbuseIPDB scores are kept;
  • at most 1,000 DNS blocklist queries are under way at once;
  • the CrowdSec decision list is fetched again a minute after the last fetch.