waf: decide every visitor request within rproxy's timeout (closes #394)
check / check (push) Failing after 2m17s

The Core Rule Set is the one check whose time a visitor can stretch.
At paranoia level 4 it took over 2 s on a request with an 8 KiB
request line, three 8 KiB Cookie lines and a 64 KiB form body.
SWW_WAF_CORE_RULE_SET_BODY_INSPECTION_SIZE now takes at most 8K. The
Core Rule Set also reads no JSON body over 1 KiB, since Coraza turns
JSON into up to 8 times its size in names and values. That brings
such a request under 1 s.

A test per kind of input times the Core Rule Set on the worst request
within the limits, at the slowest settings. SPEC.md lists the limits
that bound it.

Model: opus-5-5
This commit was merged in pull request #395.
This commit is contained in:
clawbot committed 2026-11-15 20:11:42 +01:00
1 parent 23788920f7
commit 0a2b2d809f
10 files changed
+488 -85

No files matched your search

+16 -3
View File
@@ -481,12 +481,21 @@ func (c *CoreRuleSet) Inspect(
return Result{RuleIDs: ids, Score: score(tx)}, read, err
}
// mostJSONRead is the largest JSON body the Core Rule Set reads, even while
// SWW_WAF_CORE_RULE_SET_BODY_INSPECTION_SIZE is larger. Coraza names each
// value in a JSON body by the whole path to it, so what it gives the Core
// Rule Set to read can outgrow the body: up to 8 times its size, and up to
// 4 KiB for a small body. 1 KiB of JSON thus gives at most 8 KiB, no more
// than the largest body of another kind.
const mostJSONRead = 1 << 10
// readBody reads body, the body of the request in tx, which has run on
// the request's headers, while SWW_WAF_CORE_RULE_SET_BODY_INSPECTION_SIZE is
// set and the body is of a kind the Core Rule Set reads: form data and
// multipart, of which it reads the first c.bodyInspectionSize bytes, and
// JSON and XML, which it reads only when they are no longer than that,
// since they cannot be read in part.
// since they cannot be read in part. The Core Rule Set reads no JSON body
// longer than mostJSONRead either.
// readBody reads one byte past c.bodyInspectionSize, to tell which they
// are, gives the Core Rule Set what it reads, and returns what it read and
// the error that ended the reading early, if one did.
@@ -499,6 +508,8 @@ func (c *CoreRuleSet) readBody(
}
inPart := false
// largest is the largest JSON or XML body the Core Rule Set reads.
largest := c.bodyInspectionSize
// How the body is read is a variable of the transaction, which only
// Coraza's interface for plugins reads.
state := tx.(plugintypes.TransactionState) //nolint:forcetypeassert // every one is
@@ -506,14 +517,16 @@ func (c *CoreRuleSet) readBody(
switch state.Variables().RequestBodyProcessor().Get() {
case "URLENCODED", "MULTIPART":
inPart = true
case "JSON", "XML":
case "JSON":
largest = min(largest, mostJSONRead)
case "XML":
default:
return nil, nil
}
read, err := io.ReadAll(io.LimitReader(body, c.bodyInspectionSize+1))
if inPart || (err == nil && int64(len(read)) <= c.bodyInspectionSize) {
if inPart || (err == nil && int64(len(read)) <= largest) {
// Coraza holds what it reads of the body in memory, up to the
// limit, so this cannot fail.
_, _, _ = tx.WriteRequestBody(read)