Security and quality (bandit, secrets, error handling)

ZEUS is a security platform — our own code has to be exemplary. This lesson collects the house rules on security and quality: bandit static analysis, secrets kept out of the code, and error handling that doesn't hide problems.

bandit — a code security scanner

bandit searches the code for known anti-patterns: eval, subprocess with shell=True, weak hashes, SQL injection.

bandit -r app/ -ll        # -ll = medium/high severity only
# bandit: B602 subprocess with shell=True — injection vulnerability
subprocess.run(f"nslookup {host}", shell=True)   # BAD

# good — no shell, arguments as a list
subprocess.run(["nslookup", host], check=True)

ProfessNet standard: bandit runs in CI alongside ruff/mypy. All high/medium findings must be resolved or deliberately marked # nosec with a comment.

Secrets never in code

Passwords, tokens, connection strings — exclusively from environment variables or a vault (Azure Key Vault). In the repo we keep only .env.example with empty values.

# bad — a secret in the code, it stays in git history forever
API_KEY = "sk_live_8f3a9c2e..."

# good — from configuration
from app.core.config import settings
client = Client(api_key=settings.api_key)

ProfessNet standard: .env is in .gitignore. Before a commit, detect-secrets runs (pre-commit). A secret in git history = key rotation, not just git rm.

Input validation = security

We validate every external input with Pydantic — it's not only about UX, it's a defense against injection and bad data.

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

Database queries — always parameterized

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

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

In practice we use the SQLAlchemy ORM, which parameterizes for us.

Error handling that doesn't lie

We don't swallow exceptions. We catch a specific type, log it with context, and either handle it or propagate it.

import logging

logger = logging.getLogger(__name__)

# bad — silence, the error vanishes
try:
    await collect(forest)
except Exception:
    pass

# good — a specific exception, a log, a clear response
try:
    await collect(forest)
except ProbeTimeoutError as exc:
    logger.warning("Timeout probe %s: %s", forest, exc)
    raise HTTPException(status_code=504, detail="Probe did not respond")

ProfessNet standard: never except Exception: pass. We log without secrets in the message (no tokens or passwords in logs).

RuleWhy
bandit in CIcatches known code vulnerabilities
secrets from env/Key Vaultno leaks into the repo
Pydantic on inputvalidation and defense against injection
ORM / bind parametersno SQL injection
a specific except + logerrors are visible, they don't vanish

Secure code is the sum of small habits: scanning, secrets out of the repo, validation, parameterization and honest error handling. In a security product, security is not an option — it's the definition of a finished task.