Stop Account Takeover

Stop Account Takeover: Credential Stuffing & Stolen Sessions

Leaked password lists are replayed through rotating residential proxies, one attempt per address, so per-IP limits never trip. Stolen session cookies skip the login form and are replayed in fake (antidetect) browsers. Maskbreak checks the network and the browser behind each login, password reset and account change in one server-side call, with nothing for your customer to solve.

1 callServer-side decision; timing on /status
FreePlan: 10,000 visitor checks a month, no card
3Answers: allow, review or block, with the reasons
WatchMode first: log every verdict, stop nothing

What you're up against

Credential stuffing behind residential proxies

Stolen credential lists get replayed through rotating residential proxies, so every attempt arrives from a different clean home IP. Rate limits and IP blocklists count the one thing the attacker changes with every attempt.

Stolen sessions replayed in fake browsers

Attackers import hijacked cookies into fake (antidetect) browsers such as Kameleo or GoLogin and imitate the victim's browser well enough to pass naive fingerprint checks. The session is genuine and never touches your login form, so the check that catches it sits at the next sensitive action.

Automation that paces itself like a human

Headless browsers with stealth patches spread attempts across hours and thousands of exit IPs, staying under every velocity threshold. By the time login-failure dashboards show a spike, most of the list has already been tested.

Takeovers you learn about from support tickets

A successful takeover looks like a normal login in your logs. You find out when the victim writes in — after the email was changed, the payout redirected, or the stored card used — and each of those tickets costs trust a refund won't win back.

What Maskbreak does for you

  • See the network behind every attempt: proxy (residential exits included), VPN, cloud server or Tor, with the VPN or proxy service named when known
  • Flag fake browsers and automation at login, before the password compare and before a session exists
  • Send the account ID and device.linked_accounts counts how many of your accounts one device has tried: the signature of a tool working a stolen list
  • Store device.visitor_id at each sign-in, so a device the account has never used, arriving over a VPN, gets a one-time code instead of a session
  • One server-side call per login, reset and account change: nothing for a legitimate customer to solve, and a one-time code only where you choose review
1 proxy pool

can carry a credential-stuffing run and stolen-card checkouts alike, because a residential exit passes IP reputation checks. A worked model of the card side on a D2C store and what the verdict returns at checkout. Illustrative scenario, not a customer result.

Read the Playbook

Where the check goes

In the login handler the check goes after the account lookup and before the password compare, so the verdict carries the account ID and no password hash is computed for an attempt you will refuse. On block, answer exactly like a wrong password: same status, same body. A different answer tells the attacker which attempts were caught, and wherever the check runs after the password compare, which passwords were right. Add the same call to password reset (the request and the completion) and to email, phone and payout changes, where a stolen session does its damage. The endpoint-by-endpoint guide covers all six call sites with the rule for each. The example starts in watch mode: it logs each verdict, lets every attempt through and fails open if Maskbreak is unreachable. After a few days, compare the log with the Events in your console, then uncomment the enforce line; the integration guide has the full enforce checks.

// Login: after the account lookup, before the password compare and the session
// Watch mode first: log the verdict, always continue, fail open
const body = req.body || {};  // sentinel.js adds monocle + sentinel_fp to your form
let risk = null;
try {
  const verdictRes = await fetch('https://maskbreak.com/v1/evaluate', {
    method: 'POST',
    signal: AbortSignal.timeout(5000),
    headers: {
      'Authorization': 'Bearer ' + process.env.MASKBREAK_API_KEY,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      token: body.token || body.monocle,  // network evidence
      fingerprintEventId: body.fingerprintEventId || body.sentinel_fp,  // device evidence
      tz: body.tz || body.sentinel_tz,
      accountId: user?.id  // the account looked up by email; adds device.linked_accounts
    })
  });
  if (verdictRes.ok) risk = await verdictRes.json();
} catch {}  // unreachable or timed out: your handler still runs
console.log('[maskbreak]', JSON.stringify({
  decision: risk?.decision ?? null, reasons: risk?.reasons ?? null,
  risk_score: risk?.risk_score ?? null, degraded: risk ? !!risk.degraded : null,
  visitor_id: risk?.device?.visitor_id ?? null, linked_accounts: risk?.device?.linked_accounts ?? null
}));
// Enforce later: if (risk?.decision === 'block') return res.status(401).json({ error: 'Invalid email or password' });  // your exact wrong-password answer

The signals that matter here

Network context on every attempt. A stuffing run rents rotating residential proxies so each attempt arrives on a household address no blocklist has seen. Maskbreak's network intelligence judges the address as a relay, not by its reputation: when it knows the exit, the response lists proxy_detected, with network.proxy true and network.residential false, and the default decision is block. Each attempt is judged on its own, so spreading the list at one attempt per address does not dilute it. More on how residential exits are detected.

One device, many accounts. Takeover tooling works a stolen list from one machine. Send the looked-up account's ID as accountId and the response adds device.linked_accounts: how many of your accounts this device has been seen with. Above one, the reasons include multi_account_device. The links are one-way hashes kept per customer and never cross to another customer. The reason does not change the decision on its own, because families share laptops; set your threshold in code. A Rules-tab multi-accounting action applies to that signal only: it can raise the answer, but it never lowers the proxy or automation block on the same attempt. (device.times_seen is different: it counts sightings across all Maskbreak customers, so it says whether a device is new to Maskbreak, not to your account.)

The takeover shape: a new device on an old account. An account that has signed in from the same laptop for a year suddenly authenticates from a visitor_id it has never used, over a VPN. Store device.visitor_id with each successful sign-in and compare. A VPN alone returns review, never block, and a first-seen device is not proof either; people buy laptops. Together they are the moment for a one-time code, which real customers pass and a stolen password alone does not. Add automation_detected or antidetect_browser and the default is already block. Routing review to step-up covers the thresholds.

Stolen sessions, caught at the next sensitive action. A stolen session cookie is genuine and never passes your login form. The browser replaying it often is not: fake browsers leave inconsistencies that show as device.antidetect, antidetect_browser in the reasons and a higher tampering_score. So check again at email, phone, password and payout changes, compare the visitor_id with the one that signed in, and hold the change on review. The account's owner never sees a CAPTCHA, because the verdict is an API response, not a challenge.

Example response for one attempt in a stuffing run: a proxy exit, a scripted browser, and a device that has now tried 38 of your accounts (excerpt).

{
  "decision": "block",
  "risk_score": 90,
  "reasons": ["proxy_detected", "automation_detected", "multi_account_device"],
  "network": { "vpn": false, "proxy": true, "datacenter": false, "tor": false, "residential": false, "service": "<proxy service when known, else null>" },
  "device": { "automation": true, "antidetect": false, "tampering_score": 0, "visitor_id": "<visitor ID>", "linked_accounts": 38, "multi_account": true }
}

Common questions

How does Maskbreak catch credential stuffing that rate limiting misses?
Rate limits count attempts per IP, and rotating residential proxies give each attempt a fresh one. Maskbreak judges each attempt on what the rotation does not hide: the address is a proxy exit (proxy_detected, a block by default, when Maskbreak's network intelligence knows the exit) and the browser is scripted or faked (automation_detected or antidetect_browser, also a block). Send the account ID with the check and device.linked_accounts shows one device trying many of your accounts.
Will legitimate users on VPNs get locked out?
No. A VPN on its own returns review, never block, and your code decides what review means: a one-time code at login, or a normal sign-in when the device's visitor_id is one this account has used before (you store it). Proxy, Tor, automation and fake browsers are a block by default. The Rules tab changes the action per signal, and a rule speaks only for the signal it names: set VPN to Allow and VPN-only sign-ins pass, while a proxy or fake browser on the same attempt still blocks. engine_decision keeps the default answer when a rule changes it.
Do users ever see a CAPTCHA or an extra login step?
Not from Maskbreak. A small script on the login page collects two browser tokens without showing anything, and the check is one server-side call. The only extra step is one you add, such as a one-time code when the decision is review.
What does the API return on a login check?
A decision (allow, review or block), a risk_score from 0 to 100, a reasons array with codes such as proxy_detected, vpn_detected, automation_detected and antidetect_browser, the network object (with the VPN or proxy service named when known) and, when the browser check completed, a device object with a visitor_id you can store per account. Send accountId and it adds linked_accounts. A missing device object is missing evidence, not a clean result.
Is Maskbreak ready to sit in front of a production login flow?
Maskbreak's terms cover production use on every plan, the Free plan included. The Free plan has 10,000 visitor checks a month, up to 1,000 an hour, with no card, and paid plans raise both from €29 a month; GET /v1/usage shows what is left of the hour. Start in watch mode on the login endpoint: log each verdict, let every attempt through, and compare a few days of the log with the Events in the console before you enforce block. Decide separately what a check that returned no verdict (an outage, a timeout, a key over its hourly or monthly limit) means: a login may continue, a payout or email change should wait. Log that as your fallback, never as an allow.

Start protecting your login endpoint

Free plan: 10,000 visitor checks a month, no card. Paid plans from €29 a month. Flags proxies (residential exits included), fake browsers and scripted logins.

Fraud BriefOnce a month · no spam · unsubscribe anytime
Get the new VPN, proxy & bot patterns we see each month
Short, technical breakdowns of what fraudsters changed last month — written for engineers, not marketers.
Stop fraud before it hides — try Maskbreak free. Free plan: 10,000 visitor checks a month, no card. Paid plans from €29 a month.