All checks were successful
check / check (push) Successful in 3m16s
The access log wrote one INFO line per request carrying r.URL.String(). Registered with Use, it runs ahead of the route limiter, so a client flooding the unauthenticated receiver with invented paths wrote attacker-chosen text of attacker-chosen length into the operator's log, one line per request. 3xx and 4xx responses now log the chi route pattern in place of the concrete URL, and the fixed literal "(unmatched)" when routing matched nothing at all. One line per request is retained, so real traffic stays observable and rate accounting still works, but the line's content is now bounded by the service's own route table. The pattern is only populated after routing, so it is read in the deferred part of the handler rather than before next.ServeHTTP. The route pattern alone does not close the hole, because it leaves two other ways for a request to choose the size of the line it writes. The query string is one: /.well-known/healthcheck and /s/* answer 200 to anyone with no rate limiter in front of them, and /pages/login behind only the login limiter, so appending 8 KB after the '?' bought the same amplification as an invented 404 path. The branches that keep the concrete URL now log the path only, with the query replaced by the fixed marker "?(redacted)". Nothing debuggable is lost: `page`, on the authenticated pagination links, is the only query parameter this service reads. The headers are the other: useragent and referer are logged on every line, including the correctly redacted ones, so an 8 KB User-Agent plus an 8 KB Referer produced a 24 KB line whose url field read "(unmatched)". Each field a client supplies is now truncated rather than dropped -- a truncated User-Agent is still worth reading -- to 512 bytes for url, useragent and referer, 128 for request_id (chi passes an inbound X-Request-Id header straight through), and 32 for method, which Go accepts as any token up to the header size limit. Truncation also drops invalid UTF-8, which an encoder would otherwise expand six-fold past the budget. Each budget is spent in encoded bytes rather than in the bytes the client sent, because the line an operator stores is the encoded one. slog's JSON handler escapes a quotation mark, a backslash and a tab to two bytes each and a non-printable rune to six; its text handler spells any non-printable rune the same six-byte way; and Go's header parser accepts all of them in a header value. Counted raw, a 512-byte budget therefore bought a 1,024-byte field. Plain ASCII still encodes one byte for one, so a real browser's User-Agent fits whole, while a value built out of escapes keeps a proportionally shorter prefix. A complete line is now at most 2,560 bytes: 3*(512+11) for url, useragent and referer, 128+11 for request_id, 32+11 for method and a 336-byte fixed portion come to 2,087, stated with headroom. The tests assert it against 8 KB in the path, in the query and in each of the three headers, including values built from the characters the handler escapes, and against a 5xx whose concrete url is at its own budget on the same line. The README states it so an operator can size log storage against it.
19 KiB
19 KiB