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
# nosecwith 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:
.envis in.gitignore. Before a commit,detect-secretsruns (pre-commit). A secret in git history = key rotation, not justgit 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).
| Rule | Why |
|---|---|
| bandit in CI | catches known code vulnerabilities |
| secrets from env/Key Vault | no leaks into the repo |
| Pydantic on input | validation and defense against injection |
| ORM / bind parameters | no SQL injection |
a specific except + log | errors 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.