Skip to content

5.1.0 — Security Hardening (SUF-01 through SUF-06)

Choose a tag to compare

@devondragon devondragon released this 11 Jul 19:47
· 102 commits to main since this release

Security hardening from the SUF review series (SUF-01 through SUF-06). Most changes are backward compatible.

This is a minor release: it adds new public API — the StepUpService SPI and new user.security.* properties (appUrl/trustedHosts/requireCanonicalAppUrl, allowInitialPasswordSetWithoutStepUp) — alongside the security hardening. No source- or binary-incompatible API changes were made. The one runtime behavior change (POST /user/setPassword disabled by default) is a fail-closed security default, reversible with a single documented property.

⚠️ Behavior change: POST /user/setPassword is now disabled by default (SUF-02). See Behavior changes below and MIGRATION.md.

Installation

Gradle:

implementation 'com.digitalsanctuary:ds-spring-user-framework:5.1.0'

Maven:

<dependency>
    <groupId>com.digitalsanctuary</groupId>
    <artifactId>ds-spring-user-framework</artifactId>
    <version>5.1.0</version>
</dependency>

Built and verified against Spring Boot 4.1.0 (compatible with 4.0.x), Java 21+.

Security

  • SUF-01 — Host-header link poisoning (CWE-640): the ordinary request host is now allow-listed, not just X-Forwarded-Host. When user.security.trustedHosts is configured, a non-allow-listed host falls back to the first configured trusted host instead of being emitted into password-reset / verification links. Ordinary-host matching is now case-insensitive (RFC 4343), and blank/whitespace trustedHosts entries are ignored. New opt-in user.security.requireCanonicalAppUrl (default false) fails startup unless appUrl or trustedHosts is configured (planned to become the default in a future major version).
  • SUF-02 — Step-up required to set an initial password on a passwordless account. A new SPI, StepUpService, lets consuming apps require fresh proof of presence/possession before POST /user/setPassword. If a StepUpService bean is present it is required (failure → 401, code 6); if none is present the endpoint is disabled by default (403, code 7). Set user.security.allowInitialPasswordSetWithoutStepUp=true to restore the previous session-only behavior. A startup warning is logged when the endpoint is left disabled-by-default.
  • SUF-03 — Revoke every user session on account delete/disable, after the change commits. Deleting or disabling an account now revokes all of that user's active sessions, deferred until after the transaction commits (closing a race where a login landing mid-scan could register a surviving session). If the transaction rolls back, sessions are not revoked.
  • SUF-04 — Authenticated password change participates in brute-force lockout. POST /user/updatePassword now rejects a locked account up front with 423 Locked, counts a wrong current password toward lockout, and resets the counter on success. Passwordless (passkey-only / OAuth-only) accounts are rejected up front with 400 and never feed the lockout counter.
  • SUF-05 — Password-reset token no longer leaks via Referer or caches (CWE-598). The reset pages now set Referrer-Policy: no-referrer and Cache-Control: no-store (via a scoped interceptor over the configured reset URIs). The token-in-body POST /user/savePassword contract is unchanged.

Fixes

  • SUF-06 (docs) — Corrected the documented audit-log rotation default in CONFIG.md (rotation stays opt-in / 0 by design).
  • SUF-06 (code hardening)showChangePasswordPage no longer mints an HttpSession for the anonymous token-validation request, and audits an invalid token as Failure rather than Success.

Behavior changes (client impact)

  • POST /user/setPassword is disabled by default (SUF-02). Returns 403 (code 7) unless a StepUpService bean is provided (then step-up is required; failures return 401, code 6), or user.security.allowInitialPasswordSetWithoutStepUp=true is set.
  • POST /user/updatePassword can now return 423 Locked in addition to 200/400. Clients treating any non-200 as failure need no change. A passwordless account calling this endpoint receives 400 and is directed to POST /user/setPassword.

Documentation

  • CONFIG.md and MIGRATION.md updated for requireCanonicalAppUrl, the ordinary-host allow-list, the setPassword step-up requirement / default-disabled behavior, allowInitialPasswordSetWithoutStepUp, and /user/updatePassword lockout participation. Both new properties are registered in the Spring configuration metadata for IDE auto-completion.

Upgrading? See the Migration Guide. Full details in the CHANGELOG.