8000 Reject group names containing invisible Unicode characters by siddhant-pardeshi · Pull Request #54719 · keycloak/keycloak · GitHub
[go: up one dir, main page]

Skip to content

Reject group names containing invisible Unicode characters - #54719

Open
siddhant-pardeshi wants to merge 1 commit into
keycloak:mainfrom
siddhant-pardeshi:fix/group-name-invisible-unicode
Open

siddhant-pardeshi wants to merge 1 commit into
keycloak:mainfrom
siddhant-pardeshi:fix/group-name-invisible-unicode

Conversation

@siddhant-pardeshi
@siddhant-pardeshi siddhant-pardeshi commented Nov 5, 2026 •
Copy link
Copy Markdown

Closes #54532

This follows the proposal I posted on the issue (comment) — happy to adjust the approach per review, including normalization instead of rejection if preferred.

What

Group names submitted via the Admin REST API are now checked for invisible, formatting-only Unicode characters (Unicode general category Cf): zero-width space (U+200B), word joiner (U+2060), soft hyphen (U+00AD), byte order mark (U+FEFF), bidi controls, etc. Requests that create or rename a group with such a name are rejected with 400 Bad Request and the message Group name contains invisible Unicode characters.

Why

As described in #54532, such characters are accepted today without any warning. They are invisible in the Admin UI and API responses, but they are significant for exact string comparison — so a group that looks identical to another silently breaks UMA group-path matching (exact byte comparison), producing 403 access_denied with no diagnostic hint. The most common source is copy-pasting names from Word/Excel/web editors, which frequently carry U+2060 or U+200B.

Where

  • New utility Validation.containsInvisibleCharacters(...) — checks every code point against Character.FORMAT (Unicode category Cf)
  • POST /admin/realms/{realm}/groups (top-level create — validated only when creating; requests that move an existing group are unaffected)
  • POST /admin/realms/{realm}/groups/{id}/children (subgroup create — same creation-only scope)
  • PUT /admin/realms/{realm}/groups/{id} (rename — checked only when the name actually changes)

Backward compatibility / trade-offs

  • Only new writes are validated. Existing groups whose stored names already contain invisible characters are untouched: updates that leave the name unchanged are still accepted (covered by updatingLegacyGroupWithInvisibleCharactersInNameSucceeds).
  • Names with visible special characters (accents, CJK, emoji, …) remain valid — only category Cf is rejected (covered by createGroupWithVisibleSpecialCharactersNameSucceeds).
  • Realm import and organization-managed group writes are not covered by this change; happy to follow up if the same check should be applied there.
  • Follow-up (out of scope, noted in the issue): UMA policy evaluation could log a warning when a policy path differs from the requested path only by non-printable characters.

Testing

  • 5 new tests in GroupTest:
    • createGroupWithInvisibleUnicodeNameShouldFail — word joiner, zero-width space, soft hyphen on the create path
    • renamingGroupToInvisibleUnicodeNameShouldFail — rename path
    • createSubGroupWithInvisibleUnicodeNameShouldFail — subgroup path
    • createGroupWithVisibleSpecialCharactersNameSucceeds — no over-blocking of visible characters
    • updatingLegacyGroupWithInvisibleCharactersInNameSucceeds — legacy names can still be managed
  • The rejection tests assert behavior the current code does not implement, so they fail on main and pass with this change.
  • Verified locally (Windows 11, JDK 17): full build ./mvnw install -DskipTests — BUILD SUCCESS; ./mvnw -f tests/base/pom.xml -Dtest=GroupTest test — Tests run: 39, Failures: 0, Errors: 0, Skipped: 0 (34 pre-existing + 5 new).

AI disclosure

