Private betaRequest early access →

Sign in

Abuse and bot protection

The sign-in and sign-up surfaces are the ones an attacker reaches without an account. Perihelium defends them in layers, each independent of the others, so a layer an attacker gets past still leaves the next in place. This page describes the layers from the outside in, says plainly which way each one fails when something underneath it is down, and lists the settings an organization controls.

Rate limits

Sign-in failures are counted per network address and per account. By default 30 failures from one address, or 10 for one account, inside a 15-minute window stop further attempts: the next sign-in is refused with 429 before its credentials are checked. Only failures count, so a busy office behind one address is not locked out by ordinary sign-ins, and a successful sign-in clears the account's count. The account is identified by the address typed, whether or not such an account exists, so a refusal does not reveal that one does.

This one counter fails open. If the store that counts is unreachable, sign-in continues, because refusing every sign-in during a counting outage would be an outage of its own; the credential-stuffing and takeover detections below remain the backstop.

Everything else refuses when it cannot count: sign-up, the OAuth authorize and token endpoints, the device code, the second-factor challenge, password-reset and magic-link confirmation, and the agent and MCP surfaces. A request refused for rate answers 429 with a computed Retry-After.

The emails that carry password-reset and magic links are capped per address and per account. Past the cap the caller still receives the same accepted answer and no email is sent, so the cap cannot be used to tell which addresses are registered.

Credential stuffing and takeover detection

Two detections watch the authentication chokepoint continuously (Security monitoring):

  • credential-stuffing-ip: sign-ins failing for five or more distinct accounts from one network address within five minutes, the signature of a leaked-password list being replayed. It is raised as critical.
  • ato-account-lockout-sweep: three of these security changes by one actor within ten minutes: multi-factor authentication disabled, recovery codes regenerated, a password changed. Each alone is ordinary self-service; together and that fast they are the signature of someone who already has the session and is closing the door behind them.

Both are triaged in the console like any other finding.

Breached-password screening

A new password, at sign-up, at a change, at a reset, on accepting an invitation and during onboarding, is checked against the corpus of passwords exposed in public breaches by k-anonymity: only the first five characters of the password's SHA-1 hash leave the platform, the match is made locally on the returned list, and the password itself is never transmitted. A breached password is refused with a message that says why. The check runs beside the strength check on every new-password path.

The check fails open: if the range service cannot be reached within a short timeout the password is accepted on its strength alone, because refusing every new password during an outage of a third-party service would stop people from signing up. Self-hosted installs without outbound access switch it off (breached_password_check_enabled).

Sign-up attestation

Sign-up is a surface a bot can reach, and a CAPTCHA is a surface a person resents. Perihelium asks for attestation proportionate to the risk the request carries: a sign-up from a network with a clean history completes without friction; one from a network that has produced abuse, or a burst from one network, is asked for a privacy-preserving proof, a proof-of-work puzzle the browser solves silently, before the account is created. The attestation never identifies the person; it only costs the bot.

Agent-driven onboarding carries its own proof of work at the bootstrap step (Agent-driven onboarding).

Enumeration resistance

Password reset and magic-link requests answer identically for a registered and an unregistered address, in every case, including past the send cap; the message arrives at the mailbox if there is one and nowhere if there is not.

Sign-up answers the same for every address. Asking to sign up (workforce, customer or domain join) creates nothing and answers 202 check_your_email whether or not the address, the organization name or the organization exists; the difference is sent only to the mailbox, and an account is created only when the mailed one-time link is redeemed. An address that already has an account is mailed a notice instead of a link, at most once a minute. The request is rate limited per network address, redeeming the link has its own per-network limit, and an address can hold only a few waiting requests; the limits fail closed.

Network reputation

A network address that has produced abuse, such as a credential-stuffing detection or repeated attestation failures, carries that reputation forward: later requests from it are asked for attestation sooner and throttled harder. An organization adds its own denylist and allowlist of networks under Settings → Security, and the platform's decision is logged with the reason on every refused request, so a false positive is a finding with evidence, not a mystery.

What an organization controls

SettingWhat it does
Network allowlist and denylistnetworks that are always or never admitted to the organization's sign-in
Session limits (auth.session_absolute_max_age_hours, auth.session_idle_timeout_minutes)absolute and idle lifetimes tighter than the platform's; the smaller always applies

The sign-in failure limits above, and the other limits on this page, are platform settings: a customer does not buy a larger brute-force allowance. Everything here is recorded: a changed setting is an audited action with its before and after, and every refusal is an event your monitoring and SIEM see.