Anti-Abuse

Anti-Abuse

Fixed-window rate limits, anonymous HMAC quota tokens, Wesolowski VDF challenges, CAPTCHA flow, and storage bounds.

Anti-Abuse

#

The anti-abuse layer combines fixed-window rate limits, an anonymous quota token, a Verifiable Delay Function, an ML-KEM post-quantum challenge, and an optional CAPTCHA token. Its purpose is to make mass account creation and mass deposits expensive without linking the work to a user account.

Rate limiting

core/security.rs::rate_limit_hit implements a keyed fixed-window counter. A bucket holds a window start, a window length, and a count. Buckets are evicted when the store exceeds 5000 entries.

Representative limits:

KeyLimitWindow
phantom:deposit:global201 second
phantom:poll:global201 second
phantom:prekey:global6060 seconds
prekey-fetch:user:<id>415 seconds
register:global1015 seconds
register:user:<name>330 seconds
recover:user:<name>330 seconds
auth:cap:challenge:global6010 seconds
auth:challenge:global6010 seconds
pass:redeem:global6060 seconds
chat:session:<id>3060 seconds

In addition, chat messages enforce a 400 millisecond minimum interval per session.

Anonymous quota token

Before any gated action, the client requests an anonymous quota token from GET /api/auth/challenge. The token is an HMAC-signed epoch ticket. The client computes an action-scoped nullifier and the server consumes it, preventing reuse of the same ticket for the same action. The nullifier store is RAM-only, capped at 10 000 entries, with a 3 minute TTL, and the comparison is constant time.

Verifiable Delay Function

The VDF forces a sequential squaring computation before a gated action. The client must compute y = x^(2^t) mod N and produce the Wesolowski proof pi. The server verifies the proof in logarithmic time. The default iteration count is 30 000 and the challenge expires after 3 minutes.

Post-quantum challenge

The challenge also carries an ephemeral ML-KEM-768 encapsulation key. The client encapsulates and returns the ciphertext; the server decapsulates to confirm the client performed the KEM step. The key is one-time and expires after 3 minutes.

CAPTCHA flow

The full CAPTCHA flow returns a one-time cap token that can gate a registration, login, recovery, or deposit.

client                          server
      |  GET /api/auth/cap/challenge  |
      |----------------------------->|
      |  { challengeId, scope,        |
      |    issuedAt, expiresAt,       |
      |    vdf, quotaToken, pqcKey,   |
      |    signature }                |
      |<-----------------------------|
      |  solve VDF                    |
      |  compute nullifier            |
      |  encapsulate ML-KEM           |
      |  POST /api/auth/cap/redeem    |
      |  { challenge, vdfProof,       |
      |    nullifier, pqcCiphertext } |
      |----------------------------->|
      |  { success, capToken }        |
      |<-----------------------------|

The redeem handler verifies the challenge signature, consumes the quota nullifier, verifies the VDF proof, and decapsulates the ML-KEM ciphertext. It also checks optional instrumentation: an automated browser flag and a minimum interaction time of 250 milliseconds. The cap token is HMAC-signed, expires after 5 minutes, and is consumed once.

Privacy Pass redemption

POST /api/pass/redeem is wired and implements the nonce store with a reserve, verify, commit, release cycle. The store is capped at 100 000 entries with FIFO eviction. The deposit token issuance and consumption are implemented.

Not yet wired

The VOPRF verification of the AmortizedBatchTokenResponse against a public issuer keyset is not yet wired. The verify_amortized_batch_response function currently returns an error unconditionally. As a result, the pass deposit gate cannot currently be satisfied. The cap gate is the working path.

Storage bounds and TTLs

The ephemeral stores are bounded to prevent unbounded growth.

StoreCapTTL
Room messages150 per roomn/a, lost on restart
Phantom dead-drop16 per slot, 100 000 total24 hours
Anonymous nullifiers10 0003 minutes
VDF consumed challenges10 000challenge expiry
PQC ephemeral keys10 0003 minutes
CAPTCHA tokenspruned by expiry5 minutes
Privacy Pass nonce store100 000n/a, FIFO eviction