keycloak-doctor GitHub ↗

Rule reference

The catalogue compiled into the binary, with the rationale for each rule.

keycloak-doctor ships 30 rules in 7 categories. Every rule is evaluated against a realm read either from an export file or from the Admin REST API, and every id below is stable: it is what --only, --skip and any suppression in your pipeline should pin.

client 8 rules

client/direct-grant

The resource owner password grant is off

Direct access grants make the client collect the user's password itself: no MFA prompt, no consent, no broker, and a password handled by code that should never see it. On a public client it is also unauthenticated, so anyone can spray passwords against it.

client/full-scope

Clients do not inherit every role in the realm

Full scope allowed (Keycloak's default for a new client) puts every realm and client role the user has into this client's tokens — so a compromise of one frontend yields a token good everywhere. On a service account it is worse: the account holds all realm roles by construction.

client/implicit-flow

The implicit flow is off

The implicit flow returns the access token in the URL fragment, where it lands in browser history, referrers and any script on the page. OAuth 2.1 drops it; Keycloak still offers the checkbox.

client/pkce

Public clients require PKCE with S256

Without PKCE a public client's authorization code can be redeemed by anyone who intercepts it — there is no client secret to stop them. "plain" is not a substitute: the verifier travels in the clear.

client/redirect-plain-http

Redirect URIs are HTTPS outside the loopback interface

An authorization code delivered over plaintext HTTP is readable by every hop in between. Loopback URLs are the specified exception for native and development clients; a real hostname over http:// is not.

client/redirect-wildcard

Redirect URIs do not widen past a host you control

The redirect URI is the only thing that keeps an authorization code going to the right place. A wildcard in the authority — "*", "https://*.example.com", "http://*" — lets an attacker who can answer at a matching host complete the flow with the victim's code. A wildcard in the path is milder but still turns any open redirect on that host into a token leak.

client/token-lifespan-override

No client quietly overrides the realm token lifespan

A per-client access token lifespan overrides the realm setting, so a realm audited as "5 minutes" can still be issuing day-long tokens for one client — and the override lives in the client's Advanced tab where nobody looks.

client/web-origins-wildcard

CORS web origins are listed, not wildcarded

"*" in Web origins makes Keycloak answer token and userinfo requests for any page on the internet. Combined with a session cookie in the browser, that is what turns a stray XSS anywhere into a token for this client.

federation 2 rules

federation/ldap-tls

LDAP federation talks over TLS

Keycloak authenticates users against LDAP by binding with their password. Over ldap:// without StartTLS, every one of those passwords — plus the service account's bind credential — crosses the network in the clear.

federation/ldap-truststore

LDAP TLS certificates are verified

Use Truststore SPI set to Never means Keycloak accepts any certificate on the LDAP connection, which turns TLS into encoding: anyone who can answer at that address collects the bind passwords.

idp 2 rules

idp/plaintext-endpoints

Broker endpoints are HTTPS

The token and authorization endpoints of a broker carry codes, client secrets and identity assertions. Over plaintext HTTP, whoever sits on the path can read them and, worse, answer in the provider's place.

idp/trust-email

No identity provider is trusted to assert email addresses unchecked

Trust email skips Keycloak's own verification for accounts coming from that broker. When the realm also lets users log in by email, an IdP that does not verify its addresses (or lets a user set one) can be walked into an existing local account through first-broker-login account linking.

keys 2 rules

keys/hmac-secret-size

HMAC signing secrets are at least 32 bytes

An HMAC secret shorter than the digest it feeds is the weak link in the chain: it is brute-forceable offline from a single token, and a recovered secret mints valid tokens for the whole realm.

keys/rsa-size

RSA signing keys are at least 2048 bits

The realm's RSA key signs every token it issues. A 1024-bit key is not a configuration preference: forge one signature and you can mint any identity, retroactively, for as long as the key stays published in the JWKS.

mapper 2 rules

mapper/hardcoded-audience

No mapper hands a client an audience it does not own

An audience mapper is how a token minted for one client becomes acceptable to another. Pointed at a privileged API, it lets a low-trust frontend obtain tokens that API will honour — an authorization bypass that reads as ordinary configuration.

mapper/sensitive-attribute

No protocol mapper copies credential material into a token

Mappers are the quiet way secrets leave Keycloak: a user attribute called password_hash or api_key mapped into a claim ends up in every access token for that client, and from there in proxy logs, browser storage and any downstream service that decodes the token.

realm 13 rules

realm/audit-events

Login and admin events are recorded

Events are the only record of who logged in and who changed the realm. Turned off, an incident has no timeline and a malicious admin change leaves no trace at all.

realm/browser-mfa

The bound browser flow can ask for a second factor

A browser flow with no OTP or WebAuthn step means every account in the realm is exactly one password away, however strong the OTP policy is.

realm/brute-force

Brute force detection is enabled and reacts early

Without brute force detection a Keycloak realm answers password guesses as fast as it can serve them, and the only trace is in the login events nobody reads.

realm/email-identity

An email address identifies at most one account

Duplicate emails plus login-with-email is an ambiguous identity: password reset and broker account linking then act on whichever account they happen to find first.

realm/enabled

The audited realm is actually in use

A disabled realm answers no login, so its findings are not incidents — but a realm left disabled next to a live one is usually a forgotten migration, and it still holds the users, clients and secrets that become exposure the day someone re-enables it.

realm/offline-session-expiry

Offline tokens expire

An offline token with no max lifespan never expires — it is a permanent credential handed to whoever asked for offline_access, and it survives every password change.

realm/otp-policy

The OTP policy is strong enough to be worth having

A 4-digit OTP is 10 000 guesses; with brute force detection tuned for passwords, that is a plausible online attack.

realm/password-policy

A password policy is set, with a modern hash

A realm with no password policy accepts a one-character password. The hash settings matter just as much: an offline crack of a weak PBKDF2 iteration count is arithmetic, not research.

realm/refresh-token-rotation

Refresh tokens are rotated and single-use

Without rotation a leaked refresh token is a renewable credential nobody can distinguish from the real client; with rotation, a replay invalidates the chain and shows up.

realm/self-registration

Self-registration does not create unverified identities

Open registration without email verification lets anyone create an account with someone else's address — which is also the account an identity broker will happily link to later.

realm/session-lifespan

SSO sessions expire in a working day, not a season

The SSO session is what silently re-issues tokens: a session that idles for weeks turns one browser compromise into standing access.

realm/ssl-required

HTTPS is required for every request to the realm

With sslRequired=external Keycloak still serves and accepts plaintext HTTP from private addresses, so tokens and codes travel in the clear over any internal hop — a reverse proxy, a service mesh sidecar, a developer's tunnel. With none it accepts plaintext from anywhere.

realm/token-lifespan

Access tokens are short-lived

Keycloak cannot revoke an issued access token: it is valid until it expires, whatever happens to the session behind it. The lifespan is the window an attacker keeps a stolen token.

source 1 rule

source/secret-material

The audited source is handled as credential material

A realm export is not a config file: it carries plaintext client secrets, LDAP bind credentials and broker client secrets. Exports end up in ticket attachments, shared drives and git history because they look like YAML-adjacent configuration. keycloak-doctor drops the values at load time — this rule tells you how much the file you just audited is worth to an attacker.