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
6.4 KiB
The request log's fields
smallwebwaf writes one JSON object per line on stdout for every request. The
requirements are under "Request log" in SPEC.md; this file lists
the fields. A field that does not apply to a request is left out.
Example
{"type":"request","time":"2026-10-03T12:00:00.123Z","instance":"fsn1app1/gitea","client_ip":"203.0.113.9","method":"GET","scheme":"https","host":"app.example","path":"/","query":"","protocol":"HTTP/1.1","status":200,"request_bytes":0,"response_bytes":5120,"referer":"","user_agent":"curl/8.9.1","request_id":"7Q2NHZ4KJ3VXW5YB6R3MEFTD2A","peer_ip":"172.18.0.2","forwarded_for":"203.0.113.9","client_group":"203.0.113.9/32","asn":"AS64496","as_name":"Example Net","country":"DE","request_headers":{"accept":"*/*"},"response_content_type":"text/html; charset=utf-8","upstream_status":200,"action":"forward","counts":{"minute":1,"hour":12,"day":40,"minute_bytes":5120,"hour_bytes":61440,"day_bytes":204800},"duration_total":3.217,"duration_checks":0.041,"duration_upstream_connect":0.052,"duration_upstream_first_byte":2.874,"duration_upstream_total":3.104}
Standard web log fields
time: when the request arrived, RFC 3339 with milliseconds, in UTC;instance:SWWAF_INSTANCE_NAME;client_ip,method,scheme,host,path,query,protocol;status: what the client was sent,0if nothing;request_bytesandresponse_bytes: body bytes each way;refereranduser_agent.
Request detail
request_id: theX-Request-IDa trusted proxy sent, or a new random one of 26 letters and digits. It goes to the app inX-Request-ID.peer_ip: the TCP peer, normally traefik.forwarded_for: theX-Forwarded-Forheader as received.client_group: the client as rate limiting counts it, an IPv4 address as a /32 or an IPv6 group.asn,as_nameandcountry: the client's, as looked up. Each is empty when:- lookups are off;
- the client is not looked up;
- the source cannot place the client;
- GeoJS had not answered by the time the request went on.
content_typeandcontent_length: the request's own.request_headers: the headers listed inSWWAF_LOG_REQUEST_HEADERS, by name in lower case.has_authorizationandhas_cookie: true when the request had those headers. Their values are never logged.websocket: true when the app switched the connection to another protocol.
Response detail
upstream_status: what the app answered. It differs fromstatuswhensmallwebwafanswered itself.response_content_type,cache_controlandlocation: the answer's headers.aborted: true when the client went away early.
Decision
action: what was done:forward: passed to the app;denied: refused forSWWAF_DENY_NETS, or for a reputation source with the actiondeny;banned: refused under a ban, or for abanrule, a trap path or a CrowdSec decision, each of which bans the client;country_denied,rate_limited,rule_blockedandwaf_blocked: refused by that check;too_largeandtimed_out: a size or time limit was passed;upstream_error: the app could not be reached, or its answer broke off;admin: answered at one ofsmallwebwaf's own endpoints.
would_action: inobservemode, whatenforcemode would have done with a request it would have refused.actionthen names what was done, normallyforward.limit_percentandlimit_percent_setting: the client's percentage of the rate limits and the setting that lowered it, when one did.bytes_percentandbytes_percent_settingdo the same for the byte limits.counts: the client's requests in the minute, hour and day as rate limiting counts them, this request included, asminute,hourandday; its bytes asminute_bytes,hour_bytesandday_bytes. A count can have a fraction, since each window is counted in two buckets with the earlier one weighted.countsis left out when rate limiting did not count the request.limit_hit: the limit broken:minute,hourordayfor a rate limit;minute_bytes,hour_bytesorday_bytesfor a byte limit; orerror_burst. A request whose bytes broke a limit was passed on whole, so itsactionstaysforward.rule_ids: the rule file rules that matched, in order, up to the one that refused.waf_scoreandwaf_rule_ids: the Core Rule Set's score and the rules that matched, for a request it inspected.reputation: the sources the client is on, in this order:- blocklist URLs;
- the CrowdSec decision list's URL;
- DNS blocklist zones;
abuseipdb.
offence: the kind of offence recorded, when one was.ban_expires: when the client's ban ends, for a request that made one or was refused under one: a time, orpermanent.
Timings
All in milliseconds:
duration_total: from the request's headers being read to its log line;duration_checks: to the end of the checks, before forwarding;duration_waf: the part of that the Core Rule Set took;duration_upstream_connect: from the hand-over to the app until there was a connection;duration_upstream_first_byte: until the first byte of the answer;duration_upstream_total: until the end of the answer.
Every line
Every line has type, the standard web log fields, request_id, peer_ip,
client_group, asn, as_name, country, action and duration_total. The
process's own messages share the stream with "type":"process"; request lines
carry "type":"request".
Sending the log to a syslog server
While SWWAF_LOG_REMOTE_URL is set, every line on stdout is also sent to that
server, as the message of an RFC 5424 syslog record. Over UDP each record is one
datagram. Over TCP and TLS each record follows its length in bytes and a space.
stdout is unchanged. The record carries:
- the facility from
SWWAF_LOG_REMOTE_FACILITY; - the severity informational;
- the time;
- the host's name;
- the app name from
SWWAF_LOG_REMOTE_APP_NAME.
Lines wait in a buffer of SWWAF_LOG_REMOTE_BUFFER lines, so a server that is
slow or down never holds up a request. When the buffer is full, the oldest line
is dropped. A failed connection is tried again a second later, and twice as long
after each further failure, up to a minute. A line too long for one UDP datagram
is dropped alone. As smallwebwaf stops, it spends at most two seconds sending
the lines still waiting, and drops the rest. stdout has carried them.