Reference

Risk Scoring

This page describes exactly what happens to each request, for administrators tuning the module and developers reading the event log.

Inspection pipeline

Traffic Guard listens to the Laminas route event at priority 999, which fires before routing, before any controller and before any Doctrine query. Command-line requests are ignored. For every HTTP request:

  1. If Enable Traffic Guard protection is off, stop; the request is allowed.
  2. Determine the client IP from REMOTE_ADDR, or from the first entry of X-Forwarded-For when Trust X-Forwarded-For is on.
  3. Allowlist. If the IP is private or reserved (and the auto-allowlist setting is on), or matches an entry in tg_ip_allowlist, allow. Nothing is counted or logged.
  4. Active ban. If tg_bans has an unexpired, unlifted ban for the IP, log an event with category banned, score 100, signal active_ban, and return HTTP 403. This applies to admin paths too.
  5. Classify the path into admin, api, search, item or global. Admin paths are allowed here with no counting or logging.
  6. Count the request in the fixed 60-second window for its category and read back the count.
  7. Score the request (see below). A user-agent allow rule returns immediately with allow and no log entry.
  8. Decide. Score at or above the block threshold: block (HTTP 403). At or above the throttle threshold: throttle (HTTP 429 with Retry-After: 60). Otherwise allow.
  9. Auto-ban. On a block decision with auto-ban on, insert a ban for the IP unless one is already active.
  10. Log. Throttle and block decisions are always logged; allow decisions only when Log allowed requests too is on.

Any exception anywhere in this pipeline is written to the PHP error log with a [TrafficGuard] prefix and the request is allowed.

Path categories

Category Matches Rate limited
admin Paths starting with /admin No
api Paths starting with /api Yes
search /s/{site}/search and anything under it Yes
item /s/{site}/item, /s/{site}/item-set, /s/{site}/media and anything under them Yes
global Everything else (site home pages, custom pages, feeds, the install root) Yes

Each category has its own counter, so 20 searches and 20 item views in the same minute are counted separately.

Signals and weights

The score is the sum of the weights of the signals that fired, clamped to 0 to 100. If any single signal has a weight of 80 or more, the score is raised to at least 80 so that a clearly bad indicator cannot be hidden. Weights depend on the protection mode.

Signal Fires when Normal Elevated Under Attack
ua_rule_block An enabled user-agent rule with action block matches (rules are checked first, in priority order) 80 80 80
empty_ua The User-Agent header is missing or blank 25 35 50
bot_ua_pattern The user agent contains one of the built-in bot substrings (only checked if the header is not empty) 30 40 60
rate_exceeded_soft This window's count is above 80 percent of the category limit but not above the limit 20 30 40
rate_exceeded_hard This window's count is above the category limit 30 45 60
deep_pagination The query string has page greater than 50 15 20 30
excessive_search The path contains /search and the query string is longer than 200 characters 10 15 25

A matching user-agent allow rule is recorded as ua_rule_allow and ends scoring; no other signal is evaluated.

Worked examples in Normal mode (throttle 40, block 70):

  • A scraper with python-requests that has exceeded the search limit: 30 + 30 = 60, throttled.
  • The same scraper also requesting ?page=75: 60 + 15 = 75, blocked and auto-banned.
  • A browser that exceeds the item limit with a normal user agent: 30, allowed. In Elevated mode it scores 45 and is throttled.

Built-in bot user-agent substrings

Matched case-insensitively anywhere in the header:

masscan, zgrab, nikto, sqlmap, nmap,
python-requests, go-http-client, okhttp,
headless, phantomjs, selenium, puppeteer, playwright,
scrapy, wget/, curl/,
ahrefsbot, semrushbot, dotbot, mj12bot, blexbot, petalbot, bytespider

Well-known search engine crawlers such as Googlebot and Bingbot are not on the list. Add allow rules for them if you want them exempt from rate limiting; see User-Agent Rules.

Rate-limit windows

Counters are fixed windows aligned to the clock: a 60-second window runs from hh:mm:00 to hh:mm:59 UTC and the count resets at the next minute. Counts are incremented with a single atomic INSERT ... ON DUPLICATE KEY UPDATE, so concurrent requests are counted correctly. Stale counter rows older than three windows are pruned on roughly one request in a hundred, 500 rows at a time.

Responses sent to blocked clients

Decision Status Body Headers
throttle 429 Too Many Requests Content-Type: text/plain, Retry-After: 60
block 403 Forbidden Content-Type: text/plain

The response is sent directly from the route listener; no theme or error template is rendered.

Log in for Support