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.Lockprevents concurrent create/renew from corrupting certificate files - Atomic
metadata.jsonwrites via temp-file + rename (prevents partial JSON on crash) LETSENCRYPT_EMAILenv var override now logs an explicit warning when it differs from the UI-configured value- Infisical backend: upsert pattern (try
update_secret, fallback tocreate_secret) —create_secretalways raised on renewal of an existing certificate - Azure and Infisical
list_certificates(): reads the metadata secret for the exact domain name instead of lossyreplace('-', '.')reversal, which was broken for hyphenated domains
Medium-Severity Fixes
- Audit log
limitparameter capped at 1000 (was unbounded) - Pre-save backup failure now logs a
WARNINGinstead of being silent POST /api/usersreturns409 Conflictfor duplicate usernames (was500)- Storage backend silent fallback to local filesystem now logs an
ERRORwith 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