Behavioral detection

Pattern-of-behavior rules - catching the slow-burn attacks that single-request detection misses.

4 min read · verified against [email protected]

Per-request detection catches obvious payloads. It doesn't catch attacks where each individual request looks fine, but the pattern across many requests doesn't.

The slow-burn attack problem

Single-request catches: SQLi (' OR 1=1 --), cmd injection (; rm -rf /), path traversal (../../etc/passwd), SSRF (http://localhost). One request, one match, fires immediately.

Single-request misses: credential stuffing (5,000 /login POSTs, all valid-looking), path scanning (200+ 404s as the attacker maps your app), slow exfil (a few GETs per minute, each individually small), reconnaissance (probing one path family at a time). No single request is suspicious. The shape of the traffic is.

Behavioral rules close that gap by counting things, not pattern-matching individual requests.

Three flavors of behavioral rule

The rule_type field picks the counting mechanism.

usage - request-pattern rules. Counts request activity per IP within a window. Useful for catching high-volume probing behavior that isn't tripping any single-request detection.

return_pattern - response-pattern rules. Counts responses matching a pattern (e.g., 404 status codes) per IP per window. Catches scanning behavior by what your app SAID, not what was sent. This is the type the example uses.

frequency - frequency-pattern rules. Heuristics on request cadence. Watches for unusually regular or unusually sparse patterns over time (e.g., precisely-timed requests suggesting automation).

How return_pattern counts

Every response your app emits passes through the agent's behavioral evaluator. Responses matching the rule's pattern field increment a per-IP counter; the counter is windowed.

  1. Your handler (or FastAPI itself) returns a status code, e.g., 404.
  2. Does the response match the rule's pattern field? For pattern="404", a 404 status matches; a 200 doesn't.
  3. If matched: counter[ip] += 1. The counter is bounded by the rule's window. Older hits age out.
  4. If counter > threshold within the window, the rule trips. The configured action fires (log / alert / throttle / ban).

A path-scanning attacker generates a stream of 404s as they probe URLs that don't exist. No single 404 is suspicious. A legitimate misspelled link looks the same. But 20 in 5 minutes from one IP is reconnaissance, and a return_pattern rule will see that signal.

correlate_with_detection - drop the bar when there's smoke

When True, the rule's threshold is halved for any IP that also has a recent threat detection on its record. The reasoning: an IP behaving suspiciously AND tripping detection is doubly suspicious. Be quicker to act.

Clean IPIP with active detection
ThresholdConfigured valueHalved
Example20 404s needed10 404s trigger

In practice: leave correlate_with_detection=True on most behavioral rules. Legitimate users don't carry detections; suspicious IPs often do.

Four actions

When the rule trips, the action field decides what happens. Pick by confidence: how sure are you that a trip means an attacker, not a noisy real user?

ActionWhenImpact
logNew rules in validation phaseNo production impact, dashboard visibility
alertSignals you want to know about but not act on automaticallyNotification fires (if alerting is wired)
throttleSuspect malicious behavior but worry about FPsSoft cap on the IP's traffic
banHigh-confidence rulesIP banned for auto_ban_duration - identical to threat-based ban

The example uses log - appropriate for a rule still being validated against real traffic.

Wiring

global_behavior_rules=[
    {
        "rule_type": "return_pattern",
        "threshold": 20,
        "window": 300,
        "pattern": "404",
        "action": "log",
        "correlate_with_detection": True,
    }
]

Reads as: "If an IP gets more than 20 404s in 5 minutes, log it. If that IP also has a recent threat detection, drop the threshold to 10."

FieldPurpose
rule_typeWhich counting mechanism - usage, return_pattern, frequency
thresholdCount of matches that trips the rule. Halved by correlation.
windowSeconds. 300 = 5-minute rolling window.
patternWhat return_pattern counts. "404" matches the status code.
actionWhat happens on trip
correlate_with_detectionHalve threshold when the IP has an active detection

Troubleshooting

SymptomLikely causeFix
Rule is defined but never tripsWindow too short for your traffic volume. Counter never accumulates before old hits age out. Or pattern doesn't match what your app returns.Widen the window (try 600 or 900 first). Verify the pattern field.
Rule trips on legitimate usersReal users hit 404s too (broken bookmarks, stale links). Threshold too tight. Or correlate_with_detection is False.Raise threshold AND set correlate_with_detection=True. Legit users won't have active detections; effective threshold stays higher.
Want to tune the rule from the dashboardBehavioral rules are configured in SecurityConfig at boot - NOT in the dynamic-rules schema.Edit app.py, deploy. Start in log mode for low-risk iteration.

Behavioral rules count per IP. When an attacker spreads the load across many residential-proxy IPs so that no per-IP counter ever trips, that is a coordinated distributed attack, not a behavioral one. See Coordinated attack detection for the SaaS-only cross-IP correlation that catches it.