Skip to content

Group hierarchy search discloses hidden parent groups under FGAP v2 - #50967

Open
vramik wants to merge 1 commit into
keycloak:mainfrom
vramik:cve-891
Open

Group hierarchy search discloses hidden parent groups under FGAP v2#50967
vramik wants to merge 1 commit into
keycloak:mainfrom
vramik:cve-891

Conversation

@vramik

@vramik vramik commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Closes #50966

Design decision: Align with industry standard — parent existence (name, ID, path) is inherent to the child's identity and not hidden. Only parent content (attributes, role mappings, access) is protected. Non-viewable parents are included in the hierarchy as stripped/brief representations.

Approach: when populating group hierarchy under FGAP v2, non-viewable parent groups are represented with only structural fields (id, name, description, path, parentId) — no attributes, role mappings, or access map. The hierarchy walk continues through stripped parents so the full tree structure is preserved.

Copilot AI review requested due to automatic review settings July 16, 2026 19:04
@vramik
vramik requested a review from a team as a code owner July 16, 2026 19:04
@vramik
vramik requested a review from martin-kanis July 16, 2026 19:04

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

Prevents FGAP v2 group searches from exposing protected parent-group content while preserving hierarchy structure.

Changes:

  • Returns brief representations for non-viewable ancestors.
  • Adds an integration test for redacted parent data.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
services/src/main/java/org/keycloak/utils/GroupUtils.java Applies permission-aware hierarchy redaction.
tests/base/src/test/java/org/keycloak/tests/admin/authz/fgap/GroupResourceTypeFilteringTest.java Tests stripped hidden-parent responses.

Comment thread services/src/main/java/org/keycloak/utils/GroupUtils.java
Copilot AI review requested due to automatic review settings July 16, 2026 19:22

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

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

Comment thread services/src/main/java/org/keycloak/utils/GroupUtils.java
Copilot AI review requested due to automatic review settings July 16, 2026 19:36

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

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

Copilot AI review requested due to automatic review settings July 16, 2026 20:25

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

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

Copilot AI review requested due to automatic review settings July 21, 2026 09:01

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

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

Comments suppressed due to low confidence (1)

tests/base/src/test/java/org/keycloak/tests/admin/authz/fgap/GroupResourceTypeFilteringTest.java:102

  • This parent is top-level, so the regression test only exercises one hierarchy iteration and does not verify the stated requirement that traversal continues through multiple non-viewable ancestors. Add a grandparent (or deeper chain) and assert that every hidden ancestor is retained as a stripped representation around the visible descendant.
        GroupRepresentation parentGroup = realm.admin().groups().groups("group-0", -1, -1).get(0);

@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.forms.BrowserFlowTest#testUserWithOneAdditionalFactorOtpSuccess

Keycloak CI - Base IT (5)

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

@martin-kanis martin-kanis 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.

@vramik Thanks for the PR! Few points:

  1. There might be UI regression when an admin has VIEW on a child group but not on its parent and searches for the child group - only parent is rendered as a leaf node and the child group admin searched for is invisible
  2. Consider adding an entry to "Notable changes" in upgrading guide as we have there some similar entries for listing sessions

Copilot AI review requested due to automatic review settings July 22, 2026 21:47

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

Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.

Comment thread js/apps/admin-ui/src/groups/GroupsSection.tsx
Copilot AI review requested due to automatic review settings July 22, 2026 21:54

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

Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

js/apps/admin-ui/src/groups/GroupsSection.tsx:116

  • Group names may contain / (see GroupSearchTest.testGroupsWithSlashes), and group paths are ambiguous when slash escaping is disabled by default. Splitting the leaf path positionally can therefore label an ancestor incorrectly—for example, /parent/slash/child assigns parent instead of parent/slash; obtain ancestor names from group representations keyed by the route IDs rather than parsing path.
          const segments = lastGroup.path.split("/").filter(Boolean);

Comment thread js/apps/admin-ui/src/groups/GroupsSection.tsx Outdated

@martin-kanis martin-kanis 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.

Thanks for the update.

I would like to invite @ssilvert to have a look at UI as there is more changed lines now.

Besides the Copilot's comment you are looking at, there might be a problem with GroupPickerDialog when clicking a stripped parent row, user will get 403 error page.

The backend changes, tests and documentation looks good to me.

Comment thread js/apps/admin-ui/src/components/bread-crumb/GroupBreadCrumbs.tsx Outdated
Comment thread js/apps/admin-ui/src/groups/GroupsSection.tsx
Copilot AI review requested due to automatic review settings July 23, 2026 11:00

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

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

Comments suppressed due to low confidence (1)

js/apps/admin-ui/src/groups/components/GroupTree.tsx:351

  • This also blocks navigation for organization groups because their representations do not contain access; organization authorization is enforced separately by OrganizationGroupsResource. Include isOrgGroups in the allow condition, as GroupTable.tsx:212 already does for the same reason.
    const path = findGroup(data, item.id!, []);
    if (!(canViewDetails || path.at(-1)?.access?.view)) {
      return;

Comment thread js/apps/admin-ui/src/groups/components/GroupTree.tsx
Comment thread js/apps/admin-ui/src/components/bread-crumb/GroupBreadCrumbs.tsx Outdated

@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.forms.MultipleTabsLoginTest#testLoginPageRefresh

Keycloak CI - Forms IT (chrome)

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

edewit
edewit previously approved these changes Jul 24, 2026
error instanceof NetworkError &&
error.response.status === 403
) {
group = undefined;

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.

if there is an error wouldn't group still be undefined?

@vramik vramik Jul 24, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes, group would still be undefined. The point of the catch is not to set the value — it's to prevent the 403 NetworkError from propagating to the error boundary, which would crash the entire page. Without the catch, useFetch passes the error to showBoundary() and the page is replaced with "You don't have access to the admin console." The catch swallows the 403 so execution continues to the if (!group) check, which shows a friendly "not found" message inside the dialog.

I am more than open to suggestions for improvemnt here.

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.

But I think, if we go with this approach then if (!group) throw new Error(t("notFound")) will throw then useFetch catches it and showBoundary(error) replaces the whole page.
Cannot we filter stripped parents out of the flattened list?

In GroupPickerDialog.tsx
// line 61
const groupResource = useGroupResource();
const isOrgGroups = groupResource.isOrgGroups();

// lines 258-260
.map((g) => deepGroup([g]))
.flat()
.filter((g) => isOrgGroups || g.access)
.map((g) => (

Copilot AI review requested due to automatic review settings July 27, 2026 11:47

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

Copilot reviewed 10 out of 10 changed files in this pull request and generated 1 comment.

Comment on lines +209 to +213
<span
className={
!canViewDetails && !isOrgGroups && !group.access?.view
? "keycloak-groups-tree__non-viewable"
: undefined
Copilot AI review requested due to automatic review settings July 27, 2026 12:14

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

Copilot reviewed 10 out of 10 changed files in this pull request and generated no new comments.

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.

[CVE-2026-15945] Group hierarchy search discloses hidden parent groups under FGAP v2

4 participants