Skip to content

fix: do not leak SCIM resource existence to unauthorized callers - #52151

Merged
pedroigor merged 1 commit into
keycloak:mainfrom
waterWang:fix/scim-404-leak-52147
Sep 3, 2026
Merged

pedroigor merged 1 commit into
keycloak:mainfrom
waterWang:fix/scim-404-leak-52147

Conversation

@waterWang

Copy link
Copy Markdown
Contributor

Problem

SCIM mutative operations (patch, update, delete) and GET perform an existence check before the authorization check. A caller with no view permission sees a 404 when the resource does not exist, but a 403 when it does — leaking whether a given resource id exists. This lets callers enumerate resource ids they cannot access.

Root cause

BaseResourceTypeProvider.get(id, attributes, excludedAttributes) returns null when the model is not found, before checking any permission. The resource layer (ScimResourceTypeResource) maps that null to a 404. Only when the model does exist does the code reach the hasPermission(... VIEW) check that produces a 403.

Fix

When the model is null, first check realm-level view permission on the resource type (hasPermission(getRealmResourceType(), VIEW)). If the caller lacks it, throw ForbiddenException (→ 403) so the response is indistinguishable from an existing-but-forbidden resource. Callers who do hold view permission still receive the 404 for a truly missing resource.

Test

Added testMissingResourceDoesNotLeakExistence to AuthorizationTest: an access-less client issuing get, update, patch and delete on a non-existent user id now receives 403 for all four, instead of 404.

Closes #52147

Copilot AI balanced review requested due to automatic review settings August 27, 2026 15:51
@waterWang
waterWang requested a review from a team as a code owner August 27, 2026 15:51
@waterWang waterWang mentioned this pull request Aug 27, 2026
1 of 2 tasks

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Warning

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

Prevents SCIM endpoints from leaking whether a resource ID exists by ensuring unauthorized callers receive 403 even when the resource is missing, and adds a regression test to cover the scenario.

Changes:

  • Add an authorization regression test ensuring missing resources return 403 for callers without access.
  • Update BaseResourceTypeProvider.get(...) to check realm-level view permission when the model is missing and throw ForbiddenException if unauthorized.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
scim/tests/base/src/test/java/org/keycloak/tests/scim/tck/AuthorizationTest.java Adds coverage for the “missing resource must still be 403 when unauthorized” behavior.
scim/core/src/main/java/org/keycloak/scim/resource/spi/BaseResourceTypeProvider.java Adds permission gating on missing-model path to avoid leaking existence via 404 vs 403.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

.build()));
assertAccessDenied(() -> noAccessClient.users().delete(missingId));
}

@Test

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Query-only callers can still distinguish missing resources from existing forbidden resources.

Review details

Suppressed comments (1)

scim/core/src/main/java/org/keycloak/scim/resource/spi/BaseResourceTypeProvider.java:59

  • canQuery() also accepts QUERY_USERS/QUERY_GROUPS, but those roles do not permit reading an individual resource. A query-only caller therefore still gets 404 for a missing ID and 403 for an existing ID, so the existence leak remains; check realm-level VIEW here instead (and cover the query-only roles in the regression test).
            if (!canQuery()) {
  • Files reviewed: 2/2 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

When a SCIM resource does not exist, BaseResourceTypeProvider.get() returned
null, which the resource layer maps to a 404. A caller with no view permission
could therefore distinguish a missing resource (404) from an existing-but-
forbidden one (403), leaking whether a given resource id exists.

Fix: when the model is null, first check realm-level view permission on the
resource type and throw ForbiddenException (403) when the caller lacks it.
The 404 path is preserved for callers who do hold view permission.

Closes keycloak#52147

Signed-off-by: waterWang <waterWang@users.noreply.github.com>
Copilot AI review requested due to automatic review settings September 2, 2026 13:57
@pedroigor
pedroigor force-pushed the fix/scim-404-leak-52147 branch from 2643e74 to c7ea8c1 Compare September 2, 2026 13:57

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Query-only callers can still distinguish missing resources from existing forbidden resources.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details
  • Files reviewed: 2/2 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Comment on lines +59 to +61
if (!canQuery()) {
throw new ForbiddenException();
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also discussed here #52304 (comment)

@keycloak-github-bot keycloak-github-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unreported flaky test detected, please review

@keycloak-github-bot

Copy link
Copy Markdown

Unreported flaky test detected

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.keycloak.testsuite.adapter.servlet.SAMLServletAdapterTest#salesPostTest

Keycloak CI - Adapter IT Strict Cookies

org.opentest4j.AssertionFailedError: expected: <true> but was: <false>
	at org.junit.jupiter.api.AssertionFailureBuilder.build(AssertionFailureBuilder.java:151)
	at org.junit.jupiter.api.AssertionFailureBuilder.buildAndThrow(AssertionFailureBuilder.java:132)
	at org.junit.jupiter.api.AssertTrue.failNotTrue(AssertTrue.java:63)
	at org.junit.jupiter.api.AssertTrue.assertTrue(AssertTrue.java:36)
...

Report flaky test

org.keycloak.testsuite.adapter.servlet.SAMLServletAdapterTest#testMultipleTabsParallelLogin

Keycloak CI - Adapter IT Strict Cookies

org.opentest4j.AssertionFailedError: Expected page 'login-login' to be loaded, but currently on page 'null' after timeout
	at org.junit.jupiter.api.AssertionUtils.fail(AssertionUtils.java:38)
	at org.junit.jupiter.api.Assertions.fail(Assertions.java:138)
	at org.keycloak.testsuite.pages.AbstractPage.waitForPage(AbstractPage.java:62)
	at org.keycloak.testsuite.pages.AbstractPage.assertCurrent(AbstractPage.java:47)
...

Report flaky test

@shawkins shawkins left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For v2 we're supporting create or update via PUT, so we'll need to tweak our to accommodate a change like this in #52304 - but that's not a problem.

@keycloak-github-bot keycloak-github-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unreported flaky test detected, please review

@keycloak-github-bot

Copy link
Copy Markdown

Unreported flaky test detected

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.keycloak.testsuite.adapter.servlet.SAMLServletAdapterTest#salesPostAssertionAndResponseSigTest

Keycloak CI - Adapter IT Strict Cookies

org.opentest4j.AssertionFailedError: expected: <true> but was: <false>
	at org.junit.jupiter.api.AssertionFailureBuilder.build(AssertionFailureBuilder.java:151)
	at org.junit.jupiter.api.AssertionFailureBuilder.buildAndThrow(AssertionFailureBuilder.java:132)
	at org.junit.jupiter.api.AssertTrue.failNotTrue(AssertTrue.java:63)
	at org.junit.jupiter.api.AssertTrue.assertTrue(AssertTrue.java:36)
...

Report flaky test

org.keycloak.testsuite.adapter.servlet.SAMLServletAdapterTest#employeeSigPostNoIdpKeyTestNoKeyNameInKeyInfo

Keycloak CI - Adapter IT Strict Cookies

org.opentest4j.AssertionFailedError: expected: <true> but was: <false>
	at org.junit.jupiter.api.AssertionFailureBuilder.build(AssertionFailureBuilder.java:151)
	at org.junit.jupiter.api.AssertionFailureBuilder.buildAndThrow(AssertionFailureBuilder.java:132)
	at org.junit.jupiter.api.AssertTrue.failNotTrue(AssertTrue.java:63)
	at org.junit.jupiter.api.AssertTrue.assertTrue(AssertTrue.java:36)
...

Report flaky test

@pedroigor
pedroigor merged commit 0aa156e into keycloak:main Sep 3, 2026
94 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SCIM resource ID phishing

4 participants