The version is read once at startup from the VERSION file, which is now
also copied into the Docker image. The footer is right-aligned below the
content block and only shown to logged-in users, so anonymous visitors
do not learn the exact version.
The startup log now has "Max starting." at the beginning of main() and
"Max initialized, version X." at the end, so a failed startup is visible
by the missing second line.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Login:
- One message for unknown email, wrong password and deactivated
account. Before, "This account is not enabled!" only appeared after
the right password and confirmed valid credentials.
- Email and password must be non-empty strings. A missing password
field became the string "None" and logged in a user with that
password.
Forms:
- "Create user" no longer renders the password back into the HTML after
a validation error.
- Password fields for a new password use autocomplete="new-password",
so a password manager does not fill in the admin's own password. The
email fields on the create pages use autocomplete="off".
- Email addresses are limited to 64 characters in the local part and
254 in total (RFC 5321). Longer ones caused a database error and
HTTP 500.
- alerts.html no longer translates flash messages a second time. Running
str.format() again on an already formatted message was fragile and
logged "Unknown translation" for every German message. All flashes
now translate when flashing, "Invalid data" included.
Debug mode:
- The Docker image sets MAX_PRODUCTION=1, and the app refuses to start
in debug mode then. FLASK_DEBUG=1 in production would otherwise enable
the fixed secret key, cookies without Secure, no HSTS and query
arguments (including password hashes) in the log.
- make run-dev and run-dev-as-prod bind to 127.0.0.1.
Other:
- A denied access is logged with the logger at debug level instead of
print. PermissionDenied loses its unused message, it is deliberately
answered with a 404.
- Fix the typo in /user/<id>/ser-toggle-can-use-different-alias-domain.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The app ran as root, and the production image contained gcc, vim, wget
and gnupg. Code execution in the container would have allowed changing
the app code and fetching or compiling further tools.
- Multi-stage build: the wheels (psycopg2) are built in a build stage.
The runtime image only contains libpq5, postgresql-client and
wait-for-it (724 MB -> 298 MB).
- The app runs as the system user max (uid/gid 10001). The code is
owned by root and precompiled, the user has no writable directory.
- Log to stdout only; docker collects it. The log file under /app/log
was lost on every container recreation anyway.
- docker-compose: read-only filesystem (tmpfs for /tmp), no
capabilities, no-new-privileges and log rotation (5 x 10 MB).
- Add a .dockerignore and set MAX_ORIGIN in docker-compose.yml.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- scripts/prod: entrypoint, db-setup and schema.sql shipped in the image
- scripts/dev: seed/clear db, psql and cleanup, run natively in the
devcontainer instead of via docker-compose
- move setup.cfg to the repo root so linters pick it up
- Makefile targets run directly in the devcontainer
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Move logging setup into max/log.py. The file handler is only used outside
debug mode and its directory is configurable via LOG_DIR. FLASK_ENV is
deprecated in Flask; debug mode is now controlled via FLASK_DEBUG.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>