Double Counter is a verification bot installed in more than 600,000 Discord servers. Its job is to keep banned people from coming back under a new account, and it does that the cheap way: when you click its verification link it records your IP address, hashes your browser's user agent, geolocates the IP down to postal code and ISP, and files all of it under your Discord user ID. On Sunday, October 4, 2026, an attacker spent 5 hours and 51 minutes inside the operator's cloud and copied about 12 GB of those tables.
The operator, Tellter SAS of Paris, published incident report INC-2026-10-04 the next day and notified the CNIL, France's data protection authority, under reference FR2610050000001. The report counts roughly 28 million Discord IDs and usernames, 27 million IP records with country, region, city, postal code and ISP, 25 million user-agent hashes and about one million email addresses as exposed. A subset with about 275,000 email addresses and usernames was published online and added to Have I Been Pwned on October 7. Discord said the incident "was not a breach of Discord" and disabled new installs of the bot.
The intrusion is a textbook forgotten-server story. The more interesting failure happened years earlier, when an anti-abuse tool decided that recognizing a returning user required a raw IP address and a location for everyone, kept for years. The same detection works on derived signals that are worthless to a thief.
What one verification click left behind
Double Counter's privacy policy says the bot collects your Discord user ID and IP address when you click the link, caches user-agent data temporarily for anti-abuse purposes, and uses the IP only to detect alternate accounts. The incident report is more specific: an alt-detection table of about 5.4 million records, copied in full, and a verified-users table of about 21.7 million records, of which the operator estimates 20 percent left before the session was killed. Both carry the IP and the geolocation fields.
How long is that kept? Until this week, the policy had a Data Retention section saying IP addresses and alternate-account association data is "retained for up to 24 months from the most recent detection or verification attempt" and then "securely deleted". That section is present in a Wayback Machine snapshot taken on October 5, 2026, the day the incident report went out, and absent from the policy as published on October 8. The product page still says IP addresses and account links "are kept for at most 24 months after a member's last verification, as set out in the privacy policy". The operator's own alt-detection documentation is blunter: "The alt detection is permanent." The bot's Discord application ID decodes to a creation date of April 26, 2020, so the bot has been collecting for six and a half years; whether the 21.7 million row table covers the last two of them or all of them is something only the operator can say.
A retention period that is removed from the policy within days of a breach is not much of a commitment.
Six hours through a server nobody switched off
The intrusion did not start in Double Counter's production cloud. It started on a retired OVH box from a previous hosting setup, disconnected from the live service but still reachable from the internet and still running a self-hosted Metabase instance. The report says a Metabase flaw let the attacker forge an administrator session, and it does not name a CVE. Metabase disclosed CVE-2026-72898, an unauthenticated SQL injection in the password-reset endpoint rated CVSS 10.0 and exploited in the wild, in August 2026. Tellter has not confirmed that this was the bug, so treat it as the obvious suspect and nothing more.
On that retired machine sat a cloud service-account key with administrator rights and an administrator's saved command-line session, and from 12:03 UTC those two credentials opened the production project. The detail worth remembering is the token rotation. Tellter invalidated the stolen Discord bot token at 13:39, and the attacker read the replacement within two minutes, because rotating a secret does nothing while the person reading it still has a shell in the environment that receives it. When the service-account key was disabled at 15:20, the attacker fell back to the saved administrator session and kept going until 17:54, pausing to run $7,316 of test charges with a Stripe key for Atis, a separate Tellter product.
Nothing past the first step was clever. It took a server that should have been wiped, credentials that should never have been on it, and a database reachable from the internet. The remediation list confirms all three: the legacy server is shut down, the database is no longer reachable from the internet, and the over-privileged service account is disabled.
What an IP address and a city reveal, and what they do not
Most people now searching Have I Been Pwned for their Discord username are asking one thing: does the attacker know where I live? The answer has two halves, and both matter for anyone deciding what to store.
| What people assume a leaked IP record gives away | What it actually gives away |
|---|---|
| A street address | No. IP geolocation resolves to a city or postal area, and the postal code here is the one assigned to the IP block, not the one on your mail. |
| Which device you used | Only a one-way hash of the user agent, mixed with city and country according to the report. Even if guessed from a list of common user agents, it narrows browser and OS, not the device. |
| Your exact location, always | Carrier-grade NAT puts thousands of subscribers behind one address that geolocates to the gateway, often a different city. A VPN exit is the VPN's location, not yours. |
| Nothing useful, it is "just an IP" | Wrong the other way. ISP, city, username and a timestamp confirm a match against another breach that does hold a name and address. Residential IPv4 addresses often stay stable for months, so the IP is a join key to any other log that saw it. |
| It expires | The link between a Discord ID and a city does not. People move less often than IPs change. |
The second half is why this leak is worse than its fields suggest. On its own, "AS3215, Orange, Lyon, 69003" identifies nobody. Attached to a Discord ID and username, with 27 million other rows to cross-reference, it becomes a lookup table for harassment: the moderator who banned someone can now be placed in a city and on an ISP by that person. The alt-detection table is the nastiest part, since each row records which accounts shared an IP, which is to say who lives or works together.
To be fair to the geolocation, it did what it claims. City-level resolution is right most of the time on fixed broadband and wrong in predictable ways on mobile and CGNAT ranges. The problem was never precision. The location was stored, keyed to an identity, for everybody, for years.
An IP address is personal data, and has been for ten years
Tellter's choice to notify the CNIL within a day was correct, and it was not optional. The Court of Justice of the EU settled the question in Breyer v Bundesrepublik Deutschland (C-582/14) on October 19, 2016: a dynamic IP address is personal data for an operator that has legal means to link it to a person, which any service that also holds the user's account ID plainly does. GDPR Recital 30 names "internet protocol addresses" as online identifiers in so many words, and Article 33 gives a controller 72 hours to notify a breach that is likely to put people at risk.
The policy cites legitimate interest under Article 6(1)(f), and ban evasion is a legitimate interest. The same test demands necessity, the least intrusive processing that achieves the purpose, and Article 5(1)(e) says data is kept no longer than necessary. A permanent raw-IP archive fails both the moment a keyed hash with a short window would have caught the same alts. The California Consumer Privacy Act lists "Internet Protocol address" in its definition of personal information (Civil Code section 1798.140), and a bot in 600,000 servers has Californian users whether it thinks about them or not.
Alt detection that keeps nothing worth stealing
State the design question plainly. You need to answer "has this connection been used by another account in this community recently?" You do not need to answer "what was user 734021's IP address in March 2024?" Double Counter's tables answer the second question, and the second question is what leaked.
The lookup step is where the useful information lives anyway: the ASN, whether the address belongs to an ISP or a hosting provider, and whether it is a known VPN exit, open proxy, Tor relay or residential proxy node. Double Counter already runs a check like this for its "VPN intrusion" flag, which its docs describe as catching an IP "sourced from a hosting service (VPN, BitTorrent, Proxy, Content Delivery Network, etc.)". Those flags are what a moderator actually wants, and none of them identifies a person.
For the "same connection as another account" match, replace the raw IP with a keyed hash. A plain SHA-256 of an IPv4 address is worthless, since there are only 2^32 of them and the whole space hashes in seconds. HMAC-SHA-256 with a key kept in a key management service, not in the database and not on a retired OVH box, makes the stored value noise without the key. Give each community its own key and the table cannot be joined across servers, which is the join that turns a moderation tool into a surveillance dataset.
Then expire it. A 30-day window catches the person banned on Tuesday who comes back on Wednesday, which is the case alt detection exists for. An IP-only match after eighteen months is noise anyway: the address has been reassigned, the person has moved, or the CGNAT pool has rotated through half a city. Double Counter's documentation lists a shared IP as sufficient grounds for a permanent alt flag, which in a CGNAT-heavy country means two strangers on the same carrier can be linked forever. A short window is better detection as well as better privacy.
The stored row ends up like this, with nothing in it that is personal data once the key is gone:
{
"asn": 3215,
"community_id": "1098…",
"connection_type": "isp",
"country": "FR",
"discord_id": "7340…",
"expires_at": "2026-11-07T09:14:00Z",
"ip_hmac": "b1e4a0…",
"is_residential_proxy": false,
"is_vpn": false,
"prefix_hmac": "52c9d7…",
"seen_at": "2026-10-08T09:14:00Z"
} You give up one thing: looking up an IP by hand when a user disputes a flag. Keep the match reason instead ("same connection hash as account X, 3 days earlier, AS3215, not a VPN") and nobody has to retrieve an address to review it.
A checklist for anyone running a verification bot
Most of what went wrong here is unglamorous, so the fixes are too:
- Write the retention period down, in the policy, in months, leave it there, and enforce it with a TTL in the database rather than a cron job someone has to remember.
- Store derived signals and keyed hashes, not raw IPs, and keep the key off every host that is not production.
- Skip city and postal code unless a moderator will act on them. Country and region cover almost every community rule.
- Inventory old infrastructure. A server that is "no longer in use" but has a public dashboard and admin credentials on disk is production as far as the attacker is concerned.
- Rotate secrets once the attacker is out. A new token delivered into a compromised environment is the attacker's new token.
Have I Been Pwned's listing covers only the 275,000 addresses that were published, so most of the 27 million people in the IP table will never get a notification. Anyone who verified through Double Counter in the last two years, and possibly earlier, should assume their Discord ID, city, ISP and IP address at the time are now in someone else's hands.
Ipregistry returns the derived signals this design runs on, the ASN and connection type, the country and region, and the VPN, proxy, Tor and residential-proxy flags, in a single lookup, so a bot can decide and discard the address in the same request. You can try the API with 20,000 free lookups to get started.
Keep users in the loop Ship release notes that get read. Try Noticeable