Direct answer: Keycloak 26.8.2 closes 33 CVEs. CVE-2026-117691 gives login as any SAML federated user, with no account. Today, check the version, check the realms that use SAML, then update to 26.8.2.
Red Hat gives CVE-2026-117691 a CVSS 3.1 score of 9.1. Only 26.8.2 carries the patch. The 26.7, 26.6, and 26.4 branches get no backport.
Main condition: 1 or more realms use a SAML identity provider, or a SAML client on the redirect binding. Limit: this article has no payloads, attack code, or attack sequence.
We read the CVE-2026-117691 record, the GitHub advisory GHSA-c85j-3hq2-q6wj, the Keycloak 26.8.2 release notes, the GitHub release 26.8.2, the Keycloak upgrade guide, the Red Hat CVE page, the Phase Two release analysis, and CISA KEV on 6 November 2026, from 13.35 to 13.55 WIB. We tested 3 check scripts in a podman pod with the official image on quay.io 26.8.1 and 26.8.2, plus an nginx 1.29 rule. We did not test tar.gz, the Operator, the Red Hat build, PostgreSQL, or an attack.
After publication, from 14.09 to 14.15 WIB, we tested the nginx rule again with the path prefix /auth. We also tested the kcadm login through the env KC_CLI_PASSWORD, so that the password does not show in the process list.
The problem: a fake SAML login can get in as any user
Keycloak holds the login for many apps at once, so 1 login flaw opens all the apps behind it.
Keycloak is an open source login server. Businesses use Keycloak for single sign-on to portals, internal apps, and APIs. Many teams connect Keycloak to an office SAML IdP, for example Azure AD or ADFS, through identity brokering.
The CVE record came out on 5 November 2026 at 13.27 UTC. Release 26.8.2 came out on 5 November at 16.00 UTC, and the quay.io image came 4 minutes later.

This is the third SAML flaw in the Keycloak login path in 3 months. The semicolon variant was closed in 26.8.1 on 13 October, so a server on 26.8.0 has 2 variants at once.
How it works: advisory summary
This part uses the official summary from the CVE record. It helps you pick the right steps. It does not explain how to attack.
Summary of CVE-2026-117691
According to the CVE-2026-117691 record, the Keycloak SAML HTTP-Redirect binding endpoint processes parameter names that are written with percent encoding, for example %53AMLResponse. The signature check reads only the plain parameter names. As a result, a SAML message with no valid signature can pass, and an attacker with no account can take over federated user accounts.
Red Hat gives the rating Important and says that no mitigation meets the Red Hat criteria. So the update is the only official fix.
6 other CVEs to read
| CVE | Summary | CVSS 3.1 |
|---|---|---|
| CVE-2026-117691 | SAML redirect binding signature check bypassed, broker login as any user | 9.1 |
| CVE-2026-97175 | IdP auto-linking with an unverified email gives account takeover | 8.8 |
| CVE-2026-112208 | Account takeover through organization identity-first login, 26.8.0 and 26.8.1 | 8.1 |
| CVE-2026-92857 | A default DCR mapper can write the sub and aud claims | 8.1 |
| CVE-2026-114232 | UMA permissions can open resources of other users | 8.1 |
| CVE-2026-19688 | Admin APIAPIThe official door 2 systems use to exchange data, without anybody copying it by hand.Open the glossary with no audience check. The fix adds the kc-admin-api scope | 6.8 |

The groups in the diagram come from the release note titles, made by Rama Digital. The 5 DoS CVEs are rated 7.5, and 4 of them work with no login. We covered CVE-2026-111497 in the Brotli decoder in the Node2Shell CVE-2026-111497 warning.
Exploitation status on 6 November 2026
We found no attack reports, no public attack code, and no CISA KEV entry for CVE-2026-117691. Rama Digital analysis: the fix is in the public Keycloak repository, so attackers can read the code changes. Treat this release as urgent if you use SAML.
Who is most at risk
The risk depends on how each realm is used, not only on the version. Realms that use only OIDC are not exposed to CVE-2026-117691.
| Keycloak condition | Risk | Reason |
|---|---|---|
| Realm with a SAML identity provider (broker) | Highest | CVE-2026-117691: broker login as any user, with no account |
| SAML client on the redirect binding | High | Phase Two says this path is also exposed |
| 26.8.0 or 26.8.1 with organizations on | High | CVE-2026-112208, CVSS 8.1 |
| Open Dynamic Client Registration | Check | 5 CVEs of forged token claims through mappers |
| Realm endpoints open to the internet | Check | 5 DoS CVEs, 4 with no login |
| OIDC only and already on 26.8.2 | Low | Still look for signs from before the update |

