Skip to content

Security Hardening & Rate Limiting #13

@aniebietafia

Description

@aniebietafia

Feature: Implement Security Hardening & Redis-Backed Rate Limiting

Problem
The FluentMeet API currently has no rate limiting, no secure HTTP headers, and no systematic protection against common web vulnerabilities. Auth endpoints like /signup and /login are open to brute-force attacks. High-cost AI pipeline endpoints (STT, Translation, TTS) are exposed to abuse that could rapidly exhaust API quotas and inflate costs. Beyond auth, the application has no defense-in-depth measures such as security headers or input sanitization.

Proposed Solution
Implement a layered security strategy covering rate limiting, CORS hardening, security headers, input sanitization, and a JWT/cookie security audit. Rate limiting will be Redis-backed using slowapi (already listed as a dependency), ensuring limits are enforced consistently across all application instances in production. All measures follow the OWASP Top 10 as a baseline.

User Stories

  • As a platform operator, I want rate limits on auth and AI endpoints, so that abuse and brute-force attacks cannot exhaust our infrastructure or API quotas.
  • As a security engineer, I want all HTTP responses to include appropriate security headers (HSTS, CSP, X-Frame-Options), so that browsers apply additional client-side protections.
  • As a developer, I want a CORS policy that is locked down per environment, so that only approved frontend origins can access the API in production.
  • As a user, I want my session tokens and cookies handled securely, so that my account cannot be compromised through token theft or fixation attacks.

Acceptance Criteria

  1. Redis-backed rate limiting is applied to the following endpoint groups with distinct limits:
    • Auth endpoints (/signup, /login, /forgot-password, /reset-password): max 10 requests/minute per IP.
    • AI pipeline trigger endpoints (STT/Translate/TTS): max 30 requests/minute per authenticated user.
    • WebSocket connection initiation: max 5 concurrent connections per authenticated user.
  2. Rate limit violations return a standard 429 Too Many Requests response using the uniform error response schema (code: "RATE_LIMIT_EXCEEDED").
  3. CORS configuration is environment-aware:
    • development: permissive (*).
    • staging / production: restricted to an explicit allowlist sourced from settings.ALLOWED_ORIGINS.
  4. The following security headers are applied globally via FastAPI middleware:
    • Strict-Transport-Security (HSTS)
    • Content-Security-Policy (CSP)
    • X-Content-Type-Options: nosniff
    • X-Frame-Options: DENY
    • Referrer-Policy: strict-origin-when-cross-origin
  5. All user-supplied string inputs are validated and sanitized against XSS and SQL injection via Pydantic validators.
  6. A security audit of JWT and cookie handling confirms:
    • Refresh tokens are stored as HttpOnly, Secure, SameSite=Strict cookies.
    • Access token JTI blacklisting on logout is correctly implemented in Redis.
    • Token expiry is enforced on every authenticated request.
  7. The RateLimitExceeded exception is registered in the global exception handlers (app/core/exception_handlers.py) to return the standard error format.

Proposed Technical Details

  • Rate Limiting: slowapi with a RedisRateLimiter backend. Limits are declared via @limiter.limit("10/minute") decorators on individual route functions.
  • Security Headers Middleware: A custom Starlette BaseHTTPMiddleware in app/core/middleware.py that injects all security headers into every response.
  • CORS: ALLOWED_ORIGINS added to app/core/config.py, defaulting to ["*"] in development and requiring explicit values in production. The CORSMiddleware in app/main.py is updated to use this setting.
  • Input Sanitization: Pydantic field_validator decorators on all string fields that accept freeform text (e.g., full_name, meeting room names) to strip leading/trailing whitespace and reject strings containing HTML tags.
  • JWT Audit Checklist:
    • Confirm algorithm="HS256" and SECRET_KEY entropy is sufficient.
    • Confirm jti is included in every token and stored on issue.
    • Confirm blacklist check occurs in the get_current_user dependency before any protected route is reached.
  • New/Modified Files:
    • app/core/middleware.pySecurityHeadersMiddleware [NEW]
    • app/core/config.py — Add ALLOWED_ORIGINS, ENVIRONMENT, RATE_LIMIT_* settings
    • app/core/exception_handlers.py — Register RateLimitExceeded handler
    • app/main.py — Register SecurityHeadersMiddleware and update CORSMiddleware

Tasks

  • Add ALLOWED_ORIGINS and ENVIRONMENT settings to app/core/config.py.
  • Update CORSMiddleware in app/main.py to use settings.ALLOWED_ORIGINS.
  • Implement SecurityHeadersMiddleware in app/core/middleware.py and register it in app/main.py.
  • Configure slowapi with a Redis limiter and apply rate limits to auth endpoints.
  • Apply per-user rate limits to AI pipeline trigger endpoints.
  • Register a RateLimitExceeded exception handler in app/core/exception_handlers.py returning 429 in the standard error format.
  • Add Pydantic field_validator sanitizers to all freeform string fields in user and meeting schemas.
  • Conduct and document the JWT & cookie security audit; fix any identified issues.
  • Write unit tests for the SecurityHeadersMiddleware.
  • Write unit tests for rate limiting (assert 429 is returned after the limit is exceeded).

Open Questions/Considerations

  • Should rate limits be per-IP, per-user, or a combination of both? Per-user limits require authentication but are more precise; per-IP limits protect unauthenticated endpoints.
  • What is the desired behaviour when Redis is unavailable — fail open (allow all requests) or fail closed (deny all requests)?
  • Should we implement a 429 Too Many Requests handler that adds a Retry-After header so clients can back off intelligently?
  • Is a Web Application Firewall (WAF) expected at the infrastructure level, or does the application layer need to handle all filtering?

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions