# Security Posture Analysis & Improvements

**Status**: Proposal
**Created**: 2026-02-08
**Scope**: Generated backend/frontend security, authentication, deployment, framework integrity

---

## Executive Summary

Prism demonstrates **solid security fundamentals** with excellent implementations in SQL injection prevention, password hashing, JWT cookie security, and Docker hardening. However, **three critical gaps** must be addressed before production deployment: no rate limiting on auth endpoints, in-memory OAuth state that doesn't scale, and a timing attack vulnerability in API key verification.

**Overall Security Score: 7.6/10** (suitable for MVP/internal use; hardening required for production SaaS)

---

## Critical Findings (P0 — Fix Before Production)

### P0.1: No Rate Limiting on Auth Endpoints

**Severity**: HIGH | **CVSS**: 5.3

**Location**: `src/prisme/templates/jinja2/backend/auth/routes_auth.py.jinja2`
- Login endpoint (line 270): No rate limit
- Signup endpoint (line 139): No rate limit
- Password reset endpoint (line 385+): No rate limit
- MFA endpoint (line 339+): No rate limit

**Attack**: Unlimited brute force attempts against login, credential stuffing, password reset flooding.

**Fix**:
```python
from slowapi import Limiter
from slowapi.util import get_remote_address

limiter = Limiter(key_func=get_remote_address)

@limiter.limit("5/15 minutes")
async def login(body: LoginRequest): ...

@limiter.limit("3/hour")
async def signup(body: SignupRequest): ...

@limiter.limit("3/hour")
async def reset_password(body: ResetRequest): ...
```

**Effort**: 2 hours

### P0.2: OAuth State Stored In-Memory

**Severity**: MEDIUM | **CVSS**: 4.2

**Location**: `src/prisme/templates/jinja2/backend/auth/routes_auth.py.jinja2` (lines 534-541)

```python
_oauth_states: dict[str, float] = {}  # Not shared across processes
```

**Problem**: OAuth flows fail when running multiple Uvicorn workers or horizontally scaled instances. State created on worker A cannot be validated on worker B.

**Fix**: Move to Redis or database storage:
```python
redis_client.setex(f"oauth_state:{state}", 300, "1")  # 5-min TTL
```

**Effort**: 4 hours

### P0.3: API Key Timing Attack

**Severity**: MEDIUM | **CVSS**: 4.1

**Location**: `src/prisme/templates/jinja2/backend/auth/api_key/api_key_service.py.jinja2`
- Line 65: `return api_key in valid_keys` — non-constant-time comparison

**Attack**: Attackers can determine valid API key characters through response time analysis.

**Fix**:
```python
import secrets

def verify_key(self, api_key: str) -> bool:
    valid_keys = self._load_keys()
    return any(secrets.compare_digest(api_key, k) for k in valid_keys)
```

**Effort**: 30 minutes

---

## High Priority Findings (P1 — Fix Before Launch)

### P1.1: CORS Configuration Too Permissive

**Severity**: MEDIUM | **CVSS**: 5.4

**Location**: `src/prisme/templates/jinja2/project/main_full.py.jinja2` (lines 59-66)

```python
allow_methods=["*"],     # Allows OPTIONS, TRACE, CONNECT
allow_headers=["*"],     # Accepts arbitrary headers
```

**Fix**: Restrict to used methods and headers:
```python
allow_methods=["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
allow_headers=["Content-Type", "Authorization"],
```

**Effort**: 1 hour

### P1.2: No GraphQL Query Complexity Limits

**Severity**: MEDIUM | **CVSS**: 5.7

**Location**: `src/prisme/templates/jinja2/backend/graphql/schema.py.jinja2` (lines 40-56)

**Attack**: Deeply nested queries can exhaust server resources (DoS).

**Fix**: Add query depth limits (max 10), complexity scoring, operation timeout (5s), disable introspection in production.

**Effort**: 6 hours

### P1.3: No Content Security Policy Headers

**Severity**: LOW | **CVSS**: 3.1

No CSP headers found in any template. Add to nginx/Traefik config:
```
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';
```

**Effort**: 1 hour

### P1.4: No Session Revocation on Logout

**Severity**: LOW | **CVSS**: 2.9

**Location**: `src/prisme/templates/jinja2/backend/auth/routes_auth.py.jinja2` (lines 693-717)

Logout only clears cookie client-side. Stolen tokens remain valid until expiry (default 7 days).

**Fix**: Optional Redis-backed token blacklist.

**Effort**: 4 hours

---

## Medium Priority Findings (P2)

### P2.1: No CLI Secret Generation

Config templates use `SECRET_KEY=your-secret-key-here` as placeholder. Users may forget to generate strong secrets.

**Fix**: Add `prism generate-secrets` command using `secrets.token_hex(32)`.

**Effort**: 3 hours

### P2.2: OAuth State Cleanup Incomplete

**Location**: `routes_auth.py.jinja2` (lines 537-541)

Expired states only cleaned on successful login, not on callback failure. Memory leak over time.

**Fix**: Background cleanup task every 5 minutes.

**Effort**: 2 hours

### P2.3: Dependency Version Pinning

**Location**: `src/prisme/templates/jinja2/project/pyproject.full.toml.jinja2` (lines 10-28)

