# V3 DDoS and WAF Posture

Manifest version: `v3-ddos-waf-posture.v1`

Verifier: `pnpm verify:v3 ddos-waf-posture` (proposed)

Evidence artifact: `V3/security/ddos-waf-posture-validation.json` (proposed)

Owner: Security on-call (`oshun-security-oncall`), joint with Capacity / Pixel
Streaming on-call for backpressure interactions

`V3/PIXEL_STREAMING_ABUSE_POSTURE.md` covers session-level abuse of the Pixel
Streaming relay (idle caps, per-user/per-network concurrency, free-tier minutes,
bot classification). It does **not** cover volumetric DDoS or API-abuse against
the public web and BFF surfaces — the launch landing, the auth routes, the
launch resolver, the waitlist, and the status page. This posture closes that
gap. Transport requirements (TLS 1.3 everywhere, WSS signalling, DTLS-SRTP) are
already locked by `V3/TRANSPORT_SECURITY.md` and are assumed below.

## Provider Assumption (L3/L4)

The POP fleet is AWS-primary with Azure / GCP / CoreWeave / Lambda Cloud
secondaries (`PIXEL_STREAMING_POP_CAPACITY_MANAGEMENT.md`). The public web/BFF
edge therefore standardizes on:

- **AWS Shield Advanced** on every public entry point: the CloudFront
  distributions fronting `apps/oshun/web` and the BFF API, and the ALBs they
  originate from. Shield Advanced gives always-on L3/L4 volumetric and
  state-exhaustion mitigation (SYN/UDP floods, reflection), health-based
  detection tied to our Route 53 health checks, access to the Shield Response
  Team (SRT) with the engagement contact pre-filed before GA, and DDoS cost
  protection.
- **Protection group**: all V3 public resources in one Shield protection group
  with aggregation `sum`, so a distributed low-rate attack across surfaces is
  detected as one event.
- The WebRTC media planes (voice SFU, pxstream UDP) are not behind CloudFront;
  they rely on provider-native L3/L4 scrubbing plus the relay's own admission
  control, and their signalling (WSS) **is** routed through the protected edge.
  A volumetric attack on a POP's media IPs is handled as a POP outage via the
  existing `pixel-streaming-pop-outage` runbook and multi-cloud failover drills.

## Origin-Shield Architecture

```
client → CloudFront (WAF attached, TLS 1.3)
       → CloudFront Origin Shield (us-east-1)
       → ALB (security group: CloudFront managed prefix list ONLY)
       → BFF / web origin
```

- Origins accept traffic exclusively from the CloudFront managed prefix list
  plus a per-distribution shared-secret header (`x-oshun-edge-auth`, value in
  KMS, rotated quarterly); requests arriving at the ALB without the header are
  dropped at the listener rule, so the origin cannot be addressed directly even
  from within AWS IP space.
- Origin Shield collapses regional cache misses to one origin fetch;
  `/v3/landing`, static assets, and `/status` are edge-cached (status at 30 s
  TTL) so a request flood against them never reaches the BFF.
- No DNS record points at an origin: Route 53 publishes only edge distribution
  targets; origin hostnames are randomized and not in public zones.

## WAF Rule Classes

Attached to the edge distributions, evaluated in order:

| Priority | Rule class           | Content                                                                                                                                                                                                              | Action                 |
| -------- | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------- |
| 1        | `ip-reputation`      | Managed IP reputation + anonymizer/proxy list; our own incident blocklist (security runbooks feed it)                                                                                                                | Block                  |
| 2        | `geo-policy`         | Sanctions-prohibited geos (per wave-3 note: RU is sanctions-prohibited) blocked at edge; non-wave regions are NOT blocked here — they get the app-layer regional waitlist                                            | Block (sanctions only) |
| 3        | `core-protections`   | OWASP-class managed rules: SQLi, XSS, path traversal, known bad inputs                                                                                                                                               | Block                  |
| 4        | `request-validation` | Method allowlist per route, body size cap 64 KB on JSON APIs (256 KB on DSAR upload), content-type enforcement, reject malformed JSON early                                                                          | Block                  |
| 5        | `bot-control`        | Managed bot control in targeted mode on auth + waitlist + signup; challenge (not block) on suspicion; verified-bot allowlist for status-page monitors                                                                | Challenge              |
| 6        | `auth-protection`    | ATO-focused rules on `/api/v1/auth/*`: credential-stuffing signatures, password-spray patterns, header-anomaly scoring; integrates the rate-limit escalation ladder from `V3/runbooks/security-incident-response.md` | Block/Challenge        |
| 7        | `rate-based-global`  | 2,000 requests / 5 min / IP across the distribution                                                                                                                                                                  | Block 10 min           |
| 8        | `rate-based-scoped`  | Per-endpoint limits below                                                                                                                                                                                            | Block 10 min           |

