A question waits on GeoJS at most 800ms, the new default of SWWAF_LOOKUP_TIMEOUT. In enforce mode it does not wait at all when it is over the header limits, on a fetched blocklist with deny, on the CrowdSec decision list, or over a whole rate limit. Not waiting never lowers a client's limit. Just before the Core Rule Set, servewaf refuses more than 50 cookies or 100 header fields with 431. Shared default changed: SWWAF_LOOKUP_TIMEOUT also applies to smallwebwaf with no command. Judgement call: SWWAF_REPUTATION_TIMEOUT stays 2s, since no request waits on the DNS blocklists or AbuseIPDB. Model: opus-5-5
8.1 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_large: a size limit was passed, or a header limit inservewaf;timed_out: a 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.
servewaf's lines
smallwebwaf servewaf writes two lines for each question it answers, joined by
request_id. The field servewaf_line says which of the two a line is:
decision: the line it writes once it has answered the question;report: the line it writes whenrproxy's report comes, with everything known. The repetition is intended.
A request to its own endpoints writes one line, without servewaf_line. A
report writes no line but the report line of the request it names.
These questions write no line:
- a question refused with
400(see "The question" under "servewaf" inSPEC.md); - a question whose body stopped arriving, which gets no answer.
The decision line
It has the fields above, with these meanings where they differ:
client_ip:X-Smallwebwaf-Client.peer_ip:X-Smallwebwaf-Peer.host: the site the visitor asked for.protocol: the question's own,HTTP/1.1.content_length:X-Smallwebwaf-Content-Length.request_id: the question'sX-Request-ID.status: whatservewafanswered:200forallow, the status ofdeny, or0forclose, as the visitor is then sent nothing.request_bytes: how much of the question's body the Core Rule Set read.response_bytes: the body ofservewaf's answer.action:forwardforallow, andadminfor the404for a path under/_smallwebwaf/.aborted: true whenrproxywent away before the answer.duration_total: fromservewafhaving read the question's body to the line.time: whenservewafhad read the question's body.
The line leaves out upstream_status, websocket and the
duration_upstream_... fields, since servewaf passes nothing to the app.
The bytes of an allowed request are counted when its report comes. So counts
holds the client's bytes without this request's.
The report line
It has every field of the decision line, and time stays as the decision
line gives it. These fields say what the report says:
status: what the visitor got,0if nothing;request_bytesandresponse_bytes: the body bytes each way, with what a WebSocket carried each way;duration_total: from the request's headers being read byrproxyto the end of the answer;duration_upstream_total: from the hand-over to the app to the end of the answer, for an allowed request.
For an allowed request, the report's counting also changes these fields:
counts: the byte totals now include this request's;limit_hit,offenceandban_expires: set when the report's bytes broke a byte limit.