fix(securitate): hardening prod — headere, body-cap, non-root, backup
Findings security-review P1/P2 (2026-07-03): - headere de securitate pe toate raspunsurile (nosniff, X-Frame-Options, Referrer-Policy, HSTS doar pe HTTPS) + teste - body-cap global 10MB ca middleware ASGI pur (413 inainte de parserul multipart/JSON; verificarea per-endpoint ramane strat 2) - imagine Docker non-root (uid 10001), port 8010 aliniat, loguri pe volumul /data - fail-fast la boot cu rar_env=prod fara AUTOPASS_REQUIRE_API_KEY sau AUTOPASS_SESSION_SECRET - compose: env-uri critice obligatorii (:?) ca api/worker sa nu diverga tacit; FORWARDED_ALLOW_IPS ca rate-limit-ul sa vada IP-ul real dupa Traefik - signup fara PII in stdout: log_event in loc de print cu email (idem notify degradat) - ratelimit: sterge cheile fara timestamp-uri valide (crestere monotona a memoriei pe IP-uri reale) - backup criptat SQLite (backup online API, gpg AES256) + verificare restore + docs/backup.md Suita completa verde: 1557 passed, 1 skipped (live). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
14
app/main.py
14
app/main.py
@@ -25,12 +25,13 @@ from . import errors
|
||||
from .api.v1.import_router import router as import_v1_router
|
||||
from .api.v1.integrare_router import router as integrare_v1_router
|
||||
from .api.v1.router import router as api_v1_router
|
||||
from .config import get_settings
|
||||
from .config import get_settings, validate_prod_invariants
|
||||
from .crypto import validate_creds_key
|
||||
from .db import get_connection, init_db, queue_depth, read_heartbeat
|
||||
from .observ import log_event, request_id_var
|
||||
from .security import install_log_redaction, scrub_text
|
||||
from .web.middleware import RequestIDMiddleware
|
||||
from .web.body_cap import BodyCapMiddleware
|
||||
from .web.middleware import RequestIDMiddleware, SecurityHeadersMiddleware
|
||||
from .web.routes import router as web_router
|
||||
from .web.auth_routes import router as auth_router
|
||||
from .web.admin_routes import router as admin_router
|
||||
@@ -44,6 +45,9 @@ async def lifespan(app: FastAPI):
|
||||
# Fail-fast: o cheie Fernet setata dar invalida opreste pornirea cu mesaj clar,
|
||||
# in loc de 500 brut la primul POST /v1/prezentari.
|
||||
validate_creds_key()
|
||||
# Fail-fast (prod-only): elimina clasa "am uitat env var in Dokploy" pentru
|
||||
# cheia API si secretul de sesiune, in loc de o instanta descoperita post-deploy.
|
||||
validate_prod_invariants(get_settings())
|
||||
init_db()
|
||||
yield
|
||||
|
||||
@@ -63,6 +67,12 @@ app.add_middleware(
|
||||
# OUTERMOST (add_middleware prepend), deci `X-Request-ID` se pune pe TOATE raspunsurile,
|
||||
# inclusiv 401/404/422/500 produse mai in interior.
|
||||
app.add_middleware(RequestIDMiddleware)
|
||||
# Headere de securitate (P1-4) pe TOATE raspunsurile.
|
||||
app.add_middleware(SecurityHeadersMiddleware)
|
||||
# Body-cap (P1-5): ADAUGAT ULTIMUL -> ruleaza CEL MAI OUTERMOST (add_middleware
|
||||
# prepend), deci intercepteaza `receive` inaintea oricarui parser FastAPI/Starlette
|
||||
# (multipart, JSON) — vezi docstring body_cap.py pentru motivul ASGI-pur.
|
||||
app.add_middleware(BodyCapMiddleware)
|
||||
|
||||
|
||||
@app.exception_handler(LoginRequired)
|
||||
|
||||
Reference in New Issue
Block a user