Leave SWWAF_RATE_LIMIT_EXEMPT_PATHS out of the request rate limits (closes #77)
check / check (push) Successful in 4m48s
check / check (push) Successful in 4m48s
A request whose path, as the client sent it and before the query string, starts with one of the comma-separated prefixes in SWWAF_RATE_LIMIT_EXEMPT_PATHS is neither counted nor refused by the request rate limits; the static lists, bans and the country lists still apply. The setting is empty by default, and a prefix that does not start with / stops the start. README.md documents it. Judgement call: plain prefix on the path as sent, as the issue rules, so /assets/../login matches /assets/; traefik removes such dot segments by default, but a request that reaches smallwebwaf uncleaned is matched as sent. Model: opus-5-5
This commit is contained in:
@@ -8,6 +8,8 @@ import (
|
||||
"net/http/httputil"
|
||||
"net/netip"
|
||||
"os"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
@@ -115,13 +117,16 @@ func (h *handler) newRequest(w http.ResponseWriter, r *http.Request) *request {
|
||||
// client either refuses is not looked up, and then the country lists; a
|
||||
// request any of them refuses is not counted for the rate limits. Then
|
||||
// come the rate limits, unless the client is in
|
||||
// SWWAF_RATE_LIMIT_EXEMPT_NETS, so that every other request is counted,
|
||||
// SWWAF_RATE_LIMIT_EXEMPT_NETS or the request's path, as the client sent
|
||||
// it and the log line shows it, starts with one of
|
||||
// SWWAF_RATE_LIMIT_EXEMPT_PATHS, so that every other request is counted,
|
||||
// one refused for its size too. Every refusal but the size limit's is
|
||||
// answered with SWWAF_BAN_RESPONSE. ctx is the request's own context.
|
||||
func (rq *request) check(ctx context.Context) *refusal {
|
||||
cfg := rq.h.config
|
||||
allowed := isInside(rq.client, cfg.AllowNets)
|
||||
exempt := isInside(rq.client, cfg.RateLimitExemptNets)
|
||||
exempt := isInside(rq.client, cfg.RateLimitExemptNets) ||
|
||||
startsWithAny(rq.in.URL.EscapedPath(), cfg.RateLimitExemptPaths)
|
||||
now := rq.h.now()
|
||||
|
||||
if !allowed && isInside(rq.client, cfg.DenyNets) {
|
||||
@@ -152,6 +157,13 @@ func (rq *request) check(ctx context.Context) *refusal {
|
||||
return nil
|
||||
}
|
||||
|
||||
// startsWithAny reports whether path starts with one of prefixes.
|
||||
func startsWithAny(path string, prefixes []string) bool {
|
||||
return slices.ContainsFunc(prefixes, func(prefix string) bool {
|
||||
return strings.HasPrefix(path, prefix)
|
||||
})
|
||||
}
|
||||
|
||||
// forward passes the request to the app and the app's answer back. ctx
|
||||
// is the request's own context.
|
||||
func (rq *request) forward(ctx context.Context) {
|
||||
|
||||
Reference in New Issue
Block a user