Code changes were prepared with the assistance of an AI coding agent; the author reviewed, built and tested all changes locally and takes full responsibility for the contribution (per the project's AI contribution guidelines).

@siddhant-pardeshi
siddhant-pardeshi requested a review from a team as a code owner November 5, 2026 19:16
Copilot AI balanced review requested due to automatic review settings November 5, 2026 19:16
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

Unconditional name validation incorrectly returns HTTP 400 when moving existing groups with unchanged legacy names, so both endpoints need to restrict validation to group creation.

1 open issue
What changed in this PR

This PR addresses invisible Unicode characters in realm group names that can silently break UMA group-path matching. It adds validation to Admin REST API create and rename operations.

Changes:

  • Adds a shared detector for Unicode format (Cf) characters.
  • Rejects affected group names with HTTP 400, while allowing unchanged legacy names on updates.
  • Adds five tests covering rejection, visible special characters, and legacy-name updates.
File Description
tests/​base/​src/​test/​java/​org/​keycloak/​tests/​admin/​group/​GroupTest.java Adds validation and compatibility tests.
services/​src/​main/​java/​org/​keycloak/​services/​validation/​Validation.java Adds code-point-based format-character detection.
services/​src/​main/​java/​org/​keycloak/​services/​resources/​admin/​GroupsResource.java Validates names in the top-level group endpoint.
services/​src/​main/​java/​org/​keycloak/​services/​resources/​admin/​GroupResource.java Validates names during renaming and subgroup creation.

Review effort: Balanced

Group names containing invisible or formatting-only Unicode characters
(Unicode general category Cf) such as zero-width space (U+200B), word
joiner (U+2060), soft hyphen (U+00AD), byte order mark (U+FEFF) or bidi
controls are now rejected on group creation, subgroup creation and
rename via the Admin REST API with a 400 error.

Such names were accepted silently but break exact string matching:
they are invisible in the Admin UI and API, yet significant for UMA
group-path comparison, causing authorization failures with no
diagnostic hint (most commonly after copy-pasting names from word
processors or web editors).

Updates that leave the name of an existing group unchanged are still
allowed, so realms with legacy names are not affected. Names with
visible special characters (accents, CJK, emoji) remain valid.

Closes keycloak#54532

Signed-off-by: siddhant-pardeshi <siddhantpardeshi307@gmail.com>
@siddhant-pardeshi
Copy link
Copy Markdown
Author

Good catch, thank you — confirmed and fixed.

The move branches (rep.getId() != null) in both GroupsResource.addTopLevelGroup and GroupResource.addChild do not apply the name from the representation, so validating it there wrongly blocked moving legacy groups whose stored names contain invisible characters.

The validation now runs only when a new group is created (rep.getId() == null), and a regression test was added: moveLegacyGroupWithInvisibleCharactersInNameSucceeds covers both move endpoints with an unchanged legacy name (verified locally: full GroupTest run green — numbers in the PR description).

@siddhant-pardeshi
siddhant-pardeshi force-pushed the fix/group-name-invisible-unicode branch from 0f92d13 to ad62e3c Compare November 5, 2026 20:01
Copilot AI balanced review requested due to automatic review settings November 5, 2026 20: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.

🟡 Changes recommended

The validation rejects legitimate joiners in Persian names and emoji, causing previously valid internationalized group names to receive 400 responses; targeted or contextual validation with regression tests is needed.

1 open issue
1 resolved since last review

Review effort: Balanced

if (s == null) {
return false;
}
return s.codePoints().anyMatch(cp -> Character.getType(cp) == Character.FORMAT);
@siddhant-pardeshi
Copy link
Copy Markdown
Author

Thank you for the review — this contains two different points, one factual correction and one legitimate design trade-off:

On emoji: visible characters are not affected. Emoji are Unicode category So, not Cf, and this PR includes a regression test asserting exactly that: createGroupWithVisibleSpecialCharactersNameSucceeds creates a group named "Gruppe M\u00FCnchen \u6771\u4EAC \uD83D\uDE80" (accents, CJK, emoji) and expects 201 Created.

On script-essential joiners (e.g. ZWNJ, U+200C, used in Persian orthography): this is a fair trade-off to raise. ZWNJ is category Cf, so this change does reject group names containing it. That trade-off is intentional at this scope: the failure mode described in #54532 (invisible Cf characters silently breaking UMA exact path matching) applies to ZWNJ exactly as it does to U+200B/U+2060, and rejection is the behavior requested in the issue. If maintainers prefer a more targeted approach — for example allowing the joiners U+200C/U+200D specifically, or normalizing names instead of rejecting them — I'm happy to adjust. The proposal comment on the issue already offered normalization as an alternative; this is precisely the call I'd like the core team to make.

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.

Group names with invisible Unicode characters (e.g. U+2060 WORD JOINER) are accepted without validation and silently break UMA authorization

2 participants

0