SentrySentry
Rules Engine

Shadow vs Enforce

Mode semantics per pack, uploads/posture modes, verdict policy and safe rollout

Shadow vs Enforce

"Shadow" and "enforce" appear in several places across Sentry with the same philosophy: detect first, act later. This page covers the modes of the packs, the uploads shadow mode, the posture invariant, the verdict policy ([[policy.override]]) and repeat-offender escalation.

Pack modes

Controlled by mode = "shadow" | "enforce" | "off" on each [[rules.pack]] (PackMode):

ModeBehavior
shadowThe pack's rules become Log: matches are recorded (a weight-0 RuleHit signal) but nothing blocks.
enforceThe rules become the pack's real action (Block, Challenge, RateLimit — see the packs table).
offPack disabled — its rules never join the ruleset. Unknown mode strings also become off.

Modes are read at startup (packs are compiled in build_default_ruleset_with). Changing a mode requires a daemon restart — hot-reload via sentry_rules_changed only reloads Postgres rules. Exception: crawlers_good produces Allow in any active mode; the mode only turns the pack on.

Shadow beyond packs

  • [uploads] mode = "shadow" (the F10 default, alongside enabled = false) — upload-origin signals (UploadPolyglot, UploadExecutable, UploadTypeMismatch, UploadDisallowed, UploadOversize, UploadFlood) are emitted with weight 0: detected, logged and metricized without ever raising the verdict (UploadMode). mode = "enforce" restores full weights and containment happens before the upstream sees the request.
  • Posture (F11) has no enforce mode — the PostureAdvisory signal has its weight locked at 0 in both pipeline weight functions (POSTURE_ADVISORY_WEIGHT), and not even a [scorer.weights] override can change that. Findings describe the protected site, never the visitor — advisories are shadow by construction.
  • [real_ip] shadow — IPs on the shadow list are fully analyzed but never banned: the pipeline's cap_shadow forces Allow with reason shadow ip (no ban), including on rule short-circuits.

Verdict policy ([[policy.override]])

Detection says what was found; policy says what to do about it. VerdictPolicy maps each risk level to a verdict:

LevelDefault verdict
infoallow
lowallow
mediumrate_limit
highchallenge
criticalblock

Every level can be remapped, and ordered [[policy.override]] entries (DSL expression + verdict) force a verdict for specific traffic — the first match wins and the rest are skipped. Valid verdicts: allow, rate_limit, challenge, block, quarantine. A fired override shows up as the decision's override_reason on the event.

[policy]
medium = "rate_limit"

[[policy.override]]
match = 'path=/admin/*'
verdict = "block"

[[policy.override]]
match = 'reputation = "authorized"'
verdict = "allow"

Invalid expressions here fail config loading (unlike [[rules.custom]], where only the offending rule is dropped).

Repeat-offender escalation

After the decision (rule or scorer), escalation (OffenderTracker) kicks in: every event with a verdict other than Allow records one strike per IP, and strikes crossing the thresholds only ever raise the verdict (RateLimit → Challenge → Block; a Block is never lowered, and Allow records no strike and is never escalated — escalate_at).

[escalation]
enabled = true
window_secs = 604800   # 7 days — outlives the 24h TTL of edge rules
challenge_at = 3
block_at = 5
persist = true         # mirrors strikes to ip_state (Postgres), pre-warms at startup

Because strikes persist, a returning offender that comes back after the edge TTL expires is re-blocked on its first violating event. Escalation also runs over rule short-circuits.

Safe rollout

  1. Start everything in shadow — packs, datasets (action = "log") and uploads (mode = "shadow").
  2. Observe: daemon logs, the dashboard (/api/events), metrics (sentry_signal_kinds_total{kind} per signal kind) and each match event carrying the rule id.
  3. Simulate before promoting: sentry rules test --path /admin --ip 1.2.3.4 --ua "sqlmap/1.0".
  4. Promote one pack at a time by editing mode in sentry.toml and restarting the daemon. sentry rules packs lists the state, but mode changes go through config (there is no rules packs enable --mode).
  5. sensitive_paths ships in enforce — it's the only one. Harden the rest after a few days with zero false positives.
  6. For feeds/datasets, flip action = "log" to "block"/"challenge" — the synthetic rules (feed:<name>/dataset:<name>) hot-reload.

CLI

sentry rules packs                                  # pack state
sentry rules test --path /admin --method GET --ip 1.2.3.4 --ua "sqlmap/1.0"
sentry rules list                                   # active rules (DB + config)

On this page