waf: decide every visitor request within rproxy's timeout (closes #394)
check / check (push) Successful in 6m36s

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, which
brings that 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 is contained in:
sneak committed 2026-11-15 17:52:21 +00:00
1 parent 23788920f7
commit 7a627f44e1
9 files changed
+411 -63

No files matched your search

+8 -14
View File
@@ -228,12 +228,9 @@ func newReputation(
// without the rules SWW_WAF_CORE_RULE_SET_DISABLED_RULES switches off, reading bodies
// up to SWW_WAF_CORE_RULE_SET_BODY_INSPECTION_SIZE, with the exclusions in
// SWW_WAF_CORE_RULE_SET_EXCLUSIONS_FILE and the fixes of the apps in SWW_WAF_APPS,
// or nil while SWW_WAF_CORE_RULE_SET_MODE is off. It
// reads at most QuestionBodyMaxBytes less one byte: a question's body of
// QuestionBodyMaxBytes may be cut short, and the Core Rule Set does not read a
// JSON or XML body over its limit. An exclusions file that coreruleset.New refuses is
// an error in every mode. While the mode is off, a set file is loaded with the
// Core Rule Set only to check it.
// or nil while SWW_WAF_CORE_RULE_SET_MODE is off. An exclusions file that
// coreruleset.New refuses is an error in every mode. While the mode is off, a
// set file is loaded with the Core Rule Set only to check it.
func NewCoreRuleSet(cfg *config.WAF) (*coreruleset.CoreRuleSet, error) {
if cfg.CoreRuleSetMode == config.CoreRuleSetModeOff &&
cfg.CoreRuleSetExclusionsFile == "" {
@@ -241,14 +238,11 @@ func NewCoreRuleSet(cfg *config.WAF) (*coreruleset.CoreRuleSet, error) {
}
coreRuleSet, err := coreruleset.New(coreruleset.Params{
ParanoiaLevel: cfg.CoreRuleSetParanoiaLevel,
DisabledRules: cfg.CoreRuleSetDisabledRules,
BodyInspectionSize: min(
cfg.CoreRuleSetBodyInspectionSize,
QuestionBodyMaxBytes-1,
),
ExclusionsFile: cfg.CoreRuleSetExclusionsFile,
Apps: cfg.Apps,
ParanoiaLevel: cfg.CoreRuleSetParanoiaLevel,
DisabledRules: cfg.CoreRuleSetDisabledRules,
BodyInspectionSize: cfg.CoreRuleSetBodyInspectionSize,
ExclusionsFile: cfg.CoreRuleSetExclusionsFile,
Apps: cfg.Apps,
})
if err != nil {
return nil, fmt.Errorf(
+4 -4
View File
@@ -1787,7 +1787,7 @@ func TestCoreRuleSetReadsTheBodyAQuestionCarries(t *testing.T) {
t.Parallel()
s := startWAF(t, "", map[string]string{
coreRuleSetMode: block, coreRuleSetBodyInspectionSize: "64K",
coreRuleSetMode: block, coreRuleSetBodyInspectionSize: "8K",
})
ask := func(contentType, body string) answer {
@@ -1802,10 +1802,10 @@ func TestCoreRuleSetReadsTheBodyAQuestionCarries(t *testing.T) {
got := ask(formData, "id=1'%20OR%20'1'='1")
wantDisposition(t, got, http.StatusForbidden, waf.DispositionDeny)
// The first 64 KiB of a longer JSON body cannot be parsed, which is no
// The first 8 KiB of a longer JSON body cannot be parsed, which is no
// attack, so the Core Rule Set does not read it.
cut := `{"text":"` + strings.Repeat("x", waf.QuestionBodyMaxBytes)
got = ask("application/json", cut[:waf.QuestionBodyMaxBytes])
cut := `{"text":"` + strings.Repeat("x", 8<<10)
got = ask("application/json", cut)
wantDisposition(t, got, http.StatusOK, waf.DispositionAllow)
}