keycloak-iam — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited keycloak-iam (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
This skill covers running, configuring, deploying, and integrating with Keycloak, the open-source identity & access management server. It targets the modern Quarkus-based distribution (24.x → 26.8.x); for instances still in the field on 16.x–23.x — including the WildFly "-legacy" builds that ended at 19.0.3 — and for verifying realm migrations off them, route to legacy-and-migration.md. Versions and breaking changes are current to Keycloak 26.8.0; the reference files are still written against the 26.6 feature set, so treat 26.7/26.8-only features as unresearched there and read the release notes directly.
The Red Hat build of Keycloak (RHBK) is downstream of upstream Keycloak with longer support windows and the same surface area; advice here applies to both unless explicitly noted.
Siblings in the `k8s` plugin. Keycloak is almost always deployed behind something and packaged by something, and both are common failure sources rather than Keycloak bugs: reverse-proxy headers, TLS termination and rate limiting in front of it are `traefik-hardening` (a Keycloak that emits wrong issuer URLs or loops on redirect is usually a proxy-header problem, not an IAM one); authoring or fixing the chart that deploys it is `helm`; running it under OpenShift's SCC and arbitrary-UID constraints is `openshift-app`.
Keycloak is a large product. Don't try to load everything — route to one or two reference files based on what the user is asking, then go deep.
references/
├── server-config.md → CLI/env vars, kc.sh, kcadm.sh, hostname, db, cache,
│ TLS/proxy, logging, features, bootstrap-admin, KCRAW_
├── k8s-deployment.md → Operator install, Keycloak CR, KeycloakRealmImport,
│ raw manifests, HA topology, probes, autoscaling
├── security-hardening.md → Realm policies, brute force, FGAP v2, client policies
│ (FAPI/OAuth 2.1), DPoP, redirect URI safety, recent CVEs
├── integration.md → OIDC/SAML flows, IdP brokering, LDAP/AD federation,
│ themes, SPIs, admin clients (Java/JS/kcadm/Terraform)
├── observability.md → Metrics, OTLP tracing, structured logging, health
│ probes, troubleshooting recipes
├── upgrade-and-backup.md → Upgrade matrix, zero-downtime patches, realm
│ export/import, DB backup, disaster recovery
└── legacy-and-migration.md → Identifying/operating old versions (16.x–23.x,
WildFly "-legacy" builds), per-version breaking-
change ladder 16→current, and verifying a migrated
realm/clients against the pre-migration export
(normalize+diff recipe + expected-diffs allowlist)Routing cheatsheet:
| User question | Read first |
|---|---|
| "How do I set the hostname / proxy / DB?" | server-config.md |
| "Why is my login redirecting in a loop?" | server-config.md (hostname/proxy) |
| "How do I deploy Keycloak on Kubernetes?" / "Operator vs raw manifests" | k8s-deployment.md |
"Show me a Keycloak CR" / "What goes in spec.*?" | k8s-deployment.md + assets/examples/ |
| "How do I do zero-downtime upgrades?" | k8s-deployment.md + upgrade-and-backup.md |
| "Harden my realm" / "FAPI / OAuth 2.1 / DPoP / FGAP" | security-hardening.md |
| "Recent CVEs?" / "Is 26.x.y vulnerable?" | security-hardening.md §CVE table |
| "Wire my SPA / mobile app / service to Keycloak" | integration.md |
| "Configure SAML / OIDC IdP / LDAP / social login" | integration.md |
| "Custom theme / authenticator / event listener / mapper" | integration.md §SPIs / §Themes |
| "What metrics / Grafana dashboard / Prometheus / OTLP" | observability.md |
| "Realm export gotchas" / "How do I back up?" / "Liquibase migration stuck" | upgrade-and-backup.md |
"What version is this old instance?" / anything on WildFly / -legacy / /auth paths / 16.x–23.x | legacy-and-migration.md §1–2 |
| "We're on 1x.x — what breaks if we upgrade?" | legacy-and-migration.md §3 |
| "Did the migration keep all realm/client settings?" / compare realm before vs after | legacy-and-migration.md §4 + assets/compare/ |
When in doubt about a CLI flag, the source of truth is https://www.keycloak.org/server/all-config (full option index). When in doubt about a CR field, the source of truth is the CRD YAML in keycloak-k8s-resources at the version tag (see §"Authoritative sources" below).
Latest stable: Keycloak 26.8.0 (2026-10-01). Minimum safe: 26.7.5 or 26.8.0. Every 26.7 patch was a security batch — 26.7.1 (12 CVEs), 26.7.2 (two account-takeover class), 26.7.3 (20), 26.7.4 (6, incl. CVE-2026-17526: impersonation role → realm admin), 26.7.5 (15, 2026-09-30). Anything below 26.7.5 is missing some of them. The 26.6 line ended at 26.6.4 (2026-06-26) for community users — later 26.6.x git tags ship no GitHub Release and no quay.io image.
There is no 27.x; 26.x is the current line. Plan for one thing now though: Keycloak has announced it removes all uses of SHA1 in version 27.
Support window is narrower than most people assume. The project's security policy fixes issues "in the current major.minor release … or for lower severity vulnerabilities or hardening in the following major.minor release". That is effectively the current minor only — there is no N-minors-back window. Long-term support is the commercial Red Hat build, not upstream. Do not quote specific CVE IDs from memory; pull the authoritative list with gh api repos/keycloak/keycloak/security-advisories and gh release view <version> --repo keycloak/keycloak --json body. The feed alone is not enough — its first_patched_version fields are empty, so read the candidate releases' notes to learn which version actually closes a CVE. See security-hardening.md §CVE table for how to surface the current set.
Notable changes through the 26.x line:
| Version | Key changes (operator-relevant) | |
|---|---|---|
| 26.8.x | stateless (multi-cluster v2) supported; multi-site (v1) deprecated, clusterless to be removed; login failures stored in the DB; Client Secret Rotation and SCIM API on by default; Realm Operator EOL; Quarkus 3.40. Breaking changes: §26.8.0 below | |
| 26.7.x | SCIM user provisioning (preview), multi-cluster HA without external caches (preview — supersedes the external-Infinispan topology this skill documents), OpenID Shared Signals Framework (experimental), Identity Brokering API V2 (V1 deprecated, still default-on), step-up auth for SAML clients, HAProxy + Traefik reverse-proxy blueprints. Not yet researched at reference depth — read the release notes | |
| 26.6.x | Workflows, JWT Authorization Grant, Federated client auth, Zero-downtime patch updates, KCRAW_ env prefix, automatic K8s truststore, graceful HTTP shutdown, configurable Service name/port in Operator, organization groups, sensitive-info redaction in HTTP access logs. 26.6.2, 26.6.3 and 26.6.4 are all security batches; 26.6.4 (2026-06-26) is the last 26.6 patch seen — see the advisory feed for the CVE sets they close | |
| 26.5.x | Token Exchange Standard (RFC 8693) GA, declarative-user-profile GA, FIPS via Bouncy Castle, ECC keys default for new realms, Java 25 added (server image still on JDK 21 for FIPS) | |
| 26.4.x | hostname-v2 GA + hostname-v1 removed, Quarkus 3.20 LTS, --proxy-headers (replaces `--proxy edge | reencrypt`) |
| 26.3.x | Organizations GA, Account Console v3 GA, Admin UI on PatternFly React, FGAP v2 preview | |
| 26.2.x | OIDC client policies v2, persistent user sessions GA | |
| 26.0.0 | KEYCLOAK_ADMIN removed → KC_BOOTSTRAP_ADMIN_*, persistent user sessions on by default, hostname-v2 by default |
Always cross-check with the release-notes body via gh release view <tag> --repo keycloak/keycloak --json body --jq '.body'. Do not rely solely on this table — bump the date and verify.
Do these before the upgrade unless the row says otherwise:
| Change | Action |
|---|---|
view-clients no longer sees client secrets (masked **********) | Automation that reads secrets needs manage-clients. |
| IdP mappers can no longer grant admin roles, directly or via group (CVE-2026-12388) | Default allowAdminRoleMapping=false, existing IdPs included; a manage-realm admin enables it per IdP. Earlier grants are not removed. |
Operator: Secrets behind KeycloakOIDCClient spec.client.auth.secretRef need label operator.keycloak.org/kind=KeycloakOIDCClient | Label them first, or the CR goes HasErrors. |
--features=preview no longer enables stateless | Set --features=stateless explicitly, plus --cache-embedded-cluster-name=<unique> (not ISPN) — without it the server refuses to start. |
Login failures stored in the DB by default (login-failures v2) | Expect more DB connections/CPU; --features=login-failures:v1 restores in-memory (deprecated, incompatible with stateless). |
Token-exchange delegation needs FGAP v2; scope delegation → delegation:user | Update clients requesting the old scope. |
initiating_idp logout param ignored; upstream IdP always logged out | Temporary: allow-initiating-idp-logout-param (deprecated). |
| X.509 user authenticators need "CA subject DN" | Set it per authenticator; old configs still work until the next major. |
| Offline-session indexes rebuilt | Tables > 300 000 rows: index creation is skipped at migration and done in the background after startup (PostgreSQL, Oracle, MySQL/MariaDB, MSSQL Enterprise); otherwise run the logged SQL by hand. |
| Async commit now also on MSSQL/Oracle (ephemeral tables) | MSSQL: DBA runs ALTER DATABASE … SET DELAYED_DURABILITY = ALLOWED, or opt out with --spi-connections-jpa--quarkus--async-commit=false. |
Disabled clients dropped from aud; Authorization Services resource URIs normalized | Review policies that relied on either. |
Also: the Realm Operator (KeycloakRealm/KeycloakClient) reached end of life — use KeycloakRealmImport plus the preview KeycloakOIDCClient/KeycloakSAMLClient. Extensions using Quarkus internals or the Vert.x HTTP client need retesting (Quarkus 3.40).
These bite on upgrade and several are silent. Sourced from the upgrading guide, which carries items the release notes do not.
26.7.0
| Change | Action |
|---|---|
Feature renamed dynamic-scopes → `parameterized-scopes` | --features=dynamic-scopes no longer matches. Switch the flag. The DB attribute is.dynamic.scope auto-migrates. Parameterized scopes now also need parameterized.scope.type; a custom type additionally needs parameterized.scope.regexp. |
| `view-system` admin role removed | Introduced in 26.5.4 and deprecated immediately; removed for security. Full server information now needs a master-realm user with manage-realm. |
| Identity Provider alias is immutable | Changing an alias via the Admin REST API after creation returns 400. |
| X.509 client auth: "Certificate Authority subject DN" is compulsory | "Allow regex pattern comparison" is deprecated. New client policy executor tls-client-auth-ca-subject-dn. |
| Client secrets always 86 characters | Length no longer varies with signing algorithm; existing secrets unaffected. |
shutdown-timeout default 1s → 10s | Caches now rebalance before shutdown. Restore with shutdown-timeout=1s. |
| Session cookies hashed with SHA-384 | KC_AUTH_SESSION_HASH and KEYCLOAK_SESSION replaced on next request; no config needed. |
| Removed config keys | spi-user-sessions--infinispan--use-batches, spi-user-sessions--infinispan--max-batch-size. |
26.7.1 — Admin API access tightened twice, and both can lock people out: admin roles granted via protocol mappers no longer grant Admin API access (only directly-assigned roles do), and client-scope assignment endpoints now require manage on client scopes. A regression where a custom realm role named admin could not be updated in non-master realms was fixed in the same release.
26.7.2 — the legacy client-initiated account-linking endpoint /realms/{realm}/broker/{provider}/link is disabled by default. Migrate to AIA kc_action=idp_link; allow-client-initiated-account-linking restores it temporarily and is itself deprecated.
26.7.3 — redirect URIs containing OIDC response parameters (state, code, session_state, …) are rejected by default. The escape hatch allow-oidc-params-in-redirect-uris is deprecated with removal planned for 27, and allow.oidc.params.in.redirect.uris allows per-client migration. Separately, Authorization Services now reserve the `kc.` claim prefix for server-controlled attributes and filter user-supplied claims carrying it.
26.7.5 — the client-updater-source-groups client-policy condition matches the full group path. A value without a leading slash means a top-level group (topGroup → /topGroup, unchanged); a subgroup configured by simple name must become its full path (/topGroup/level2group).
Feature status moved too. step-up-authentication-saml went preview → supported. SCIM is now preview (--features=scim-api). Identity Brokering API V2 is supported but off by default while V1 is deprecated and still on by default. The experimental token-exchange-external-internal:v2 was removed.
stateless, multi-cluster v2)On 26.8+ it is supported and v1 is deprecated: multi-site (v1, external Infinispan) is deprecated for removal in a future major, clusterless will be removed — migrate both to stateless (keycloak.org/high-availability/multi-cluster-v2/migrate-from-v1-to-v2). The bullets below describe 26.7, where it was still preview.
Enable with `--features=stateless` (26.8+: plus --cache-embedded-cluster-name). It connects clusters through their embedded Infinispan caches and propagates invalidation through the database using an outbox table that other sites poll, which is what removes the external Infinispan requirement and the Kubernetes/AWS-specific assumptions with it.
On 26.7:
v1 and multi-cluster v2 side by side as current choices; only v2 carries the preview admonition. v1 remains the non-preview path.
since session data lives in the database and less is cached.
26.7.0 with preview features on failed when the stateless cluster provider captured a null NodeInfo before postInit.
When the user asks about something specific, prefer these sources over generic recall. Cited paths are relative to repo root — access via a local clone, or fetch via gh api repos/<owner>/<repo>/contents/<path>.
keycloak/keycloak)quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/*PropertyMappers.javaquarkus/config-api/src/main/java/org/keycloak/config/*Options.javaoperator/src/main/java/org/keycloak/operator/themes/src/main/resources/theme/ + js/apps/{account-ui,admin-ui}/docs/guides/server/*.adoc, docs/guides/operator/*.adoc, docs/guides/high-availability/*.adockeycloak/keycloak-k8s-resources)26.7.0) with the manifests under kubernetes/. Through 26.6 that was three files; 26.7+ has more, and the operator crash-loops (`Couldn't start informer for keycloaksamlclients...`) if the extra CRDs aren't applied — vendor the whole directory listing, not a remembered file list:keycloaks.k8s.keycloak.org-v1.yml — the Keycloak CRD. Write CRs as `k8s.keycloak.org/v2beta1` (storage version since 26.6); v2alpha1 is deprecated but served with an identical schema, so migrating is an apiVersion editkeycloakrealmimports.k8s.keycloak.org-v1.yml — the KeycloakRealmImport CRDkeycloakoidcclients…/keycloaksamlclients….yml (26.7+) — client CRDs, required by the operator even if unusedkubernetes.yml — Operator Deployment + RBAC + ServiceAccount (assumes install namespace keycloak; one hardcoded ClusterRoleBinding subject — use the docs' kustomize-overlay approach for other namespaces)kustomization.yml, cluster-wide/ (26.7+). Don't copy `kustomization.yml` into a directory that has its own `kustomization.yaml` — kustomize then fails with Found multiple kustomization files. Either apply upstream's directory as-is (kubectl apply --server-side -k), or vendor only the four CRD files plus kubernetes.yml into your own overlay; a cp *.yml loop picks up both.main branch has only the README + LICENSE; the actual manifests live in tags. Use git checkout <tag> against a clone, or gh api repos/keycloak/keycloak-k8s-resources/contents/kubernetes?ref=<tag> to fetch raw. (Do NOT assume the repo is stale just because main looks empty.)https://www.keycloak.org//server/all-config — every CLI option, every default/server/configuration — concept overview, build-time vs runtime/operator/installation, /operator/basic-deployment, /operator/advanced-configuration, /operator/realm, /operator/keycloak-cr, /operator/rolling-updates/high-availability/ — multi-cluster, external Infinispan, CloudNativePG recipe/securing-apps/ — OIDC layers, SAML, DPoP, JWT-Authz-Grant, MCP authorization server (CIMD)/docs/<version>/server_admin/ and /docs/<version>/server_development/ — admin and SPI guidesgh-cli-preferred rule):gh release view <tag> --repo keycloak/keycloak --json body --jq '.body'gh issue view <N> --repo keycloak/keycloakgh search issues --repo keycloak/keycloak "<query>"gh api repos/keycloak/keycloak/security-advisoriesWhen the user references a specific behavior, check the source clone or CRD YAML before answering — option names, defaults, and JSON field names get renamed across releases (e.g. --proxy edge|reencrypt|passthrough → --proxy-headers xforwarded|forwarded in 26.4; KEYCLOAK_ADMIN → KC_BOOTSTRAP_ADMIN_* in 26.0; the hostname-v2 rewrite). Stale answers are worse than "let me check."
These are the things that bite operators most. Don't suggest a Keycloak deployment that violates them without a stated reason.
spec.startOptimized: true only after baking the build. Otherwise startup pays the auto-build cost on every pod start.--hostname-strict=true (default). Do not run with hostname-strict=false outside of dev — it lets clients dictate the issuer.--proxy-trusted-addresses to a CIDR that covers the proxy. Without this, login redirects loop.dev-file H2 is not for production and silently disables clustering. Postgres is the only DB that gets tested under load by upstream.quay.io/keycloak/keycloak:26.8.0), never latest. The nightly tag is for CI only./health/started, /health/live, /health/ready — not the main HTTP port. As of 26.6, probes return UP during DB migrations so Liquibase can finish without K8s killing the pod.PodDisruptionBudget, not an HPA.KC_BOOTSTRAP_ADMIN_USERNAME/PASSWORD exists only to create the first real admin via kcadm.sh, then should be removed. The bootstrap admin auto-expires after 120 minutes.26.8.0 is meant to manage Keycloak 26.8.0. Mixing major.minor versions across the operator/server boundary is unsupported and often breaks the CRD schema.pkce.code.challenge.method=S256; never clear it or set plain (RFC 9700: public clients MUST use PKCE, and S256 is the only method that does not expose the verifier). A client library that can only do plain is the thing to upgrade (keycloak-js and oidc-client-ts do S256). Never redirectUris/webOrigins * on a production client: any site then receives the authorization code. Ephemeral preview URLs get a separate preview client or realm with a host-scoped pattern, or per-PR registration from CI via kcadm.sh.When the user says "just stand one up so I can play," don't point them at kc.sh start-dev if they care about production fidelity — show them this instead. It's a Keycloak CR + Postgres + Ingress, no extra moving parts:
# 1. Install the operator (do this once per cluster)
# kubectl apply -f https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/26.8.0/kubernetes/keycloaks.k8s.keycloak.org-v1.yml
# kubectl apply -f https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/26.8.0/kubernetes/keycloakrealmimports.k8s.keycloak.org-v1.yml
# kubectl apply -f https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/26.8.0/kubernetes/keycloakoidcclients.k8s.keycloak.org-v1.yml
# kubectl apply -f https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/26.8.0/kubernetes/keycloaksamlclients.k8s.keycloak.org-v1.yml # 26.7+: operator crash-loops without both client CRDs
# kubectl apply -f https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/26.8.0/kubernetes/kubernetes.yml
# 2. Create a TLS secret (cert-manager / hand-roll / etc.)
# 3. Create DB credentials secret (keys: username, password)
# 4. Deploy Keycloak
apiVersion: k8s.keycloak.org/v2beta1
kind: Keycloak
metadata:
name: keycloak
namespace: iam
spec:
instances: 2
image: quay.io/keycloak/keycloak:26.8.0
startOptimized: false # set true when the image is pre-baked with `kc.sh build`
hostname:
hostname: https://auth.example.com
strict: true
proxy:
headers: xforwarded
http:
tlsSecret: keycloak-tls # remove and use ingress for edge termination
db:
vendor: postgres
host: postgres-rw.iam.svc
database: keycloak
usernameSecret: { name: keycloak-db, key: username }
passwordSecret: { name: keycloak-db, key: password }
bootstrapAdmin:
user:
secret: keycloak-bootstrap-admin # keys: username, password
update:
strategy: Auto # rolling when the compatibility check passes: patches, and usually minors (26.6+)
features:
enabled: ["organizations", "admin-fine-grained-authz:v2"]See assets/examples/ for fuller examples (with HA tuning, KeycloakRealmImport, raw-manifest deployment without the operator, network policies).
manage-realm get the version from GET /admin/serverinfo (26.5.4+): kcadm.sh get serverinfo | jq -r .systemInfo.version — note kcadm's --fields is useless here (it trims systemInfo but still dumps the ~0.5 MB remainder of the document); the view-system workaround was removed in 26.7. Infra-side: image tag, boot log line, or kcadm.sh help | head -1 in the pod./admin/{login-realm}/console/#/{managed-realm} — the first segment picks which realm authenticates you (its security-admin-console client), the fragment picks the realm being edited. Realm-local admins: /admin/<their-realm>/console/ (no realm dropdown, their realm only). Master admins: /admin/master/console/#/<realm>. End-user self-service: /realms/<realm>/account/. Prefix all with the server's relative path (/auth on legacy-layout installs).--proxy-headers correctly instead.kc.sh build re-run; dropping a theme jar into a stock image at runtime no longer works. Themes are packaged into the optimized server jar.#47872, fixed in 26.6.1. Upgrade to the latest 26.6.x.KeycloakRealmImport is create-or-replace; it won't merge. Use kcadm.sh for incremental edits, or accept the realm-as-config GitOps trade-off.@keycloak/keycloak-admin-client package, fixed in 26.6.1. Use the latest 26.6.x./health/ready instead.DELETE FROM DATABASECHANGELOGLOCK WHERE ID=1 (after confirming no other instance is migrating). See observability.md §troubleshooting for the safe procedure. substitution surprises with $ in passwords"** → Use the new KCRAW_` prefix (26.6+) instead of `KC_ for any value that contains $`.--proxy-headers / KC_PROXY_HEADERS `. Operators copy-paste; getting it half right costs them an hour.~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.