Vulnerability Management¶
Software has vulnerabilities. The goal isn't to have zero — it's to find and fix them quickly, and to be honest about the ones you've accepted.
What Runs in CI Today¶
The CI pipeline (.github/workflows/ci.yml) runs on every push:
- bandit — static security analysis over
shared/,worker/, andsecurity/ - pip-audit — dependency advisory scanning against
requirements.txtandrequirements-test.txt - Reports are uploaded as build artifacts
pip-audit currently runs non-blocking (|| true) — findings surface in the artifacts, not as failed builds. Review them; don't assume a green build means a clean scan.
The Accepted-Risk Register¶
SECURITY.md at the repository root carries the accepted risks table: findings that could not be remediated by a dependency bump, each with the reason and a review-by date. Check it before re-reporting a known finding, and re-evaluate entries at their review dates — don't let that table go stale.
Dependency Scanning (locally)¶
pip install pip-audit
pip-audit -r requirements.txt
# Per-service requirements
for f in worker/*/requirements.txt shared/requirements.txt security/requirements.*.txt; do
echo "=== $f ==="
pip-audit -r "$f"
done
Inline dependency drift
Every service's Dockerfile must install from its requirements.txt files, never an inline pip install list — inline lists drift from shared/'s actual imports and have crash-looped services in production. The security-service Dockerfiles document this lesson; keep the pattern.
Docker Image Scanning¶
Scan container images for OS-level vulnerabilities:
# Using Trivy
trivy image --severity HIGH,CRITICAL mailyte-email-server-api:latest
# Scan every locally built image
docker images --format '{{.Repository}}:{{.Tag}}' | grep mailyte | while read img; do
echo "=== $img ==="
trivy image --severity HIGH,CRITICAL "$img"
done
Trivy is not currently wired into CI — run it before releases, or add a job.
Base Images¶
Current state of the repo's Dockerfiles (verified 2026-08-30):
| Base Image | Count | Used by |
|---|---|---|
python:3.11-slim | 25 | All Python services, including worker/analytics and worker/api/Dockerfile.migrate (the last two 3.9 stragglers, migrated 2026-08-30) |
ubuntu:22.04 | 4 | Postfix, Dovecot, Rspamd, cert_manager |
php:8.2-apache-bookworm | 1 | ActiveSync |
(docker-compose.yml's secrets-check helper still runs a stock python:3.9-slim image on purpose — it is stdlib-only and bind-mounted, per its comment; it is not a Dockerfile and not part of the count above.)
Update Process¶
# Pull latest base images and rebuild
docker compose build --pull
# Test before deploying
docker compose up -d
curl http://localhost:8085/heartbeat
# Deploy through the normal pipeline -- not by hand-editing a pinned deployment
The migrate image goes stale
The migrate service's image is built from the repo and silently no-ops on new migrations if not rebuilt — rebuild it whenever migrations are added, and before blaming schema drift on anything else.
Avoid auto-updaters (Watchtower-style) on a mail server: an unattended image swap that restarts Postfix/Dovecot mid-traffic is an outage mechanism, and this stack's deploys already rebuild deliberately.
Security Patches¶
Response Timeline¶
| Severity | Response Time | Patch Time |
|---|---|---|
| Critical (CVSS 9.0+) | Same day | 24 hours |
| High (CVSS 7.0-8.9) | 24 hours | 72 hours |
| Medium (CVSS 4.0-6.9) | 1 week | Next release |
| Low (CVSS < 4.0) | Next sprint | Next release |
Patch Process¶
- Assess — determine if the vulnerability affects Mailyte
- Isolate — if actively exploited, apply a workaround immediately
- Fix — update the dependency or apply a code fix
- Test — verify the fix doesn't break anything
- Deploy — push to production (hotfixes go to the
currentsymlink, never a pinned deployment directory; verify by checking the new code is in the running container) - Communicate — notify affected users if data was exposed; record accepted residuals in
SECURITY.md
Common Vulnerability Areas¶
Email-Specific Risks¶
| Risk | Mitigation |
|---|---|
| Open relay | SASL auth required for submission, minimal mynetworks |
| Sender spoofing by authenticated users | reject_sender_login_mismatch on 587/465 |
| Content spoofing | SPF/DKIM/DMARC validation via Rspamd |
| Buffer overflow in SMTP | Keep Postfix/Dovecot base images updated |
| SSRF via webhooks | Validate webhook URLs; endpoints are operator-configured, not tenant-arbitrary |
Application Risks¶
| Risk | Mitigation |
|---|---|
| SQL injection | Parameterized queries throughout; bandit in CI |
| Auth bypass | Credential validation on every route; scope split (ADR-002) |
| Secrets in logs | RedactingFilter in the shared logging config, plus never logging secrets in the first place |
| Weak default secrets | secrets-check refuses to start the stack with missing/weak/known-default secrets |
| Dependency vulnerabilities | pip-audit in CI + this page's local scans |
Known Open Items¶
Flagged here so they aren't rediscovered as surprises (see also SECURITY.md):
api_keys.key_idstores the raw API key and is the lookup column — hash-based verification is an open hardening itemWEBHOOK_SECRET/OAUTH_TOKEN_SECRETare plain env vars, not mounted files (accepted risk with a call-site migration plan)- Alertmanager's webhook receiver endpoint is unimplemented — alert delivery is currently UI-only
Checklist¶
- Python dependency scanning in CI (pip-audit, non-blocking — review artifacts)
- Static security analysis in CI (bandit)
- Fail-closed secrets validation at stack startup
- Docker image scanning (run Trivy manually or add a CI job)
- Dependabot or equivalent automated update PRs
- Migrate the last two
python:3.9-slimDockerfiles to 3.11 (done 2026-08-30:worker/api/Dockerfile.migrate,worker/analytics/Dockerfile) - Security patch response timeline defined
- Accepted-risk register maintained in
SECURITY.mdwith review dates - Regular penetration testing