github fabriziosalmi/certmate v2.2.1
v2.2.1 — Critical Reliability, Security, and Correctness Fixes

latest release: v2.2.2
8 hours ago

Overview

This release resolves a series of critical and high-severity bugs identified during a comprehensive 360-degree codebase audit, including three GitHub issues (#83, #84, #80). Several of the fixes address defects that rendered core functionality entirely non-operational in all deployments.

Critical Fixes

APScheduler never started — auto-renewal completely disabled
setup_scheduler() was defined in factory.py but never called in create_app(). No background jobs ran in any deployment: certificates were never auto-renewed and digest emails were never sent. Fixed by adding the missing call.

AttributeError crash on audit log endpoints
misc_routes.py called audit_manager.get_recent_logs(), but the method is named get_recent_entries(). Both /api/activity and /api/web/audit-logs crashed on every request with an unhandled AttributeError.

Unauthenticated endpoints
/api/activity, /metrics, and /api/web/logs/stream were missing @require_role() decorators and were publicly accessible without authentication.

CORS allow-all misconfiguration
flask_cors.CORS(app, origins=None) permits cross-origin requests from any domain. Changed to origins=[] (deny-all).

Issues Closed

#83 and #84 — Settings save wipes user accounts and re-triggers setup wizard
The POST /api/settings handler saved request.json directly, discarding fields the UI form does not submit (users, api_keys, local_auth_enabled). On every settings save, all user accounts were deleted and local_auth_enabled was reset to False, causing the setup wizard to re-appear on next page load. Fixed by merging incoming data with existing settings and hard-protecting the three auth-critical keys.

#80 — PORT and GUNICORN_TIMEOUT environment variables ignored in Docker
The Dockerfile CMD used exec form (JSON array), which does not perform shell variable expansion. PORT and GUNICORN_TIMEOUT had no ENV defaults. Fixed with ENV PORT=8000, ENV GUNICORN_TIMEOUT=300, shell-form CMD, and a dynamic HEALTHCHECK.

High-Severity Fixes

  • Per-domain threading.Lock prevents concurrent create/renew from corrupting certificate files
  • Atomic metadata.json writes via temp-file + rename (prevents partial JSON on crash)
  • LETSENCRYPT_EMAIL env var override now logs an explicit warning when it differs from the UI-configured value
  • Infisical backend: upsert pattern (try update_secret, fallback to create_secret) — create_secret always raised on renewal of an existing certificate
  • Azure and Infisical list_certificates(): reads the metadata secret for the exact domain name instead of lossy replace('-', '.') reversal, which was broken for hyphenated domains

Medium-Severity Fixes

  • Audit log limit parameter capped at 1000 (was unbounded)
  • Pre-save backup failure now logs a WARNING instead of being silent
  • POST /api/users returns 409 Conflict for duplicate usernames (was 500)
  • Storage backend silent fallback to local filesystem now logs an ERROR with the reason

Test Suite

160 passed, 0 failed — including a real certificate lifecycle on certmate.org via Cloudflare DNS (create, list, download ZIP, TLS components, renew).

Files Changed

Dockerfile, docker-compose.yml, modules/core/certificates.py, modules/core/factory.py, modules/core/settings.py, modules/core/storage_backends.py, modules/core/file_operations.py, modules/web/misc_routes.py, modules/web/settings_routes.py, nginx.conf, RELEASE_NOTES.md

Don't miss a new certmate release

NewReleases is sending notifications on new releases.