# `mgf-fastapi` — architecture & replaceability

> DP-08: the swap recipe for each abstraction, in one place.

`mgf-fastapi` is a thin adapter layer. Its replaceable abstractions:

| Abstraction | Default | How to replace | Where |
|---|---|---|---|
| Auth verification | consumer-implemented `AuthVerifier` Protocol | implement the Protocol in your app; pass to `bearer_session` | `security.py` |
| Rate-limit backend | in-process `TokenBucket` | swap to a Redis-backed bucket for multi-process limits | `_rate_limit.py` |
| DB session | `mgf-sqlalchemy` engine/sessionmaker (`[sqlalchemy]` extra) | provide your own session dependency | `db.py` |
| Log stream transport | `mgf-livepush` broker (`[livepush]` extra) | swap the broadcaster's broker | `logs.py` |
| Webhook header schema | `SVIX_SCHEMA` | pass a custom `WebhookHeaderSchema` (GitHub/Stripe/…) | `webhooks/_verifier.py` |

Everything framework-neutral (errors, settings, observability) is delegated to
`mgf-common` — see [`DEPS.md`](../DEPS.md).

## Threat model

`mgf-fastapi` ships the request-edge security primitives a Magogi web app
inherits: webhook-HMAC verification (`webhooks/`), bearer-session auth
(`security.py` `bearer_session`), and an IP allowlist (`security.py`
`IpAllowlist`). Its threat model (SC §12) covers exactly those boundaries; the
consuming app owns everything behind them.

**Trust boundaries.** The boundary is the public HTTP edge. Three primitives
gate it: (1) a webhook route trusts a payload only after `verify_request`
confirms a valid Svix-shape HMAC over `<id>.<ts>.<body>` keyed on the shared
secret; (2) an auth-gated route trusts a caller only after `bearer_session`
resolves its token via the consumer-supplied `AuthVerifier`; (3) an
`IpAllowlist`-gated route trusts a peer only if its source IP is in a configured
CIDR set. Everything inbound is untrusted until it crosses one of these.

**Attacker capabilities.** A network attacker can send arbitrary requests to any
route: forged or replayed webhook payloads, oversized/chunked bodies, absent or
lying `Content-Length`, mixed-case / duplicated / tab-mangled signature headers,
stolen or expired bearer tokens, and — when `trust_proxy=True` — spoofed
`X-Forwarded-For` headers. They can race requests and probe for timing oracles.

**Out of scope.** Confidentiality/integrity of the transport (TLS termination at
the proxy), secret storage and rotation (the consumer supplies
`webhook_secret` / the `AuthVerifier`), the verifier provider's own correctness,
application-layer authorization (what an authenticated principal may *do*),
DoS at the network/proxy layer (connection floods, slow-loris), and CSRF/CORS
(app-level concerns). mgf-fastapi assumes it runs behind a reverse proxy that
terminates TLS and, when `trust_proxy=True`, strips client-supplied
`X-Forwarded-For`.

**Mitigations.** HMAC is compared in constant time (`hmac.compare_digest`) to
deny timing oracles; the replay window bounds both stale and future-skewed
timestamps; signatures are tokenised on any whitespace and matched per version
prefix for key rotation; the webhook body is capped (`max_body_bytes`,
default 1 MiB) and rejected *before* buffering/HMAC — on a declared
`Content-Length` and again mid-stream — to close the pre-auth memory/CPU DoS
(SC-08); a too-short `signature_prefix` is rejected at construction (SH-02); the
body-already-consumed footgun is detected and surfaced (RA-04); auth
misconfiguration fails closed with a 401 that leaks no internal state; and every
rejection at any of the three boundaries emits an `audit=True` structured event
(LG-15, see [`reference/EVENTS.md`](reference/EVENTS.md)) for detection.

**Residual risks.** A leaked webhook secret or bearer token defeats the
respective boundary entirely (rotation is the only remedy — out of scope here).
`IpAllowlist` with `trust_proxy=True` behind a misconfigured proxy that does
*not* strip `X-Forwarded-For` is bypassable (a logged WARNING flags the risky
construction, but cannot verify the deployment). Replay within the window is
accepted unless the consumer additionally de-dupes on the returned `event_id`.
Per-key rate-limit state (`TokenBucket`) is unbounded in memory, so a
high-cardinality key space is a residual memory-pressure vector until a bounded
or Redis-backed backend is swapped in.
