Last updated: September 2026
CVE-2026-79651 is an unauthenticated denial-of-service bug in Keycloak. The public endpoint that serves a theme’s translated messages accepted any locale tag a client sent and kept a cached copy of the messages for every distinct tag, with no upper bound. By requesting that endpoint with an endless stream of made-up locales, anyone who can reach your Keycloak hostname can grow that cache until the JVM runs out of heap. The fix shipped in Keycloak 26.7.4 (released 16 September 2026) and was backported to 26.6.7 and 26.4.16. If you cannot upgrade today, filter the localization endpoint at your reverse proxy so only the locales you actually support get through.
This post covers what the bug is, how to tell whether you are exposed, the upgrade path, and a proxy rule that closes the hole until you patch. It is one of six Keycloak CVEs fixed in the 26.7.4 release; the impersonation issue from the same batch has its own write-up.
What does CVE-2026-79651 break?
It breaks availability, and it does so without any credentials. Keycloak’s ThemeResource class exposes GET /resources/{realm}/{themeType}/{locale}, which returns a theme’s message bundle as JSON so that the JavaScript-based consoles and login pages can render translated text. Before the fix, that handler turned the {locale} path segment straight into a Java Locale with Locale.forLanguageTag and used it as a key in the theme’s message cache.
The cache lives in DefaultThemeManager, and in the vulnerable versions it was a plain ConcurrentHashMap keyed by locale with nothing that ever evicted an entry. Every new tag, such as zz-x-00000001, zz-x-00000002 and so on, created a fresh cache entry: its own small set of message properties (at minimum a translated name for every language the theme offers) plus references to the parent locale’s messages. Each entry is small, but nothing ever removed one, so a client that sends enough unique tags makes the server hold more and more of them until the heap fills, garbage collection starts thrashing, and the node stops answering logins. The endpoint also takes an optional ?theme= query parameter, which lets a caller choose which installed theme’s cache to fill.
Because the endpoint has to work before anyone logs in (it is how the login page gets its strings), it is open by design. Nothing about realm configuration puts it behind authentication, which is why the CVE title says “unauthenticated”.
Why a DoS on the identity provider is worse than it sounds
When Keycloak is down, every application that depends on it for single sign-on is effectively down too, because no one can get a new session. MFA prompts, identity-provider brokering, token refreshes and SAML assertions all stop together. A memory-exhaustion attack on one Keycloak node also tends to spread, because a load balancer shifts the traffic, including the attacker’s, onto the surviving nodes. That is the reason an availability bug on an identity provider deserves the same urgency you would give a data-exposure bug on a smaller system.
How did the Keycloak team fix it?
The fix is commit 6f42ea1 on the 26.7 branch, titled “CVE-2026-79651 Bound the locales accepted by theme message lookups”, with matching commits on the 26.6 and 26.4 branches. It does two things:
- It only accepts locales the server already knows about. A new helper,
LocaleUtil.resolveSupportedLocale, matches the requested tag against the locales declared by the theme, the realm’s supported locales and the realm default. Anything else falls back to the realm default locale (or English), so a request forzz-x-00000001now returns the same body as a request for the realm’s default locale (English when internationalization is turned off). The admin console’s message lookups inAdminRootgo through the same helper. - It caps the cache anyway. The per-bundle message cache became a size-limited, least-recently-used map with a ceiling of 200 locales, so even an unexpected path that slips a strange locale through cannot grow memory without limit.
The first change is what removes the attack, and the second is defense in depth. The fix also ships a regression test, ThemeMessageLocaleTest, that requests /resources/{realm}/login/zz-x-00000001 and asserts that the response matches the realm’s default locale. That test is a convenient description of the attack surface if you want to check your own build.
Am I exposed to CVE-2026-79651?
You are exposed if you run a community Keycloak build older than the fixed release on your line and the /resources/ path is reachable by people you do not trust. In practice that covers almost every deployment that has an internet-facing login page, since the localization endpoint shares a hostname and a path prefix with the static theme assets the login page needs.
| Release line | First fixed version |
|---|---|
| 26.7.x | 26.7.4 |
| 26.6.x | 26.6.7 |
| 26.4.x | 26.4.16 |
The version numbers come from the release tags and commit history in the Keycloak repository; the 26.7.4 release notes list the CVE under “Security fixes”. Keycloak does ship point releases on older minor lines, so you do not have to jump to 26.7 to get this fix if you are on 26.4 or 26.6. No 26.5.x release carries the fix as of publication (the last tag on that line is 26.5.7), so if you are on 26.5 or anything older than 26.4, the upgrade to a fixed line is the fix. If you run the Red Hat build of Keycloak, check Red Hat’s own advisory for the product version that carries it rather than mapping community version numbers yourself.
A few setups raise the stakes. Deployments with several custom themes installed have more caches to fill, because each theme keeps its own per-bundle cache and the ?theme= parameter lets a caller pick one. Deployments that give Keycloak a small container memory limit hit the ceiling sooner. Keycloak’s sizing guidance on keycloak.org (“Concepts for sizing CPU and memory resources”) sets the heap to roughly 70 percent of the container memory limit and puts the baseline at about 1250 MB of memory per pod, so a small pod does not have much room to spare.
How do I patch it?
Upgrade to the fixed version on your current line. This is a patch release on each line, so you are not taking on a new minor version’s migration notes unless you choose to move up. The usual rolling-upgrade process applies: upgrade one node at a time behind the load balancer and confirm each node rejoins the cluster before moving on. Our Keycloak upgrade strategy post covers that process in detail, including the case where you are several patch versions behind.
After the upgrade, confirm the version each node is actually running rather than trusting the deployment manifest. The admin console shows it under the master realm’s server information page, and the container image tag or the startup log line gives the same answer. Do not try to confirm the fix by sending made-up locales to production: an unpatched server usually returns the same fallback text either way, so the response tells you nothing, while each request with a new tag adds to the very cache the attack fills.
What can I do before I can upgrade?
Filter the localization endpoint at your reverse proxy or ingress so that only the locales you support reach Keycloak. The rule needs care, because /resources/ also serves static theme assets under a longer path shape, /resources/{version}/{themeType}/{themeName}/{path}, and blocking the whole prefix breaks the login page. The localization endpoint is exactly three segments after /resources/, so you can match that shape and allow-list the last segment.
The rule also has to look at the raw request line rather than NGINX’s decoded $uri. NGINX decodes %2F into a slash and merges repeated slashes before it matches location blocks, while proxy_pass forwards the original request, so a rule written against $uri can be sidestepped with an encoded slash inside the locale segment. An NGINX example that avoids this, for a realm that supports English, German and French:
# http {} context. The first matching regex wins, so the allow line comes first.
map $request_uri $kc_locale_blocked {
default 0;
"~*^/resources/.*(%2f|//)" 1;
"~^/resources/[^/?]+/[^/?]+/(en|de|fr)/?(?.*)?$" 0;
"~^/resources/[^/?]+/[^/?]+/[^/?]+/?(?.*)?$" 1;
}
# server {} context, before any location that proxies to Keycloak.
if ($kc_locale_blocked) {
return 404;
}
The second line rejects encoded slashes and empty segments under /resources/, which Keycloak’s own theme assets never use. The last two lines allow the localization endpoint only for the listed locales, and everything with more path segments, such as the static theme assets, falls through to the default and is proxied as before. (A malformed asset request with an empty final segment, such as a bare theme directory ending in a slash, would also be caught, but Keycloak serves nothing useful at those paths.) Test it in staging with the encoded-slash case as well as the plain one.
Adjust the locale list to match your realm’s supported locales and any region variants you enabled (for example pt-BR). The same endpoint also serves the account console and the admin console, so include any language your administrators pick in the admin console, even if the realm itself does not offer it to end users, otherwise that console loads without its text. Rate limiting on the same path is a useful second layer, but on its own it only slows the attack down, because each unique tag costs the attacker a single cheap request. A web application firewall rule that caps distinct values of the last path segment per client achieves something similar if your proxy cannot do regular-expression matching.
Once you have upgraded, you can keep the rule or drop it. Keeping it does no harm, provided you remember to update the locale list when you add a language to the realm, otherwise the new language’s translations will stop loading.
What does managed Keycloak change here?
On a managed service, the provider carries the patch and the rollout, and you keep ownership of the realm configuration. That is how we run Skycloak’s managed hosting: we apply Keycloak security releases to customer clusters, and customers own their realms. For this CVE the patch is the whole fix, so there is nothing realm-level for a customer to change. That matters for a bug like this one because the risky period is the gap between the release and your next maintenance window, and a provider that tracks upstream releases closes that gap without you scheduling anything.
Whether that trade is worth it depends on your team. If you already run Keycloak well and patch within days, a managed service mostly saves you time. If upgrades tend to slip for weeks because nobody owns them, it removes the gap that makes a bug like this dangerous. Our self-hosting cost breakdown walks through that decision without assuming the answer.
What else shipped in Keycloak 26.7.4?
Five other CVEs landed in the same release, according to the 26.7.4 release notes: CVE-2026-90997 (MySQL and MariaDB row counts letting stateless replay checks accept reused artifacts), CVE-2026-74909 (an incomplete fix for matrix-parameter stripping in the policy enforcer’s PathMatcher), CVE-2026-19607 (username takeover leading to account lockout), CVE-2026-17526 (the impersonation role impersonating a realm administrator) and CVE-2026-18212 (the SAML Redirect binding’s DEFLATE helpers leaking native zlib state). If you are upgrading for the locale bug, you pick up all of them at once, which makes it worth doing properly rather than as a quick hotfix. The previous batch is covered in our 26.7.3 patch checklist, and the broader production baseline is in the Keycloak security hardening checklist.
How do I keep track of future Keycloak vulnerabilities?
The most reliable feed is the release notes on the keycloak/keycloak GitHub releases page, where every Keycloak CVE fixed in a release is listed under “Security fixes” with a link to its tracking issue. Watching that repository for releases, and checking the commit history of the release branch you run, tells you whether a fix reached your line. It is worth doing for older minor lines in particular, because backports like the 26.4.16 and 26.6.7 fixes here can land in a release you would otherwise skip.
Frequently asked questions
Does CVE-2026-79651 need any credentials to exploit?
No. The theme localization endpoint at /resources/{realm}/{themeType}/{locale} is public because the login page needs it before anyone signs in. An attacker only needs network access to your Keycloak hostname and a realm name, and realm names are visible in every login URL.
Can CVE-2026-79651 leak data or give an attacker access?
Nothing in the fix points to data exposure. The endpoint returns translated message strings that are already public, and the bug is about how many copies of them the server keeps in memory. The impact is availability: a node that runs out of heap stops serving logins, token requests and admin traffic until it restarts.
Which Keycloak versions fix CVE-2026-79651?
Community Keycloak 26.7.4, 26.6.7 and 26.4.16 carry the fix, according to the commit history on each release branch. Versions before those on each line are affected. Red Hat build of Keycloak users should follow Red Hat’s advisory for the matching product release.
Will filtering the endpoint at the proxy break my login page?
Not if the rule only matches the three-segment localization path and allows every locale your realm supports. The static theme assets under /resources/ use a longer path, so a rule anchored to exactly three segments leaves them alone. Test in staging with each enabled language, because a missing locale in the allow list will make that language’s translations fail to load.
Is rate limiting enough on its own?
It reduces the risk but does not remove it. Each request that uses a new locale adds a cache entry, so a slow attacker can still build up memory over hours if the node is not restarted. An allow list on the locale segment, or the upgrade itself, stops the growth entirely.