WAF logs stream to the security account's locked log bucket with 13-month
retention; the `v3-bff-auth-abuse` dashboard reads from them.

## Per-Endpoint Rate Limits

Enforced as WAF scoped rate rules at the edge, with matching (slightly looser)
BFF middleware limits as defense in depth — the origin never trusts the edge to
have done its job:

| Endpoint                                  | Limit (per source IP unless noted)                                   | Burst | Rationale                                                                    |
| ----------------------------------------- | -------------------------------------------------------------------- | ----- | ---------------------------------------------------------------------------- |
| `POST /api/v1/auth/login`                 | 10/min (drops to 3/min under ATO ladder)                             | 15    | Stuffing chokepoint; CAPTCHA after 3 consecutive failures                    |
| `POST /api/v1/auth/signup`                | 5/min, 20/day                                                        | 8     | Bulk account creation                                                        |
| `POST /api/v1/auth/refresh`               | 6/min per session, 30/min per IP                                     | 10    | Rotation is once-per-TTL; more is replay probing (`JWT_REFRESH_ROTATION.md`) |
| `GET /api/v3/lilith/launch`               | 60/min                                                               | 90    | Launch resolver is polled by clients; cached 10 s per identity               |
| `POST /api/v3/lilith/launch/waitlist`     | 3/min, 10/day                                                        | 5     | Waitlist stuffing pollutes wave planning                                     |
| `GET /api/v3/lilith/launch/waitlist/:id`  | 20/min                                                               | 30    | Record retrieval                                                             |
| Queue ticket refresh (surge plan)         | 12/min per ticket                                                    | 20    | Silent refresh cadence is 1/min; 12 allows clock skew                        |
| `GET /status`                             | 30/min                                                               | 60    | Edge-cached 30 s; limit protects against cache-buster query spam             |
| DSAR / privacy request routes             | 2/min, 10/day per account                                            | 4     | Statutory workflows are human-paced                                          |
| `GET /api/v3/pxstream/admission/evaluate` | Internal only — edge returns 403 unless from operator-tooling IP set | —     | Drill/operator surface, not public                                           |

Limits apply per IPv4 address and per IPv6 /64. Institutional NAT exceptions
reuse the institutional-network uplift mechanism from the abuse posture
(tenant-scoped, approved, audited) rather than ad-hoc allowlisting.

## Backpressure and Surge Interaction

- When the surge plan's waiting room is armed
  (`V3/launch/launch-surge-plan.md`), the queue sits **behind** the WAF: flood
  traffic is shed at the edge before it can occupy queue slots, so attackers
  cannot fill the waiting room and starve real users.
- Rate-based blocks emit the same telemetry stream as the abuse classifier; an
  IP blocked at the edge ≥ 3 times in 24 h is promoted to the `ip-reputation`
  incident blocklist automatically (TTL 7 days, extensions require security
  on-call review).
- WAF block-rate > 5,000 blocks/min or a Shield detected-event page the security
  on-call; ≥ 50,000 blocks/min or origin p95 degradation during an attack
  escalates to SRT engagement.

## Gate

The release gate fails closed when any of the following holds:

- Shield Advanced is not active on every public distribution and ALB in the
  evidence file, or the protection group is missing;
- the SRT engagement contact is unfiled, or the DDoS escalation path is not in
  the on-call rota;
- any origin ALB security group admits sources beyond the CloudFront prefix
  list, or the `x-oshun-edge-auth` header rule is absent (verified by a
  direct-to-origin probe that must be **dropped**, recorded in evidence);
- any endpoint in the rate-limit table lacks both its edge rule and its BFF
  middleware twin, or a limit is raised without a recorded security review;
- the WAF rule classes are not attached in the documented priority order;
- WAF logs are not flowing to the locked bucket (probe: a synthetic blocked
  request must appear in the log stream within 5 minutes);
- the sanctions geo-block does not match the current wave-3 sanctions note in
  `V3/REGION_ROLLOUT_COMPLIANCE.md`;
- the attack drill is missing: a staged drill must show (a) a 10x request flood
  against `/api/v1/auth/login` is shed at the edge with origin p95 unchanged,
  and (b) the queue-starvation scenario (flood while waiting room armed) admits
  zero attacker sessions — both with evidence snapshots in the validation
  artifact.
