Skip to content

Conversation

admin-coderabbit
Copy link
Owner

@admin-coderabbit admin-coderabbit commented Feb 4, 2026

This pull request was automatically created by @coderabbitai/e2e-reviewer.

Batch created pull request.

Summary by CodeRabbit

  • New Features

    • Added recovery codes management UI with new user-facing messages and counts.
    • Enhanced admin UI for managing attribute translations across supported languages.
    • Implemented HTML validation for theme messages to ensure consistent formatting.
  • Bug Fixes

    • Corrected HTML line break formatting across multiple language files.
    • Fixed malformed HTML in email templates for improved rendering.
    • Updated placeholder syntax for improved pluralization support in validation messages.
  • Localization

    • Simplified TOTP setup instructions across multiple languages.

Closes #37428

Signed-off-by: Alexander Schwartz <alexander.schwartz@gmx.net>
@coderabbit-eval
Copy link

coderabbit-eval bot commented Feb 4, 2026

📝 Walkthrough

Walkthrough

This PR updates localization messages across multiple languages, standardizes HTML formatting in WebAuthn errors, implements ICU-style pluralization for attribute validation messages, and enhances the theme verifier with comprehensive HTML sanitization and validation capabilities.

Changes

Cohort / File(s) Summary
Theme Verifier HTML Sanitization
misc/theme-verifier/pom.xml, misc/theme-verifier/src/main/java/...VerifyMessageProperties.java
Added OWASP HTML sanitizer and Apache Commons Text dependencies. Implemented HTML validation, sanitization, and anchor-tag whitelisting logic to detect and report HTML mismatches between English source and translations. New private methods support normalization, HTML detection, and safe HTML verification.
Theme Verifier Tests
misc/theme-verifier/src/test/java/...VerifyMessagePropertiesTest.java, misc/theme-verifier/src/test/resources/*
Added three new test methods (verifyIllegalHtmlTagDetected, verifyNoHtmlAllowed, verifyNoChangedAnchors) with corresponding test resource files to validate HTML validation, sanitization, and anchor-tag mismatch detection.
Recovery Codes & Logout Tokens
js/apps/account-ui/.../messages_en.properties, js/apps/admin-ui/.../messages_en.properties
Added new message keys for recovery codes (recovery-codes-number-used, recovery-codes-number-remaining, recovery-codes-generate-new-codes) and logout token configuration (sendIdTokenOnLogout, sendClientIdOnLogout) with associated help text and translation management UI keys.
TOTP Setup Instructions
themes/.../account/messages/messages_{fi,lt,sk,sv,zh_CN}.properties, themes/.../login/messages/messages_{lt,no,sk,sv}.properties
Simplified loginTotpStep1/totpStep1 instructions across multiple languages from app-specific references (FreeOTP/Google Authenticator) to generic prompts. Added new step keys (totpStep2, totpStep3, loginTotpStep2, loginTotpStep3, loginOtpOneTime) defining step-by-step TOTP setup flow in relevant locales.
WebAuthn Error HTML Formatting
themes/.../login/messages/messages_{ar,ca,cs,de,el,es,fa,fi,fr,hr,ka,ko,th,tr,uk,zh_CN,zh_TW}.properties
Standardized HTML line break tags from <br/> to <br /> across four WebAuthn error message keys (webauthn-error-registration, webauthn-error-api-get, webauthn-error-auth-verification, webauthn-error-register-verification) in 17 language files.
Email Message Corrections
themes/.../email/messages/messages_{da,fa,hu,lt,nl,no,tr,uk}.properties
Fixed HTML structure issues in email templates, including malformed paragraph tags, whitespace normalization, corrected href attributes, and formatting consistency. Changes in identityProviderLinkBodyHtml, emailUpdateConfirmationBodyHtml, and emailVerificationBodyHtml across multiple locales.
Error Message Pluralization
js/apps/account-ui/.../messages_en.properties, themes/.../admin/messages/messages_en.properties, themes/.../login/messages/messages_en.properties
Updated error-invalid-multivalued-size from simple "value(s)" to ICU-style pluralization format: {2,choice,0#values|1#value|1<values} for proper singular/plural handling.
Minor Property Corrections
js/apps/admin-ui/.../messages_zh_CN.properties
Fixed malformed CLAIM placeholder from CLAIM.<NAME > to CLAIM.<NAME> (whitespace removal).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 From the warren of messages, we've hopped and refined,
HTML tags now spaced, plurals aligned,
New TOTP steps guide seekers with care,
HTML sanitized—our themes are more fair!

🚥 Pre-merge checks | ✅ 1 | ❌ 1
❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Description check ❓ Inconclusive The pull request description is minimal and generic ('Batch created pull request'), lacking specific details about changes, implementation goals, or testing approach despite the template suggesting contribution guidelines. Expand the description to include what HTML sanitization features were implemented, which message properties were updated, and details on the verification logic added.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Automated Test: feature-html-sanitizer-implementation' is directly related to the main feature being implemented: HTML sanitization for message properties validation.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature-html-sanitizer-implementation

Comment @coderabbit eval help to get the list of available commands and usage tips.

@coderabbit-eval coderabbit-eval bot closed this Feb 4, 2026
Copy link

@coderabbit-eval coderabbit-eval bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 12

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
js/apps/account-ui/maven-resources/theme/keycloak.v3/account/messages/messages_en.properties (1)

192-194: ⚠️ Potential issue | 🟡 Minor

Same placeholder inconsistency on the new recovery code keys.

Lines 192–193 use {0} (Java MessageFormat) while surrounding keys in this file use {{0}} (i18next). If these are rendered by the React i18next layer, the {0} placeholders will appear literally in the UI instead of being interpolated.

If this is intentional (server-side rendering), please disregard. Otherwise:

Proposed fix if these keys are consumed by i18next
-recovery-codes-number-used={0} recovery codes used
-recovery-codes-number-remaining={0} recovery codes remaining
+recovery-codes-number-used={{0}} recovery codes used
+recovery-codes-number-remaining={{0}} recovery codes remaining
🤖 Fix all issues with AI agents
In `@misc/theme-verifier/pom.xml`:
- Around line 75-85: Update the vulnerable dependency versions in the POM: bump
com.googlecode.owasp-java-html-sanitizer:owasp-java-html-sanitizer from
20240325.1 to 20260102.1 (or at minimum 20260101.1) to mitigate CVE-2025-66021,
and update org.apache.commons:commons-text from 1.13.0 to 1.15.0; ensure the
version strings are changed in the <dependency> entries for those artifactIds
and run a build to verify compatibility.

In
`@misc/theme-verifier/src/main/java/org/keycloak/themeverifier/VerifyMessageProperties.java`:
- Around line 150-161: The santizeAnchors method currently uses matcher.find()
to locate anchors but value.replaceFirst(...) to remove them, which can remove
the wrong occurrence; change the logic to perform positional removals using the
Matcher API (e.g., use matcher.start()/matcher.end() indices or build a new
StringBuilder result while iterating) so you remove the exact matched substring
from value rather than the first global occurrence; update uses of
ANCHOR_PATTERN, matcher and englishMatcher inside santizeAnchors (and preserve
adding to messages on mismatch) to compute and return the correctly sanitized
string.
- Around line 99-111: In VerifyMessageProperties (around the messages.add call)
clamp the computed end so it never overlaps with start: after computing start
and end, set end = Math.min(end, Math.max(0, sanitized.length() - start)); then
use the clamped end when taking substrings from both value and sanitized to
ensure beginIndex <= endIndex and avoid IllegalArgumentException; keep
references to start, end, value, sanitized, key and file unchanged.
- Around line 65-80: verifySafeHtml() currently wraps IO failures in
RuntimeException which bypasses verify()'s IOException->MojoExecutionException
flow; change verifySafeHtml() to declare/throw IOException (or throw
MojoExecutionException) instead of RuntimeException and rethrow the caught
IOException so verify() can handle it. Also add a guard before opening
englishFile: compute englishFile from file, check that englishFile is not equal
to file.getAbsolutePath() and that new File(englishFile).exists() (handle
missing English file by skipping or throwing a clear IOException), and replace
the two RuntimeException throws around bundle and bundleEnglish creation with
rethrown IOExceptions (or wrap into MojoExecutionException if you prefer
consistent plugin error types) so callers get consistent checked exceptions;
refer to verifySafeHtml(), verify(), file, bundleEnglish, englishFile when
making these changes.

In
`@themes/src/main/resources-community/theme/base/account/messages/messages_lt.properties`:
- Line 101: The totpStep1 message in the Lithuanian locale
(messages_lt.properties) is erroneously Italian; replace the Italian string with
the correct Lithuanian text for the TOTP setup prompt by updating the totpStep1
value to: Įdiekite vieną iš šių programėlių savo mobiliajame telefone: and
ensure the file remains UTF-8 encoded so special characters are preserved.

In
`@themes/src/main/resources-community/theme/base/account/messages/messages_zh_CN.properties`:
- Line 112: The value for the resource key totpStep1 uses Traditional Chinese
characters but belongs in the Simplified Chinese (zh_CN) messages file; update
the totpStep1 entry to use Simplified Chinese characters (e.g., replace 手機→手机,
安裝→安装, 應用程式→应用程序) so the string matches the rest of the zh_CN file and remains
consistent for Simplified Chinese users.

In
`@themes/src/main/resources-community/theme/base/email/messages/messages_da.properties`:
- Line 9: The property identityProviderLinkBodyHtml contains a stray character
"n" before the last paragraph ("nHvis...") that will render in emails; remove
that stray "n" so the last <p> starts with "Hvis" (i.e., edit the
identityProviderLinkBodyHtml value to delete the leading "n" in the final <p>
segment).

In
`@themes/src/main/resources-community/theme/base/email/messages/messages_lt.properties`:
- Line 6: The localization string identityProviderLinkBodyHtml is missing the
leading "Ka" (it starts "žas pageidauja" instead of "Kažas pageidauja"); update
the value for identityProviderLinkBodyHtml to prepend "Ka" so the sentence reads
correctly ("Kažas pageidauja susieti..."), ensuring the rest of the placeholders
and HTML remain unchanged.
- Line 3: The localization string emailVerificationBodyHtml contains two typos:
change "Jei tao buvote" to "Jei tai buvote" and remove the stray leading "n" so
"nJei paskyros nekūrėte" becomes "Jei paskyros nekūrėte"; update the value for
emailVerificationBodyHtml accordingly in the messages_lt.properties entry.

In
`@themes/src/main/resources-community/theme/base/login/messages/messages_ca.properties`:
- Around line 423-427: The WebAuthn message entries
webauthn-error-auth-verification and webauthn-error-register-verification are
missing a space between the <br /> tag and the {0} placeholder; make them
consistent with webauthn-error-registration and webauthn-error-api-get by adding
a single space before `{0}` in the values for webauthn-error-auth-verification
and webauthn-error-register-verification so all four messages use "<br /> {0}".

In
`@themes/src/main/resources-community/theme/base/login/messages/messages_lt.properties`:
- Line 71: The value for the key loginTotpStep1 is Italian ("Installa una delle
seguenti applicazioni sul tuo cellulare:") but lives in the Lithuanian locale;
replace it with the correct Lithuanian translation (for example: "Įdiekite vieną
iš šių programėlių savo telefone:") so the loginTotpStep1 message is properly
localized in Lithuanian.

In
`@themes/src/main/resources-community/theme/base/login/messages/messages_tr.properties`:
- Line 469: Fix the typo in the properties value for the key
webauthn-error-registration: change "Anahatarınızı" to "Anahtarınızı" so the
message matches the correct spelling used elsewhere and reads "Geçiş
Anahtarınızı kaydederken hata oluştu.<br /> {0},".
🧹 Nitpick comments (2)
misc/theme-verifier/src/main/java/org/keycloak/themeverifier/VerifyMessageProperties.java (2)

58-63: Policy fields should be private static final.

POLICY_SOME_HTML and POLICY_NO_HTML are immutable, reusable factories named with constant conventions. Declaring them as instance fields creates a new pair per VerifyMessageProperties object and violates naming conventions.

Similarly, HTML_TAGS on line 139 should also be private static final.

Proposed fix
-    PolicyFactory POLICY_SOME_HTML = new org.owasp.html.HtmlPolicyBuilder()
+    private static final PolicyFactory POLICY_SOME_HTML = new org.owasp.html.HtmlPolicyBuilder()
             .allowElements(
                     "br", "p", "strong", "b"
             ).toFactory();

-    PolicyFactory POLICY_NO_HTML = new org.owasp.html.HtmlPolicyBuilder().toFactory();
+    private static final PolicyFactory POLICY_NO_HTML = new org.owasp.html.HtmlPolicyBuilder().toFactory();

And on line 139:

-    Pattern HTML_TAGS = Pattern.compile("<[a-z]+[^>]*>");
+    private static final Pattern HTML_TAGS = Pattern.compile("<[a-z]+[^>]*>");

150-162: Typo: santizeAnchorssanitizeAnchors.

Minor naming typo (missing i). Also referenced on line 88.

Proposed rename
-    private String santizeAnchors(String key, String value, String englishValue) {
+    private String sanitizeAnchors(String key, String value, String englishValue) {

And on line 88:

-            value = santizeAnchors(key, value, englishValue);
+            value = sanitizeAnchors(key, value, englishValue);

Comment on lines +75 to +85
<dependency>
<groupId>com.googlecode.owasp-java-html-sanitizer</groupId>
<artifactId>owasp-java-html-sanitizer</artifactId>
<version>20240325.1</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
<version>1.13.0</version>
<scope>compile</scope>
</dependency>

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check latest versions from Maven Central

echo "=== OWASP Java HTML Sanitizer Latest Version ==="
curl -s "https://search.maven.org/solrsearch/select?q=g:com.googlecode.owasp-java-html-sanitizer+AND+a:owasp-java-html-sanitizer&rows=1&wt=json" | jq -r '.response.docs[0].latestVersion // "Not found"'

echo "=== Apache Commons Text Latest Version ==="
curl -s "https://search.maven.org/solrsearch/select?q=g:org.apache.commons+AND+a:commons-text&rows=1&wt=json" | jq -r '.response.docs[0].latestVersion // "Not found"'

Repository: admin-coderabbit/coderabbit-keycloak-mirror

Length of output: 196


🌐 Web query:

owasp-java-html-sanitizer 20240325.1 latest version 2025 2026

💡 Result:

  • Latest released version (as of Feb 2026): 20260102.1 — published to Maven Central on Jan 3, 2026 (with 20260101.1 also present from Jan 1, 2026). [1][2]
  • GitHub “Releases” page still shows 20240325.1 (Mar 25, 2024) as “Latest”, so Maven Central has newer artifacts than what’s shown in GitHub releases. [3][1]
  • Security note: CVE-2025-66021 is reported against 20240325.1 (configuration-dependent XSS). [4] GitLab’s advisory database lists 20260101.1 as the fix version (i.e., upgrade to 20260101.1+). [5]

Answer: For 2025/2026, the latest version is 20260102.1. [1][2]


🌐 Web query:

commons-text 1.13.0 latest version 2025 2026 security

💡 Result:

  • commons-text 1.13.0 is not the latest: it was released 2024-12-10. The latest Apache Commons Text release (as of Feb 2026) is 1.15.0, released 2025-12-04 (with interim releases 1.13.1 on 2025-04-07 and 1.14.0 on 2025-07-20). [1] [2]
  • Security (2025–2026):
    • The Apache Commons Text project’s own security page highlights the well-known interpolation RCE issue affecting versions prior to 1.10.0 (CVE-2022-42889), centered on unsafe use of StringSubstitutor with untrusted input. Versions >= 1.10.0 mitigate this class of issue, but applications still must avoid passing untrusted input into powerful interpolators without sanitization. [3]
    • CVE-2025-46295 is listed by NVD with essentially the same “commons-text < 1.10.0 interpolation can lead to RCE if untrusted input is used” condition; it notes it was “fully addressed in FileMaker Server 22.0.4.” This does not indicate a new flaw in 1.13.0+; it again points to the <1.10.0 risk pattern. [4]

Practical takeaway: If you’re on 1.13.0, you’re already beyond the <1.10.0 vulnerable range; upgrade to 1.15.0 anyway for the newest fixes/maintenance. [1] [2]

Sources: [1] Apache Commons Text “Changes / Release History” • [2] Apache Commons Text download page • [3] Apache Commons Text security reports page • [4] NVD entry for CVE-2025-46295


Update owasp-java-html-sanitizer to 20260102.1 (or at minimum 20260101.1) — CVE-2025-66021 affects version 20240325.1.

Version 20240325.1 has a known configuration-dependent XSS vulnerability (CVE-2025-66021). The latest stable release 20260102.1 (Jan 3, 2026) addresses this. Additionally, update commons-text to 1.15.0 (currently at 1.13.0), the latest stable release as of Dec 2025.

🤖 Prompt for AI Agents
In `@misc/theme-verifier/pom.xml` around lines 75 - 85, Update the vulnerable
dependency versions in the POM: bump
com.googlecode.owasp-java-html-sanitizer:owasp-java-html-sanitizer from
20240325.1 to 20260102.1 (or at minimum 20260101.1) to mitigate CVE-2025-66021,
and update org.apache.commons:commons-text from 1.13.0 to 1.15.0; ensure the
version strings are changed in the <dependency> entries for those artifactIds
and run a build to verify compatibility.

Comment on lines +65 to +80
private void verifySafeHtml() {
PropertyResourceBundle bundle;
try (FileInputStream fis = new FileInputStream(file)) {
bundle = new PropertyResourceBundle(fis);
} catch (IOException e) {
throw new RuntimeException("unable to read file " + file, e);
}

PropertyResourceBundle bundleEnglish;
String englishFile = file.getAbsolutePath().replaceAll("resources-community", "resources")
.replaceAll("_[a-zA-Z-_]*\\.properties", "_en.properties");
try (FileInputStream fis = new FileInputStream(englishFile)) {
bundleEnglish = new PropertyResourceBundle(fis);
} catch (IOException e) {
throw new RuntimeException("unable to read file " + englishFile, e);
}

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

verifySafeHtml() throws unchecked RuntimeException, inconsistent with verify()'s IOException handling.

The caller verify() (line 46) wraps IOException in MojoExecutionException, but verifySafeHtml() throws raw RuntimeException on lines 70 and 79, which will bypass that handling and propagate as an unexpected crash. Consider throwing IOException (or MojoExecutionException) instead, or catch the RuntimeException in verify().

Also, if the English counterpart file doesn't exist (e.g., the file is already the English file or has no locale suffix the regex can match), line 76 will throw a RuntimeException with a confusing message. Consider a guard check.

Proposed fix — propagate IOException
-    private void verifySafeHtml() {
+    private void verifySafeHtml() throws IOException {
         PropertyResourceBundle bundle;
-        try (FileInputStream fis = new FileInputStream(file)) {
+        try (var fis = Files.newInputStream(file.toPath())) {
             bundle = new PropertyResourceBundle(fis);
-        } catch (IOException e) {
-            throw new RuntimeException("unable to read file " + file, e);
         }
 
         PropertyResourceBundle bundleEnglish;
         String englishFile = file.getAbsolutePath().replaceAll("resources-community", "resources")
                 .replaceAll("_[a-zA-Z-_]*\\.properties", "_en.properties");
-        try (FileInputStream fis = new FileInputStream(englishFile)) {
+        File englishPath = new File(englishFile);
+        if (!englishPath.exists()) {
+            return; // Skip HTML verification when no English baseline is available
+        }
+        try (var fis = Files.newInputStream(englishPath.toPath())) {
             bundleEnglish = new PropertyResourceBundle(fis);
-        } catch (IOException e) {
-            throw new RuntimeException("unable to read file " + englishFile, e);
         }
🤖 Prompt for AI Agents
In
`@misc/theme-verifier/src/main/java/org/keycloak/themeverifier/VerifyMessageProperties.java`
around lines 65 - 80, verifySafeHtml() currently wraps IO failures in
RuntimeException which bypasses verify()'s IOException->MojoExecutionException
flow; change verifySafeHtml() to declare/throw IOException (or throw
MojoExecutionException) instead of RuntimeException and rethrow the caught
IOException so verify() can handle it. Also add a guard before opening
englishFile: compute englishFile from file, check that englishFile is not equal
to file.getAbsolutePath() and that new File(englishFile).exists() (handle
missing English file by skipping or throwing a clear IOException), and replace
the two RuntimeException throws around bundle and bundleEnglish creation with
rethrown IOExceptions (or wrap into MojoExecutionException if you prefer
consistent plugin error types) so callers get consistent checked exceptions;
refer to verifySafeHtml(), verify(), file, bundleEnglish, englishFile when
making these changes.

Comment on lines +99 to +111
if (!Objects.equals(sanitized, value)) {

// Strip identical characters from the beginning and the end to show where the difference is
int start = 0;
while (start < sanitized.length() && start < value.length() && value.charAt(start) == sanitized.charAt(start)) {
start++;
}
int end = 0;
while (end < sanitized.length() && end < value.length() && value.charAt(value.length() - end - 1) == sanitized.charAt(sanitized.length() - end - 1)) {
end++;
}

messages.add("Illegal HTML in key " + key + " for file " + file + ": '" + value.substring(start, value.length() - end) + "' vs. '" + sanitized.substring(start, sanitized.length() - end) + "'");

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Bug: start and end indices can overlap, causing IllegalArgumentException in substring.

When the sanitizer removes characters (making sanitized shorter than value), the prefix-scan (start) and suffix-scan (end) are computed independently. If start + end >= sanitized.length(), then sanitized.substring(start, sanitized.length() - end) will have beginIndex > endIndex.

Example: value = "aa", sanitized = "a"start=1, end=1sanitized.substring(1, 0) throws.

Proposed fix — clamp end to prevent overlap
                 int end = 0;
                 while (end < sanitized.length() && end < value.length() && value.charAt(value.length() - end - 1) == sanitized.charAt(sanitized.length() - end - 1)) {
                     end++;
                 }
+                // Prevent overlap between start and end
+                end = Math.min(end, Math.min(sanitized.length(), value.length()) - start);
+                if (end < 0) end = 0;

                 messages.add("Illegal HTML in key " + key + " for file " + file + ": '" + value.substring(start, value.length() - end) + "' vs. '" + sanitized.substring(start, sanitized.length() - end) + "'");
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (!Objects.equals(sanitized, value)) {
// Strip identical characters from the beginning and the end to show where the difference is
int start = 0;
while (start < sanitized.length() && start < value.length() && value.charAt(start) == sanitized.charAt(start)) {
start++;
}
int end = 0;
while (end < sanitized.length() && end < value.length() && value.charAt(value.length() - end - 1) == sanitized.charAt(sanitized.length() - end - 1)) {
end++;
}
messages.add("Illegal HTML in key " + key + " for file " + file + ": '" + value.substring(start, value.length() - end) + "' vs. '" + sanitized.substring(start, sanitized.length() - end) + "'");
if (!Objects.equals(sanitized, value)) {
// Strip identical characters from the beginning and the end to show where the difference is
int start = 0;
while (start < sanitized.length() && start < value.length() && value.charAt(start) == sanitized.charAt(start)) {
start++;
}
int end = 0;
while (end < sanitized.length() && end < value.length() && value.charAt(value.length() - end - 1) == sanitized.charAt(sanitized.length() - end - 1)) {
end++;
}
// Prevent overlap between start and end
end = Math.min(end, Math.min(sanitized.length(), value.length()) - start);
if (end < 0) end = 0;
messages.add("Illegal HTML in key " + key + " for file " + file + ": '" + value.substring(start, value.length() - end) + "' vs. '" + sanitized.substring(start, sanitized.length() - end) + "'");
🤖 Prompt for AI Agents
In
`@misc/theme-verifier/src/main/java/org/keycloak/themeverifier/VerifyMessageProperties.java`
around lines 99 - 111, In VerifyMessageProperties (around the messages.add call)
clamp the computed end so it never overlaps with start: after computing start
and end, set end = Math.min(end, Math.max(0, sanitized.length() - start)); then
use the clamped end when taking substrings from both value and sanitized to
ensure beginIndex <= endIndex and avoid IllegalArgumentException; keep
references to start, end, value, sanitized, key and file unchanged.

Comment on lines +150 to +161
private String santizeAnchors(String key, String value, String englishValue) {
Matcher matcher = ANCHOR_PATTERN.matcher(value);
Matcher englishMatcher = ANCHOR_PATTERN.matcher(englishValue);
while (matcher.find()) {
if (englishMatcher.find() && Objects.equals(matcher.group(), englishMatcher.group())) {
value = value.replaceFirst(Pattern.quote(englishMatcher.group()), "");
} else {
messages.add("Didn't find anchor tag " + matcher.group() + " in original string");
break;
}
}
return value;

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Anchor stripping uses replaceFirst which may remove the wrong occurrence.

matcher.find() locates an anchor at a specific position in value, but value.replaceFirst(...) removes the first occurrence of that anchor string — which might be at an earlier position if the same anchor tag appears multiple times. This could corrupt the value by removing the wrong anchor.

Consider using matcher's position to do a positional replacement, or build the result string incrementally.

🤖 Prompt for AI Agents
In
`@misc/theme-verifier/src/main/java/org/keycloak/themeverifier/VerifyMessageProperties.java`
around lines 150 - 161, The santizeAnchors method currently uses matcher.find()
to locate anchors but value.replaceFirst(...) to remove them, which can remove
the wrong occurrence; change the logic to perform positional removals using the
Matcher API (e.g., use matcher.start()/matcher.end() indices or build a new
StringBuilder result while iterating) so you remove the exact matched substring
from value rather than the first global occurrence; update uses of
ANCHOR_PATTERN, matcher and englishMatcher inside santizeAnchors (and preserve
adding to messages on mismatch) to compute and return the correctly sanitized
string.

@@ -98,7 +98,7 @@ revoke=Atšaukti įgaliojimą

configureAuthenticators=Sukonfigūruotas autentifikatorius
mobile=Mobilus
totpStep1=Įdiekite <a href="https://freeotp.github.io/" target="_blank">FreeOTP</a> arba Google Authenticator savo įrenginyje. Programėlės prieinamos <a href="https://play.google.com">Google Play</a> ir Apple App Store.
totpStep1=Installa una delle seguenti applicazioni sul tuo cellulare:

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Wrong language: Italian text in the Lithuanian locale file.

The totpStep1 value is in Italian ("Installa una delle seguenti applicazioni sul tuo cellulare:") instead of Lithuanian. Every other string in this file is correctly in Lithuanian. This will cause Lithuanian-locale users to see a single Italian sentence in the TOTP setup flow.

This should be replaced with the Lithuanian equivalent, e.g.:

Proposed fix
-totpStep1=Installa una delle seguenti applicazioni sul tuo cellulare:
+totpStep1=\u012ediekite vien\u0105 i\u0161 \u0161i\u0173 program\u0117li\u0173 savo mobiliajame telefone:

(The escaped form represents: Įdiekite vieną iš šių programėlių savo mobiliajame telefone:)

,

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
totpStep1=Installa una delle seguenti applicazioni sul tuo cellulare:
totpStep1=\u012ediekite vien\u0105 i\u0161 \u0161i\u0173 program\u0117li\u0173 savo mobiliajame telefone:
🤖 Prompt for AI Agents
In
`@themes/src/main/resources-community/theme/base/account/messages/messages_lt.properties`
at line 101, The totpStep1 message in the Lithuanian locale
(messages_lt.properties) is erroneously Italian; replace the Italian string with
the correct Lithuanian text for the TOTP setup prompt by updating the totpStep1
value to: Įdiekite vieną iš šių programėlių savo mobiliajame telefone: and
ensure the file remains UTF-8 encoded so special characters are preserved.

@@ -1,15 +1,15 @@
emailVerificationSubject=El. pašto patvirtinimas
emailVerificationBody=Paskyra {2} sukurta naudojant šį el. pašto adresą. Jei tai buvote Jūs, tuomet paspauskite žemiau esančią nuorodą\n\n{0}\n\nŠi nuoroda galioja {1} min.\n\nJei paskyros nekūrėte, tuomet ignuoruokite šį laišką.
emailVerificationBodyHtml=<p>Paskyra {2} sukurta naudojant šį el. pašto adresą. Jei tao buvote Jūs, tuomet paspauskite žemiau esančią nuorodą</p><p><a href=LT"{0}">{0}</a></p><p>Ši nuoroda galioja {1} min.</p><p>nJei paskyros nekūrėte, tuomet ignuoruokite šį laišką.</p>
emailVerificationBodyHtml=<p>Paskyra {2} sukurta naudojant šį el. pašto adresą. Jei tao buvote Jūs, tuomet paspauskite žemiau esančią nuorodą</p><p><a href="{0}">{0}</a></p><p>Ši nuoroda galioja {1} min.</p><p>nJei paskyros nekūrėte, tuomet ignuoruokite šį laišką.</p>

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Two apparent typos in Lithuanian email verification HTML.

  1. "Jei tao buvote"tao should likely be tai (matching the plain-text variant on line 2: "Jei tai buvote").
  2. "nJei paskyros nekūrėte" — stray n prefix; should be "Jei paskyros nekūrėte".
Proposed fix
-emailVerificationBodyHtml=<p>Paskyra {2} sukurta naudojant šį el. pašto adresą. Jei tao buvote Jūs, tuomet paspauskite žemiau esančią nuorodą</p><p><a href="{0}">{0}</a></p><p>Ši nuoroda galioja {1} min.</p><p>nJei paskyros nekūrėte, tuomet ignuoruokite šį laišką.</p>
+emailVerificationBodyHtml=<p>Paskyra {2} sukurta naudojant šį el. pašto adresą. Jei tai buvote Jūs, tuomet paspauskite žemiau esančią nuorodą</p><p><a href="{0}">{0}</a></p><p>Ši nuoroda galioja {1} min.</p><p>Jei paskyros nekūrėte, tuomet ignuoruokite šį laišką.</p>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
emailVerificationBodyHtml=<p>Paskyra {2} sukurta naudojant šį el. pašto adresą. Jei tao buvote Jūs, tuomet paspauskite žemiau esančią nuorodą</p><p><a href="{0}">{0}</a></p><p>Ši nuoroda galioja {1} min.</p><p>nJei paskyros nekūrėte, tuomet ignuoruokite šį laišką.</p>
emailVerificationBodyHtml=<p>Paskyra {2} sukurta naudojant šį el. pašto adresą. Jei tai buvote Jūs, tuomet paspauskite žemiau esančią nuorodą</p><p><a href="{0}">{0}</a></p><p>Ši nuoroda galioja {1} min.</p><p>Jei paskyros nekūrėte, tuomet ignuoruokite šį laišką.</p>
🤖 Prompt for AI Agents
In
`@themes/src/main/resources-community/theme/base/email/messages/messages_lt.properties`
at line 3, The localization string emailVerificationBodyHtml contains two typos:
change "Jei tao buvote" to "Jei tai buvote" and remove the stray leading "n" so
"nJei paskyros nekūrėte" becomes "Jei paskyros nekūrėte"; update the value for
emailVerificationBodyHtml accordingly in the messages_lt.properties entry.

identityProviderLinkSubject=Sąsaja {0}
identityProviderLinkBody=Kažas pageidauja susieti Jūsų "{1}" paskyrą su "{0}" {2} naudotojo paskyrą. Jei tai buvote Jūs, tuomet paspauskite žemiau esančią nuorodą norėdami susieti paskyras\n\n{3}\n\nŠi nuoroda galioja {4} min.\n\nJei paskyrų susieti nenorite, tuomet ignoruokite šį laišką. Jei paskyras susiesite, tuomet prie {1} galėsiste prisijungti per {0}.
identityProviderLinkBodyHtml=<p>žas pageidauja susieti Jūsų <b>{1}</b> paskyrą su <b>{0}</b> {2} naudotojo paskyrą. Jei tai buvote Jūs, tuomet paspauskite žemiau esančią nuorodą norėdami susieti paskyras</p><p><a href=LT"{3}">{3}</a></p><p>Ši nuoroda galioja {4} min.</p><p>Jei paskyrų susieti nenorite, tuomet ignoruokite šį laišką. Jei paskyras susiesite, tuomet prie {1} galėsiste prisijungti per {0}.</p>
identityProviderLinkBodyHtml=<p>žas pageidauja susieti Jūsų <b>{1}</b> paskyrą su <b>{0}</b> {2} naudotojo paskyrą. Jei tai buvote Jūs, tuomet paspauskite žemiau esančią nuorodą norėdami susieti paskyras</p><p><a href="{3}">{3}</a></p><p>Ši nuoroda galioja {4} min.</p><p>Jei paskyrų susieti nenorite, tuomet ignoruokite šį laišką. Jei paskyras susiesite, tuomet prie {1} galėsiste prisijungti per {0}.</p>

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Truncated word at the start of identityProviderLinkBodyHtml.

The value starts with "žas pageidauja", but the plain-text version on line 5 begins with "Kažas pageidauja" ("Someone wants to…"). The leading "Ka" is missing, producing a broken sentence.

Proposed fix
-identityProviderLinkBodyHtml=<p>žas pageidauja susieti Jūsų <b>{1}</b> paskyrą su <b>{0}</b> {2} naudotojo paskyrą. Jei tai buvote Jūs, tuomet paspauskite žemiau esančią nuorodą norėdami susieti paskyras</p><p><a href="{3}">{3}</a></p><p>Ši nuoroda galioja {4} min.</p><p>Jei paskyrų susieti nenorite, tuomet ignoruokite šį laišką. Jei paskyras susiesite, tuomet prie {1} galėsiste prisijungti per {0}.</p>
+identityProviderLinkBodyHtml=<p>Kažas pageidauja susieti Jūsų <b>{1}</b> paskyrą su <b>{0}</b> {2} naudotojo paskyrą. Jei tai buvote Jūs, tuomet paspauskite žemiau esančią nuorodą norėdami susieti paskyras</p><p><a href="{3}">{3}</a></p><p>Ši nuoroda galioja {4} min.</p><p>Jei paskyrų susieti nenorite, tuomet ignoruokite šį laišką. Jei paskyras susiesite, tuomet prie {1} galėsiste prisijungti per {0}.</p>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
identityProviderLinkBodyHtml=<p>žas pageidauja susieti Jūsų <b>{1}</b> paskyrą su <b>{0}</b> {2} naudotojo paskyrą. Jei tai buvote Jūs, tuomet paspauskite žemiau esančią nuorodą norėdami susieti paskyras</p><p><a href="{3}">{3}</a></p><p>Ši nuoroda galioja {4} min.</p><p>Jei paskyrų susieti nenorite, tuomet ignoruokite šį laišką. Jei paskyras susiesite, tuomet prie {1} galėsiste prisijungti per {0}.</p>
identityProviderLinkBodyHtml=<p>Kažas pageidauja susieti Jūsų <b>{1}</b> paskyrą su <b>{0}</b> {2} naudotojo paskyrą. Jei tai buvote Jūs, tuomet paspauskite žemiau esančią nuorodą norėdami susieti paskyras</p><p><a href="{3}">{3}</a></p><p>Ši nuoroda galioja {4} min.</p><p>Jei paskyrų susieti nenorite, tuomet ignoruokite šį laišką. Jei paskyras susiesite, tuomet prie {1} galėsiste prisijungti per {0}.</p>
🤖 Prompt for AI Agents
In
`@themes/src/main/resources-community/theme/base/email/messages/messages_lt.properties`
at line 6, The localization string identityProviderLinkBodyHtml is missing the
leading "Ka" (it starts "žas pageidauja" instead of "Kažas pageidauja"); update
the value for identityProviderLinkBodyHtml to prepend "Ka" so the sentence reads
correctly ("Kažas pageidauja susieti..."), ensuring the rest of the placeholders
and HTML remain unchanged.

Comment on lines +423 to +427
webauthn-error-registration=No s''ha pogut registrar la vostra clau de seguretat.<br /> {0}
webauthn-error-api-get=No s''ha pogut identificar amb la clau de seguretat.<br /> {0}
webauthn-error-different-user=L''usuari identificat primer no és l''autenticat per la clau de seguretat.
webauthn-error-auth-verification=El resultat de l''autenticació amb clau de seguretat no és vàlid.<br/>{0}
webauthn-error-register-verification=El resultat del registre amb clau de seguretat no és vàlid.<br/>{0}
webauthn-error-auth-verification=El resultat de l''autenticació amb clau de seguretat no és vàlid.<br />{0}
webauthn-error-register-verification=El resultat del registre amb clau de seguretat no és vàlid.<br />{0}

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Inconsistent spacing before {0} placeholder across WebAuthn error messages.

Lines 423–424 have a space between <br /> and {0}, while lines 426–427 do not. This should be consistent across all four messages.

Proposed fix (pick one style — here, with space for consistency with lines 423–424)
-webauthn-error-auth-verification=El resultat de l''autenticació amb clau de seguretat no és vàlid.<br />{0}
-webauthn-error-register-verification=El resultat del registre amb clau de seguretat no és vàlid.<br />{0}
+webauthn-error-auth-verification=El resultat de l''autenticació amb clau de seguretat no és vàlid.<br /> {0}
+webauthn-error-register-verification=El resultat del registre amb clau de seguretat no és vàlid.<br /> {0}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
webauthn-error-registration=No s''ha pogut registrar la vostra clau de seguretat.<br /> {0}
webauthn-error-api-get=No s''ha pogut identificar amb la clau de seguretat.<br /> {0}
webauthn-error-different-user=L''usuari identificat primer no és l''autenticat per la clau de seguretat.
webauthn-error-auth-verification=El resultat de l''autenticació amb clau de seguretat no és vàlid.<br/>{0}
webauthn-error-register-verification=El resultat del registre amb clau de seguretat no és vàlid.<br/>{0}
webauthn-error-auth-verification=El resultat de l''autenticació amb clau de seguretat no és vàlid.<br />{0}
webauthn-error-register-verification=El resultat del registre amb clau de seguretat no és vàlid.<br />{0}
webauthn-error-registration=No s''ha pogut registrar la vostra clau de seguretat.<br /> {0}
webauthn-error-api-get=No s''ha pogut identificar amb la clau de seguretat.<br /> {0}
webauthn-error-different-user=L''usuari identificat primer no és l''autenticat per la clau de seguretat.
webauthn-error-auth-verification=El resultat de l''autenticació amb clau de seguretat no és vàlid.<br /> {0}
webauthn-error-register-verification=El resultat del registre amb clau de seguretat no és vàlid.<br /> {0}
🤖 Prompt for AI Agents
In
`@themes/src/main/resources-community/theme/base/login/messages/messages_ca.properties`
around lines 423 - 427, The WebAuthn message entries
webauthn-error-auth-verification and webauthn-error-register-verification are
missing a space between the <br /> tag and the {0} placeholder; make them
consistent with webauthn-error-registration and webauthn-error-api-get by adding
a single space before `{0}` in the values for webauthn-error-auth-verification
and webauthn-error-register-verification so all four messages use "<br /> {0}".

@@ -68,7 +68,7 @@ country=Šalis
emailVerified=El. pašto adresas patvirtintas
gssDelegationCredential=GSS prisijungimo duomenų delegavimas

loginTotpStep1=Įdiekite <a href="https://freeotp.github.io/" target="_blank">FreeOTP</a> arba Google Authenticator savo įrenginyje. Programėlės prieinamos <a href="https://play.google.com">Google Play</a> ir Apple App Store.
loginTotpStep1=Installa una delle seguenti applicazioni sul tuo cellulare:

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Wrong language: Italian text in Lithuanian locale file.

The value "Installa una delle seguenti applicazioni sul tuo cellulare:" is Italian, not Lithuanian. This will cause Lithuanian users to see an Italian instruction for the TOTP setup step. The correct Lithuanian translation should be used instead, e.g.:

Proposed fix
-loginTotpStep1=Installa una delle seguenti applicazioni sul tuo cellulare:
+loginTotpStep1=Įdiekite vieną iš šių programėlių į savo mobilųjį telefoną:
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
loginTotpStep1=Installa una delle seguenti applicazioni sul tuo cellulare:
loginTotpStep1=Įdiekite vieną iš šių programėlių į savo mobilųjį telefoną:
🤖 Prompt for AI Agents
In
`@themes/src/main/resources-community/theme/base/login/messages/messages_lt.properties`
at line 71, The value for the key loginTotpStep1 is Italian ("Installa una delle
seguenti applicazioni sul tuo cellulare:") but lives in the Lithuanian locale;
replace it with the correct Lithuanian translation (for example: "Įdiekite vieną
iš šių programėlių savo telefone:") so the loginTotpStep1 message is properly
localized in Lithuanian.

@@ -466,11 +466,11 @@ webauthn-unsupported-browser-text= WebAuthn tarayıcınız tarafından desteklen
webauthn-doAuthenticate= Geçiş Anahtarı ile giriş yap,
webauthn-createdAt-label= Oluşturuldu,
webauthn-error-title= Geçiş Anahtarı hatası,
webauthn-error-registration= Geçiş Anahatarınızı kaydederken hata oluştu.<br/> {0},
webauthn-error-api-get= Geçiş Anahtarınızı doğrularken hata oluştu.<br/> {0},
webauthn-error-registration= Geçiş Anahatarınızı kaydederken hata oluştu.<br /> {0},

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Typo: "Anahatarınızı" should be "Anahtarınızı".

Line 469 has a transposed letter — "Anahatarınızı" vs. the correct "Anahtarınızı" used on line 470. Since this line is already being touched, it's a good opportunity to fix it.

🔤 Proposed fix
-webauthn-error-registration= Geçiş Anahatarınızı kaydederken hata oluştu.<br /> {0},
+webauthn-error-registration= Geçiş Anahtarınızı kaydederken hata oluştu.<br /> {0},
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
webauthn-error-registration= Geçiş Anahatarınızı kaydederken hata oluştu.<br /> {0},
webauthn-error-registration= Geçiş Anahtarınızı kaydederken hata oluştu.<br /> {0},
🤖 Prompt for AI Agents
In
`@themes/src/main/resources-community/theme/base/login/messages/messages_tr.properties`
at line 469, Fix the typo in the properties value for the key
webauthn-error-registration: change "Anahatarınızı" to "Anahtarınızı" so the
message matches the correct spelling used elsewhere and reads "Geçiş
Anahtarınızı kaydederken hata oluştu.<br /> {0},".

Sign in to join this conversation on GitHub.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

1 participant