Changelog¶
All notable changes to this project are documented here. The format follows Keep a Changelog and the project uses semantic versioning.
1.0.0 - 2026-10-16¶
First production release. It contains the remediation of the October 2026 security audit (report), the production hardening listed below, and the fixes of a follow-up security audit (report).
Upgrade notes¶
- Schema migrations. The schema is now managed with Alembic. The service
migrates at startup (set
database.auto_migrate: falseto runpython -m app.migrateyourself). Databases created bycreate_allsince October 2026 are adopted automatically; older schemas are refused with an explanation. Back up the database first. - PostgreSQL and Redis are required in production, both over verified TLS.
The chart no longer accepts
latestimages, wildcard proxy trust, missing NetworkPolicy peers or missing TLS/credential Secrets. - Redis outages no longer fail readiness.
/readyzonly requires the database and reports"rate_limit_storage": "degraded"when Redis is unreachable; alert on that and onaction=admission_storage_failed. - API responses now carry timestamps with an explicit
+00:00offset. - The chart's startup probe now allows 180 seconds (
startupProbe), enough for a pod to wait for another pod's migration; migration connections wait up to 30 seconds for locks. - Redis 8. The hardened Redis image is based on Redis 8.10 (Alpine 3.23) instead of 7.4 (Alpine 3.21, end of support 2026-11-01). Redis holds only rate-limit counters, which reset on the switch.
- Stricter production checks.
FORWARDED_ALLOW_IPSandtoken_validation_exemptmust be addresses or networks written without host bits and at most/8wide; database and Redis URLs must not repeat a query parameter; the database role must not be able to read all data, call server-file functions or change server parameters. Fix such settings before upgrading, or startup refuses them. - Chart. The chart
versionnow equals the release version. Unknown top-level values are refused, andshutdownDelaySecondsmust be at least 5 seconds shorter thanterminationGracePeriodSeconds. - PostgreSQL administrator. New volumes accept the administrator only
inside the database pod; the restore procedure runs that step with
kubectl exec. For existing volumes, see the upgrade guide. - Docker Compose. The application sits on a fixed
edgenetwork whose gateway is the trusted proxy (FORWARDED_ALLOW_IPSdefault172.30.240.1), and PostgreSQL and Redis on an internal network. If you setFORWARDED_ALLOW_IPSyourself, check that the access log shows clients' addresses. - Local accounts are refused while
local.enabledis false. - Releases are made from tags on commits of
mainwhose CI passed; the publishing jobs use thereleaseenvironment (add reviewers there).
Added¶
- Alembic migrations (
app/migrations) with automatic, lock-protected startup migration and apython -m app.migrate [upgrade|check|current]command. - Operator commands:
python -m app.manage create-local-adminandgrant-admin, for deployments without OAuth and for regaining administrator access. - Access log: one line per request with route template, status, duration and
client address; never query strings (
app.access_log). - Admin UI: grant or remove administrator rights, OAuth subjects shown for each account, paging through all tokens.
- Credential containment: rotate a token, revoke all of one's own tokens and sessions, administrator revocation of another account's credentials, restoration of a disabled OAuth subject.
- Helm: optional nightly PostgreSQL backups (
postgresql.backup), graceful rollouts (shutdownDelaySeconds,terminationGracePeriodSeconds), a disruption budget, a values schema and CI values. - Container images carry OCI labels (source, version, revision).
- Documentation website (
docs/, published with GitHub Pages).
Changed¶
- The service keeps serving when Redis is unavailable: admission falls back to per-process limits and retries Redis every 5 seconds. The per-account local password limiter still fails closed. This deliberately reverses the audit's fail-closed choice; see Security.
- The validation API (
/api/v1/) never needs a CSRF token; a call without a bearer token gets401withWWW-Authenticate: Bearer, and the scheme is matched case-insensitively. - Failed sign-ins (cancelled at the identity provider, expired state, disabled account, provider outage) and unknown pages show an HTML page with the right status instead of JSON.
- Dependencies updated (FastAPI 0.143, Starlette 1.8, Pydantic 2.14, SQLAlchemy 2.1, Uvicorn 0.54).
Fixed¶
- Dashboard and admin pages showed times shifted by the viewer's UTC offset.
/loginredirected to a broken URL when no OAuth client was configured.uv syncfailed because setuptools found several top-level packages.- The image build pinned an exact Debian package version, which breaks as soon as Debian publishes the next security update.
Security¶
- All findings of the October 2026 audit (F01-F34) are fixed in code, deployment templates and tests; see the report for what still needs evidence from the real environment.
- Follow-up audit (F35-F69, none critical or high); see its report. Among the fixes:
- Releases publish exactly the scanned images: built once without caches
by a job without credentials, scanned per architecture, pushed by a
separate job; tags only from
mainafter CI; no overwriting. - A hung Redis no longer causes bursts of 503 for users, the gateway and readiness; a full Redis shows as degraded.
- Docker Compose behind a host proxy no longer puts every client into one rate-limit budget.
- The Redis TLS check could be bypassed with repeated URL parameters; the database role check missed whole-cluster and server-file privileges; proxy trust accepted entries that Uvicorn ignores or that trust everyone.
- NUL characters no longer cause errors on PostgreSQL; names refuse invisible formatting characters.
- WebSocket handshakes (unlimited, logged with query strings) are refused; FastAPI's own tracing, which records OAuth codes, is off.
- The last-administrator rule counts only administrators who can sign in.
- Migrations start without the schema lock when nothing is pending, and adopt old databases only when their schema matches.
- The backup job retries until the database is reachable and deletes only its own dumps; long release names no longer break the database pod.
- Fuller audit trail, safer restore and upgrade procedures.