SPEC.md is the only place that says how smallwebwaf behaves and why; docs/ holds lists linking to it; the README is short and links to both. Passed independent review in round 8. Model: fable-5-1 (writing); opus-5-5 (review, merge)
5.4 KiB
The request log's fields
The fields of the JSON object smallwebwaf writes for each request. "Request
log" in SPEC.md says when a line is written and how the log is
sent to a syslog server. 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}
Every line
Every line has:
type:request(the process's own messages haveprocess);- the standard web log fields;
request_id,peer_ipandclient_group;asn,as_nameandcountry;actionandduration_total.
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.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 totals as rate limiting counts them, this request included. It is left out when rate limiting did not count the request. It holds:minute,hourandday: requests;minute_bytes,hour_bytesandday_bytes: bytes.
A count can have a fraction (see "Counting method" in
SPEC.md). -
limit_hit: the limit broken:minute,hourordayfor a rate limit;minute_bytes,hour_bytesorday_bytesfor a byte limit;error_burst.
A request whose bytes broke a limit was passed on whole, so its
actionstaysforward. -
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: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, 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.