Uses `>=` (minimum versions) which allows potentially breaking major updates:
```python
"fastapi>=0.109.0",  # Can update to 0.200.0
```

**Fix**: Use `~=` for compatible release:
```python
"fastapi~=0.109.0",  # >=0.109.0, <0.110.0
```

**Effort**: 1 hour

---

## Positive Findings (Strengths)

### SQL Injection Prevention — Excellent

All queries use SQLAlchemy ORM with parameterized operations. No string concatenation, no direct SQL.

**Location**: `src/prisme/templates/jinja2/backend/services/service_base.py.jinja2`

### Password Hashing — Excellent

bcrypt with configurable policy (min length, complexity requirements, account lockout after 5 failures).

**Location**: `src/prisme/templates/jinja2/backend/auth/password_service.py.jinja2`

### JWT Security — Good

HS256 (configurable), httpOnly cookies, Secure flag, SameSite=Lax. Proper claim structure (sub, email, roles, iat, exp).

**Location**: `src/prisme/templates/jinja2/backend/auth/token_service.py.jinja2`

### Frontend Token Storage — Excellent

No localStorage/sessionStorage for tokens. Uses httpOnly cookies with `credentials: 'include'`. No `dangerouslySetInnerHTML`, no `eval()`.

**Location**: `src/prisme/templates/jinja2/frontend/auth/AuthContext.tsx.jinja2`

### Email Verification & Password Reset — Good

Cryptographically secure tokens (`secrets.token_urlsafe(32)`), 24h email verification expiry, 1h password reset expiry, one-time use, email enumeration prevention (always returns 200 OK).

### MFA (TOTP) — Good

Uses pyotp library with `random_base32()` generation and 1-window drift tolerance.

### Docker Security — Excellent

Non-root user in containers, specific version pinning (python:3.13-slim, postgres:16-alpine), multi-stage builds, health checks on all services.

### Deployment Security — Excellent

Let's Encrypt TLS via Traefik, internal network isolation, SSH key management via GitHub secrets.

### Framework Integrity — Excellent

No eval(), exec(), pickle.loads(), unsafe yaml.load(). Jinja2 configured with `StrictUndefined` and `autoescape=True`. subprocess.run uses list args (no shell=True).

---

## OWASP Top 10 Compliance

| OWASP Category | Status | Notes |
|----------------|--------|-------|
| A01: Broken Access Control | Good | JWT + RBAC, proper authorization checks |
| A02: Cryptographic Failures | Good | bcrypt, secure JWT, HTTPS |
| A03: Injection | Excellent | SQLAlchemy ORM, no direct SQL |
| A04: Insecure Design | **Gap** | GraphQL complexity limits missing |
| A05: Security Misconfiguration | **Gap** | CORS too permissive, no CSP |
| A06: Vulnerable Components | Fair | `>=` pinning risks unexpected updates |
| A07: Identification & AuthN | **Gap** | No rate limiting on auth endpoints |
| A08: Software & Data Integrity | Good | File hashing, manifest tracking |
| A09: Security Logging & Monitoring | Fair | Basic logging, no auth event audit trail |
| A10: Server-Side Request Forgery | Good | No SSRF vectors found |

---

## Production Deployment Checklist

- [ ] **P0.1**: Rate limiting on all auth endpoints (5/15 min)
- [ ] **P0.2**: OAuth state moved to Redis/database
- [ ] **P0.3**: API key verification uses `secrets.compare_digest()`
- [ ] **P1.1**: CORS methods/headers restricted
- [ ] **P1.2**: GraphQL query complexity limits (depth=10, timeout=5s)
- [ ] **P1.3**: CSP headers configured
- [ ] **P1.4**: Session blacklist implemented (optional)
- [ ] **P2.1**: Strong secrets generated (not template defaults)
- [ ] **P2.2**: OAuth state cleanup running
- [ ] **P2.3**: Dependencies use compatible release pinning
- [ ] All secrets in environment variables / secret manager
- [ ] SSL certificate renewal tested
- [ ] Database backups configured
- [ ] Auth event logging enabled
- [ ] Security headers set (X-Frame-Options, X-Content-Type-Options, HSTS)

---

## Risk Assessment Matrix

| Finding | Severity | Likelihood | CVSS | Priority |
|---------|----------|------------|------|----------|
| No rate limiting | High | High | 5.3 | **P0** |
| OAuth state in-memory | Medium | High | 4.2 | **P0** |
| API key timing attack | Medium | Medium | 4.1 | **P0** |
| CORS too permissive | Medium | Medium | 5.4 | **P1** |
| No GraphQL limits | Medium | Medium | 5.7 | **P1** |
| No CSP headers | Low | Medium | 3.1 | **P1** |
| No session revocation | Low | Low | 2.9 | **P2** |

---

## Effort Summary

| Priority | Items | Total Effort |
|----------|-------|-------------|
| P0 (Critical) | Rate limiting, OAuth state, API key timing | ~7 hours |
| P1 (High) | CORS, GraphQL limits, CSP, session revocation | ~12 hours |
| P2 (Medium) | Secret generation, cleanup, dependency pinning | ~6 hours |
| **Total** | | **~25 hours (~3 days)** |

---

**Analysis Date**: 2026-02-08
