We take security seriously. This page documents how to report issues, which versions we support, and the defences already in place.
| Version | Supported |
|---|---|
| 2.x | ✅ active |
| 1.x | ❌ end-of-life (2026-04) |
Please do not open a public issue. Report privately via GitHub's private vulnerability disclosure:
https://github.com/Lunicorn/HetznerDDNSPy/security/advisories/new
Or email the maintainers (PGP key pinned in the repo) with:
- Affected version / commit SHA
- Reproduction steps or proof-of-concept
- Impact assessment (confidentiality / integrity / availability)
- Your preferred handle for credit (optional)
Target response times
| Severity | First response | Fix target |
|---|---|---|
| Critical | 24h | 7 days |
| High | 48h | 14 days |
| Medium | 5 days | 30 days |
| Low | 10 days | next minor |
We follow a coordinated disclosure model: once a fix is available we publish a GHSA, CVE, and release notes. Credit goes to the reporter unless they prefer otherwise.
- uv lockfile committed and verified in CI.
- pip-audit + OSV Scanner on every PR.
- Dependabot weekly updates for
pip,github-actions,docker. - GitHub Dependency Review blocks PRs introducing high-severity CVEs or GPL-incompatible licences (AGPL-1.0/3.0-or-later, GPL-2.0-only). The project itself is GPL-3.0-or-later, so GPL-3 dependencies are allowed.
- OpenSSF Scorecard runs weekly.
- SLSA Level 3 provenance attached to every tagged release.
- Sigstore cosign signs container images keyless via OIDC.
- SBOM (SPDX + CycloneDX) generated for every release and the container.
- Trusted publishing to PyPI via GitHub OIDC (no long-lived tokens).
- CodeQL (
security-and-qualitysuite) on push/PR. - Bandit (medium+) and Semgrep (OWASP Top 10, Python, secrets, supply-chain, Dockerfile rule-packs) on every PR with SARIF upload.
- Gitleaks secret scanning on the full history.
- Strict input validation for zone, record name, IPv4, IPv6, TTL,
interval — see
src/hetzner_ddns/validation.py. - Log redaction of any
API_TOKEN/Auth-API-Token/Beareroccurrence. - No
eval, noexec, no shell invocation with untrusted data.
- Dedicated system user
hetzner-ddns, no shell, no home. - Service runs as
Type=oneshottriggered by a timer — the previous long-lived daemon-loop-inside-a-timer bug is gone. - Hardening:
NoNewPrivileges,ProtectSystem=strict,ProtectHome,PrivateTmp,PrivateDevices,RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX,SystemCallFilter=@system-serviceminus@privileged @resources @mount @debug @reboot @swap @raw-io, emptyCapabilityBoundingSet,MemoryDenyWriteExecute,LockPersonality,RestrictNamespaces,RestrictRealtime,RestrictSUIDSGID,UMask=0077. - Resource limits:
MemoryMax=128M,TasksMax=32,CPUQuota=25%,LimitNOFILE=256. - Config file mode
0600/0640root-owned, readable by the service group only. - State file mode
0600in/var/lib/hetzner_ddns(StateDirectoryMode=0750).
- Base:
gcr.io/distroless/python3-debian12:nonroot(pinned by digest). - No shell, no package manager, UID/GID
65532. - Read-only root FS (
read_only: truein the compose file). - All Linux capabilities dropped.
no-new-privileges: true.- Python venv built in a separate stage and copied over.
- Multi-arch (amd64 + arm64), SBOM + SLSA provenance attached by buildx.
- TLS 1.2+ with full certificate verification (
ssl.CERT_REQUIRED+check_hostname=True). Plain HTTP is rejected except for loopback addresses used by the test suite. - Per-request 15s timeout, exponential backoff with jitter on retries, explicit handling of HTTP 401/403/429/5xx.
- Multiple IPv4 / IPv6 providers with ordered fallback; any response that fails IP validation is discarded.
API_TOKENnever logged. Redaction filter inlogging_setup.pycoversAuth-API-Token,api_token=, andBearer <token>forms.- Three supported sources (env > config file > 1Password CLI). The CLI
path is the only place we invoke
subprocess— always with a fixedargvlist, nevershell=True. - The 1Password lookup can be disabled via
HETZNER_DDNS_DISABLE_OP=1.
| Threat | Mitigation |
|---|---|
| Leaked API token in logs | Redacting logger filter |
| Malicious IP-provider returning bad data | Strict IP parsing + validation + provider fallback |
| Hetzner API MITM / downgrade | TLS 1.2+, full cert verification, HSTS-like scheme enforcement |
| Config file tampering | Mode 0600, root-owned; state file mode 0600 |
| Service privilege escalation | systemd hardening, dropped capabilities, seccomp filter |
| Supply-chain injection (dep) | uv.lock pinning, pip-audit, OSV, dependency-review, SLSA |
| Supply-chain injection (image) | Distroless base pinned by digest, Trivy + Grype + cosign |
| Secret commit | Gitleaks in CI + pre-commit |
| Weak auth error handling | 401/403 mapped to AuthError, no retries, explicit exit code |
| Runtime OOM / DoS | MemoryMax, TasksMax, CPUQuota, LimitNOFILE |
See docs/security.md for the extended threat model.