Credential stuffing rarely looks convincing when every login request is viewed alone. The useful evidence appears across time: one account suddenly touches many IP addresses, devices, networks, or countries while failed logins accumulate. Cloudflare’s new Account Abuse Protection dashboard is designed to organize that history around a privacy-preserving account identifier so defenders can move from a population-wide anomaly to an individual investigation.
Cloudflare introduced the dashboard on October 2, 2026. It is initially available to Account Abuse Protection Early Access customers and requires Bot Management Enterprise. This is a product launch and defensive workflow update, not evidence of a new breach or a universal account-takeover detector. Its value depends on correct endpoint coverage, useful baselines, careful access control, and analysts who treat signals as leads rather than verdicts.
Why request-by-request controls miss account abuse
Rate limits, bot scores, leaked-credential checks, and multifactor authentication remain important. Their weakness is not that they are useless, but that each can be evaluated too narrowly. Attackers distribute attempts across residential proxies, rotate devices, slow down campaigns, and mix automated and human activity. A single request may stay below a threshold even when the account’s recent history is highly abnormal.
Account Abuse Protection adds state. Customers select an identifier already present in a login or signup flow, such as an email address, username, or phone number. Cloudflare converts it into an opaque Hashed User ID that is unique to the customer’s zone. Login and signup events can then be grouped around that identifier without forcing analysts to begin every investigation with raw account data.
This account-centered approach complements broader application controls. Our OWASP web application security guide covers common application risks, while the Identity and Access Management guide explains the access, authentication, and lifecycle controls that should surround any account recovery decision.
How the investigation workflow works
Start with the population view
The dashboard begins with aggregate login and signup activity. Analysts can review event volume, affected accounts, unique IP addresses and devices, countries, and autonomous system numbers. The first question is scope: is the anomaly limited to one user, or is it a campaign affecting a large part of the account population?
Build a defensible cohort
Filters narrow the population to accounts with a concerning combination of signals. Cloudflare’s published example uses at least three failed logins, at least three leaked-credential matches, and activity from at least five unique IP addresses. Those values demonstrate the workflow; they are not universal thresholds. A consumer application, workforce portal, and high-value administrative interface should not share the same baseline.
Reconstruct one account’s timeline
The individual view summarizes login success, network and location changes, devices, and leaked-credential matches. Each event includes a Ray ID, which gives analysts a pivot into Cloudflare Security Events. That correlation matters because the account view explains the sequence while the security event can provide request-level context and the mitigation already applied.
Respond only after corroboration
A Hashed User ID can be used in a WAF rule to log, challenge, or block later requests. Begin with observation or a managed challenge when the evidence is incomplete. A confirmed compromise may also require session revocation, password reset, factor re-enrollment, token invalidation, user notification, and a review of activity performed after the suspicious login. Blocking traffic alone does not recover an account.
Telemetry defenders can operationalize
| Field or signal | Investigation use | Caution |
|---|---|---|
| UserID | Groups events around a zone-specific account identifier | It identifies a profile, not malicious intent |
| AuthenticationStatus | Separates successes, incorrect passwords, unknown users, locks, and pending MFA | Application errors can distort status quality |
| ClientIP, ASN, country | Shows network spread and changes over time | NAT, VPNs, mobile carriers, and travel create legitimate variation |
| BotScore | Adds automation likelihood from 1 to 99 | Low or high scores should not be used as a verdict alone |
| JA4 and EphemeralID | Helps compare TLS client and Turnstile device characteristics | Shared clients and privacy tools can reduce uniqueness |
| RayID | Pivots to the associated request in Security Events | Retention and access must support the investigation window |
| FraudEmailRisk | Adds risk context for signup email patterns | High entropy is a signal, not proof of a fake account |
Cloudflare documents an Account Abuse Protection Events dataset for Logpush with fields including authentication method and status, Bot Score, IP, ASN, country, JA4, Ray ID, timestamp, event type, and User ID. Exporting this data can support SIEM correlation, but it also expands the number of systems that hold account-level evidence. Limit destinations, service-account permissions, retention, and analyst access.
Deployment checklist for security teams
- Confirm eligibility and data-location constraints. Account Abuse Protection is Early Access. Cloudflare also documents that account-takeover detections are unavailable when Regional Services in the EU are used with the Data Localization Suite because the required zone logs are not ingested.
- Enable User ID deliberately. It is opt-in. Document the identifier source and ensure its handling matches privacy, legal, and retention requirements.
- Verify endpoint coverage. Common login and signup routes may be identified automatically. Label nontraditional login endpoints with the documented
cf-log-inendpoint label so relevant traffic is evaluated. - Enable supporting controls. Leaked-credential detection, bot management, rate limiting, MFA, and secure account recovery provide evidence and enforcement that the dashboard cannot replace.
- Separate investigation from PII access. Cloudflare provides distinct Account Abuse Protection and Account Abuse Protection PII roles. Grant the PII role only where the investigation truly requires it.
- Stage enforcement. Start with logging, measure false positives, then use challenges or narrowly scoped blocks. Keep a recovery path for legitimate users affected by a rule.
Hands-on lab: find risky account patterns offline
This lab processes synthetic newline-delimited JSON that resembles documented Account Abuse Protection Logpush fields. It does not call Cloudflare, send login requests, use real credentials, or modify a WAF rule. Its thresholds are educational examples that must be replaced with application-specific baselines.
Prerequisites and isolated setup
- Python 3.8 or later
- An empty local working directory
- No external packages, elevated privileges, or network access
mkdir -p account-abuse-lab
cd account-abuse-lab
python3 -m venv .venv
. .venv/bin/activateCreate events.jsonl:
{"Timestamp":"2026-10-06T07:00:00Z","UserID":"7b1f9a2c","EventType":"login","AuthenticationStatus":"failureIncorrectPassword","ClientIP":"198.51.100.10","ClientCountry":"US","BotScore":8,"JA4":"t13d1516h2_a","RayID":"ray-a1"}
{"Timestamp":"2026-10-06T07:00:15Z","UserID":"7b1f9a2c","EventType":"login","AuthenticationStatus":"failureIncorrectPassword","ClientIP":"198.51.100.11","ClientCountry":"US","BotScore":12,"JA4":"t13d1516h2_a","RayID":"ray-a2"}
{"Timestamp":"2026-10-06T07:01:10Z","UserID":"7b1f9a2c","EventType":"login","AuthenticationStatus":"failureUserNotFound","ClientIP":"203.0.113.20","ClientCountry":"NL","BotScore":35,"JA4":"t13d1517h2_b","RayID":"ray-a3"}
{"Timestamp":"2026-10-06T07:01:45Z","UserID":"7b1f9a2c","EventType":"login","AuthenticationStatus":"failureIncorrectPassword","ClientIP":"203.0.113.21","ClientCountry":"NL","BotScore":5,"JA4":"t13d1517h2_b","RayID":"ray-a4"}
{"Timestamp":"2026-10-06T07:02:20Z","UserID":"7b1f9a2c","EventType":"login","AuthenticationStatus":"failureIncorrectPassword","ClientIP":"192.0.2.30","ClientCountry":"DE","BotScore":45,"JA4":"t13d1518h2_c","RayID":"ray-a5"}
{"Timestamp":"2026-10-06T07:03:00Z","UserID":"2c8e41d0","EventType":"login","AuthenticationStatus":"success","ClientIP":"192.0.2.44","ClientCountry":"MA","BotScore":92,"JA4":"t13d1519h2_d","RayID":"ray-b1"}Create detect_account_abuse.py:
#!/usr/bin/env python3
import collections
import json
import sys
REQUIRED = {
"Timestamp", "UserID", "EventType", "AuthenticationStatus",
"ClientIP", "ClientCountry", "BotScore", "JA4", "RayID"
}
def load_events(path):
with open(path, encoding="utf-8") as handle:
for number, line in enumerate(handle, 1):
if not line.strip():
continue
event = json.loads(line)
missing = REQUIRED - set(event)
if missing:
raise ValueError(
f"line {number}: missing fields: {', '.join(sorted(missing))}"
)
yield event
def main(path):
users = collections.defaultdict(lambda: {
"failures": 0, "ips": set(), "countries": set(), "ja4": set(),
"low_bot": 0, "first_ray": None
})
for event in load_events(path):
if event["EventType"] != "login":
continue
state = users[event["UserID"]]
state["ips"].add(event["ClientIP"])
state["countries"].add(event["ClientCountry"])
state["ja4"].add(event["JA4"])
state["first_ray"] = state["first_ray"] or event["RayID"]
if event["AuthenticationStatus"].startswith("failure"):
state["failures"] += 1
if int(event["BotScore"]) <= 20:
state["low_bot"] += 1
alerts = 0
for user_id in sorted(users):
state = users[user_id]
risky = (
state["failures"] >= 3
and len(state["ips"]) >= 5
and (state["low_bot"] >= 2 or len(state["countries"]) >= 3)
)
if risky:
alerts += 1
print(
f"ALERT user= failures={state['failures']} "
f"ips={len(state['ips'])} countries={len(state['countries'])} "
f"ja4={len(state['ja4'])} low_bot={state['low_bot']} "
f"first_ray={state['first_ray']}"
)
print(f"SUMMARY users={len(users)} alerts={alerts}")
if __name__ == "__main__":
if len(sys.argv) != 2:
raise SystemExit(f"usage: {sys.argv[0]} EVENTS.jsonl")
main(sys.argv[1])Run the detector:
python3 detect_account_abuse.py events.jsonlExpected output:
ALERT user=7b1f9a2c failures=5 ips=5 countries=3 ja4=3 low_bot=3 first_ray=ray-a1
SUMMARY users=2 alerts=1Interpretation, troubleshooting, and cleanup
The alert identifies a synthetic user with five failed logins from five IP addresses, three countries, three JA4 values, and three low bot scores. That combination is a queue for investigation, not automatic proof of compromise. Use the first Ray ID as a pivot, review adjacent events, and compare the pattern with the user’s history. If Python reports invalid JSON, validate that each line is one complete JSON object. If it reports missing fields, compare field names and capitalization with the script’s required set.
deactivate
cd ..
rm -rf account-abuse-labOperational takeaway
The dashboard’s most useful idea is not a single score. It is the investigative thread connecting an account, its authentication outcomes, its device and network changes, and the request records behind each event. Teams should use that thread to prioritize review, preserve evidence, and select a proportional response. Stateful visibility improves the investigation, but secure authentication, least privilege, recovery controls, and measured enforcement still determine the outcome.



