Origin-side abuse signal middleware for FastAPI apps behind Cloudflare. Part of AppFirewall by Sireto.
Project description
appfirewall-fastapi
Origin-side abuse signal middleware for FastAPI apps behind Cloudflare.
Part of the AppFirewall platform by Sireto. Cloudflare protects your edge; AppFirewall sees what your CDN can't — parse failures, auth failures, and application-layer abuse signals at your origin — and closes the loop back to the edge.
Status: v0.1, pre-release. The public API (
AppFirewallMiddleware,appfirewall.record) is stable; internals may change.
Install
pip install appfirewall-fastapi
Quick start
from fastapi import FastAPI
from appfirewall_fastapi import AppFirewallMiddleware
app = FastAPI()
app.add_middleware(
AppFirewallMiddleware,
api_key="afw_live_...", # or set APPFIREWALL_API_KEY
)
That's it. The middleware will:
- Resolve the real client IP from
cf-connecting-ip(validated against Cloudflare's published IP ranges — never spoofable from outside). - Classify 404s as
scanner/benign-miss/unknownusing a pattern library of known probes (/wp-admin,/.env,/.git/config, path-traversal, etc.). - Ship events in batches (2s / 500 events, gzipped JSONL) to the AppFirewall ingest endpoint out-of-band, so your request path is never blocked.
Recording app-layer signals
The value AppFirewall provides over edge-only protection comes from signals
your app can see but Cloudflare can't. Use appfirewall.record() inside your
handlers:
from fastapi import HTTPException, UploadFile
from appfirewall_fastapi import appfirewall
@app.post("/upload")
async def upload(file: UploadFile):
try:
parsed = parse_flint(await file.read())
except ParseError as e:
appfirewall.record("upload.parse_failed", reason=str(e))
raise HTTPException(400, "invalid format")
appfirewall.record("upload.success", size=len(parsed))
return {"ok": True}
record() is synchronous, non-blocking, and never raises — safe to sprinkle
anywhere. Outside a request, it's a silent no-op.
Configuration
All options can be passed as keyword arguments to add_middleware or set via
environment variables.
| Option | Env var | Default | Purpose |
|---|---|---|---|
api_key |
APPFIREWALL_API_KEY |
(none) | Bearer token. If unset, mode forces to "off". |
endpoint |
APPFIREWALL_ENDPOINT |
https://ingest.appfirewall.io/v1/events |
Override for self-hosted ingest. |
environment |
— | None |
Tag attached to every event. Useful for production/staging. |
mode |
— | "ship" |
"ship" | "local" | "off". |
local_log_path |
— | None |
In mode="local", write JSONL to this path instead of shipping. |
trusted_proxies |
— | ("cloudflare",) |
Accept cf-connecting-ip / XFF from these peers. |
classify_404 |
— | True |
Classify unknown 404s into scanner / benign / unknown. |
rate_limit |
— | {"scanner": (10, 60.0)} |
Per-class limits: max per window, window seconds. |
enforce_rate_limit |
— | False |
If True, send 429 when an IP exceeds its per-class limit in-process. Off by default — enforcement belongs at the edge. |
on_error |
— | "ignore" |
"ignore" | "warn" | "raise". |
Fail-open guarantees
This middleware is deliberately conservative:
- If the middleware crashes, your app still serves the request.
- If ingest is down, events are buffered and dropped silently when the buffer fills. A circuit breaker prevents retry storms.
- If the API key is missing,
modeflips to"off"with a single warning. appfirewall.record()never raises, never blocks, never awaits.- Request latency overhead target: <1ms p99.
The tradeoff: the SDK's default is observation, not enforcement. Blocking bad
actors happens at the Cloudflare edge via the AppFirewall control plane, which
this SDK feeds. This is the design. For local enforcement in emergencies,
set enforce_rate_limit=True.
Local development
Point mode="local" at a file on disk to iterate without an ingest endpoint:
app.add_middleware(
AppFirewallMiddleware,
api_key="dev",
mode="local",
local_log_path="/tmp/appfirewall.jsonl",
)
Tail the file to watch events as your app runs:
tail -f /tmp/appfirewall.jsonl | jq .
Development
This SDK lives in the appfirewall-sdk monorepo under
python/appfirewall-fastapi/. From this directory:
pip install -e ".[dev]"
pytest
mypy src/
ruff check src/
All three must pass. See ../../docs/CONTRIBUTING.md
for the cross-SDK PR workflow and docs/ARCHITECTURE.md
for this SDK's module layout and design decisions. If you're an AI coding
agent, start with ../../AGENTS.md.
License
Apache-2.0.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file appfirewall_fastapi-0.2.0.tar.gz.
File metadata
- Download URL: appfirewall_fastapi-0.2.0.tar.gz
- Upload date:
- Size: 37.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b427939dd39dbd451f98a852c70b0537abfb560abbd263733b2e5f351f6857ab
|
|
| MD5 |
52ff3bf00ac7a247a513bc6fa0cc2ef2
|
|
| BLAKE2b-256 |
5ca29d920969401c78c724e80cb336ebefed559800287d8cafc136c8f934efa2
|
Provenance
The following attestation bundles were made for appfirewall_fastapi-0.2.0.tar.gz:
Publisher:
python-fastapi-publish.yml on cloudfirewall/appfirewall-sdk
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
appfirewall_fastapi-0.2.0.tar.gz -
Subject digest:
b427939dd39dbd451f98a852c70b0537abfb560abbd263733b2e5f351f6857ab - Sigstore transparency entry: 1391188718
- Sigstore integration time:
-
Permalink:
cloudfirewall/appfirewall-sdk@a378cfd48f3050e228fd1fe5f95b165923a7d4db -
Branch / Tag:
refs/tags/python-fastapi-v0.2.0 - Owner: https://github.com/cloudfirewall
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
python-fastapi-publish.yml@a378cfd48f3050e228fd1fe5f95b165923a7d4db -
Trigger Event:
release
-
Statement type:
File details
Details for the file appfirewall_fastapi-0.2.0-py3-none-any.whl.
File metadata
- Download URL: appfirewall_fastapi-0.2.0-py3-none-any.whl
- Upload date:
- Size: 28.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
84a768518281bf7aa325d2d28aa5797d472ab49beeeddb912a62ee6a5b820792
|
|
| MD5 |
72dd64cc2c79c60614a36d1539f83f4c
|
|
| BLAKE2b-256 |
2f0a377fb19caa128e51c7eeb5515c8b42d5fb2b733dce0f42b8466b5731bfe9
|
Provenance
The following attestation bundles were made for appfirewall_fastapi-0.2.0-py3-none-any.whl:
Publisher:
python-fastapi-publish.yml on cloudfirewall/appfirewall-sdk
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
appfirewall_fastapi-0.2.0-py3-none-any.whl -
Subject digest:
84a768518281bf7aa325d2d28aa5797d472ab49beeeddb912a62ee6a5b820792 - Sigstore transparency entry: 1391188878
- Sigstore integration time:
-
Permalink:
cloudfirewall/appfirewall-sdk@a378cfd48f3050e228fd1fe5f95b165923a7d4db -
Branch / Tag:
refs/tags/python-fastapi-v0.2.0 - Owner: https://github.com/cloudfirewall
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
python-fastapi-publish.yml@a378cfd48f3050e228fd1fe5f95b165923a7d4db -
Trigger Event:
release
-
Statement type: