Product

Automating Your Keycloak Audit with an AI Security Agent

How CloudThinker keeps a Keycloak audit running continuously over a read-only service account, and investigates each access-widening change (who, from where, is it used) before proposing a fix. Realm changes go through Auto Mode.

10 min read
Cover Image for Automating Your Keycloak Audit with an AI Security Agent

Automating Your Keycloak Audit with an AI Security Agent

Someone on the payments team registered a new Keycloak client at 4:50 p.m. on a Friday. It shipped as a public client with a wildcard redirect URI and Full Scope Allowed left on, so every token it minted carried every role the user held, and its login link would hand an authorization code to any matching domain. You found it three weeks later. Nothing alerted. Someone happened to scroll the client list. That's what a Keycloak audit gap looks like in practice: no breach headline, just a realm that drifts a little further from its reviewed state every sprint.

Part one mapped the misconfigurations that actually expose SSO: wildcard redirect URIs, weak client secrets, overprivileged service accounts, and defaults left in place on brute-force protection and token lifetimes. Part two audited all of it with kcadm.sh, jq and the Admin REST API, and ended on the honest problem: the audit is a snapshot, and the realm changes every time someone ships a client.

This part covers the step after. CloudThinker runs the part-two inspection continuously, investigates each change that widens access the way an identity engineer would, and proposes the fix. Because this is your identity provider, it also covers exactly what the agent won't do without your approval.


Why a diff isn't an investigation

The weekly diff from part two is worth having. It tells you that ci-deployer gained manage-users since last week's run. Then the real work starts, and none of it is in the diff. Who made the grant? Was it in a change request? Did the same admin change anything else in that session? Has the service account ever used the new permission, or was it granted to unblock something and forgotten? Was the grant made from the admin's usual network, or from an address nobody recognizes?

Each answer sends you somewhere different. A deliberate change with a ticket behind it is fine. A forgotten unblock is a finding with an easy fix. A grant from an unfamiliar IP at 3 a.m. is an incident. A script can't make that call, because the next question depends on what the last one returned. That's why identity changes get reviewed in batches, late, by whoever has time.


Connecting Keycloak: a read-only service account, about five minutes

01Identity provider

Keycloak

  • Realms and clientsview-realm, view-clients
  • Users and role mappingsview-users
  • Login and admin eventsview-events

Read-only service account

02Investigation

CloudThinkerCloudThinker

  • Baseline the realmThe reviewed state of every client and role
  • Investigate changesWho, from where, what else, is it used
  • Propose the fixExact kcadm.sh change, with rollback

Nothing installed in Keycloak

03Where the team works

Chat and tickets

  • SlackSlackFindings and approvals in the thread
  • Microsoft TeamsMicrosoft TeamsSame flow for Teams workspaces
  • JiraFollow-ups with the evidence linked

Nothing new to watch

The permission boundary is Keycloak's own role model. Without a manage-* role, the connection can't write.

CloudThinker connects to Keycloak the way you'd want any third-party integration reviewed by your security team: a confidential client with a service account, granted only the view-side roles from the realm-management client: view-realm, view-clients, view-users and view-events. No manage-* roles, no realm-admin, no stored admin passwords. It's the client-credentials pattern Keycloak documents for service accounts, scoped to read, and the same setup as the audit-cli client from part two.

You create the client, assign the four roles, paste the client ID and secret into CloudThinker, and the first realm sweep starts. The Keycloak connection guide lists the exact roles, so your review can confirm what's granted and what isn't.

One deliberate consequence of this design: if you never grant a manage-* role, CloudThinker can't change your realm, whatever any Auto Mode setting says. The boundary is enforced by Keycloak, not by a policy inside CloudThinker.


What it inspects continuously

Once connected, CloudThinker runs the part-two audit on a loop, across every watched realm, client and role mapping:

  1. Client configuration. Public clients with wildcard (*) or http:// entries in Valid redirect URIs, permissive Web origins, Direct Access Grants on browser-facing clients, Full Scope Allowed left on.
  2. Secret hygiene. Confidential clients whose secrets haven't rotated within your policy window, and secrets reused across environments. The same value in a staging and a production client is a finding, not a coincidence.
  3. Role and scope creep. Service accounts holding realm-management roles, composite roles that quietly include admin capabilities, offline tokens reachable through clients that don't need them.
  4. Realm security settings. Brute-force protection off, password policy at defaults, access-token and SSO-session lifetimes far beyond your stated policy.
  5. Event coverage. Login or admin events not being saved, which means the forensic trail you'd need after an incident doesn't exist.

None of these checks is new. You ran most of them by hand in part two. What changes is the cadence and the baseline. CloudThinker keeps the realm's reviewed state, so the useful output isn't "here are 40 clients". It's "this client is new since Tuesday, it's public, and its redirect URI ends in a wildcard."

The changes that get investigated, not just reported

  • A new client in any watched realm, with a configuration review attached.
  • A role grant that widens access: a user or service account picking up realm-admin, manage-users, manage-clients, or a composite role that resolves to them.
  • An edit that loosens an existing client: a broadened redirect URI, Full Scope Allowed switched on, a protocol mapper that puts group membership into tokens.
  • Realm-setting changes: brute-force protection off, token lifespans extended, required actions removed.

01Signal

Realm change

A new client, a role grant or a loosened setting shows up against the baseline.

02Context

Investigate

Admin events, the actor, the session, and whether the new access is ever used.

03Judgment

Weigh it

Deliberate change, forgotten unblock, or a compromised admin.

04Output

Finding

What changed, the evidence, the likely reading, and a proposed fix.

A diff stops at step one. The steps after it are where an analyst's time used to go.

One change, investigated

Here's what that looks like on the role grant from earlier. The details are illustrative.

On Friday at 16:52, the baseline check sees that service-account-ci-deployer in production now holds manage-users from realm-management. Three readings fit, and each predicts different evidence:

  • A deliberate change. Predicts a known admin, a linked change request, and the pipeline starting to create or update users right after.
  • A forgotten unblock. Predicts a known admin fixing a failing job, a broad role where a narrow one would do, and no user writes from the service account afterwards.
  • A compromised admin account. Predicts an unusual IP or hour, and other changes in the same session (a new client, a second grant, a disabled lockout).

Who made the grant, and from where?

kcadm.sh get admin-events -r production \
  -q resourceTypes=CLIENT_ROLE_MAPPING -q dateFrom=2026-10-02 \
  | jq -r '.[] | "\(.time/1000 | todate)\t\(.operationType)\t\(.authDetails.userId)\t\(.authDetails.ipAddress)\t\(.resourcePath)"'

One CREATE on the service account's role mappings, by the user ID of a platform engineer, from the office VPN range the same admin has used for every change in the last 90 days. Nothing else in that session: no new clients, no realm-setting edits. That makes the third reading unlikely.

Has the service account ever used it? Admin events record writes, so any user the pipeline created or updated would show up with the service account as the actor:

kcadm.sh get admin-events -r production \
  -q authUser=SERVICE_ACCOUNT_USER_ID -q dateFrom=2026-10-02 \
  | jq '[.[] | select(.operationType == "CREATE" or .operationType == "UPDATE")] | length'

Zero, across five pipeline runs since the grant. The job that failed on Friday looked up users by email, which view-users covers. No change request mentions the grant. The second reading is the strong one.

The finding, posted in the identity team's channel:

ci-deployer holds manage-users it doesn't use. Granted Friday 16:52 by a platform engineer from the office VPN. No user writes by the service account in five runs since. The failing job needed user lookups only. Proposed: replace manage-users with view-users. Rollback: re-add manage-users. Needs approval from: the identity team, which owns the production realm.

The change it would run once approved (which needs a connection with manage-users rights of its own, granted separately):

kcadm.sh add-roles -r production --uusername service-account-ci-deployer \
  --cclientid realm-management --rolename view-users
kcadm.sh remove-roles -r production --uusername service-account-ci-deployer \
  --cclientid realm-management --rolename manage-users

Found within minutes of the grant, with the attacker reading already ruled out, instead of three weeks later in someone's scroll.


Nothing changes in the realm without you

Remediation permissions are a separate, deliberate grant, never part of onboarding. Without scoped manage-* roles on the connection, CloudThinker can only report and propose. With them, it acts within Auto Mode, so you decide what runs on its own and what waits for approval. For production identity, most teams want a person on every change.

Every action lands in an audit trail: finding, evidence, proposal, approver, execution, result. If your compliance program asks "who reviewed access to the identity provider and when", the answer is a queryable log instead of a quarterly spreadsheet.


What a first scan typically finds

These numbers are illustrative, a composite of first scans against mid-market deployments with about three realms and 40 to 60 clients. Your realm will differ.

FindingDetailSeverity
Wildcard redirect URIs3 public clients with https://*.example.com/* patternsHigh
Service account with admin roles1 CI service account holding manage-users and manage-clientsHigh
Brute-force protection offOff in 2 of 3 realms, including productionHigh
Full Scope Allowed on new clients12 clients issuing every assignable role in tokensMedium
Stale client secrets9 confidential clients, no rotation in 12+ months; 2 shared valuesMedium
Admin events not savedLogin events on, admin events off, so no change attributionMedium
Session lifetimes beyond policySSO Session Max at 30 days against a stated 12-hour policyMedium
Direct Access Grants on browser clients4 SPA clients accepting password grantsLow

Look at the shape. Nothing here is exotic. Every row is a part-one misconfiguration that built up because nobody re-ran the part-two audit after the realm changed. The findings aren't clever, they're current, and that's the whole argument for continuous inspection.

Why this beats the traditional approach

Quarterly kcadm.sh auditCloudThinker on top
When drift is foundAt the next review, weeks or months laterThe day it appears against the baseline
A new role grantA line in a diff, if anyone reads itInvestigated: actor, IP, session, usage
ContextIn the auditor's head, rebuilt each timeWritten into the finding with evidence
From finding to fixA ticket that needs an ownerA proposed change routed to the realm owner
ChangesManual, logged only if events are onThrough Auto Mode, fully audited

The deeper difference is where the analyst's time goes. In the quarterly audit, most of it goes to pulling admin events and asking around for context. With the agent doing that, the time goes to the one decision that needs a person: whether the access should exist.


Prompts to try in your first session

CloudThinker is conversational. Ask in plain language, and each answer cites the same Admin API evidence you'd pull yourself, so you can verify it rather than trust it:

"Review every client in the production realm and flag public clients with wildcard or http redirect URIs. Report findings only, change nothing."

"List every service account that holds any realm-management role, and tell me which of those roles show up in the admin event log as actually used."

"Which clients were created or modified in the last 14 days, who made each change, and can any of them now request offline tokens?"

"Compare brute-force protection, password policy and token lifetimes across all our realms, and show where each one departs from production."

What the agent won't do

"AI agent connected to the identity provider" should get exactly the scrutiny it sounds like it deserves. Here are the answers:

  • Read-only by default, enforced by Keycloak's own role model. Without manage-* roles on the connection's service account, no write is possible. There's no permission to escalate.
  • No realm changes you haven't allowed. Every change is proposed with its evidence and logged with who approved it.
  • No credentials it doesn't need. The connection is a client-credentials grant. CloudThinker never sees user passwords, and client secrets it flags as stale are referenced, not copied into findings.
  • No identity-architecture decisions. It'll tell you a service account holds realm-admin. Whether that pipeline genuinely needs realm administration is a judgment call, and it stays yours.

If you ran the part-two audit by hand, the right mental model is that audit running continuously, with an investigation step and an approval-gated remediation queue attached. It isn't an autopilot for your identity provider.

From yearly review to standing control

You know which Keycloak misconfigurations expose SSO, and you know how to find every one with native tools. What's left is that a realm drifts faster than any team re-audits it. Teams that move from periodic manual reviews to continuous, approval-gated inspection typically catch widening-access changes within hours instead of quarters, which is the difference between revoking a token and writing an incident report.

Related reading

Try CloudThinker free with 100 premium credits and no card required, and follow the Keycloak connection guide to see your own realm's first findings table within the hour.

keycloaksecurityiamssoidentitymanagementaiagentcloudthinker