fix: critical security hardening (Phase A permissions audit)

- Remove role field from UserCreate schema, hardcode 'engineer' at registration
- Escape all user content in HTML export with html.escape() (XSS fix)
- Add field_validator to reject default SECRET_KEY when DEBUG=False
- Add CHECK constraint on users.role ('engineer'|'viewer') + migration 011
- Fix test_admin fixture to properly grant is_super_admin via ORM
- Fix circular FK (users↔invite_codes) in test DB setup with DROP SCHEMA CASCADE
- Add 5 new security tests (role validation + XSS prevention)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
chihlasm
2026-02-05 22:04:37 -05:00
parent fd8fab97bd
commit 3e0fb92012
10 changed files with 236 additions and 48 deletions

View File

@@ -3,6 +3,9 @@ from pydantic import field_validator
from typing import Optional
_DEFAULT_SECRET_KEY = "your-secret-key-change-in-production-use-openssl-rand-hex-32"
class Settings(BaseSettings):
# Application
APP_NAME: str = "ResolutionFlow"
@@ -26,7 +29,19 @@ class Settings(BaseSettings):
return self.DATABASE_URL.replace("postgresql+asyncpg://", "postgresql://", 1)
# JWT Settings
SECRET_KEY: str = "your-secret-key-change-in-production-use-openssl-rand-hex-32"
SECRET_KEY: str = _DEFAULT_SECRET_KEY
@field_validator("SECRET_KEY", mode="after")
@classmethod
def reject_default_secret_in_production(cls, v: str, info) -> str:
"""Fail loudly if the default secret key is used outside of DEBUG mode."""
debug = info.data.get("DEBUG", False)
if v == _DEFAULT_SECRET_KEY and not debug:
raise ValueError(
"SECRET_KEY must be set to a secure value in production. "
"Generate one with: openssl rand -hex 32"
)
return v
ALGORITHM: str = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES: int = 15
REFRESH_TOKEN_EXPIRE_DAYS: int = 7