Summary
POST /user/setPassword (adding an initial password to a passwordless / passkey-only account) requires only an authenticated session. It performs no step-up (fresh WebAuthn assertion or recent-auth proof), and it does not invalidate or rotate sessions after the credential is added.
Severity: Medium (requires a pre-existing authenticated session; additive-only; documented limitation)
Affected code
src/main/java/com/digitalsanctuary/spring/user/api/UserAPI.java — setPassword (~488/511) only null-checks the principal.
src/main/java/com/digitalsanctuary/spring/user/service/UserService.java — setInitialPassword (~837), persistInitialPassword (~873): no session invalidation.
Details
The endpoint is correctly guarded to initial password only — hasPassword() is checked at both the API and service layers, so it cannot overwrite an existing credential, and the account's existing passkey remains valid. However, on a passwordless account there is no way to prove the caller is the legitimate owner beyond "the session is authenticated," and no step-up primitive exists in the library yet. This is acknowledged in the endpoint Javadoc and MIGRATION.md.
Impact
A caller who already controls an authenticated session on a passwordless account can add a durable password credential (additive persistence; reversible via the email reset flow). This is a persistence concern, not privilege escalation or lockout.
Recommended fix
- Implement a WebAuthn step-up assertion bound to user, session, action (
set-password), RP ID, origin, challenge, and short expiry, and require it here.
- Until step-up exists, consider disabling this endpoint by default or requiring a consumer-provided step-up service.
- Note: simply invalidating other sessions after the change would evict the legitimate user (default keeps the current session), not the attacker — step-up is the real remedy.
- Add endpoint-level tests for the auth/step-up/session behavior of
/user/setPassword (currently only service-level coverage exists).
Identified during a code-first security assessment of the current main branch.
Summary
POST /user/setPassword(adding an initial password to a passwordless / passkey-only account) requires only an authenticated session. It performs no step-up (fresh WebAuthn assertion or recent-auth proof), and it does not invalidate or rotate sessions after the credential is added.Severity: Medium (requires a pre-existing authenticated session; additive-only; documented limitation)
Affected code
src/main/java/com/digitalsanctuary/spring/user/api/UserAPI.java—setPassword(~488/511) only null-checks the principal.src/main/java/com/digitalsanctuary/spring/user/service/UserService.java—setInitialPassword(~837),persistInitialPassword(~873): no session invalidation.Details
The endpoint is correctly guarded to initial password only —
hasPassword()is checked at both the API and service layers, so it cannot overwrite an existing credential, and the account's existing passkey remains valid. However, on a passwordless account there is no way to prove the caller is the legitimate owner beyond "the session is authenticated," and no step-up primitive exists in the library yet. This is acknowledged in the endpoint Javadoc andMIGRATION.md.Impact
A caller who already controls an authenticated session on a passwordless account can add a durable password credential (additive persistence; reversible via the email reset flow). This is a persistence concern, not privilege escalation or lockout.
Recommended fix
set-password), RP ID, origin, challenge, and short expiry, and require it here./user/setPassword(currently only service-level coverage exists).Identified during a code-first security assessment of the current
mainbranch.