Bezpieczeństwo i jakość (bandit, sekrety, obsługa błędów)

ZEUS to platforma bezpieczeństwa — nasz własny kod musi być wzorem. Ta lekcja zbiera house rules dotyczące bezpieczeństwa i jakości: statyczna analiza bandit, sekrety poza kodem i obsługa błędów, która nie ukrywa problemów.

bandit — skaner bezpieczeństwa kodu

bandit przeszukuje kod pod kątem znanych antywzorców: eval, subprocess z shell=True, słabe hasze, wstrzyknięcia SQL.

bandit -r app/ -ll        # -ll = tylko medium/high severity
# bandit: B602 subprocess with shell=True — podatność na injection
subprocess.run(f"nslookup {host}", shell=True)   # ŹLE

# dobrze — bez powłoki, argumenty jako lista
subprocess.run(["nslookup", host], check=True)

Standard ProfessNet: bandit działa w CI obok ruff/mypy. Wszystkie findingi high/medium muszą być rozwiązane lub świadomie oznaczone # nosec z komentarzem.

Sekrety nigdy w kodzie

Hasła, tokeny, connection stringi — wyłącznie ze zmiennych środowiskowych lub sejfu (Azure Key Vault). W repo trzymamy tylko .env.example z pustymi wartościami.

# źle — sekret w kodzie, trafi do git history na zawsze
API_KEY = "sk_live_8f3a9c2e..."

# dobrze — z konfiguracji
from app.core.config import settings
client = Client(api_key=settings.api_key)

Standard ProfessNet: .env jest w .gitignore. Przed commitem działa detect-secrets (pre-commit). Sekret w historii git = rotacja klucza, nie tylko git rm.

Walidacja wejścia = bezpieczeństwo

Każde wejście zewnętrzne walidujemy Pydantikiem — to nie tylko UX, to obrona przed injection i nieprawidłowymi danymi.

class HostQuery(BaseModel):
    forest: str = Field(pattern=r"^[a-z0-9.\-]+$", max_length=255)

Zapytania do bazy — zawsze parametryzowane

# źle — SQL injection
await db.execute(f"SELECT * FROM hosts WHERE forest = '{forest}'")

# dobrze — parametry bind
await db.execute(
    text("SELECT * FROM hosts WHERE forest = :forest"),
    {"forest": forest},
)

W praktyce używamy ORM SQLAlchemy, który parametryzuje za nas.

Obsługa błędów, która nie kłamie

Nie połykamy wyjątków. Łapiemy konkretny typ, logujemy z kontekstem i albo obsługujemy, albo propagujemy.

import logging

logger = logging.getLogger(__name__)

# źle — cisza, błąd znika
try:
    await collect(forest)
except Exception:
    pass

# dobrze — konkretny wyjątek, log, czytelna reakcja
try:
    await collect(forest)
except ProbeTimeoutError as exc:
    logger.warning("Timeout probe %s: %s", forest, exc)
    raise HTTPException(status_code=504, detail="Probe nie odpowiedział")

Standard ProfessNet: nigdy except Exception: pass. Logujemy bez sekretów w treści (żadnych tokenów ani haseł w logach).

RegułaDlaczego
bandit w CIwyłapuje znane podatności kodu
sekrety z env/Key Vaultbrak wycieków do repo
Pydantic na wejściuwalidacja i obrona przed injection
ORM / parametry bindbrak SQL injection
konkretny except + logbłędy są widoczne, nie znikają

Bezpieczny kod to suma drobnych nawyków: skan, sekrety poza repo, walidacja, parametryzacja i uczciwa obsługa błędów. W produkcie bezpieczeństwa to nie opcja — to definicja ukończonego zadania.