How a request flows through fastapi-guard

The single mental model every config knob hangs off: the exact order checks run, where a request can be short-circuited, and what emits telemetry.

5 min read · verified against [email protected]

Every configuration knob in fastapi-guard maps to a specific point in a single ordered pipeline. Understand the pipeline once and every config decision becomes obvious.

The pipeline in one diagram

A request passes through the following stages in order:

  1. HTTPS enforce - if enforce_https=True, non-HTTPS requests are redirected before anything else runs. Governed by Passive mode vs fail-secure.
  2. Trusted-proxy / IP resolve - client IP is resolved once from X-Forwarded-For using trusted_proxies and trusted_proxy_depth. Every downstream stage uses this resolved IP. Governed by Trusted proxies & client IP detection.
  3. IP / ban check - the resolved IP is checked against the ban list (in-memory cache first, Redis on miss). Governed by Rate limiting & auto-ban.
  4. Geo / cloud block - blocked countries and cloud providers are evaluated against the resolved IP. Governed by Geo & cloud blocking.
  5. Rate limit - sliding-window counter is evaluated per IP (and per route where decorators apply). Governed by Rate limiting & auto-ban.
  6. Detection - SQLi, command injection, SSRF, file inclusion, and behavioral rules are evaluated. Governed by Honeypots & threat-based banning, Behavioral detection, and Detection exclusions.
  7. Ban application - if a detection threshold is crossed, the ban is written to Redis and the in-memory cache. Governed by Honeypots & threat-based banning.
  8. Handler - the FastAPI route handler runs for requests that reached this stage.
  9. Security headers - response headers (CSP, HSTS, X-Frame-Options, etc.) are applied on every outbound response. Governed by Security headers & CORS.
  10. Response - the response is returned to the client.

Stage order and what each can do

StageCan short-circuit?Emits event?Configured by
HTTPS enforceYes - 301 redirectNo02-passive-mode-fail-secure
Trusted-proxy / IP resolveNoNo01-trusted-proxies
IP / ban checkYes - 403 blockNo03-rate-limiting-auto-ban
Geo / cloud blockYes - 403 blockNo07-geo-cloud-blocking
Rate limitYes - 429 rejectYes03-rate-limiting-auto-ban
DetectionYes - block (when not passive)Yes04-honeypots-threat-banning, 06-behavioral-detection, 08-detection-exclusions
Ban applicationNoNo04-honeypots-threat-banning
HandlerNo (handler errors are separate)Noyour app
Security headersNoNo15-security-headers-cors

Where a request gets short-circuited

Four early-exit points exist. A request that exits early never reaches later stages.

HTTPS enforce (stage 1): when enforce_https=True and the request arrives on plain HTTP, fastapi-guard returns a 301 redirect to the HTTPS equivalent, not a 403. Browsers cache the redirect; API clients retry on the correct scheme. Confirmed in Passive mode vs fail-secure.

Ban check (stage 3): a banned IP is rejected with 403. The decision is immediate and comes from the in-memory hot cache if the IP was recently banned; otherwise from Redis. No further stages run.

Rate limit exceeded (stage 5): when the sliding-window counter exceeds the limit, the request is rejected with 429. The IP's strike counter is incremented (shared with detection strikes toward auto_ban_threshold).

Detection block (stage 6): when a detection fires and passive_mode=False, the request is blocked. When passive_mode=True, the detection is computed and emitted to the agent, but the request is not blocked, it continues to the handler.

What emits telemetry, and when

Telemetry is emitted by the detection stage and the rate-limit stage. The key behavior: detections emit even in passive mode. When passive_mode=True, the request is not blocked but the event still flows to the agent buffer and appears in the dashboard. This is the mechanism that makes passive mode useful for pre-production validation. You see the signal without disrupting traffic.

The agent maintains two independent buffers: one for events (detections, bans, rate-limit hits) and one for metrics (aggregated counters and timings). Both flush on a size-or-time trigger. The split means disabling metrics doesn't affect event delivery and vice versa. See Agent buffering & backpressure for buffer sizing and flush behavior.

passive_mode and fail_secure overlaid on the pipeline

These two settings operate at different points and on different failure modes. They compose independently.

passive_mode affects the detection stage: it controls whether a detection result in blocking the request or only emitting an event. It has no effect on ban checks, rate limits, or geo blocks, those block regardless.

fail_secure affects what happens when any check in the pipeline raises an exception - Redis unreachable, detector timeout, agent unreachable. With fail_secure=True (the default since 7.0.0), a pipeline exception returns HTTP 500. With fail_secure=False, the failing check is skipped and the request proceeds as if the check returned clean. One carve-out since guard-core 3.5.0: when the exception is specifically a Redis error and redis_fail_open=True, the failing check is skipped regardless of fail_secure - see Redis backend & failure modes.

The four combinations produce four distinct behaviors: see the matrix in Passive mode vs fail-secure for the full breakdown and the four-stage rollout playbook.

Why ordering matters for your config

The pipeline runs in sequence and each early exit prevents later stages from seeing the request at all.

Worked example - detection exclusions and banned IPs. Detection exclusions (configured via excluded_detection_headers, excluded_detection_params, excluded_detection_body_fields) only take effect if the request reaches the detection stage (stage 6). A request from a banned IP exits at stage 3, the detection stage never runs, exclusions are irrelevant, and no detection event is emitted. This means:

  • Tuning exclusions to reduce false positives has no effect on IPs that are already banned.
  • A banned IP generates no detection telemetry. Gaps in the events stream from a known-bad IP are expected, not a bug.
  • Ban duration (not exclusion tuning) is the right lever for already-banned IPs.