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):
| Mode | Behavior |
|---|---|
shadow | The pack's rules become Log: matches are recorded (a weight-0 RuleHit signal) but nothing blocks. |
enforce | The rules become the pack's real action (Block, Challenge, RateLimit — see the packs table). |
off | Pack 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, alongsideenabled = 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
PostureAdvisorysignal 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'scap_shadowforcesAllowwith reasonshadow 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:
| Level | Default verdict |
|---|---|
info | allow |
low | allow |
medium | rate_limit |
high | challenge |
critical | block |
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 startupBecause 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
- Start everything in
shadow— packs, datasets (action = "log") and uploads (mode = "shadow"). - Observe: daemon logs, the dashboard (
/api/events), metrics (sentry_signal_kinds_total{kind}per signal kind) and each match event carrying the rule id. - Simulate before promoting:
sentry rules test --path /admin --ip 1.2.3.4 --ua "sqlmap/1.0". - Promote one pack at a time by editing
modeinsentry.tomland restarting the daemon.sentry rules packslists the state, but mode changes go through config (there is norules packs enable --mode). sensitive_pathsships inenforce— it's the only one. Harden the rest after a few days with zero false positives.- 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)