Skip to content

feat(perf): per-key rate-limit bucketing (auth-before-middleware ordering) #3

Description

@Metbcy

Problem

Today's rate-limit middleware (backend/securescan/middleware/rate_limit.py) buckets by raw X-API-Key header value or remote IP. Bogus keys still get their own bucket — a caller can spoof X-API-Key: anything to refresh their rate-limit quota before auth catches the bad key.

Real per-key bucketing requires the rate-limit decision to happen AFTER require_api_key resolves the key to an authenticated Principal. FastAPI middleware runs before route dependencies, so the current ordering can't see the principal.

Acceptance criteria

  • Rate-limit identity for authenticated requests = key:<key_id> from the resolved Principal, not the raw header.
  • Anonymous requests still bucket by IP (no regression).
  • Spoofed-key requests are rejected by auth before they enter the rate limiter (no bucket-spoof surface).
  • Existing SECURESCAN_RATE_LIMIT_* env vars keep working.
  • Tests cover: same authenticated key shares bucket; two different valid keys have separate buckets; revoked-key requests are 401, not rate-limited.

Suggested approach

  1. Convert rate-limiting from BaseHTTPMiddleware to a route-level dependency (Depends(rate_limit_dep)) that runs after require_api_key.
  2. Read the principal off request.state.principal (already populated by require_api_key in v0.8.0+).
  3. Apply the dependency on the small set of routes that need it (today: just POST /api/v1/scans per the existing scope).

Alternative: keep middleware but have it call require_api_key itself synchronously. Less idiomatic, more coupling.

Difficulty

~3 hr. Architectural reordering + tests. Tracked from v0.7.0 deferral.

Metadata

Metadata

Assignees

No one assigned

    Labels

    help wantedExtra attention is neededperformanceSpeed / scale / footprint

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions