fastapi-guard: security middleware and per-route decorators for FastAPI
A security library for FastAPI that provides middleware to control IPs, log requests, detect penetration attempts, honeypot setup, behavioural analysis, emergency mode lockdown, block Cloud Provider IPs, country blocking, and much more... It integrates seamlessly with FastAPI to offer robust protection against various security threats.
At a glance
- What is it?
- fastapi-guard is an MIT licensed Python package that puts IP filtering, rate limiting, penetration pattern detection and a set of per-route security decorators in front of a FastAPI application. Its documentation is unusually specific about the edges, including WebSockets, Unix sockets and the rule that an authentication decorator without a verifier rejects the request.
- Who is it for?
- fastapi-guard suits teams that already run FastAPI and want request level protections and per-route policies inside the application, without adding a separate gateway to deploy and maintain.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 2 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A middleware that treats every request as untrusted
fastapi-guard is a security library for FastAPI that ships a middleware and a set of per-route decorators. The package metadata calls it production ready security middleware for FastAPI, with IP filtering, rate limiting, signature based attack pattern detection, and more than twenty per-route security decorators.
The installation is unremarkable, which is the point. It is a normal PyPI package:
uv add fastapi-guard # uv (recommended)
pip install fastapi-guard # pip
poetry add fastapi-guard # poetryThe package requires Python 3.10 or newer and below 3.15, and it depends on FastAPI, Starlette, uvicorn and a separate package called guard-core at version 4.4.0 or newer. That last dependency is worth noticing early, because it means a meaningful part of the implementation lives outside this repository, in a package with its own release cycle.
The audience is Python teams running an HTTP API who want rate limiting, IP banning, country filtering, cloud provider blocking and request pattern detection in the same process as the application, and who want those rules expressible per route rather than only globally.
What it is not is a web application firewall in the network sense. Everything here runs inside your process, after your ASGI server has already accepted the connection.
One config object decides the global policy
The middleware is installed with a single configuration object, and the documented example shows how much of the policy can be expressed there:
config = SecurityConfig(
enable_rate_limiting=True,
rate_limit=100,
rate_limit_window=60,
enable_ip_banning=True,
auto_ban_threshold=5,
auto_ban_duration=86400,
custom_log_file="security.log",
enforce_https=True,
enable_cors=True,
cors_allow_origins=["*"],
cors_allow_methods=["GET", "POST"],The rest of that example configures the CORS allow list, exposed headers and max age, and blocks cloud provider address ranges for AWS, GCP and Azure. The middleware is then attached with:
app.add_middleware(SecurityMiddleware, config=config)Two behaviours in that block are worth pausing on, because they are defaults rather than opinions. The rate limit is 100 requests in a 60 second window, and the IP ban triggers after 5 failures for 86400 seconds, which is a day. Those numbers ship as the starting point, so a deployment that has not thought about them is running with a day long ban for anyone who trips a threshold five times.
The CORS settings in the example are also permissive by construction, with a wildcard origin, wildcard headers and credentials enabled, which browsers will not accept together in most configurations. The example is showing the shape of the configuration, not a hardened setting.
One operational note is explicit in the documentation: for production you should wire guard.lifespan.guard_lifespan into FastAPI(lifespan=...) so that initialisation happens at startup rather than on the first request. That matters for latency and for fail-fast behaviour, since a misconfigured proxy or missing credential should surface at boot, not in front of a user.
Decorators compose, but authentication needs a verifier
The per-route decorators are where the finer policy lives, and they are grouped into six categories. Access covers require_ip, block_countries, allow_countries, block_clouds and bypass. Auth covers require_https, require_auth, api_key_auth and require_headers. Rate limiting covers rate_limit and geo_rate_limit. Content covers block_user_agents, content_type_filter, max_request_size, require_referrer, custom_validation and detection_exclusion. Behavioural covers usage_monitor, return_monitor, suspicious_frequency and behavior_analysis. Advanced covers time_window, honeypot_detection and suspicious_detection.
The documented example stacks four of them on one endpoint:
@app.get("/api/payments")
@guard.require_auth(type="bearer")
@guard.rate_limit(requests=10, window=60)
@guard.block_countries(["CN", "RU"])
@guard.require_https()
async def process_payment():
return {"status": "ok"}The rule that catches people is the authentication one. require_auth and api_key_auth require a verifier, either a per-route verifier argument or a global SecurityConfig.auth_verifier, and without one the request is rejected with 401. That is a safe default, since a protected route with no way to check a credential fails closed rather than open, but it does mean that copying a decorator without its verifier produces a route that rejects everyone.
If you only want to require the presence of an Authorization header rather than validate it, the documentation points at require_authorization_header with a scheme argument as the separate mechanism for that.
The edges the documentation admits to
Three limitations are spelled out in the project documentation, and they are the parts to read before deploying.
The first is WebSockets. SecurityMiddleware protects HTTP requests only, and never runs for WebSocket connections. A route declared with @app.websocket is unprotected by the middleware, and the documented fix is to secure it explicitly with Depends(guard_websocket). This is the kind of gap that never appears in a demo because demos are HTTP.
The second is the absence of a client address. A connection with no client address, which is what a Unix domain socket or some serverless ASGI adapters produce, is rejected with 403 by default, under a setting called fail_secure. Setting fail_secure to false runs the pipeline with the identity "unknown" instead, and that identity is allowed unless a whitelist or a country allow list is configured. The reasoning is spelled out: blacklist, country and cloud checks cannot match without an address, while detection and the shared rate limit bucket still apply. So the relaxed mode is not a bypass of everything, but it is a bypass of the address based rules.
The third is proxy handling. The documentation says to add the literal string "unix" to trusted_proxies to resolve the real client from X-Forwarded-For on such a connection. That is a sharp edge, because trusted_proxies is the setting that decides which headers you believe, and a string that is not an address has to be added to a list that otherwise holds addresses.
Taken together, these three cases are the difference between a middleware that works in a test environment and one that works behind your ingress, in a container, or on a platform that hands you an anonymous connection.
Redis and IP geolocation in the example stack
The repository ships a compose file that shows what the library expects around it, and it is more informative than the prose.
There is a Redis service on redis:alpine with a data volume, and both application services receive REDIS_URL pointing at it along with a REDIS_PREFIX and an IPINFO_TOKEN. The example app runs uvicorn against examples.simple_app.main:app with reload enabled and publishes port 8000, and the second service runs pytest with coverage over the package and the tests.
Redis is there because rate limiting and banning need shared state across workers, which is the normal requirement for a multi-process ASGI deployment. The prefix exists so that several environments can share one Redis instance without colliding, and the default in the file differs between the example service and the test service, which is the kind of detail that causes cross-talk if you copy the compose file without reading it.
The IPINFO_TOKEN points at address geolocation, which is what makes the country rules and the geo rate limit possible at all. Filtering by country cannot be done from a socket address alone.
For running the example locally, the Makefile wraps the compose workflow, and the supported Python versions run from 3.10 to 3.14 with 3.10 as the default:
@COMPOSE_BAKE=true PYTHON_VERSION=$(DEFAULT_PYTHON) docker compose up --build fastapi-guard-exampleThe same Makefile carries uv sync for installing, uv lock and uv lock --upgrade for dependency work, and a lint target that runs Ruff formatting checks, Ruff linting and mypy inside the container. That the test and lint tooling runs in Docker rather than on your machine is a reasonable choice for a project that supports five Python versions.
A hosted control plane beside an MIT library
The library is MIT licensed, and the licence file is in the repository root. That is the code you install.
Alongside it there is a commercial platform. The documentation lists a Dashboard for real-time security events, threat intelligence and attack pattern analytics, a Playground for trying the features in a browser against a live server, Dynamic Rules, which are described as the ability to update security configuration from the dashboard without redeploying, and GDPR tools for consent management and data handling, with the list cut off in the documentation excerpt.
That combination is worth thinking about carefully before you adopt it. Dynamic rules from a hosted dashboard mean your runtime policy can change without a deployment, which is convenient and is also a new dependency on somebody else's service for a security control. If your threat model assumes the configuration of your perimeter is under your control, that feature cuts the other way.
The dependency on guard-core, a separate package at version 4.4.0 or newer, is the other thing to check. It means the MIT licence covers this repository, and the licence of the package that carries part of the implementation is a separate question for your counsel. The project also uses a pre-commit configuration, a safety configuration file for dependency scanning, a CODEOWNERS file and a vulture whitelist for dead code detection, which together suggest a fairly serious release process.
The homepage for the project is guard-core.com, and the documentation is published under the Guard-Core GitHub Pages site with a versioned latest path, and the changelog is maintained in the repository.
A fast release line across five Python versions
The release record shows a project shipping quickly. Recent versions include v8.1.2 on 2026-10-17, v8.1.1 on 2026-10-15 and v8.1.0 on 2026-10-10, with the last push on 2026-10-19. Patch releases days apart are what you would expect from a library that ships CI and release workflows, and the project has both a CI badge and a separate release workflow badge.
That pace cuts both ways. It means fixes arrive quickly, and it means the version you pin today may be behind within a week. The versioning scheme helps here, since the major version has stayed at 8 across the recent releases, so the churn is in the patch level rather than in the API surface.
The package declares itself as Development Status 5, Production/Stable, marks itself as typed, and supports Python 3.10 through 3.14 as well as operating system independent installs. The dependencies are deliberately small at the top level, FastAPI, Starlette, uvicorn and guard-core, with an optional agent extra that pulls in guard-agent.
The repository also contains a CLAUDE.md and an AGENTS.md file next to the code, a convention that has become common in projects where part of the work is done with coding assistants, and a context7.json file for documentation context. If you are evaluating how the project is maintained, the changelog and the release workflow are the two places to look.
Editorial conclusion
fastapi-guard suits teams that already run FastAPI and want request level protections and per-route policies inside the application, without adding a separate gateway to deploy and maintain. It is the wrong choice for a service whose real perimeter is a reverse proxy or a managed WAF, since it only sees traffic that reaches the ASGI application, and it is the wrong choice for applications that rely on WebSocket routes without adding the documented Depends(guard_websocket) protection, because the middleware never runs for them. Verify three things before you turn it on in production: that your deployment exposes a real client address, because the default is to reject addressless connections with 403, that every protected route has an authentication verifier wired in, and whether you are willing to depend on a hosted platform for dynamic rule updates.
Frequently asked questions
How do I install fastapi-guard and add it to a FastAPI app?
Install it with uv add fastapi-guard, pip install fastapi-guard or poetry add fastapi-guard, then create a SecurityConfig and attach the middleware with app.add_middleware(SecurityMiddleware, config=config). In production the documentation asks you to wire guard.lifespan.guard_lifespan into FastAPI(lifespan=...) so initialisation happens at startup.
Does fastapi-guard protect WebSocket routes?
No. SecurityMiddleware protects HTTP requests only and never runs for WebSocket connections. A route declared with @app.websocket has to be secured explicitly with Depends(guard_websocket).
What happens when there is no client address, as with a Unix socket?
By default such a connection is rejected with 403 under fail_secure. Setting fail_secure to false runs the pipeline with the identity "unknown", which is allowed unless a whitelist or a country allow list is configured, and adding the literal string "unix" to trusted_proxies lets the real client be resolved from X-Forwarded-For.
Why does my protected route return 401 for every request?
require_auth and api_key_auth need a verifier, either a per-route verifier argument or a global SecurityConfig.auth_verifier, and without one the request is rejected with 401. For a presence-only check of the Authorization header, the documentation points to require_authorization_header with a scheme argument.
What do I need to run alongside fastapi-guard?
The example stack in the repository runs Redis on redis:alpine and passes REDIS_URL, REDIS_PREFIX and IPINFO_TOKEN to the application, and the Makefile builds and runs it with docker compose. Redis provides shared state for rate limiting and bans, and the token covers address geolocation, which the country rules need.
Can I change the security configuration without redeploying?
The documentation lists Dynamic Rules as a feature of the hosted Guard-Core dashboard, which lets you update security configuration from the dashboard without redeploying. The library itself is MIT licensed and is installed as a normal Python package.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/guard-core-fastapi-guard)Was this useful?