This order follows the Phase Two release analysis: update today if you use a SAML broker or organizations on 26.8.x. Update this week if realm endpoints are open to the internet or DCR is on.
Vulnerable and safe versions
What counts is the Keycloak version that runs, not the Java or operating system version.
| Version | Status on 6 November 2026 | Action |
|---|---|---|
| 26.8.0 and 26.8.1 | Vulnerable, 33 CVEs. 26.8.0 also has the semicolon variant | Update to 26.8.2 today |
| 26.7.x | Vulnerable. The branch stopped at 26.7.5 | Move to 26.8.2 |
| 26.6.x, 26.4.x, and older | Vulnerable. No backport | Move to 26.8.2 |
| Red Hat build of Keycloak | Affected, no patch yet | Step 4, watch for an RHSA |
| Red Hat SSO 7 | Not affected by CVE-2026-117691 | Check the other CVEs |
| 26.8.2 | Safe for the 33 CVEs | Check the kc-admin-api scope, then step 5 |

The advisory has no list of affected versions. Phase Two advises you to treat all versions before 26.8.2 as vulnerable. According to the Red Hat CVE page, the Red Hat build of Keycloak is Affected with no patch on 6 November 2026, and Red Hat SSO 7 is not affected.
Rama Digital analysis: a move from 26.4 or 26.6 to 26.8.2 crosses several migration notes. Read the Keycloak upgrade guide for each version that you skip, then test on staging first.
Prerequisites
- SSH access to the Keycloak server, or access to the Docker host or the Kubernetes cluster.
- A master realm admin account and
kcadm.shfrom the Keycloakbin/folder. jqandcurlon the machine that runs the scripts.- A backup of the Keycloak database, for example a PostgreSQL dump, before the update.
- A list of the clients and service accounts that call the Admin API.
- An update window of 30 to 60 minutes. A restart stops logins for a short time.
Step 1: Check the Keycloak version
Save this script as check-version-keycloak.sh. The script reads a Keycloak folder, a Docker container, or a version that you type, then rates that version.
#!/usr/bin/env bash
# Check the Keycloak version for CVE-2026-117691 (SAML HTTP-Redirect, signature check bypass) and 32 other CVEs in release 26.8.2.
# The script only reads. Needs bash. For a container name it needs docker or podman (DOCKER=podman).
# Use: bash check-version-keycloak.sh /opt/keycloak
# or a container name: bash check-version-keycloak.sh keycloak
# or type the version: bash check-version-keycloak.sh 26.8.1
set -u
SAFE_V=26.8.2
# less_than A B: true when A < B
less_than() { [ "$1" != "$2" ] && [ "$(printf '%s\n%s\n' "$1" "$2" | sort -V | head -n 1)" = "$1" ]; }
rate() {
local raw=$1 v
case "$raw" in *[Rr]ed[Hh]at*|*RHBK*) echo "RED HAT BUILD: Red Hat lists Affected with no patch on 6 November 2026. Use step 4 and watch for an RHSA."; return ;; esac
v=$(printf '%s' "$raw" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -n 1)
[ -z "$v" ] && { echo "CHECK: unknown version ${raw:-empty}"; return; }
if ! less_than "$v" $SAFE_V; then echo "SAFE for the 33 CVEs of release $SAFE_V"; return; fi
case "$v" in
26.8.*) if less_than "$v" 26.8.1; then echo "VULNERABLE: update to $SAFE_V (also the ';' variant closed in 26.8.1)"; else echo "VULNERABLE: update to $SAFE_V"; fi ;;
26.7.*) echo "VULNERABLE: the 26.7 branch stopped at 26.7.5, move to $SAFE_V" ;;
*) if less_than "$v" 26.7.0; then echo "VULNERABLE: no patch on this branch, move to $SAFE_V"; else echo "CHECK: unknown version $v"; fi ;;
esac
}
ARG=${1:-/opt/keycloak}
if printf '%s' "$ARG" | grep -qE '^[0-9]+\.[0-9]+'; then
echo "Keycloak $ARG: $(rate "$ARG")"; exit 0
fi
if [ -d "$ARG" ]; then
DIR=$ARG
echo "== Folder: $(cd "$DIR" && pwd)"
if [ -f "$DIR/version.txt" ]; then
RAW=$(head -n 1 "$DIR/version.txt"); echo "version.txt: $RAW"
else
RAW=$(ls "$DIR"/lib/lib/main/ 2>/dev/null | grep -E 'keycloak-services-' | head -n 1)
[ -n "$RAW" ] && echo "jar: $RAW"
fi
[ -z "${RAW:-}" ] && { echo "Folder $DIR is not a Keycloak folder (no version.txt or keycloak-services-*.jar)"; exit 1; }
RAW=${RAW#Keycloak - Version }
echo "Keycloak ${RAW#*keycloak-services-}: $(rate "${RAW#*keycloak-services-}")"
exit 0
fi
# Not a version and not a folder: treat it as a container name.
D=${DOCKER:-docker}
RAW=$($D exec "$ARG" cat /opt/keycloak/version.txt 2>/dev/null | head -n 1)
[ -z "$RAW" ] && RAW=$($D exec "$ARG" ls /opt/keycloak/lib/lib/main 2>/dev/null | grep -E 'keycloak-services-' | head -n 1)
[ -z "$RAW" ] && { echo "No folder, version, or container named $ARG"; exit 1; }
echo "Container $ARG: $RAW"
RAW=${RAW#Keycloak - Version }
echo "Keycloak ${RAW#*keycloak-services-}: $(rate "${RAW#*keycloak-services-}")"
# Install from zip or tar.gz
bash check-version-keycloak.sh /opt/keycloak
# Docker container named keycloak (Podman: put DOCKER=podman before the command)
bash check-version-keycloak.sh keycloak
# A version that you copied from another server
bash check-version-keycloak.sh 26.7.5

Check the result: "VULNERABLE" means go to steps 2 and 3 today. "RED HAT BUILD" means use step 4 and watch for an RHSA. "SAFE" still means run step 5, because the flaws existed before you updated.
Step 2: Check the realms that use SAML
This step answers 3 questions: which realms have an active SAML identity provider, which realms have a SAML client, and which realms keep login events.
Save this script as check-exposure-keycloak.sh. The script only reads the configuration through kcadm.sh and does not keep the login token.
#!/usr/bin/env bash
# Check the Keycloak realms that use SAML (CVE-2026-117691). The script only reads. Needs bash, jq, and curl.
# Use: bash check-exposure-keycloak.sh [public URL]
# Env: KCADM (default /opt/keycloak/bin/kcadm.sh), KC_URL (default http://localhost:8080),
# KC_USER (default admin), KC_PASSWORD (if empty, the script asks on the terminal and hides the password).
# Optional public URL argument: the script sends one GET without parameters to the broker endpoint of each SAML IdP and prints the HTTP code.
set -u
KCADM=${KCADM:-/opt/keycloak/bin/kcadm.sh}
KC_URL=${KC_URL:-http://localhost:8080}
KC_USER=${KC_USER:-admin}
PUB=${1:-}
if [ -z "${KC_PASSWORD:-}" ]; then read -rs -p "Password $KC_USER: " KC_PASSWORD < /dev/tty; echo; fi
CFG=$(mktemp); trap 'rm -f "$CFG"' EXIT
kc() { "$KCADM" "$@" --config "$CFG"; }
# Send the password in the env KC_CLI_PASSWORD, not in the --password argument. Arguments show in the process list (ps).
KC_CLI_PASSWORD="$KC_PASSWORD" "$KCADM" config credentials --server "$KC_URL" --realm master --user "$KC_USER" --config "$CFG" >/dev/null 2>&1 \
|| { echo "Login to $KC_URL failed"; exit 1; }
unset KC_PASSWORD
NREALM=0; NIDP=0; NCLI=0
echo "== 1. Realms at $KC_URL"
for R in $(kc get realms --fields realm 2>/dev/null | jq -r '.[].realm'); do
INFO=$(kc get "realms/$R" --fields eventsEnabled,organizationsEnabled 2>/dev/null)
IDPS=$(kc get identity-provider/instances -r "$R" 2>/dev/null | jq -c '[.[] | select(.providerId=="saml")]')
CLI=$(kc get clients -r "$R" --fields clientId,protocol 2>/dev/null | jq -r '.[] | select(.protocol=="saml") | .clientId')
NI=$(echo "$IDPS" | jq 'length'); NC=$(printf '%s' "$CLI" | grep -c .)
echo "Realm $R: SAML IdP $NI, SAML client $NC, organizations $(echo "$INFO" | jq -r '.organizationsEnabled'), events $(echo "$INFO" | jq -r '.eventsEnabled')"
[ "$NI" -gt 0 ] || [ "$NC" -gt 0 ] && NREALM=$((NREALM + 1))
while IFS=$'\t' read -r A EN VS PB; do
[ -z "$A" ] && continue
echo " IdP $A: enabled=$EN validateSignature=$VS postBindingResponse=$PB"
if [ "$EN" = true ]; then echo " AFFECTED: active SAML IdP $A"; NIDP=$((NIDP + 1)); fi
if [ -n "$PUB" ]; then
K=$(curl -s -o /dev/null -w '%{http_code}' --max-time 15 "${PUB%/}/realms/$R/broker/$A/endpoint")
case "$K" in 000|403|404) echo " Broker endpoint from this machine: HTTP $K" ;; *) echo " OPEN from this machine: HTTP $K ${PUB%/}/realms/$R/broker/$A/endpoint" ;; esac
fi
done < <(echo "$IDPS" | jq -r '.[] | [.alias, .enabled, (.config.validateSignature // "-"), (.config.postBindingResponse // "-")] | @tsv')
for C in $CLI; do echo " CHECK: SAML client $C"; NCLI=$((NCLI + 1)); done
[ "$(echo "$INFO" | jq -r '.organizationsEnabled')" = true ] && echo " CHECK: organizations enabled (CVE-2026-112208 in 26.8.0 and 26.8.1)"
[ "$(echo "$INFO" | jq -r '.eventsEnabled')" != true ] && echo " CHECK: login events are not stored"
done
echo "== 2. Summary"
echo "Realms with SAML: $NREALM. Active SAML IdPs: $NIDP. SAML clients: $NCLI."
exit 0
# Run on the Keycloak server. The script asks for the master realm admin password.
KC_URL=http://localhost:8080 bash check-exposure-keycloak.sh
# Add the public URL to check the SAML broker endpoints from this machine
KC_URL=http://localhost:8080 bash check-exposure-keycloak.sh https://sso.example.com

Check the result: write down every "AFFECTED" and "CHECK" line. A realm with "AFFECTED: active SAML IdP" needs step 3 today. If the update cannot run yet, apply step 4 now.
Step 3: Update to 26.8.2
The update is the only step that closes these 33 CVEs. This release also changes some behavior, so read the Keycloak upgrade guide before the update.
3a. Official image
# 3a. Official image (tested with podman, the docker commands are the same)
docker pull quay.io/keycloak/keycloak:26.8.2
docker stop keycloak && docker rm keycloak
# Use the same volumes, env, ports, and start command as the old container
docker run -d --name keycloak -p 127.0.0.1:8080:8080 \
--env-file keycloak.env -v keycloak-data:/opt/keycloak/data \
quay.io/keycloak/keycloak:26.8.2 start
# Your own image: change the FROM line to quay.io/keycloak/keycloak:26.8.2, then build again

In our test, Keycloak 26.8.2 was ready in 15 seconds, and the master, toko, and internal realms stayed. Our test used start-dev with the H2 database on a volume. In production, use the start command and the same database as the old container.
3b. tar.gz install or Keycloak Operator
# 3b. Install from the GitHub release tar.gz: download and check SHA-1
curl -LO https://github.com/keycloak/keycloak/releases/download/26.8.2/keycloak-26.8.2.tar.gz
curl -LO https://github.com/keycloak/keycloak/releases/download/26.8.2/keycloak-26.8.2.tar.gz.sha1
[ "$(sha1sum keycloak-26.8.2.tar.gz | cut -d ' ' -f 1)" = "$(cat keycloak-26.8.2.tar.gz.sha1)" ] && echo SHA1 MATCH
# Copy your conf/, providers/, and themes/ to the new folder, then build and start (not tested)
tar xzf keycloak-26.8.2.tar.gz && cd keycloak-26.8.2
bin/kc.sh build && bin/kc.sh start --optimized
# Keycloak Operator: apply the 26.8.2 operator manifests (not tested)
kubectl -n keycloak apply -f https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/26.8.2/kubernetes/kubernetes.yml
The .sha1 file in the GitHub release 26.8.2 holds only the hash, so the command above compares the file content directly. We did not test the build from tar.gz or the Operator upgrade, because those steps depend on your providers and themes.
3c. Add the kc-admin-api scope to your service accounts
26.8.2 closes CVE-2026-19688 with an audience check in the Admin API. The built-in clients admin-cli and security-admin-console get the kc-admin-api scope automatically. Clients and service accounts that you made yourself do not.
# 3c. Log kcadm.sh in to the master realm. kcadm.sh asks for the admin password.
kcadm.sh config credentials --server http://localhost:8080 --realm master --user admin
# Add the kc-admin-api scope to a client that you made (example: ci-bot in realm toko)
CID=$(kcadm.sh get clients -r toko -q clientId=ci-bot --fields id --format csv --noquotes)
SID=$(kcadm.sh get client-scopes -r toko --fields id,name --format csv --noquotes | grep kc-admin-api | cut -d, -f1)
kcadm.sh update clients/$CID/default-client-scopes/$SID -r toko -n
# Check: a ci-bot token to the Admin API. The correct result: 200. CI_SECRET holds the ci-bot client secret.
T=$(curl -s -d grant_type=client_credentials -d client_id=ci-bot -d client_secret="$CI_SECRET" \
http://localhost:8080/realms/toko/protocol/openid-connect/token | jq -r .access_token)
curl -s -o /dev/null -w '%{http_code}\n' -H "Authorization: Bearer $T" 'http://localhost:8080/admin/realms/toko/users?max=1'

In our test, the ci-bot service account got 200 from the Admin API on 26.8.1, then 401 on 26.8.2. After we added the kc-admin-api scope, the code was 200 again. Do this command again for each client that calls the Admin API.
3d. Read the other behavior changes
- Only realm admins can manage protocol mappers.
- The allowed DCR mappers now follow a new realm option. That option is empty by default.
- The SAML broker requires a valid bearer SubjectConfirmation. SAML 1.1 parsing is off by default.
- IdP auto-linking requires a verified email. A token from token exchange cannot live longer than the original token.
- The SOAP endpoints deny requests above 120000 bytes.

Check the result: the version script prints "SAFE for the 33 CVEs", your realms and IdPs are still there, and every service account gets 200 from the Admin API.
Step 4: Reduce the impact until the update is done
This step reduces the impact, but it does not make the server safe. The 32 other CVEs stay open until you update.
Rama Digital analysis: Red Hat says that no mitigation meets the Red Hat criteria. We chose 2 temporary steps. Condition: the update cannot run today, or you use the Red Hat build. Limit: request forms that we did not test can get past the proxy rule.
4a. Deny percent-encoded parameter names at the reverse proxy
# Step 4a: deny SAML parameters with percent-encoded names or a ';' separator in nginx, before the request reaches Keycloak.
# This is a temporary step if you cannot update to 26.8.2 yet. The rule does not replace the update.
# Test: nginx 1.29 in front of Keycloak (port 8080) on port 8081. On your server, put the location block in your existing 443 server.
server {
listen 8081;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
}
# SAML broker and SAML client endpoints. Answer 403 if a parameter name has %XX or the query has ';'.
# The regex has no ^, so it also matches a path prefix, for example /auth from KC_HTTP_RELATIVE_PATH.
# Put this block above other regex locations. A ^~ or = location for the Keycloak path makes nginx skip this rule.
location ~ /realms/[^/]+/(broker/[^/]+/endpoint|protocol/saml) {
if ($args ~ "(^|&)[^=&]*%[0-9A-Fa-f]{2}[^=&]*=") { return 403; }
if ($args ~ ";") { return 403; }
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
}
}

In our test, nginx answered 403 to 3 test requests with percent-encoded names or a semicolon. A plain SAML request still reached Keycloak and got 400 from Keycloak. OIDC discovery got 200.
We also tested this rule with Keycloak on the path prefix /auth. The result was the same: 403 for the test requests, 400 and 200 for plain requests.
Put this block above other regex locations on your server. A ^~ or = location for the Keycloak path makes nginx skip this rule.
After you apply the rule, test it from outside through your public URL. The correct result is 403.
# 4a. Test the rule from outside through the public URL. Replace realm toko and alias saml-kantor.
# If Keycloak uses a path prefix (example /auth), write that prefix before /realms.
# The correct result: 403 from the proxy.
curl -s -o /dev/null -w '%{http_code}\n' 'https://sso.example.com/realms/toko/broker/saml-kantor/endpoint?%53AMLResponse=x'
Also test 1 real SAML login from your IdP. We did not test this rule with a real IdP.
Also close ports 8080 and 8443 at the firewall, so that Keycloak can only be reached through the proxy.
4b. Disable SAML IdPs that you do not use
# 4b. Log kcadm.sh in first, as in step 3c
# Disable a SAML IdP that you do not use (alias from step 2)
kcadm.sh update identity-provider/instances/saml-kantor -r toko -s enabled=false
# Enable it again after the update to 26.8.2
kcadm.sh update identity-provider/instances/saml-kantor -r toko -s enabled=true

Users who log in through that IdP cannot log in until you enable it again. Use this step only for IdPs that you do not use, or when the business team accepts a login pause.
Check the result: the exposure script shows that IdP as disabled, and the public URL test command from step 4a gets 403 from the proxy.
Step 5: Look for signs of use
These flaws existed before you updated, so also look for signs from before the update.
Save this script as check-signs-keycloak.sh. The script looks for unusual SAML requests in the access logs, login events through an IdP, and new users since the date that you choose.
#!/usr/bin/env bash
# Look for signs that CVE-2026-117691 was used, in access logs and in Keycloak. The script only reads. Needs bash, jq, and zgrep.
# Use: bash check-signs-keycloak.sh <date YYYY-MM-DD> [access log folder]
# Access logs: access.log* files (including .gz) from nginx or another proxy in front of Keycloak.
# Env: KCADM (default /opt/keycloak/bin/kcadm.sh), KC_URL (default http://localhost:8080),
# KC_USER (default admin), KC_PASSWORD (if empty, the script asks on the terminal and hides the password).
set -u
SINCE=${1:?start date, for example 2026-10-01}
LOGDIR=${2:-}
KCADM=${KCADM:-/opt/keycloak/bin/kcadm.sh}
KC_URL=${KC_URL:-http://localhost:8080}
KC_USER=${KC_USER:-admin}
SIGNS=0; CHECKS=0
sign() { echo " SIGN: $1"; SIGNS=$((SIGNS + 1)); }
check() { echo " CHECK: $1"; CHECKS=$((CHECKS + 1)); }
SAML='"GET /realms/[^/ ]+/(broker/[^/ ]+/endpoint|protocol/saml)'
access() { find "$LOGDIR" -type f -name 'access.log*' 2>/dev/null | sort; }
search() { access | xargs -r zgrep -hE "$1" 2>/dev/null; }
echo "== 1. Access logs (${LOGDIR:-no folder given}, since $SINCE)"
if [ -n "$LOGDIR" ]; then
echo " $(access | wc -l) access.log files"
while IFS= read -r l; do sign "percent-encoded parameter name or ';': ${l:0:150}"; done \
< <(search "$SAML"'\?([^ ]*&)?[^=& ]*%[0-9A-Fa-f]{2}[^=& ]*=|'"$SAML"'\?[^ ]*;' | head -n 5)
echo " SAML GETs per IP (top 5):"
search "$SAML" | awk '{print $1}' | sort | uniq -c | sort -rn | head -n 5 | sed 's/^ */ INFO: /'
fi
if [ -z "${KC_PASSWORD:-}" ]; then read -rs -p "Password $KC_USER: " KC_PASSWORD < /dev/tty; echo; fi
CFG=$(mktemp); trap 'rm -f "$CFG"' EXIT
kc() { "$KCADM" "$@" --config "$CFG"; }
# Send the password in the env KC_CLI_PASSWORD, not in the --password argument. Arguments show in the process list (ps).
KC_CLI_PASSWORD="$KC_PASSWORD" "$KCADM" config credentials --server "$KC_URL" --realm master --user "$KC_USER" --config "$CFG" >/dev/null 2>&1 \
|| { echo "Login to $KC_URL failed"; exit 1; }
unset KC_PASSWORD
echo "== 2. Realm events since $SINCE"
for R in $(kc get realms --fields realm 2>/dev/null | jq -r '.[].realm'); do
if [ "$(kc get "realms/$R" --fields eventsEnabled 2>/dev/null | jq -r '.eventsEnabled')" != true ]; then
check "realm $R: events are off"; continue
fi
while IFS= read -r l; do check "realm $R: $l"; done < <(kc get events -r "$R" -q "dateFrom=$SINCE" -q max=1000 2>/dev/null \
| jq -r '.[] | select(.type=="IDENTITY_PROVIDER_LOGIN" or .type=="IDENTITY_PROVIDER_FIRST_LOGIN" or .type=="FEDERATED_IDENTITY_LINK") | "\((.time/1000)|todate) \(.type) user \(.userId // "-")"' | head -n 20)
done
echo "== 3. Users created since $SINCE"
LIMIT=$(date -d "$SINCE" +%s)000
for R in $(kc get realms --fields realm 2>/dev/null | jq -r '.[].realm'); do
while IFS= read -r u; do check "realm $R: new user $u"; done < <(kc get users -r "$R" --fields username,createdTimestamp -q max=1000 2>/dev/null \
| jq -r --argjson t "$LIMIT" '.[] | select(.createdTimestamp >= $t) | .username')
done
echo "== Result: SIGNS $SIGNS, CHECKS $CHECKS"
[ "$SIGNS" -gt 0 ] && echo "Keep the logs as evidence, cut access from the internet, then follow step 5 in the article."
exit 0
# nginx access logs in front of Keycloak, from 1 October 2026
bash check-signs-keycloak.sh 2026-10-01 /var/log/nginx

In this test, the script found 3 SIGNS from 1 dummy IP in 2 log files, including 1 .gz file. The script also printed 4 CHECKS: events off in the master and internal realms, and 2 new users. The user admin is the bootstrap admin of the test, so that line has an explanation.

Keycloak keeps login events only when "Save events" is on in the realm. If events are off, you only have the proxy access logs. Turn on events today, so that the next incident leaves a trace.
Check the result: the script prints "SIGNS 0", and every CHECK has an explanation from your team. Attackers can delete logs, so 0 signs does not prove that the server is safe.
If you find even 1 sign, keep the logs as evidence. Then sign out all sessions of that realm, change the admin passwords and client secrets, check the admin roles, and update to 26.8.2.
If Keycloak holds customer or staff accounts, Indonesian Law 27/2022 on Personal Data Protection requires a written notice within 3 times 24 hours after a personal data breach. Also read the LXD and Incus CVE-2026-112816 warning.
Example: 1 afternoon of response for 3 Keycloak servers
This simulation uses dummy data. The server names, times, and results are not customer data.
| Time (WIB) | Dummy server | What was checked | Result | Action |
|---|---|---|---|---|
| 13.00 | sso-portal (Docker 26.8.1) | Steps 1 and 2 | VULNERABLE, 1 active SAML IdP | Step 4a now, update this afternoon |
| 13.30 | sso-partner (zip 26.6.7, OIDC only) | Steps 1 and 2 | VULNERABLE, 0 SAML realms | Plan the move to 26.8.2 this week |
| 14.00 | sso-internal (Red Hat build) | Steps 1 and 2 | RED HAT BUILD, 1 SAML client | Step 4a, watch for an RHSA |
| 16.00 | sso-portal | Steps 3 and 5 | SAFE, 1 service account gets 401 | Add the kc-admin-api scope |
The lesson from the simulation: a server with OIDC only still needs the update, because the 32 other CVEs do not depend on SAML.
Who installs the patch
Keycloak often runs on your own server, so your hosting provider does not install this patch for you.
| Service type | Patch owner | What you do |
|---|---|---|
| Keycloak on a VPS, Docker, or Kubernetes | Your team | Steps 1 to 5 |
| Red Hat build of Keycloak | Red Hat, through an RHSA | Steps 2, 4, and 5 until the RHSA comes out |
| Managed Keycloak from a vendor | The vendor, as the contract says | Ask for the update date and the step 5 result |
| An app that ships Keycloak inside | The app vendor | Ask for the Keycloak version in writing and the patch date |
Rama Digital recommendation: watch the security releases of all your login systems and web apps on a fixed schedule. This week we also wrote the Apache OFBiz CVE-2026-83542 warning and the Rails Active Storage CVE-2026-122006 warning.
Checklist for today
- List all Keycloak servers, including staging. Owner: IT team. Evidence: a list of names and versions.
- Check the version with step 1. Owner: IT team. Evidence: script output.
- Check SAML realms with step 2. Owner: IT team. Evidence: script output.
- Apply step 4 on every SAML server that cannot update today. Owner: IT team. Evidence: a 403 code from the proxy.
- Update to 26.8.2 and add the kc-admin-api scope. Owner: IT team. Evidence: script output after the update and a 200 code from the Admin API.
- Turn on login events in every realm. Owner: IT team. Evidence: exposure script output.
- Look for signs with step 5. Owner: IT team and security team. Evidence: script output.
Questions and answers
Has this flaw been used in attacks?
On 6 November 2026, we found no attack reports, and CVE-2026-117691 is not on CISA KEV. But the fix is in public code, so update today if you use SAML.
My realms use only OIDC. Am I safe?
According to Phase Two, realms with OIDC only are not exposed to CVE-2026-117691. Still update this week, because the 32 other CVEs, including 5 DoS, do not depend on SAML.
I use the Red Hat build of Keycloak. What do I do?
On 6 November 2026, Red Hat lists the status Affected with no patch. Apply step 4, run step 5, and watch for an RHSA on the Red Hat CVE page.
I am still on 26.4 or 26.6. Why is there no patch?
Keycloak released this fix only in 26.8.2. Move to 26.8.2, and read the migration notes for each version that you skip.
Why does my service account get 401 after the update?
26.8.2 checks the audience in the Admin API. Add the kc-admin-api client scope to that client as in step 3c. In our test, the code changed from 401 to 200.
Does the image update delete my realms?
No, if you use the same database or volume. In our test, the realm and the SAML IdP stayed after the image change. Still make a backup before the update.
Do the check scripts change anything on my server?
No. The 3 scripts only read. The commands that change something are only in steps 3 and 4, and each command is written separately.
Next steps
Limits that still apply: step 4 does not replace the update, and the update does not undo access that already happened. If your Keycloak shows signs in step 5, or you need help with the update and an audit, use Vibe Code Rescue. For 1 urgent problem today, book a 30-minute session.
Sources
- CVE-2026-117691 record: the official description, CWE-347, and CVSS 3.1 9.1, 5 November 2026.
- GitHub advisory GHSA-c85j-3hq2-q6wj: the Keycloak advisory for CVE-2026-117691.
- Keycloak 26.8.2 release notes: the list of 33 CVEs in 26.8.2.
- GitHub release 26.8.2: release files, hashes, and the release time.
- Keycloak upgrade guide: the kc-admin-api scope and the 26.8.2 behavior changes.
- Red Hat CVE page: rating, package status, and the mitigation note.
- Phase Two release analysis: exposed configurations, branches with no backport, and update order.
- official image on quay.io: tags 26.8.1 and 26.8.2.
- CISA KEV: status on 6 November 2026.
- Indonesian Law 27/2022 on Personal Data Protection: the duty to report a personal data breach.




