You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Always validate post-logout redirect URIs in SecureRedirectUrisEnforcerExecutor - #53038
The secure-redirect-uris-enforcer executor skipped post-logout redirect URI validation when both standard flow and implicit flow were disabled, and read post-logout URIs only from the proposed representation. Together this allowed a two-step bypass: store an HTTP post-logout URI with flows off, then re-enable flows via a partial update that omits the attributes map. The stored HTTP URI survived and was used at RP-initiated logout.
What changed
verifyPostLogoutRedirectUriUpdate now runs on every CREATE/UPDATE, regardless of flow state.
When the proposed representation omits post.logout.redirect.uris, the executor revalidates the value stored on the client instead of silently accepting the update.
Upgrade impact
Two classes of client are now rejected on CREATE/UPDATE:
Both flows disabled with HTTP redirect URIs, and either no post.logout.redirect.uris attribute or the
+placeholder — both inherit the redirect URIs.
An explicit HTTP post.logout.redirect.uris stored in attributes, whether set before the policy was active or retained
through the bypass above. Partial updates that omit the attributes map now revalidate it, so even a rename is
rejected.
Remediation: set post.logo
8000
ut.redirect.uris to - to opt out of enforcement, or migrate to HTTPS. Both work before and after upgrading. Documented in the 26.8.0 upgrading guide.
An explicitly supplied "-" is converted to an empty list, so this condition replaces the intended disabled configuration with the stored URI list. A client with a pre-existing invalid URI is therefore unable to remediate it by disabling post-logout redirects; distinguish an omitted attribute from the explicit "-" sentinel before falling back.
Hi @jimmychakkalakal — I validated this PR against the two-step bypass scenario from #50982 and all 14 tests pass. The fix is solid: moving the stored ClientModel into verifyPostLogoutRedirectUriUpdate correctly closes both the flow-disabled seed path and the partial-update omission path.
While reviewing the executor layer for the same pattern, I noticed two other executors with a related hardening gap worth flagging:
SecureClientUrisExecutor
On UPDATE, confirmSecureUris(proposed) is called with no fallback to the stored model. A partial update that omits the attributes map skips revalidation of already-stored URI fields — backchannelLogoutUrl, frontChannelLogoutUrl, jwksUri, requestUris, cibaClientNotificationEndpoint, postLogoutRedirectUris, and redirectUris. Unlike #50982 there is no conditional guard that allows seeding a bad value at creation time, so this is not an exploitable bypass — but it means a pre-existing non-compliant URI (e.g. registered before the policy was active) will not be caught when the client is later updated.
SecureClientUrisPatternExecutor
Same gap — validateClientUris(proposed) on UPDATE with no stored-model fallback. Any URI field in the configured clientUriFields absent from a partial update payload is silently skipped.
Neither rises to the level of a vulnerability given that creation-time validation is not bypassable in these executors, but both are worth a follow-up hardening issue to bring them in line with the pattern this PR establishes for SecureRedirectUrisEnforcerExecutor.
For reference, RejectResourceOwnerPasswordCredentialsGrantExecutor and SamlSecureClientUrisExecutor already handle the partial-update case correctly.
Those are no deal breakers but weaknesses that might worth to consider cc @ahus1
Thanks @abstractj for validating the fix and reviewing the related executors.
I’m happy to open a separate hardening issue for partial-update handling in SecureClientUrisExecutor and SecureClientUrisPatternExecutor and follow up with a separate Issue/PR, keeping this PR focused on #50982.
Hi @mposolda , Bruno just approved this PR. Whenever you have a moment, could you take a look and merge it if everything looks good on your end? Thanks!
The reason will be displayed to describe this comment to others. Learn more.
🔵 Needs a closer look
Filtering empty post-logout redirect entries skips validation even though they remain persisted, allowing an empty logout redirect to resolve to an attacker-controlled HTTP root URL.
…erExecutor
Validation was previously skipped when standard and implicit flows were disabled. This allowed a bypass where an insecure HTTP post-logout URI could be saved while flows were off, then kept active when flows were re-enabled via a partial update (which omitted the attributes map).
Fixed by running verifyPostLogoutRedirectUriUpdate on every CREATE/UPDATE regardless of flow state. Partial updates missing the attributes map now fall back to stored ClientModel URIs to prevent retaining policy-violating values.
Closeskeycloak#50982
Signed-off-by: Jimmy Chakkalakal <jimmy.chakkalakal@ibm.com>
…erExecutor
Validation was previously skipped when standard and implicit flows were disabled. This allowed a bypass where an insecure HTTP post-logout URI could be saved while flows were off, then kept active when flows were re-enabled via a partial update (which omitted the attributes map).
Fixed by running verifyPostLogoutRedirectUriUpdate on every CREATE/UPDATE regardless of flow state. Partial updates missing the attributes map now fall back to stored ClientModel URIs to prevent retaining policy-violating values.
Closeskeycloak#50982
Signed-off-by: Jimmy Chakkalakal <jimmy.chakkalakal@ibm.com>
Clarified instructions for handling post-logout URI enforcement and updated information regarding OIDC Client CRs.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Signed-off-by: jimmychakkalakal <47317106+jimmychakkalakal@users.noreply.github.com>
If the flaky tests below are affected by the changes, please review and update the changes accordingly. Otherwise, a maintainer should report the flaky tests prior to merging the PR.
org.jboss.arquillian.graphene.enricher.exception.PageObjectInitializationException:
Can not instantiate Page Object
class org.keycloak.testsuite.adapter.page.Employee2Servlet
declared in:
org.keycloak.testsuite.adapter.servlet.SAMLLoginResponseHandlingTest
...
org.jboss.arquillian.graphene.enricher.exception.PageObjectInitializationException:
Can not instantiate Page Object
class org.keycloak.testsuite.adapter.page.Employee2Servlet
declared in:
org.keycloak.testsuite.adapter.servlet.SAMLLoginResponseHandlingTest
...
org.jboss.arquillian.graphene.enricher.exception.PageObjectInitializationException:
Can not instantiate Page Object
class org.keycloak.testsuite.adapter.page.Employee2Servlet
declared in:
org.keycloak.testsuite.adapter.servlet.SAMLLoginResponseHandlingTest
...
org.jboss.arquillian.graphene.enricher.exception.PageObjectInitializationException:
Can not instantiate Page Object
class org.keycloak.testsuite.adapter.page.Employee2Servlet
declared in:
org.keycloak.testsuite.adapter.servlet.SAMLLoginResponseHandlingTest
...
org.jboss.arquillian.graphene.enricher.exception.PageObjectInitializationException:
Can not instantiate Page Object
class org.keycloak.testsuite.adapter.page.SalesPostClockSkewServlet
declared in:
org.keycloak.testsuite.adapter.servlet.SAMLClockSkewAdapterTest
...
org.jboss.arquillian.graphene.enricher.exception.PageObjectInitializationException:
Can not instantiate Page Object
class org.keycloak.testsuite.adapter.page.SalesPostClockSkewServlet
declared in:
org.keycloak.testsuite.adapter.servlet.SAMLClockSkewAdapterTest
...
org.jboss.arquillian.graphene.enricher.exception.PageObjectInitializationException:
Can not instantiate Page Object
class org.keycloak.testsuite.adapter.page.SalesPostClockSkewServlet
declared in:
org.keycloak.testsuite.adapter.servlet.SAMLClockSkewAdapterTest
...
org.jboss.arquillian.graphene.enricher.exception.PageObjectInitializationException:
Can not instantiate Page Object
class org.keycloak.testsuite.adapter.page.EmployeeServlet
declared in:
org.keycloak.testsuite.adapter.servlet.SAMLLogoutAdapterTest
...
java.lang.RuntimeException: Could not create statement
at org.jboss.arquillian.junit.Arquillian.methodBlock(Arquillian.java:307)
at org.jboss.arquillian.junit.Arquillian$2.evaluate(Arquillian.java:160)
at org.jboss.arquillian.junit.Arquillian.multiExecute(Arquillian.java:344)
at org.jboss.arquillian.junit.Arquillian.access$200(Arquillian.java:48)
...
If the flaky tests below are affected by the changes, please review and update the changes accordingly. Otherwise, a maintainer should report the flaky tests prior to merging the PR.
org.jboss.arquillian.graphene.enricher.exception.PageObjectInitializationException:
Can not instantiate Page Object
class org.keycloak.testsuite.adapter.page.Employee2Servlet
declared in:
org.keycloak.testsuite.adapter.servlet.SAMLLoginResponseHandlingTest
...
org.jboss.arquillian.graphene.enricher.exception.PageObjectInitializationException:
Can not instantiate Page Object
class org.keycloak.testsuite.adapter.page.Employee2Servlet
declared in:
org.keycloak.testsuite.adapter.servlet.SAMLLoginResponseHandlingTest
...
org.jboss.arquillian.graphene.enricher.exception.PageObjectInitializationException:
Can not instantiate Page Object
class org.keycloak.testsuite.adapter.page.Employee2Servlet
declared in:
org.keycloak.testsuite.adapter.servlet.SAMLLoginResponseHandlingTest
...
org.jboss.arquillian.graphene.enricher.exception.PageObjectInitializationException:
Can not instantiate Page Object
class org.keycloak.testsuite.adapter.page.Employee2Servlet
declared in:
org.keycloak.testsuite.adapter.servlet.SAMLLoginResponseHandlingTest
...
org.jboss.arquillian.graphene.enricher.exception.PageObjectInitializationException:
Can not instantiate Page Object
class org.keycloak.testsuite.adapter.page.SalesPostClockSkewServlet
declared in:
org.keycloak.testsuite.adapter.servlet.SAMLClockSkewAdapterTest
...
org.jboss.arquillian.graphene.enricher.exception.PageObjectInitializationException:
Can not instantiate Page Object
class org.keycloak.testsuite.adapter.page.SalesPostClockSkewServlet
declared in:
org.keycloak.testsuite.adapter.servlet.SAMLClockSkewAdapterTest
...
org.jboss.arquillian.graphene.enricher.exception.PageObjectInitializationException:
Can not instantiate Page Object
class org.keycloak.testsuite.adapter.page.SalesPostClockSkewServlet
declared in:
org.keycloak.testsuite.adapter.servlet.SAMLClockSkewAdapterTest
...
org.jboss.arquillian.graphene.enricher.exception.PageObjectInitializationException:
Can not instantiate Page Object
class org.keycloak.testsuite.adapter.page.EmployeeServlet
declared in:
org.keycloak.testsuite.adapter.servlet.SAMLLogoutAdapterTest
...
java.lang.RuntimeException: Could not create statement
at org.jboss.arquillian.junit.Arquillian.methodBlock(Arquillian.java:307)
at org.jboss.arquillian.junit.Arquillian$2.evaluate(Arquillian.java:160)
at org.jboss.arquillian.junit.Arquillian.multiExecute(Arquillian.java:344)
at org.jboss.arquillian.junit.Arquillian.access$200(Arquillian.java:48)
...
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The
secure-redirect-uris-enforcerexecutor skipped post-logout redirect URI validation when both standard flow and implicit flow were disabled, and read post-logout URIs only from the proposed representation. Together this allowed a two-step bypass: store an HTTP post-logout URI with flows off, then re-enable flows via a partial update that omits theattributesmap. The stored HTTP URI survived and was used at RP-initiated logout.What changed
verifyPostLogoutRedirectUriUpdatenow runs on every CREATE/UPDATE, regardless of flow state.post.logout.redirect.uris, the executor revalidates the value stored on the client instead of silently accepting the update.Upgrade impact
Two classes of client are now rejected on CREATE/UPDATE:
Both flows disabled with HTTP redirect URIs, and either no
post.logout.redirect.urisattribute or the+placeholder — both inherit the redirect URIs.An explicit HTTP
post.logout.redirect.urisstored in attributes, whether set before the policy was active or retainedthrough the bypass above. Partial updates that omit the
attributesmap now revalidate it, so even a rename isrejected.
Remediation: set
post.logo 8000 ut.redirect.uristo-to opt out of enforcement, or migrate to HTTPS. Both work before and after upgrading. Documented in the 26.8.0 upgrading guide.Closes #50982
PR Description Generated with Claude Code