fix: deny Spring Security's built-in WebAuthn credential-delete endpoint (GHSA-3cv9-vgqh-jwpm) - #366
Conversation
http.webAuthn(...) registers WebAuthnRegistrationFilter, exposing
DELETE /webauthn/register/{id}. That endpoint deletes a passkey after
checking only credential ownership, bypassing the framework's own
DELETE /user/webauthn/credentials/{id} safeguards: last-credential
lockout protection, current-password re-authentication, and audit
logging. An attacker with a valid session could silently delete a
victim's passkeys and permanently lock out a passkey-only account.
Deny DELETE /webauthn/register/** in the filter chain whenever
framework WebAuthn is enabled. The rule is registered before
anyRequest(), so it is evaluated ahead of WebAuthnRegistrationFilter
(added after AuthorizationFilter) and covers both deny and allow modes.
Consumers relying on the endpoint should migrate to
DELETE /user/webauthn/credentials/{id}.
Fixes GHSA-3cv9-vgqh-jwpm (CWE-862).
There was a problem hiding this comment.
Pull request overview
This PR addresses GHSA-3cv9-vgqh-jwpm by preventing use of Spring Security’s built-in WebAuthn credential deletion endpoint (DELETE /webauthn/register/{id}) when the framework’s WebAuthn feature is enabled, ensuring credential deletion only occurs via the framework-managed endpoint that enforces additional safeguards.
Changes:
- Deny
DELETE /webauthn/register/**in the security filter chain when WebAuthn is enabled. - Add an integration test asserting the endpoint returns 403 and does not delete the credential.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| src/main/java/com/digitalsanctuary/spring/user/security/WebSecurityConfig.java | Adds an authorization rule to deny Spring Security’s built-in WebAuthn credential-delete endpoint when WebAuthn is enabled. |
| src/test/java/com/digitalsanctuary/spring/user/api/WebAuthnFeatureEnabledIntegrationTest.java | Adds an integration test to verify the denied endpoint returns 403 and preserves the credential. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
ReviewSolid, well-scoped fix for a real CWE-862 gap. What's good:
A few things worth considering:
Nothing here blocks merging; the core fix looks correct and the reasoning in the comments/commit message is unusually thorough. The two test-coverage suggestions (#1 especially) would make the safety net match the threat model actually described in the PR. |
The deny rule for DELETE /webauthn/register/** is registered ahead of both defaultAction branches, but the existing test only exercised the test profile's default (defaultAction=deny). Allow mode is the scenario the fix actually targets: anyRequest().permitAll() would otherwise expose the endpoint to anonymous callers. Add a @securitytest under defaultAction=allow asserting anonymous DELETE is redirected to login (302) and authenticated DELETE is 403. Relates to GHSA-3cv9-vgqh-jwpm.
Ticket-Grounded Review: GHSA-3cv9-vgqh-jwpmGround truth for intent is security advisory GHSA-3cv9-vgqh-jwpm (no numeric issue). AC inferred from the advisory's Impact + Planned fix. Acceptance Criteria
Ticket-Scope Drift
Code review findings
Verification
Verdict: clean. No out-of-scope changes, no missing AC. Three commits: the fix, the original deny test, and the review-added allow-mode test. Remaining are non-code release follow-ups (already noted on this PR): MIGRATION/CHANGELOG note, demo-app Playwright validation, and publishing the advisory's patched version after release. Reviewed via |
ReviewThis is a well-scoped fix for GHSA-3cv9-vgqh-jwpm. Traced the change through Correctness
Test coverage
Minor / non-blocking
SecurityThe fix directly addresses the CWE-862 gap described in the advisory (ownership-only check bypassing lockout protection, re-auth, and audit logging). Denying at the filter-chain level rather than patching the built-in filter's behavior is the right call — it can't be undermined by future Spring Security changes to that filter's internals, and it fails closed if No concerns blocking merge. Nice, minimal, well-tested fix. |
Summary
Closes the missing-authorization vulnerability in GHSA-3cv9-vgqh-jwpm (CWE-862, medium).
WebSecurityConfig.setupWebAuthncallshttp.webAuthn(...), which registers Spring Security'sWebAuthnRegistrationFilterand itsDELETE /webauthn/register/{id}endpoint. That endpoint deletes a passkey after checking only credential ownership, bypassing every safeguard the framework applies on its ownDELETE /user/webauthn/credentials/{id}:Any authenticated session (stolen cookie, XSS, shared browser) could silently delete a victim's passkeys.
Change
Deny
DELETE /webauthn/register/**in the filter chain whenever framework WebAuthn is enabled. The deny rule is registered beforeanyRequest(), so it is evaluated ahead ofWebAuthnRegistrationFilter(added afterAuthorizationFilter) and applies in both deny and allow modes.POST /webauthn/registeris left untouched.Consumers relying on the Spring endpoint should migrate to
DELETE /user/webauthn/credentials/{id}.Tests
Added a case to
WebAuthnFeatureEnabledIntegrationTestasserting an authenticatedDELETE /webauthn/register/{id}returns 403 and the credential survives. Full./gradlew testsuite passes.Follow-ups (not in this PR)