github ovumcy/ovumcy-web v0.9.5

6 hours ago

Ovumcy v0.9.5

Release date: 2026-05-15

Highlights

  • Five-sprint security hardening pass driven by a methodology audit of every auth- and session-relevant surface in the codebase.
  • Two Medium-severity findings closed end-to-end (#1 OIDC end_session_endpoint host-pinning, #3 register-pickup cookie single-use), three Low-severity findings closed (#2 TOTP secret AAD binding, #5 session-version bump on 2FA toggle, #6 OIDC signing-algorithm allowlist), and three Info-level findings closed (#7 session-version bump on clear-data, #9 safe-by-construction HTMX error swap, runtime PoC suite for #1/#6).
  • New JavaScript unit-test suite, CLI subprocess smoke test, and OIDC runtime PoC suite — every new contract has a regression test.

Security

  • OIDC end_session_endpoint is now host-pinned to the configured issuer. Discovery metadata that advertises an end-session endpoint on a different host is rejected at provider load time and the logout flow falls back to local-only logout. Closes a defense-in-depth gap where a compromised or look-alike discovery document could redirect the logout flow (including any id_token_hint carried in the URL) to an attacker-controlled host.
  • OIDC ID-token verifier carries an explicit signing-algorithm allowlist (RS256, RS384, RS512, ES256, ES384, ES512, PS256, PS384, PS512, EdDSA). Symmetric algorithms and none cannot be negotiated even if the upstream provider advertises them, closing an algorithm-confusion downgrade lane.
  • Register-pickup cookie is now single-use server-side. POST /api/auth/register persists an opaque nonce in the new register_pickup_tokens table (migration 022); GET /register/welcome atomically consumes the nonce in the same UPDATE. A captured sealed ovumcy_register_pickup cookie can no longer be replayed within its 5-minute TTL to mint a second auth session — the second consume falls through to the same neutral /login redirect as a decoy or expired pickup.
  • Encrypted TOTP secrets are now bound to the owner's user id via AES-GCM additional-authenticated-data (ovumcy.field.totp_secret:<userID>). A database-level swap of one user's encrypted secret into another user's row no longer opens under the second user's id, so an attacker with database write privilege cannot pass 2FA for another account by lifting the ciphertext. A legacy fallback opens pre-aad ciphertexts written by older versions and lazily re-encrypts them under the new format on the next successful 2FA login, without bumping auth_session_version.
  • 2FA enable and disable now bump users.auth_session_version atomically with the TOTP-field update. Every other auth cookie issued before the toggle is invalidated on its next request; the originating session is refreshed inline so the user that performed the toggle stays signed in on their current device. Matches the existing contract for password change, password reset, and recovery-code regeneration.
  • POST /api/settings/clear-data now bumps users.auth_session_version atomically with the data wipe. A "panic clear" gesture from one device now invalidates every other session, and a stolen session that triggered the wipe loses its authenticated access on the next request.
  • HTMX error responses no longer flow through target.innerHTML on the client. The status-error fragment returned by the server is parsed with DOMParser, the message text is extracted via textContent, and a fresh <div class="status-error"> is built via document.createElement + replaceChildren. Server-rendered error templates already escape user-supplied values, so this is purely defense-in-depth — but any future regression that lets unescaped HTML into an error response is now safe by construction rather than an instant DOM-XSS sink.

Added

  • Frontend JavaScript unit-test suite under web/src/js/__tests__/, run via npm run test:unit (Node's built-in --test runner backed by jsdom). Twenty-seven tests pin the four security-sensitive client-side surfaces previously exercised only indirectly through Playwright: CSRF token injection on htmx:configRequest, the safe-by-construction DOM swap on htmx:responseError, the isSafeClientTimezone validator backing the ovumcy_tz cookie write, and the navigator.clipboarddocument.execCommand("copy") fallback used by the recovery-code copy UI. Wired into the CI workflow alongside lint:js.
  • Subprocess smoke test for the operator CLI (go test ./cmd/ovumcy -run TestCLISubprocessSmoke). Builds the real ovumcy binary into a temp directory and runs three subprocess invocations to cover argv parsing, env-var pickup, exit codes, and the SECRET_KEY-validator independence of users / reset-password subcommands. Skipped under go test -short.
  • Runtime PoC regression tests for the OIDC hardening (internal/security/oidc_runtime_poc_test.go). Stands up a controlled OIDC provider over real TLS with a per-test CA and exercises the production security.OIDCClient.loadProvider + *oidc.IDTokenVerifier against malicious discovery metadata and a forged algorithm-confusion token. Five contracts pinned: cross-origin end_session_endpoint rejected, same-origin accepted, HS256-with-RSA-public-key-as-HMAC-secret rejected, alg=none rejected, mock-provider sanity smoke.

Upgrade notes

  • Schema migration: running v0.9.5 for the first time will apply migration 022_register_pickup_tokens.sql (SQLite and PostgreSQL). The migration is additive — no downtime expected. The new table is bounded by the register rate limit and a 5-minute row TTL; rows are cleaned up by DeleteExpired (see SECURITY.md for the contract).
  • Downgrade caveats: as documented in docs/self-hosted.md → Downgrade Caveats, the two migrations that warrant operator attention are 019 (date canonicalization) and 022 (register-pickup tokens). Both downgrade scenarios produce graceful UX degradation rather than auth bypass, but the operator should keep a pre-019 / pre-022 backup if they intend to roll back through either boundary.
  • Legacy TOTP secrets: existing TOTP-enrolled accounts whose secret was encrypted by a pre-v0.9.5 binary continue to work. The new code opens them through a legacy fallback path and lazily re-encrypts them under the aad-bound format on the next successful 2FA login. No operator action is required.
  • OIDC operators: the new signing-algorithm allowlist is RS/ES/PS + EdDSA. If your IdP only signs with HS256, sign-in will fail closed. Reconfigure the IdP to use RS256 or one of the other allowed asymmetric algorithms.
  • Tagged images publish under ghcr.io/ovumcy/ovumcy-web:v0.9.5.
  • Existing deployments can upgrade in place by pulling the new image or by setting OVUMCY_IMAGE=ghcr.io/ovumcy/ovumcy-web:v0.9.5.

Full changelog

Don't miss a new ovumcy-web release

NewReleases is sending notifications on new releases.