Upgrade guide - breaking changes by version
Every breaking change across fastapi-guard, guard-agent, and guard-core in one place, with the exact config edit each one requires.
How to use this guide
This guide consolidates every documented breaking change across fastapi-guard, guard-agent, and guard-core into one place. Each entry covers three things: what changed and why, who is affected, and the exact config edit required. Entries are grounded in the onboarding decks. Cross-links below point to the deck that covers each topic in depth.
If you are upgrading across multiple versions, work through the sections in order. A change introduced in 6.0.0 is still in effect when you upgrade to 7.0.0.
fastapi-guard 6.0.0 - CORS moved inside SecurityMiddleware
What changed. CORS handling moved from a separate external middleware into SecurityMiddleware itself. Before 6.0.0, integrating CORS required a second wiring call, SecurityMiddleware.configure_cors(app, config), in addition to app.add_middleware(SecurityMiddleware, config=config). That method was removed in 6.0.0. CORS now activates automatically based on your cors_* SecurityConfig fields whenever SecurityMiddleware is registered.
A secondary consequence: OPTIONS preflight requests now pass through the full security pipeline - IP filtering, rate limiting, and detection - before receiving a CORS response. Previously, the external middleware short-circuited preflights before any of those checks ran, which meant banned IPs and rate-limited clients could preflight freely.
Who is affected. Anyone upgrading from a version prior to 6.0.0 who had the separate configure_cors call in their application wiring.
Exact edit required.
Before (pre-6.0.0):
app.add_middleware(SecurityMiddleware, config=security_config)
SecurityMiddleware.configure_cors(app, security_config)After (6.0.0+):
app.add_middleware(SecurityMiddleware, config=security_config)Remove the configure_cors call entirely. CORS is now driven exclusively by the cors_* fields in SecurityConfig - enable_cors, cors_allow_origins, cors_allow_methods, cors_allow_headers, cors_allow_credentials. No other change to your config is required.
For the full CORS configuration reference and the credentials-plus-wildcard-origin gotcha, see Security headers & CORS.
fastapi-guard 7.0.0 - fail_secure defaults to True
What changed. The default value of fail_secure flipped from fail-open (False) to fail-secure (True). Before 7.0.0, when a check could not run - Redis down, detector timeout, agent unreachable - the pipeline exception was silently swallowed and the request was allowed through as if the check had returned clean. Starting in 7.0.0, the same condition returns HTTP 500 "Security check failed" and drops the request.
Who is affected. Apps upgrading from a version prior to 7.0.0 that relied on the prior fail-open behavior, intentionally or not. If your app was silently absorbing pipeline exceptions and passing traffic through, those requests now return 500. If your Redis or agent was intermittently unreachable and you were unaware, you will see that surface immediately after upgrading.
Exact edit required.
To preserve the old behavior explicitly:
SecurityConfig(
fail_secure=False,
)For the two-by-two matrix of fail_secure vs passive_mode and the four-stage rollout playbook, see Passive mode vs fail-secure.
fastapi-guard 7.1.0 - guard_lifespan helper
What changed. fastapi-guard 7.1.0 introduced guard_lifespan, a lifespan context manager exported from guard.lifespan. Pass it as lifespan=guard_lifespan when constructing your FastAPI app. It starts the background tasks, buffer flushing and dynamic rule polling, that the agent requires to deliver events.
from fastapi import FastAPI
from guard import SecurityMiddleware
from guard.lifespan import guard_lifespan
app = FastAPI(
title="My App",
lifespan=guard_lifespan,
)
app.add_middleware(SecurityMiddleware, config=security_config)This is not a breaking change. It is a new recommended wiring. Apps that do not wire guard_lifespan continue to work, but the agent buffer's background flush task does not start. Events accumulate in memory and are never sent until a process restart or manual flush. The Quickstart documents this as the expected wiring for any app that enables the agent.
Who is affected. Any new integration that enables enable_agent=True. Existing integrations that were already managing the flush loop manually are unaffected by the 7.1.0 addition but should migrate to guard_lifespan to reduce boilerplate.
Apps with an existing lifespan. If your app already passes its own lifespan to FastAPI(...) - for database pools, schedulers, or other startup work - do not replace it with guard_lifespan, which would drop that startup logic. guard.lifespan also exports make_lifespan(existing_lifespan=...), which composes guard-core's startup initialization (security pipeline, agent integration, OTEL providers, Redis connection, cloud-IP cache) with your existing lifespan and returns a single lifespan callable:
from guard.lifespan import make_lifespan
app = FastAPI(
title="My App",
lifespan=make_lifespan(existing_lifespan=my_lifespan),
)This also supersedes the older lazy_init=True workaround: without startup wiring, guard-core initializes on the first request; make_lifespan moves that work to ASGI startup so the Redis connection and cloud-IP cache are warm before the first request is served.
guard-agent 2.4.0 - buffer_overflow_policy
What changed. AgentConfig gained a new field: buffer_overflow_policy. This controls what happens when the in-memory event buffer fills faster than the agent can flush it, a condition that occurs during sustained traffic spikes or prolonged Core API outages.
Three values are available:
| Policy | Behavior |
|---|---|
"drop" (default) | Silently evict the oldest entry. No exception, no log, no metric. Preserves prior behavior verbatim. |
"block" | Backpressure the caller until a flush frees space. Only safe when start_auto_flush is wired or a flush callback is in place. Otherwise the caller hangs indefinitely. clear_buffer() is the manual escape hatch. |
"raise" | Raise BufferFullError to the caller. Useful in tests or strict environments where dropping events is unacceptable. |
Who is affected. All guard-agent integrations. The default ("drop") preserves the behavior that existed before 2.4.0, so this is an opt-in change. No existing integration breaks. You are affected only if you want to change the overflow behavior away from silent drop.
Exact edit required. The field lives on AgentConfig, not SecurityConfig. Construct AgentConfig directly to use a non-default value:
from guard_agent import AgentConfig
agent_config = AgentConfig(
buffer_overflow_policy="raise",
)For the full buffer sizing formula, flush trigger mechanics, and Redis spillover for delivery durability, see Agent buffering & backpressure.
guard-agent 2.6.0
No breaking change is documented in these docs for guard-agent 2.6.0. Consult the official guard-agent release notes for the full changelog for this version.
guard-core 3.5.0
Decorated routes now enforce global IP and country rules. Before 3.5.0, a route carrying any decorator-level config silently bypassed the global IP/country checks. Since 3.5.0 the global rules run on those routes as well, with two precise carve-outs: a route-level whitelist_countries match for the resolved country still skips the global country gate, and a route-level ip_whitelist match grants access through the IP checks only. The request still passes rate limiting, user-agent, cloud-provider, and detection checks instead of being trusted outright. If you relied on decorator config to opt a route out of global blocking, re-verify that route after the upgrade. See Geo + cloud-provider blocking.
No other breaking change is documented in these docs for guard-core 3.x. Consult the official guard-core release notes for the full changelog for this version range.
Upgrade checklist
Work through this list before upgrading in production. Each item maps to a section above.
- Pin versions before upgrading. Set explicit version pins (
fastapi-guard>=X.Y.0,<X.Y+1.0,guard-agent>=A.B.0,<A.B+1.0) in your requirements file before starting. - Remove
configure_corscalls (6.0.0+). Search your codebase forconfigure_cors. Delete every call. Verify CORS behavior in staging with an actual cross-origin browser request. - Review
fail_secure(7.0.0+). Confirm whether any code path depended on the prior fail-open behavior. If your Redis or agent endpoint is intermittently unreachable, you will see 500s after upgrading. Fix the dependency, or setfail_secure=Falsetemporarily while you investigate. Do not leave it off permanently. - Wire
guard_lifespanif enabling the agent (7.1.0+). Addlifespan=guard_lifespanto yourFastAPI(...)constructor. Verify events appear in the dashboard within oneagent_flush_intervalwindow after startup. - Decide on
buffer_overflow_policy(guard-agent 2.4.0+). The default"drop"matches prior behavior. If event integrity matters for your use case, switch to"block"or"raise"and verify the buffer sizing formula covers your peak rate. - Re-check decorated routes (guard-core 3.5.0+). Routes with decorator-level IP/country config now also enforce the global rules they previously bypassed. Verify no decorated route depended on that bypass.
- Test in staging before promoting to production. Run the full rollout playbook - passive mode first, fail-secure on, then promote - as documented in Passive mode vs fail-secure. Do not skip staging.