Split out of relay#9, which recorded that raising the sign-up limit from 5/IP/hour to 50 removed an accidental backstop and nothing deliberate replaced it. That is still true, measured 2026-09-04: no FN_SIGNUP_* is set in production at all.
The designed backstop exists and is off
FN_SIGNUP_POW_BITS gates sign-up behind a proof-of-work challenge. It is the right shape for this problem — it costs an attacker CPU per account while barely touching one person creating one account, and unlike a per-IP limit it does not punish everybody behind a shared NAT, which is why the 5/hour limit was raised in the first place.
Any client that does not send a challenge and nonce is refused outright. So turning this on breaks sign-up for every already-published build that lacks proof-of-work support — and it would present to a new user as the app simply not working, with no way for them to tell why.
What would make it safe
Some combination of:
confirm which shipped builds carry SignupProofOfWork (Android has it; iOS has it on main since the reinstate stack landed 2026-09-04, but has not shipped);
make enforcement conditional on the client advertising support, so an older build degrades to the rate limit rather than to a wall;
or hold it until the shipped floor on both platforms supports it, and say so.
⚠️ Enforcement being unconditional is not obviously wrong — a backstop a client can opt out of is not a backstop. That is exactly why this needs a decision rather than a flag flip.
Meanwhile
The other half of relay#9 is done: LiveTarget.refuseProduction throws if a live suite is pointed at production, and LiveTargetTest covers it (production hosts are refused / the disposable fleet and local relays are allowed). So the path that created those accounts is closed even with no rate-limit backstop.
Split out of relay#9, which recorded that raising the sign-up limit from 5/IP/hour to 50 removed an *accidental* backstop and nothing deliberate replaced it. That is still true, measured 2026-09-04: no `FN_SIGNUP_*` is set in production at all.
## The designed backstop exists and is off
`FN_SIGNUP_POW_BITS` gates sign-up behind a proof-of-work challenge. It is the right shape for this problem — it costs an attacker CPU per account while barely touching one person creating one account, and unlike a per-IP limit it does not punish everybody behind a shared NAT, which is why the 5/hour limit was raised in the first place.
## 🔴 Why it cannot just be enabled
`RoutesAccount.swift:173` is unconditional:
```swift
if signupProofOfWorkBits > 0 {
guard let challenge = input.challenge, let nonce = input.nonce else {
throw WireError("challenge_required", ...)
```
Any client that does not send a challenge and nonce is refused outright. So turning this on **breaks sign-up for every already-published build that lacks proof-of-work support** — and it would present to a new user as the app simply not working, with no way for them to tell why.
## What would make it safe
Some combination of:
- confirm which shipped builds carry `SignupProofOfWork` (Android has it; iOS has it on main since the reinstate stack landed 2026-09-04, but has not shipped);
- make enforcement conditional on the client advertising support, so an older build degrades to the rate limit rather than to a wall;
- or hold it until the shipped floor on both platforms supports it, and say so.
⚠️ Enforcement being unconditional is not obviously wrong — a backstop a client can opt out of is not a backstop. That is exactly why this needs a decision rather than a flag flip.
## Meanwhile
The other half of relay#9 is done: `LiveTarget.refuseProduction` throws if a live suite is pointed at production, and `LiveTargetTest` covers it (*production hosts are refused* / *the disposable fleet and local relays are allowed*). So the path that created those accounts is closed even with no rate-limit backstop.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Split out of relay#9, which recorded that raising the sign-up limit from 5/IP/hour to 50 removed an accidental backstop and nothing deliberate replaced it. That is still true, measured 2026-09-04: no
FN_SIGNUP_*is set in production at all.The designed backstop exists and is off
FN_SIGNUP_POW_BITSgates sign-up behind a proof-of-work challenge. It is the right shape for this problem — it costs an attacker CPU per account while barely touching one person creating one account, and unlike a per-IP limit it does not punish everybody behind a shared NAT, which is why the 5/hour limit was raised in the first place.🔴 Why it cannot just be enabled
RoutesAccount.swift:173is unconditional:Any client that does not send a challenge and nonce is refused outright. So turning this on breaks sign-up for every already-published build that lacks proof-of-work support — and it would present to a new user as the app simply not working, with no way for them to tell why.
What would make it safe
Some combination of:
SignupProofOfWork(Android has it; iOS has it on main since the reinstate stack landed 2026-09-04, but has not shipped);⚠️ Enforcement being unconditional is not obviously wrong — a backstop a client can opt out of is not a backstop. That is exactly why this needs a decision rather than a flag flip.
Meanwhile
The other half of relay#9 is done:
LiveTarget.refuseProductionthrows if a live suite is pointed at production, andLiveTargetTestcovers it (production hosts are refused / the disposable fleet and local relays are allowed). So the path that created those accounts is closed even with no rate-limit backstop.mentioned in merge request !74
mentioned in commit
ebea63a3ff