Skip to main content
Glama
shigechika

keycloak-mcp

by shigechika

spray_check

Detect password-spray sources by IP from Keycloak login events and list the user accounts each source breached.

Instructions

Detect password-spray sources and name the accounts they breached — one rule, one call.

For every EXTERNAL source IP (anything outside the ranges declared in KEYCLOAK_SITES_INI) seen in LOGIN / LOGIN_ERROR events during the last hours, compute distinct users and success rate. An IP is a spray source when unique_users >= min_users AND success_rate < max_success_rate. Its successful logins are the breach CANDIDATES; whether they may be called breached depends on the row's confidence.

The breach list is built ONLY from LOGIN events whose source IP is the flagged IP, inside the window. Every entry carries the evidence tuple {time, ip, username, user_id, client_id}. A compromised account can therefore never be reported without an actual login event from the spray source — do not add names that are not in spray[].breached.

confidence separates a spray from a shared egress (school NAT, home line, VDI) that merely looks like one by volume. It is "low" — treat the successes as "verify with the owner", never publish them as breached — when any of these signals holds: user_success_rate (distinct users that logged in at least once ÷ distinct users) >= max_user_success_rate (real sprays sit at 0.0–0.06; a school NAT with students retyping a mistyped domain sat at 0.47), or failure_concentration (share of failures on the single most-failing username) >= max_failure_concentration (one locked-out user retrying from a shared line produced 0.91), or the IP is in KEYCLOAK_KNOWN_EGRESS. Only confidence: high rows are a breach verdict. Read top_failed_users and not_found_domains (domains of usernames that do not exist — typos of the real domain are humans, not a scraped list) before writing anything up.

Returns a fixed-shape dict: window: {hours, since, until} actually scanned. complete: False if event pagination was cut short (KEYCLOAK_DEADLINE / KEYCLOAK_MAX_EVENTS). When False, treat the result as a lower bound and do NOT publish a definitive verdict; narrow hours and retry. spray: flagged IPs (see external_ips for the row shape), each with breached = list of evidence tuples; confidence: high rows first. external_ips: every external IP with at least min_report_users distinct users, flagged or not, sorted flagged-first, then high-confidence first, then by ascending success rate — so near-misses (e.g. 8 users at 14%) are visible without a second rule. Row: ip, known_egress, flagged, confidence ("high"/"low"), signals (list of the reasons for "low"), unique_users, users_with_success, user_success_rate, failure_concentration, top_failed_users (up to 5 {username, failures}), not_found_domains (domain -> count for user_not_found usernames), attempts, successes, failures, success_rate, errors (error-code counter; user_not_found mixed with invalid_user_credentials on many DIFFERENT names indicates a scraped username list — on the same few names it is a human retyping), first_seen, last_seen, unresolved_user_ids, breached. breached_total: number of evidence tuples across all flagged IPs. breached_low_confidence: how many of those sit on confidence: low rows (candidates to verify, not breaches). internal_events_excluded: events dropped because the IP is internal. resolves_used / resolve_capped: how many GET /users/{id} lookups were spent resolving success userIds, and whether max_resolves (or the shared deadline) stopped further lookups. A row's unresolved_user_ids counts successes keyed by bare userId; if that is non-zero on a flagged row, unique_users may be slightly over-counted and breached[].username is the id. known_egress_configured: whether KEYCLOAK_KNOWN_EGRESS is set. IPs in those ranges are LABELED known_egress: true, never excluded — a shared VDI/VPN/proxy egress with many real users is expected to show a high success rate and usually is not flagged anyway.

Users are keyed by lowercased username on both sides: LOGIN_ERROR carries details.username; LOGIN usually carries only userId. userIds are mapped from the fetched events first (any event carrying both fields), then via GET /users/{id} — only for IPs below the success-rate ceiling (the only ones that can be flagged), at most max_resolves times, and never past the shared KEYCLOAK_DEADLINE.

Args: hours: Look-back window (default 24 — sized for a once-a-day patrol). min_users: Distinct users an IP must touch to count as a spray (default 10). max_success_rate: Success-rate ceiling for a spray (default 0.2). min_report_users: Distinct users an IP needs to appear in external_ips at all (default 3; clamped to min_users). max_resolves: Cap on GET /users/{id} lookups per call (default 200). max_user_success_rate: user_success_rate at or above this marks the row confidence: low (default 0.3). max_failure_concentration: failure_concentration at or above this marks the row confidence: low (default 0.5).

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
hoursNo
min_usersNo
max_resolvesNo
max_success_rateNo
min_report_usersNo
max_user_success_rateNo
max_failure_concentrationNo

Schema Changelog

Changes observed during successful MCP inspections.

  1. Addedv0.19.0

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden, and it succeeds comprehensively. It discloses partial-result behavior via complete=False, resolve caps and unresolved_user_ids, internal-event exclusion, known-egress labeling without exclusion, and the rule that breached entries require actual LOGIN events. This is far more than a typical tool description provides.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is long, but appropriately so: there is no output schema and no annotations, so it must document return fields, edge cases, and semantics. It is front-loaded with the core purpose and organized into clear paragraphs and an Args list, with no filler or redundant sentences.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

It covers the algorithm, confidence classification, return shape, resolution limits, pagination caveats, and egress behavior — essentially everything needed to call the tool correctly without annotations or an output schema. It is not a 5 because of the max_user_success_rate default discrepancy, which introduces avoidable ambiguity, and because it never explicitly contrasts itself with similar sibling tools.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

All 7 parameters are given real meaning with defaults, thresholds, and behavioral implications, which is essential because schema description coverage is 0%. However, the description states max_user_success_rate defaults to 0.3 while the input schema declares 0.2, an inconsistency that could mislead an agent during invocation.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Opens with a precise verb and resource: 'Detect password-spray sources and name the accounts they breached'. The 'one rule, one call' framing and the explicit focus on EXTERNAL source IPs in LOGIN/LOGIN_ERROR events distinguish it clearly from generic login-stat or IP-lookup siblings.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives clear operational context: it is for password-spray detection over external IPs, with explicit thresholds, confidence logic, and caveats. It does not explicitly name sibling tools or state when not to use it, so it falls just short of a 5, but the intended use is evident and actionable.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.