-
Notifications
You must be signed in to change notification settings - Fork 0
Add HTML sanitizer for translated message resources #7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: feature-html-sanitizer-baseline
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -72,6 +72,17 @@ | |
| <version>2.2</version> | ||
| <scope>test</scope> | ||
| </dependency> | ||
| <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> | ||
|
Comment on lines
+75
to
+85
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🌐 Web query:
💡 Result: Summary — latest known vulnerability affecting OWASP Java HTML Sanitizer v20240325.1:
Recommended immediate actions:
Sources: 🌐 Web query:
💡 Result: Short answer: Apache Commons Text 1.13.0 (released Dec 10, 2024) is not listed as containing new security fixes beyond earlier advisories (the known RCE class CVE-2022-42889 affects versions before 1.10.0 and was addressed by the project). The Commons Text project has continued releases after 1.13.0 (1.13.1, 1.14.0, 1.15.0), so you should upgrade to a supported/latest release to get any subsequent fixes and improvements. [1][2][3] References
If you want, I can check the very latest Commons Text release now and recommend the minimum safe version to upgrade to. Do not use OWASP HTML Sanitizer v20240325.1 without mitigation: CVE-2025-66021 (high-severity XSS). A high-severity XSS vulnerability (CVE-2025-66021, published Nov 26, 2025) affects this exact version. It is exploitable when the HTML policy permits both
Monitor the OWASP/GitHub advisory (GHSA-g9gq-3pfx-2gw2) for a patched version release and upgrade as soon as available. For Apache Commons Text: v1.13.0 is secure from CVE-2022-42889 (which affected versions < 1.10.0). Newer releases (1.14.0, 1.15.0) are available if you want to capture subsequent fixes. 🤖 Prompt for AI Agents |
||
| </dependencies> | ||
|
|
||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -17,15 +17,22 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| package org.keycloak.themeverifier; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.apache.maven.plugin.MojoExecutionException; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.owasp.html.PolicyFactory; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.io.BufferedReader; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.io.File; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.io.FileInputStream; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.io.IOException; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.io.StringReader; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.nio.file.Files; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.ArrayList; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.HashSet; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.List; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.MissingResourceException; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.Objects; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.PropertyResourceBundle; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.regex.Matcher; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.regex.Pattern; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public class VerifyMessageProperties { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -41,12 +48,129 @@ public List<String> verify() throws MojoExecutionException { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| String contents = Files.readString(file.toPath()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| verifyNoDuplicateKeys(contents); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| verifySafeHtml(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (IOException e) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw new MojoExecutionException("Can not read file " + file, e); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return messages; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 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 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); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+73
to
+80
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# First, examine the full context of the Java file to understand the code flow
echo "=== Reading VerifyMessageProperties.java ==="
wc -l misc/theme-verifier/src/main/java/org/keycloak/themeverifier/VerifyMessageProperties.javaRepository: AI-Code-Review-Evals/coderabbit-keycloak Length of output: 222 🏁 Script executed: #!/bin/bash
# Get full context around lines 73-80
head -120 misc/theme-verifier/src/main/java/org/keycloak/themeverifier/VerifyMessageProperties.java | tail -60Repository: AI-Code-Review-Evals/coderabbit-keycloak Length of output: 2994 🏁 Script executed: #!/bin/bash
# Check what property files exist and their naming patterns
echo "=== Property files in the repository ==="
fd -e properties . --exec bash -c 'echo "{}"; basename "{}"' \; | head -100Repository: AI-Code-Review-Evals/coderabbit-keycloak Length of output: 5408 🏁 Script executed: #!/bin/bash
# Read the complete VerifyMessageProperties.java file
cat -n misc/theme-verifier/src/main/java/org/keycloak/themeverifier/VerifyMessageProperties.javaRepository: AI-Code-Review-Evals/coderabbit-keycloak Length of output: 9448 🏁 Script executed: #!/bin/bash
# Also check if there's any filtering of files before calling verifySafeHtml
# Search for where this method is called
grep -n "verifySafeHtml" misc/theme-verifier/src/main/java/org/keycloak/themeverifier/VerifyMessageProperties.javaRepository: AI-Code-Review-Evals/coderabbit-keycloak Length of output: 155 🏁 Script executed: #!/bin/bash
# Check what types of files are actually being processed by looking at the main entry point
grep -n "public\|private\|void\|static" misc/theme-verifier/src/main/java/org/keycloak/themeverifier/VerifyMessageProperties.java | head -20Repository: AI-Code-Review-Evals/coderabbit-keycloak Length of output: 830 🏁 Script executed: #!/bin/bash
# Search for where VerifyMessageProperties is instantiated
rg "new VerifyMessageProperties\|VerifyMessageProperties\(" --type java -B 3 -A 3Repository: AI-Code-Review-Evals/coderabbit-keycloak Length of output: 66 🏁 Script executed: #!/antml/bash
# Look for the Maven Mojo or entry point
fd -e java . -path "*/themeverifier/*" --exec basename {} \;Repository: AI-Code-Review-Evals/coderabbit-keycloak Length of output: 252 🏁 Script executed: #!/bin/bash
# Find all Java files in the themeverifier module
fd -e java . -path "*/themeverifier/*"Repository: AI-Code-Review-Evals/coderabbit-keycloak Length of output: 252 🏁 Script executed: #!/bin/bash
# Find all Java files in the themeverifier module
find misc/theme-verifier -name "*.java"Repository: AI-Code-Review-Evals/coderabbit-keycloak Length of output: 441 🏁 Script executed: #!/bin/bash
# Search for where VerifyMessageProperties is instantiated
grep -r "new VerifyMessageProperties\|VerifyMessageProperties(" misc/theme-verifier --include="*.java" -B 3 -A 3Repository: AI-Code-Review-Evals/coderabbit-keycloak Length of output: 2675 🏁 Script executed: #!/bin/bash
# Look for the entry point/Mojo
grep -r "Mojo\|execute\|getMojoDescriptor" misc/theme-verifier --include="*.java" -lRepository: AI-Code-Review-Evals/coderabbit-keycloak Length of output: 351 🏁 Script executed: #!/bin/bash
# Check the MessagePropertiesFilter to see what files are being selected
cat -n misc/theme-verifier/src/main/java/org/keycloak/themeverifier/MessagePropertiesFilter.javaRepository: AI-Code-Review-Evals/coderabbit-keycloak Length of output: 1397 🏁 Script executed: #!/bin/bash
# Also check the test file to understand expected behavior
cat -n misc/theme-verifier/src/test/java/org/keycloak/themeverifier/VerifyMessagePropertiesTest.javaRepository: AI-Code-Review-Evals/coderabbit-keycloak Length of output: 2911 🏁 Script executed: #!/bin/bash
# Check the test fixtures to see what patterns are used
find misc/theme-verifier/src/test/resources -name "*.properties" -exec basename {} \;Repository: AI-Code-Review-Evals/coderabbit-keycloak Length of output: 238 🏁 Script executed: #!/bin/bash
# Let's verify the regex behavior with a concrete example
# If messages_en.properties is processed, what does the transformation produce?
python3 << 'EOF'
import re
test_cases = [
"/path/to/messages_en.properties",
"/path/to/messages_de.properties",
"/path/to/messages_pt_BR.properties",
"/path/to/messages_zh_TW.properties",
"/path/to/resources-community/messages_hu.properties",
"/path/to/resources-community/messages_pt_BR.properties",
]
for path in test_cases:
result = path.replace("resources-community", "resources")
result = re.sub(r"_[a-zA-Z-_]*\.properties", "_en.properties", result)
is_same = result == path
print(f"Input: {path}")
print(f"Output: {result}")
print(f"Same file? {is_same}\n")
EOFRepository: AI-Code-Review-Evals/coderabbit-keycloak Length of output: 170 🏁 Script executed: #!/bin/bash
# Test the regex behavior with basic shell
# Simulate what happens when messages_en.properties is processed
path="/path/to/messages_en.properties"
echo "Input: $path"
# First replaceall: resources-community -> resources
result="${path/resources-community/resources}"
echo "After first replaceall: $result"
# Second replaceall: _[a-zA-Z-_]*.properties -> _en.properties
# Using sed instead
result=$(echo "$result" | sed -E 's/_[a-zA-Z_-]*\.properties/_en.properties/')
echo "After second replaceall: $result"
echo "---"
# Test with a different locale
path="/path/to/messages_de.properties"
echo "Input: $path"
result="${path/resources-community/resources}"
echo "After first replaceall: $result"
result=$(echo "$result" | sed -E 's/_[a-zA-Z_-]*\.properties/_en.properties/')
echo "After second replaceall: $result"
echo "---"
# Test with community variant
path="/path/to/resources-community/messages_pt_BR.properties"
echo "Input: $path"
result="${path/resources-community/resources}"
echo "After first replaceall: $result"
result=$(echo "$result" | sed -E 's/_[a-zA-Z_-]*\.properties/_en.properties/')
echo "After second replaceall: $result"Repository: AI-Code-Review-Evals/coderabbit-keycloak Length of output: 593 Unnecessary file load when English file is already the input. When if (englishFile.equals(file.getAbsolutePath())) {
bundleEnglish = bundle;
} else {
try (FileInputStream fis = new FileInputStream(englishFile)) {
bundleEnglish = new PropertyResourceBundle(fis);
} catch (IOException e) {
throw new RuntimeException("unable to read file " + englishFile, e);
}
}Note: The 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| bundle.getKeys().asIterator().forEachRemaining(key -> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| String value = bundle.getString(key); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| value = normalizeValue(key, value); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| String englishValue = getEnglishValue(key, bundleEnglish); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| englishValue = normalizeValue(key, englishValue); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| value = santizeAnchors(key, value, englishValue); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Only if the English source string contains HTML we also allow HTML in the translation | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| PolicyFactory policy = containsHtml(englishValue) ? POLICY_SOME_HTML : POLICY_NO_HTML; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| String sanitized = policy.sanitize(value); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Sanitizer will escape HTML entities for quotes and also for numberic tags like '<1>' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sanitized = org.apache.commons.text.StringEscapeUtils.unescapeHtml4(sanitized); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Sanitizer will add them when there are double curly braces | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sanitized = sanitized.replace("<!-- -->", ""); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 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) + "'"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private String normalizeValue(String key, String value) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (key.equals("templateHelp")) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Allow "CLAIM.<NAME>" here | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| value = value.replaceAll("CLAIM\\.<[A-Z]*>", ""); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else if (key.equals("optimizeLookupHelp")) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Allow "<Extensions>" here | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| value = value.replaceAll("<Extensions>", ""); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else if (key.startsWith("linkExpirationFormatter.timePeriodUnit") || key.equals("error-invalid-multivalued-size")) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // The problem is the "<" that appears in the choice | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| value = value.replaceAll("\\{[0-9]+,choice,[^}]*}", "..."); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Unescape HTML entities, as we later also unescape HTML entities in the sanitized value | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| value = org.apache.commons.text.StringEscapeUtils.unescapeHtml4(value); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (file.getAbsolutePath().contains("email")) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // TODO: move the RTL information for emails | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| value = value.replaceAll(Pattern.quote(" style=\"direction: rtl;\""), ""); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return value; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Pattern HTML_TAGS = Pattern.compile("<[a-z]+[^>]*>"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private boolean containsHtml(String englishValue) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return HTML_TAGS.matcher(englishValue).find(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private static final Pattern ANCHOR_PATTERN = Pattern.compile("</?a[^>]*>"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Allow only those anchor tags from the source key to also appear in the target key. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 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; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+145
to
+162
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Anchor synchronization logic has issues.
🔧 Suggested approachConsider building a list of anchors from both strings first, then comparing them: private String santizeAnchors(String key, String value, String englishValue) {
+ List<String> valueAnchors = new ArrayList<>();
+ List<String> englishAnchors = new ArrayList<>();
+
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;
- }
+ while (matcher.find()) {
+ valueAnchors.add(matcher.group());
+ }
+
+ Matcher englishMatcher = ANCHOR_PATTERN.matcher(englishValue);
+ while (englishMatcher.find()) {
+ englishAnchors.add(englishMatcher.group());
+ }
+
+ // Compare and validate anchors, then remove matched ones from value
+ for (int i = 0; i < valueAnchors.size(); i++) {
+ String anchor = valueAnchors.get(i);
+ if (i < englishAnchors.size() && Objects.equals(anchor, englishAnchors.get(i))) {
+ value = value.replaceFirst(Pattern.quote(anchor), "");
+ } else {
+ messages.add("Didn't find anchor tag " + anchor + " in original string");
+ break;
+ }
}
return value;
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private static String getEnglishValue(String key, PropertyResourceBundle bundleEnglish) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| String englishValue; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| englishValue = bundleEnglish.getString(key); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (MissingResourceException ex) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| englishValue = ""; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return englishValue; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private void verifyNoDuplicateKeys(String contents) throws IOException { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| BufferedReader bufferedReader = new BufferedReader(new StringReader(contents)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| String line; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| # | ||
| # Copyright 2025 Red Hat, Inc. and/or its affiliates | ||
| # and other contributors as indicated by the @author tags. | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
| # | ||
| key=Some <a href="http://malicious.com">link</a> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| # | ||
| # Copyright 2025 Red Hat, Inc. and/or its affiliates | ||
| # and other contributors as indicated by the @author tags. | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
| # | ||
| key=Some <a href="http://example.com">link</a> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| # | ||
| # Copyright 2025 Red Hat, Inc. and/or its affiliates | ||
| # and other contributors as indicated by the @author tags. | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
| # | ||
| key=Some <div>tag</div |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| # | ||
| # Copyright 2025 Red Hat, Inc. and/or its affiliates | ||
| # and other contributors as indicated by the @author tags. | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
| # | ||
| key=Some <b>HTML</b> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| # | ||
| # Copyright 2025 Red Hat, Inc. and/or its affiliates | ||
| # and other contributors as indicated by the @author tags. | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
| # | ||
| key=No HTML |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -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: | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wrong language in Lithuanian locale file. Line 101 contains Italian text ("Installa una delle seguenti applicazioni sul tuo cellulare:") in a Lithuanian locale file. This breaks the TOTP setup flow for Lithuanian users who will not understand the Italian instructions. 🌍 Proposed fix with proper Lithuanian translation-totpStep1=Installa una delle seguenti applicazioni sul tuo cellulare:
+totpStep1=Įdiekite vieną iš šių programėlių į savo mobilųjį telefoną:📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||
| totpStep2=Atidarykite programėlę ir nuskenuokite barkodą arba įveskite kodą. | ||||||
| totpStep3=Įveskite programėlėje sugeneruotą vieną kartą galiojantį kodą ir paspauskite Saugoti norėdami prisijungti. | ||||||
|
|
||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -109,7 +109,7 @@ revoke=收回授权 | |||||
|
|
||||||
| configureAuthenticators=配置的认证者 | ||||||
| mobile=手机 | ||||||
| totpStep1=在你的设备上安装 <a href="https://fedorahosted.org/freeotp/" target="_blank">FreeOTP</a> 或者 Google Authenticator.两个应用可以从 <a href="https://play.google.com">Google Play</a> 和 Apple App Store下载。 | ||||||
| totpStep1=在您的手機上安裝以下應用程式之一: | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Language variant mismatch: Traditional Chinese used in Simplified Chinese file. The text uses Traditional Chinese characters ("手機", "應用程式") in a file designated for Simplified Chinese (zh_CN). Users in mainland China expect Simplified Chinese. 🌐 Proposed fix using Simplified Chinese characters-totpStep1=在您的手機上安裝以下應用程式之一:
+totpStep1=在您的手机上安装以下应用程序之一:📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||
| totpStep2=打开应用扫描二维码输入验证码 | ||||||
| totpStep3=输入应用提供的一次性验证码单击保存 | ||||||
|
|
||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -6,7 +6,7 @@ emailTestBody=Dette er en test besked | |||||
| emailTestBodyHtml=<p>Dette er en test besked</p> | ||||||
| identityProviderLinkSubject=Link {0} | ||||||
| identityProviderLinkBody=Nogen vil forbinde din "{1}" konto med "{0}" kontoen som er tilknyttet brugeren {2}. Hvis dette var dig, bedes du klikke på forbindet herunder for at forbinde de to konti\n\n{3}\n\nDette link vil udløbe efter {5}.\n\nHvis du ikke vil forbinde disse konti, kan du bare ignore denne besked. Hvis du vælger at forbinde de to konti, kan du logge ind som {1} via {0}. | ||||||
| identityProviderLinkBodyHtml=<p>Nogen vil forbinde din <b>{1}</b> konto med <b>{0}</b> kontoen som er tilknyttet brugeren {2}. Hvis dette var dig, bedes du klikke på forbindet herunder for at forbinde de to konti</p><p><a href="{3}">Bekræft</a></p><p>Dette link vil udløbe efter {5}.</p><p>nHvis du ikke vil forbinde disse konti, kan du bare ignore denne besked. Hvis du vælger at forbinde de to konti, kan du logge ind som {1} via {0}. | ||||||
| identityProviderLinkBodyHtml=<p>Nogen vil forbinde din <b>{1}</b> konto med <b>{0}</b> kontoen som er tilknyttet brugeren {2}. Hvis dette var dig, bedes du klikke på forbindet herunder for at forbinde de to konti</p><p><a href="{3}">Bekræft</a></p><p>Dette link vil udløbe efter {5}.</p><p>nHvis du ikke vil forbinde disse konti, kan du bare ignore denne besked. Hvis du vælger at forbinde de to konti, kan du logge ind som {1} via {0}.</p> | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix typographical error in Danish text. The last paragraph contains "nHvis" which should be "Hvis". This appears to be a leftover artifact from a newline character when converting to HTML format. The plain text version on line 8 correctly uses "Hvis". 📝 Proposed fix-identityProviderLinkBodyHtml=<p>Nogen vil forbinde din <b>{1}</b> konto med <b>{0}</b> kontoen som er tilknyttet brugeren {2}. Hvis dette var dig, bedes du klikke på forbindet herunder for at forbinde de to konti</p><p><a href="{3}">Bekræft</a></p><p>Dette link vil udløbe efter {5}.</p><p>nHvis du ikke vil forbinde disse konti, kan du bare ignore denne besked. Hvis du vælger at forbinde de to konti, kan du logge ind som {1} via {0}.</p>
+identityProviderLinkBodyHtml=<p>Nogen vil forbinde din <b>{1}</b> konto med <b>{0}</b> kontoen som er tilknyttet brugeren {2}. Hvis dette var dig, bedes du klikke på forbindet herunder for at forbinde de to konti</p><p><a href="{3}">Bekræft</a></p><p>Dette link vil udløbe efter {5}.</p><p>Hvis du ikke vil forbinde disse konti, kan du bare ignore denne besked. Hvis du vælger at forbinde de to konti, kan du logge ind som {1} via {0}.</p>📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||
| passwordResetSubject=Gendan adgangskode | ||||||
| passwordResetBody=Nogen har forsøgt at nulstille adgangskoden til {2}. Hvis dette var dig, bedes du klikke på linket herunder for at nulstille adgangskoden.\n\n{0}\n\nDette link og kode vil udløbe efter {3}.\n\nHvis du ikke ønsker at nulstille din adgangskode, kan du se bort fra denne besked. | ||||||
| passwordResetBodyHtml=<p>Nogen har forsøgt at nulstille adgangskoden til {2}. Hvis dette var dig, bedes du klikke på linket herunder for at nulstille adgangskoden.</p><p><a href="{0}">Nulstil adgangskode</a></p><p>Dette link og kode vil udløbe efter {3}.</p><p>Hvis du ikke ønsker at nulstille din adgangskode, kan du se bort fra denne besked.</p> | ||||||
|
|
||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: AI-Code-Review-Evals/coderabbit-keycloak
Length of output: 113
🏁 Script executed:
Repository: AI-Code-Review-Evals/coderabbit-keycloak
Length of output: 66
🏁 Script executed:
Repository: AI-Code-Review-Evals/coderabbit-keycloak
Length of output: 294
🏁 Script executed:
Repository: AI-Code-Review-Evals/coderabbit-keycloak
Length of output: 669
🏁 Script executed:
Repository: AI-Code-Review-Evals/coderabbit-keycloak
Length of output: 111
🏁 Script executed:
Repository: AI-Code-Review-Evals/coderabbit-keycloak
Length of output: 66
🏁 Script executed:
Repository: AI-Code-Review-Evals/coderabbit-keycloak
Length of output: 1139
🏁 Script executed:
Repository: AI-Code-Review-Evals/coderabbit-keycloak
Length of output: 205
🏁 Script executed:
Repository: AI-Code-Review-Evals/coderabbit-keycloak
Length of output: 3682
🏁 Script executed:
# Examine the i18n configuration file cat js/apps/admin-ui/src/i18n/i18n.tsRepository: AI-Code-Review-Evals/coderabbit-keycloak
Length of output: 1040
🏁 Script executed:
Repository: AI-Code-Review-Evals/coderabbit-keycloak
Length of output: 66
🏁 Script executed:
Repository: AI-Code-Review-Evals/coderabbit-keycloak
Length of output: 719
🏁 Script executed:
Repository: AI-Code-Review-Evals/coderabbit-keycloak
Length of output: 66
🏁 Script executed:
Repository: AI-Code-Review-Evals/coderabbit-keycloak
Length of output: 66
🏁 Script executed:
Repository: AI-Code-Review-Evals/coderabbit-keycloak
Length of output: 66
🏁 Script executed:
Repository: AI-Code-Review-Evals/coderabbit-keycloak
Length of output: 66
🏁 Script executed:
Repository: AI-Code-Review-Evals/coderabbit-keycloak
Length of output: 66
🏁 Script executed:
Repository: AI-Code-Review-Evals/coderabbit-keycloak
Length of output: 213
🏁 Script executed:
Repository: AI-Code-Review-Evals/coderabbit-keycloak
Length of output: 803
🏁 Script executed:
Repository: AI-Code-Review-Evals/coderabbit-keycloak
Length of output: 319
🏁 Script executed:
Repository: AI-Code-Review-Evals/coderabbit-keycloak
Length of output: 3318
🏁 Script executed:
Repository: AI-Code-Review-Evals/coderabbit-keycloak
Length of output: 4698
🏁 Script executed:
Repository: AI-Code-Review-Evals/coderabbit-keycloak
Length of output: 66
🏁 Script executed:
Repository: AI-Code-Review-Evals/coderabbit-keycloak
Length of output: 3091
🏁 Script executed:
Repository: AI-Code-Review-Evals/coderabbit-keycloak
Length of output: 66
🏁 Script executed:
Repository: AI-Code-Review-Evals/coderabbit-keycloak
Length of output: 7335
🏁 Script executed:
Repository: AI-Code-Review-Evals/coderabbit-keycloak
Length of output: 39636
🏁 Script executed:
Repository: AI-Code-Review-Evals/coderabbit-keycloak
Length of output: 66
🏁 Script executed:
Repository: AI-Code-Review-Evals/coderabbit-keycloak
Length of output: 18989
🏁 Script executed:
Repository: AI-Code-Review-Evals/coderabbit-keycloak
Length of output: 7335
🏁 Script executed:
Repository: AI-Code-Review-Evals/coderabbit-keycloak
Length of output: 66
🏁 Script executed:
Repository: AI-Code-Review-Evals/coderabbit-keycloak
Length of output: 111
🏁 Script executed:
# Look at MultiValueValidator to understand how the message is used cat services/src/main/java/org/keycloak/userprofile/validator/MultiValueValidator.javaRepository: AI-Code-Review-Evals/coderabbit-keycloak
Length of output: 5650
🏁 Script executed:
Repository: AI-Code-Review-Evals/coderabbit-keycloak
Length of output: 169
🏁 Script executed:
Repository: AI-Code-Review-Evals/coderabbit-keycloak
Length of output: 233
🏁 Script executed:
Repository: AI-Code-Review-Evals/coderabbit-keycloak
Length of output: 7897
🏁 Script executed:
Repository: AI-Code-Review-Evals/coderabbit-keycloak
Length of output: 532
🏁 Script executed:
Repository: AI-Code-Review-Evals/coderabbit-keycloak
Length of output: 2751
🏁 Script executed:
Repository: AI-Code-Review-Evals/coderabbit-keycloak
Length of output: 66
🏁 Script executed:
Repository: AI-Code-Review-Evals/coderabbit-keycloak
Length of output: 66
🏁 Script executed:
Repository: AI-Code-Review-Evals/coderabbit-keycloak
Length of output: 66
🏁 Script executed:
Repository: AI-Code-Review-Evals/coderabbit-keycloak
Length of output: 66
🏁 Script executed:
Repository: AI-Code-Review-Evals/coderabbit-keycloak
Length of output: 66
🏁 Script executed:
Repository: AI-Code-Review-Evals/coderabbit-keycloak
Length of output: 66
🏁 Script executed:
Repository: AI-Code-Review-Evals/coderabbit-keycloak
Length of output: 169
🏁 Script executed:
Repository: AI-Code-Review-Evals/coderabbit-keycloak
Length of output: 66
🏁 Script executed:
Repository: AI-Code-Review-Evals/coderabbit-keycloak
Length of output: 66
🏁 Script executed:
Repository: AI-Code-Review-Evals/coderabbit-keycloak
Length of output: 228
🏁 Script executed:
# Look at UserProfileResource to see how validation errors are handled cat services/src/main/java/org/keycloak/services/resources/admin/UserProfileResource.javaRepository: AI-Code-Review-Evals/coderabbit-keycloak
Length of output: 5023
🏁 Script executed:
Repository: AI-Code-Review-Evals/coderabbit-keycloak
Length of output: 66
🏁 Script executed:
Repository: AI-Code-Review-Evals/coderabbit-keycloak
Length of output: 66
🏁 Script executed:
Repository: AI-Code-Review-Evals/coderabbit-keycloak
Length of output: 66
🏁 Script executed:
Repository: AI-Code-Review-Evals/coderabbit-keycloak
Length of output: 66
🏁 Script executed:
Repository: AI-Code-Review-Evals/coderabbit-keycloak
Length of output: 66
🏁 Script executed:
Repository: AI-Code-Review-Evals/coderabbit-keycloak
Length of output: 267
🏁 Script executed:
Repository: AI-Code-Review-Evals/coderabbit-keycloak
Length of output: 66
🏁 Script executed:
Repository: AI-Code-Review-Evals/coderabbit-keycloak
Length of output: 66
🏁 Script executed:
Repository: AI-Code-Review-Evals/coderabbit-keycloak
Length of output: 177
🏁 Script executed:
Repository: AI-Code-Review-Evals/coderabbit-keycloak
Length of output: 66
🏁 Script executed:
Repository: AI-Code-Review-Evals/coderabbit-keycloak
Length of output: 171
🏁 Script executed:
Repository: AI-Code-Review-Evals/coderabbit-keycloak
Length of output: 377
🏁 Script executed:
Repository: AI-Code-Review-Evals/coderabbit-keycloak
Length of output: 66
🏁 Script executed:
Repository: AI-Code-Review-Evals/coderabbit-keycloak
Length of output: 166
Replace
choicesyntax with i18next pluralizationThe Admin UI uses i18next (not Java MessageFormat), which does not support
{n,choice,...}syntax. The message will not be formatted correctly if rendered by the Admin UI.Replace with i18next-compatible pluralization:
Proposed change
📝 Committable suggestion
🤖 Prompt for AI Agents