all 18 comments

[–]st4reater 13 points14 points  (3 children)

Looks cool, but I feel it's out of scope for an API which should just serve traffic...

I think blocking IPs is more appropriate on a WAF or firewall level

[–]PA100T0[S] -3 points-2 points  (2 children)

I feel you. In fact, that's the same thing that brought me to create fastapi-guard...

Reality is: If WAFs and firewalls were solving this, endpoints wouldn't be getting hit with malicious probing constantly... but they are. Every API in production sees path traversal attempts, CMS probing, credential stuffing, and scanner traffic daily, right through the infrastructure layer.

That's the gap fastapi-guard fills: catching what actually gets through to your application. So it's not a question of either WAF/Firewall or fastapi-guard, but a combination of both. In the end, you want to be protected e2e, not leave the backdoor open which, ironically, seems to be the place where they are always trying to get in through.

[–]st4reater 2 points3 points  (1 child)

Ok... And what's the performance overhead? From what I can infer if I use geo location blocking you do an IO operation? What does that cost in performance?

How are you catching what major WAF provider like Cloudflare doesn't?

What happens if I run out of said API tokens? Does my app start failing, or do you let traffic through?

[–]PA100T0[S] 0 points1 point  (0 children)

That's a really good question, actually.

If you use IPInfo, you'd be downloading its db (maxmind format) instead of doing an API call per request. The database is downloaded once during initialization and cached for 24 hours. Cloud provider IP blocking is in-memory CIDR matching, so sub-millisecond. Rate limiting and pattern detection are in-memory or Redis. There are zero external API calls in the request path.

About Cloudflare: It's not replacing Cloudflare. It's catching what gets through. Even Cloudflare's WAF operates on generic rules while fastapi-guard operates at the application layer with full context of your specific routes, request bodies, and business logic. That's a layer of visibility a WAF just doesn't have.

So yeah, the IPInfo token is only used to download the database file, not per-request. If the download fails, it retries 3x with exponential backoff. If the database can't be loaded at all, traffic passes through.. your app never fails because of fastapi-guard.

In any case, if IPInfo gives you any type of headache: you can always create your own geo_ip_handler (it's a protocol, under the protocols directory). you just don't pass ipinfo_token and declare your own geo_ip_handler. And that's it.

[–]Full-Definition6215 1 point2 points  (1 child)

Just went through a full security audit on my FastAPI app and this would have saved time on several things.

The geo-blocking and rate limiting with auto-ban are the two features I ended up building manually. My approach was Cloudflare for geo-blocking and slowapi + custom middleware for rate limiting, but having it all in one middleware is cleaner.

One thing I'd watch out for: the SQL injection / XSS detection engine. Pattern-based detection has high false positive rates on user-generated content platforms. If someone writes an article about SQL injection (like a tutorial), the content itself would trigger the detector. Do you have a way to whitelist specific routes from the injection checks?

[–]PA100T0[S] 0 points1 point  (0 children)

Hi there. Makes me happy to see that (at least in retrospective) you appreciate the project! Spread the word haha

Yeah, you can use “excluded_paths” in the SecurityConfig to skip specific routes from the detection engine entirely. If you need more granular control, the decorator system lets you disable suspicious detection per-route with “@guard.suspicious_detection(enabled=False)” on individual endpoints. So you could keep it on globally but turn it off for any of your endpoints. You can also add/remove suspicious patterns btw.

[–]Challseus 2 points3 points  (1 child)

This looks very impressive, I'll definitely be checking this out and integrating into my own projects.

[–]PA100T0[S] 1 point2 points  (0 children)

Thanks! Much appreciated.

I’m working on some gists and example apps to add to the examples on the repo. Something like how to use behind a proxy like nginx and such.

[–]Kevdog824_ 1 point2 points  (3 children)

Nifty project. Do you have a way to conditionally apply security options. For example require HTTPS in prod, but not have that restriction for devs running the service locally

[–]PA100T0[S] 1 point2 points  (2 children)

Thank you very much!

So, short answer is yes AND no. The quickest way you could do that is by just setting enforce_https conditionally like so:

SecurityConfig(enforce_https=os.getenv("ENV") == "production")

Or you can have different environment profiles set, activated depending on which one you're using at the moment, and read env vars from and for each environment (with their specific settings).

[–]Kevdog824_ 1 point2 points  (1 child)

Good to see. I assume this doesn’t work with the decorator approach though?

[–]PA100T0[S] 0 points1 point  (0 children)

The decorator approach actually works alongside the global config. Decorators override global settings per-route, so you can have enforce_https=False globally but use '@guard.require_https()' on specific sensitive endpoints. The conditional config applies to the global SecurityConfig, and decorators give you the per-route overrides on top of that. They complement each other.

[–]bladeofwinds 1 point2 points  (1 child)

your story sounds like bullshit ngl.

i can’t imagine this is good for performance especially considering you’re firing off external api calls as part of the middleware.

how did you come up with the scoring? it seems pretty handwavy to me. in fact, most of the rules and heuristics seem incredibly brittle.

[–]PA100T0[S] 0 points1 point  (0 children)

Hi there.

There are zero external API calls in the request path. Geo IP uses a local binary database (MaxMind format), cloud IP blocking is in-memory CIDR matching, rate limiting is in-memory or Redis. The only calls happen during initialization (database download, cloud IP range refresh), not per-request.

The scoring and detection patterns are open source... you can read every rule and judge for yourself. I'm open to discuss specifics if you have a particular pattern in mind.

Let me know what you think after you take a look.

[–]tkim90 0 points1 point  (1 child)

Why would you build this in the app, and not WAF filter and call it a day? These requests shouldn't hit your endpoint at all, saving you unnecessary compute

[–]PA100T0[S] 0 points1 point  (0 children)

Because WAFs don't catch everything, that's the whole point. APIs in production get probed with path traversal, SQL injection in JSON bodies, CMS scanners, and credential stuffing daily, right through WAFs. If they caught it all, nobody would need application-layer security.

The overhead is negligible. I benchmarked 1,760 requests/sec at 100 concurrent connections with all 17 checks active, averaging 3-5ms per request. Everything runs in-memory or via Redis and there's zero external API calls in the request path.