check / check (push) Waiting to run
Each check servewaf runs on a request is a type in its own file behind one interface, heuristic, and is registered in one of four ordered lists in heuristics.go. Order and behaviour are unchanged. Every refusal now logs heuristic, the name of the heuristic that made it, and heuristic_figures, the figures it acted on. A ban by refusal_limit or byte_limits after the answer logs after_answer_heuristic and after_answer_heuristic_figures instead, so a line can name both. All four are in docs/request-log.md, and SPEC.md names each heuristic at its step. Model: opus-5-5
72 lines
2.1 KiB
Go
72 lines
2.1 KiB
Go
package servewaf
|
|
|
|
// The heuristic header_limits. It reads no setting: its limits are fixed.
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"time"
|
|
|
|
"sneak.berlin/go/smallwebwaf/internal/requestlog"
|
|
)
|
|
|
|
// The header limits: the most cookies and header fields a request may have
|
|
// when the checks reach the Core Rule Set. Real clients never reach them.
|
|
const (
|
|
maxCookies = 50
|
|
maxHeaderFields = 100
|
|
)
|
|
|
|
// headerLimits refuses a request over the header limits, with 431. Its figures
|
|
// are the request's header fields and cookies, and the limit of each.
|
|
type headerLimits struct{}
|
|
|
|
func (headerLimits) name() string { return "header_limits" }
|
|
|
|
func (headerLimits) check(_ context.Context, rq *request, _ time.Time) *verdict {
|
|
fields, cookies := rq.headerCounts()
|
|
if !overHeaderLimits(fields, cookies) {
|
|
return nil
|
|
}
|
|
|
|
return &verdict{
|
|
action: requestlog.ActionTooLarge,
|
|
figures: map[string]any{
|
|
"fields": fields, "fields_limit": maxHeaderFields,
|
|
"cookies": cookies, "cookies_limit": maxCookies,
|
|
},
|
|
}
|
|
}
|
|
|
|
// overHeaderLimits reports whether a request with these counts of header fields
|
|
// and cookies, as headerCounts counts them, is over the header limits: it has
|
|
// more than maxHeaderFields fields or more than maxCookies cookies.
|
|
func overHeaderLimits(fields, cookies int) bool {
|
|
return fields > maxHeaderFields || cookies > maxCookies
|
|
}
|
|
|
|
// headerCounts returns the request's header fields, each value of a header sent
|
|
// twice being one, and its cookies, each part of a Cookie header between
|
|
// semicolons being one unless it is empty or only spaces and tabs. The Core
|
|
// Rule Set runs each of its rules on each cookie, so that thousands of small
|
|
// cookies keep it busy for over a second. It trims only spaces and control
|
|
// characters, of which a header value can hold only tabs, so a part of Unicode
|
|
// spaces is a cookie to it.
|
|
func (rq *request) headerCounts() (int, int) {
|
|
fields, cookies := 0, 0
|
|
|
|
for _, values := range rq.in.Header {
|
|
fields += len(values)
|
|
}
|
|
|
|
for _, value := range rq.in.Header.Values("Cookie") {
|
|
for part := range strings.SplitSeq(value, ";") {
|
|
if strings.Trim(part, " \t") != "" {
|
|
cookies++
|
|
}
|
|
}
|
|
}
|
|
|
|
return fields, cookies
|
|
}
|