Skip to main content

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 / unknown using 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, mode flips 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)

Source distribution for appfirewall-fastapi 0.2.0
File Size Uploaded
appfirewall_fastapi-0.2.0.tar.gz 37.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for appfirewall-fastapi 0.2.0
File Interpreter ABI Platform
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 log

Release 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

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files

0.1.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page