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
- 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.
- Rate limit violations return a standard
429 Too Many Requests response using the uniform error response schema (code: "RATE_LIMIT_EXCEEDED").
- CORS configuration is environment-aware:
development: permissive (*).
staging / production: restricted to an explicit allowlist sourced from settings.ALLOWED_ORIGINS.
- 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
- All user-supplied string inputs are validated and sanitized against XSS and SQL injection via Pydantic validators.
- 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.
- 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.py — SecurityHeadersMiddleware [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
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?
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
/signupand/loginare 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
Acceptance Criteria
/signup,/login,/forgot-password,/reset-password): max 10 requests/minute per IP.429 Too Many Requestsresponse using the uniform error response schema (code: "RATE_LIMIT_EXCEEDED").development: permissive (*).staging/production: restricted to an explicit allowlist sourced fromsettings.ALLOWED_ORIGINS.Strict-Transport-Security(HSTS)Content-Security-Policy(CSP)X-Content-Type-Options: nosniffX-Frame-Options: DENYReferrer-Policy: strict-origin-when-cross-originHttpOnly,Secure,SameSite=Strictcookies.RateLimitExceededexception is registered in the global exception handlers (app/core/exception_handlers.py) to return the standard error format.Proposed Technical Details
slowapiwith aRedisRateLimiterbackend. Limits are declared via@limiter.limit("10/minute")decorators on individual route functions.BaseHTTPMiddlewareinapp/core/middleware.pythat injects all security headers into every response.ALLOWED_ORIGINSadded toapp/core/config.py, defaulting to["*"]in development and requiring explicit values in production. TheCORSMiddlewareinapp/main.pyis updated to use this setting.field_validatordecorators 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.algorithm="HS256"andSECRET_KEYentropy is sufficient.jtiis included in every token and stored on issue.get_current_userdependency before any protected route is reached.app/core/middleware.py—SecurityHeadersMiddleware[NEW]app/core/config.py— AddALLOWED_ORIGINS,ENVIRONMENT,RATE_LIMIT_*settingsapp/core/exception_handlers.py— RegisterRateLimitExceededhandlerapp/main.py— RegisterSecurityHeadersMiddlewareand updateCORSMiddlewareTasks
ALLOWED_ORIGINSandENVIRONMENTsettings toapp/core/config.py.CORSMiddlewareinapp/main.pyto usesettings.ALLOWED_ORIGINS.SecurityHeadersMiddlewareinapp/core/middleware.pyand register it inapp/main.py.slowapiwith a Redis limiter and apply rate limits to auth endpoints.RateLimitExceededexception handler inapp/core/exception_handlers.pyreturning429in the standard error format.field_validatorsanitizers to all freeform string fields in user and meeting schemas.SecurityHeadersMiddleware.429is returned after the limit is exceeded).Open Questions/Considerations
429 Too Many Requestshandler that adds aRetry-Afterheader so clients can back off intelligently?