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.
Metadata
Release files for appfirewall-fastapi 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| appfirewall_fastapi-0.2.0.tar.gz | 37.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| appfirewall_fastapi-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 66.0 kB
Release files / appfirewall_fastapi-0.2.0.tar.gz
| Download URL | appfirewall_fastapi-0.2.0.tar.gz |
|---|---|
| Size | 37.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
b427939dd39dbd451f98a852c70b0537abfb560abbd263733b2e5f351f6857ab
|
|
BLAKE2b-256 checksum How to use checksums |
5ca29d920969401c78c724e80cb336ebefed559800287d8cafc136c8f934efa2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Apr 26, 2026.
Transparency logRelease files / appfirewall_fastapi-0.2.0-py3-none-any.whl
| Download URL | appfirewall_fastapi-0.2.0-py3-none-any.whl |
|---|---|
| Size | 28.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
84a768518281bf7aa325d2d28aa5797d472ab49beeeddb912a62ee6a5b820792
|
|
BLAKE2b-256 checksum How to use checksums |
2f0a377fb19caa128e51c7eeb5515c8b42d5fb2b733dce0f42b8466b5731bfe9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Apr 26, 2026.
Transparency log