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>
87 lines
3.0 KiB
Python
87 lines
3.0 KiB
Python
"""Teste E1 (hardening 2026-07-03): invarianta de startup fail-fast, prod-only.
|
|
|
|
`validate_prod_invariants` (app/config.py) trebuie sa refuze boot-ul cand
|
|
`rar_env=="prod"` si lipseste `require_api_key` sau `session_secret` — dar sa
|
|
ramana un no-op pe `rar_env` implicit ("test"), altfel ar opri toata suita.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import tempfile
|
|
|
|
import pytest
|
|
from fastapi.testclient import TestClient
|
|
|
|
|
|
def _clear(monkeypatch):
|
|
# NU delenv REQUIRE_API_KEY: `.env` real (dev) are AUTOPASS_REQUIRE_API_KEY=true,
|
|
# iar conftest il neutralizeaza cu os.environ.setdefault("...", "false") — un delenv
|
|
# aici ar re-expune valoarea din `.env` (precedenta pydantic-settings: env var > .env
|
|
# file). Fiecare test seteaza explicit ce valoare vrea pentru acest flag.
|
|
monkeypatch.delenv("AUTOPASS_RAR_ENV", raising=False)
|
|
monkeypatch.delenv("AUTOPASS_SESSION_SECRET", raising=False)
|
|
|
|
|
|
def test_prod_fara_require_api_key_refuza_boot(monkeypatch):
|
|
_clear(monkeypatch)
|
|
monkeypatch.setenv("AUTOPASS_RAR_ENV", "prod")
|
|
monkeypatch.setenv("AUTOPASS_REQUIRE_API_KEY", "false")
|
|
monkeypatch.setenv("AUTOPASS_SESSION_SECRET", "x" * 32)
|
|
from app.config import get_settings
|
|
get_settings.cache_clear()
|
|
from app.main import app
|
|
|
|
with pytest.raises(RuntimeError, match="AUTOPASS_REQUIRE_API_KEY"):
|
|
with TestClient(app):
|
|
pass
|
|
get_settings.cache_clear()
|
|
|
|
|
|
def test_prod_fara_session_secret_refuza_boot(monkeypatch):
|
|
_clear(monkeypatch)
|
|
monkeypatch.setenv("AUTOPASS_RAR_ENV", "prod")
|
|
monkeypatch.setenv("AUTOPASS_REQUIRE_API_KEY", "true")
|
|
from app.config import get_settings
|
|
get_settings.cache_clear()
|
|
from app.main import app
|
|
|
|
with pytest.raises(RuntimeError, match="AUTOPASS_SESSION_SECRET"):
|
|
with TestClient(app):
|
|
pass
|
|
get_settings.cache_clear()
|
|
|
|
|
|
def test_prod_cu_invarianta_satisfacuta_boot_ok(monkeypatch):
|
|
_clear(monkeypatch)
|
|
tmp = tempfile.mkdtemp()
|
|
monkeypatch.setenv("AUTOPASS_DB_PATH", os.path.join(tmp, "inv.db"))
|
|
monkeypatch.setenv("AUTOPASS_LOG_DIR", os.path.join(tmp, "logs"))
|
|
monkeypatch.setenv("AUTOPASS_RAR_ENV", "prod")
|
|
monkeypatch.setenv("AUTOPASS_REQUIRE_API_KEY", "true")
|
|
monkeypatch.setenv("AUTOPASS_SESSION_SECRET", "x" * 32)
|
|
from app.config import get_settings
|
|
get_settings.cache_clear()
|
|
from app.main import app
|
|
|
|
with TestClient(app) as c:
|
|
r = c.get("/healthz")
|
|
assert r.status_code == 200
|
|
get_settings.cache_clear()
|
|
|
|
|
|
def test_mediu_test_nu_declanseaza_invarianta(monkeypatch):
|
|
"""Default rar_env="test" -> no-op, indiferent de require_api_key/session_secret."""
|
|
_clear(monkeypatch)
|
|
tmp = tempfile.mkdtemp()
|
|
monkeypatch.setenv("AUTOPASS_DB_PATH", os.path.join(tmp, "inv2.db"))
|
|
monkeypatch.setenv("AUTOPASS_LOG_DIR", os.path.join(tmp, "logs"))
|
|
from app.config import get_settings
|
|
get_settings.cache_clear()
|
|
from app.main import app
|
|
|
|
with TestClient(app) as c:
|
|
r = c.get("/healthz")
|
|
assert r.status_code == 200
|
|
get_settings.cache_clear()
|