Skip to content

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/, and security/
  • pip-audit — dependency advisory scanning against requirements.txt and requirements-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)

Bash
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:

Bash
# 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

Bash
# 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

  1. Assess — determine if the vulnerability affects Mailyte
  2. Isolate — if actively exploited, apply a workaround immediately
  3. Fix — update the dependency or apply a code fix
  4. Test — verify the fix doesn't break anything
  5. Deploy — push to production (hotfixes go to the current symlink, never a pinned deployment directory; verify by checking the new code is in the running container)
  6. 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_id stores the raw API key and is the lookup column — hash-based verification is an open hardening item
  • WEBHOOK_SECRET / OAUTH_TOKEN_SECRET are 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-slim Dockerfiles 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.md with review dates
  • Regular penetration testing