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 report_heuristic and report_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
40 lines
1.1 KiB
Go
40 lines
1.1 KiB
Go
package servewaf
|
|
|
|
// The heuristic trap_paths. It reads SWWAF_TRAP_PATHS.
|
|
|
|
import (
|
|
"context"
|
|
"slices"
|
|
"time"
|
|
|
|
"sneak.berlin/go/smallwebwaf/internal/bans"
|
|
"sneak.berlin/go/smallwebwaf/internal/requestlog"
|
|
"sneak.berlin/go/smallwebwaf/internal/rules"
|
|
)
|
|
|
|
// trapPaths refuses a request for a path in SWWAF_TRAP_PATHS: its path as a
|
|
// path rule sees it, without the query, as sent or once percent-decoded, is one
|
|
// of them. Such a request is a clear sign of attack, as a ban rule's match is:
|
|
// it bans the client's netblock, or in observe mode raises the alert for the
|
|
// ban it would have made. The ban's notes give the path as sent, and so does
|
|
// its figure.
|
|
type trapPaths struct{}
|
|
|
|
func (trapPaths) name() string { return "trap_paths" }
|
|
|
|
func (trapPaths) check(_ context.Context, rq *request, now time.Time) *verdict {
|
|
path := rules.Path(rq.in)
|
|
if !slices.Contains(rq.h.config.TrapPaths, path) &&
|
|
!slices.Contains(rq.h.config.TrapPaths, rules.DecodeOnce(path)) {
|
|
return nil
|
|
}
|
|
|
|
rq.attack = true
|
|
rq.banForAttack(now, bans.Notes{TrapPath: path})
|
|
|
|
return &verdict{
|
|
action: requestlog.ActionBanned,
|
|
figures: map[string]any{"path": path},
|
|
}
|
|
}
|