0.6.2 (2026-09-26)¶
Hardening release: closes API security gaps (password confirmation, CORS, CSP, request limits and audit trail), fixes version reporting that had gone stale, and brings coverage reporting back to the test suite.
💥 Breaking changes¶
PUT /api/v1/users/change-passwordnow requirescurrent_password. A valid token used to be enough to set a new password; the current one must be confirmed. Clients must send that field alongsidenew_password.- Accounts that only sign in through an external provider get a 403 from that endpoint. Their stored password is a random value nobody knows, so there is nothing to confirm: to create a password for email sign-in they use the reset flow, which proves control of the address.
Actual impact of this change: the frontend bundled with this release already sends the field, so updating everything together does not break the application. The impact is limited to direct API consumers (scripts or integrations) and to deployments where frontend and backend ship separately — in that case deploy the frontend first, because an older frontend against this backend would still receive a 400.
🔒 Security¶
- Current-password confirmation on password change, verified server-side. A stolen token can no longer set a password and take over the account.
- CORS restricted to explicit methods and headers instead of wildcards that let any custom header through.
- Content-Security-Policy with a restrictive policy on API responses when the application runs in production.
- Per-IP request limits on the exposed surfaces: registration (20/hour), password reset requests (10/hour), MFA verification (10 per 5 minutes) and the public render endpoint (60/minute). All answer
429with aRetry-Afterheader. - 100,000-character cap on the source code accepted by the public render endpoint, which runs unauthenticated and consumes renderer resources.
- Account deletion no longer returns internal error details: the client gets a generic message and the exception is logged server-side.
- Complete audit trail: the four events that were defined but never recorded now fire — password reset requested, MFA enabled, MFA disabled and account deleted.
🐛 Fixes¶
- The version reported by the API was wrong:
GET /announced0.6.0while 0.6.1 was already released. It now reports the real version, as do Swagger and the Sentry release tag. - The CHANGELOG was missing its
## [0.6.0]section (its entries had been merged under 0.6.1) and the 0.6.1 entry was absent from the documentation navigation. - Coverage reporting never ran:
pytest.inisilently overrode thepyproject.tomlconfiguration. Coverage is measured again on every run. backend/run-tests.shfailed with--unitand--integration(the flag was passed twice) and could not run inside the container because it started with#!/bin/bashand the image ships no bash.- A chat streaming property test failed on inputs containing a Spanish end marker: the test's preconditions only covered the English markers while the extractor normalizes both languages. The test now covers all five variants.
- The external-MongoDB scenario started the development server: its
docker-compose.ymlforceduvicorn --reloadand never ranstart.sh, so it ignoredWORKERSand skipped the production worker. It now starts gunicorn (2 workers by default). - The installer pointed users at
/docson production-mode installations, where Swagger and ReDoc are disabled by design. It now says so and explains how to enable them. - Dark mode in the admin plans view: the per-plan price table (currency and amount) and the create/edit plan modals used light colours with no dark variant, leaving them nearly invisible on the dark card. They now follow the theme.
📝 Documentation¶
- Publishing on a domain: what to change before exposing an installation —
BACKEND_CORS_ORIGINS,FRONTEND_URLandVITE_API_URL, which otherwise leave CORS and Stripe redirects pointing atlocalhost. Documented inINSTALL.mdanddeploy/README.md, and linked from each scenario README. - Managed platforms (DigitalOcean App Platform and similar): variables live in the platform settings, frontend and backend run as separate apps,
KROKI_URLmust be reachable outside Docker, and without bind mounts every change needs a new build and deploy.
🧪 Tests¶
- 45% coverage floor on the full run (measured baseline: 49.4%). Subset runs (
-m unit,-k …) are deliberately exempt, since a partial suite cannot reach a whole-project floor. - 29 new tests: shared sliding-window limiter, HTTP throttling for registration, reset and MFA, payload cap exactly at and above the limit, and one test per audit event.
🔧 Internal¶
scripts/check-version.sh: fail-closed verification of every file carrying the version number (code, configuration, documentation, release notes, navigation and CHANGELOG). TheVERSIONING.mdchecklist requires it before any release merge.- Shared sliding-window limiter in
app/core/rate_limit.py, reused by the users, MFA and diagrams modules instead of duplicating the logic.