Overview
Authenticated rate limits now use a server-side reputation layer. The layer scores users, sessions, API keys, IPs, CIDRs, and user-location pairs from auditable signals, then returns a coarse risk tier and rate-limit multiplier to the API and PostgREST guards. The policy is intentionally server-only. The repository is open source, so do not move scoring thresholds, bypass rules, or detailed reason codes into client code, response headers, or public docs. User-agent, missing-header, and request-shape checks are weak signals; they can raise caution but cannot earn trust by themselves.Trust Tiers
Trust never bypasses active user suspension, active IP blocks, sensitive auth
route protections, workspace-secret hard overrides, or severe backend abuse
cascades.
One exception exists for active IP blocks: an authenticated user who passes
Turnstile through
/api/v1/rate-limit-appeals can receive a short Redis relief
key scoped to the same browser session and ip:<address>. The relief only lets
that session continue while admins review the appeal; it does not clear the
global blocked_ips row or help unrelated users behind the same IP.
API Abuse IP Blocks
Session-authenticated routes may defer proxy-sideapi_abuse IP blocks only
long enough to validate route authentication. Do not treat bearer-shaped tokens,
ttr_ strings, Supabase auth cookie names, or app-session cookie presence as
proof of an authenticated session. If the route auth check fails, return the
original block response before invoking the handler or recording normal auth
failure side effects.
Emergency Protection Controls
Platform admins with root workspacemanage_workspace_roles can temporarily
disable edge IP blocking or route rate limits from Infrastructure -> Rate
Limits. The controls are stored in Redis for the proxy runtime.
Redis-backed protection is intentionally availability-sensitive. When Redis is
not configured, cannot initialize, or a Redis command fails, apps/web fails
open for route rate limits, abuse counters, and IP-block enforcement. The app
continues to run authentication, authorization, payload-size checks, Turnstile,
user suspension, and normal request validation, but it does not enforce stale
database/manual IP blocks or process-local fallback buckets. Restore Redis to
restore rate-limit and IP-block enforcement.
- Disabling IP blocking stops enforcement of cached
ip:blocked:<ip>entries and prevents proxy abuse counters from creating new edge IP blocks. It does not deleteblocked_ipsrows or existing Redis block keys. - Disabling route rate limits skips the proxy read/mutate buckets while leaving payload size checks and malformed request validation active.
Signals
The layer records rolling signals for:- rate-limit hits, failed auth, repeated
4xx,401,403, and429 - payload abuse and automation-like clients
- missing or scripted browser headers as weak negative signals
- older accounts, stable sessions, successful organic usage, and passed challenges as positive signals
- admin overrides and override revocations
Backend 429 Cascades
Supabase Auth and PostgREST/backend429 responses are availability signals,
not user-scoped abuse proof. Handlers return 429 to the caller with
Retry-After, but a single backend rate limit must not create or extend
ip:blocked:<ip>, blocked_ips, or user_suspensions. User suspension
requires manual operator action or repeated, user-attributable abuse counters,
and automated suspensions should be expiry-bound unless a separate policy
explicitly justifies permanence.
Shared-IP Login Traffic
English centers, classrooms, and offices often put many legitimate teachers behind one public NAT IP. The proxy guard therefore keeps password login, OTP send, and OTP verify on separate auth buckets instead of sharing the generic mutation budget:
OTP send also has a shared-IP abuse guard with
ABUSE_OTP_SEND_IP_LIMIT_MINUTE, ABUSE_OTP_SEND_IP_LIMIT_HOUR, and
ABUSE_OTP_SEND_IP_LIMIT_DAY. Keep the per-email cooldown/hour/day limits in
place; they are the primary protection against repeated sends to the same
mailbox. Human auth route 429 responses should return Retry-After,
X-RateLimit-Client-IP, and X-RateLimit-Policy to the caller so the
rate-limit details dialog can show the server-observed public IP and policy
without asking the customer to visit a separate IP lookup page. These auth
429s must not be converted into api_abuse IP blocks by the proxy escalation
path. Utility-level OTP send, OTP verify, MFA verify, reauth verify, and
password-login failure counters also throttle without writing blocked_ips
rows; their abuse events include hard_block_suppressed metadata when the old
hard-block threshold would have been crossed. Generic anonymous API route-limit
hits, malformed auth-cookie abuse, scanner-like requests, api_auth_failed, and
manual operator blocks can still escalate to hard IP blocks.
Trusted-Location Read Uplift
High-density centers (many staff behind one office NAT IP) can hit the read-limit toast (“Bạn đang bị giới hạn tần suất. Thử lại sau 60 giây…”) during normal browsing. Untrusted read traffic keys perip:<ip> against the anonymous default
(60 / 240 / 1200 per minute/hour/day), so everyone behind the shared IP draws
from one bucket. A trusted-location override keyed by CIDR scales that bucket at
the edge (e.g. 60 -> 300/min at 5x) and lets trusted sessions key
per-session. The subject_key must match the edge getCidrSubjectKeyEdge form
(cidr:<a.b.c>.0/24 for IPv4, cidr:<prefix>::/64 for IPv6).
- Confirm the center’s public IP via Infrastructure -> Abuse Intelligence or by
asking the center, then derive the
/24. - Insert a time-bound override. An operator runs this against production; the platform never auto-pushes production SQL:
- The edge trust cache (
EDGE_TRUST_CACHE_TTL_SECONDS, ~1h) reconciles vialist_trusted_subjects_for_cache()and thesync-trust-cachecron, so the uplift takes effect within the TTL without a deploy. SetAPI_PROXY_EDGE_TRUST_ENABLED=0as a kill switch to fall back to legacy per-IP read limiting.
Trusted-Workspace Uplift
Admins can also raise limits for a legitimate high-volume workspace from Infrastructure -> Rate Limits by creating aworkspace subject rule:
subject_type:workspacesubject_key:workspace:<workspace_uuid>tier:trustedlimit_mode:inherit_multipliertrust_multiplier: start at3expires_at: prefer 30-90 days
Rate-Limit Appeals
When the details dialog showsX-Proxy-Block-Reason: ip-already-blocked, the
request is being rejected by an active hard IP block. Raising route limits alone
will not unblock that user. Admins should first inspect Infrastructure ->
Blocked IPs and Abuse Intelligence, then clear the block if it is a false
positive.
Legitimate authenticated users can submit a review request from the rate-limit
details dialog. The submission route is the only route allowed through an active
IP block, and it still requires:
- an authenticated session cookie
- a valid Turnstile token
- bounded sanitized diagnostics from the dialog
- a low per-user/IP appeal throttle
rate_limit_appeals and grants only temporary
session+IP relief. From Infrastructure -> Rate Limit Appeals, admins can approve,
reject, or close the appeal. Approval clears the active IP block and, by
default, creates a time-bound trusted workspace rule with 3x multiplier for 30
days. Admins can edit the workspace ID, multiplier, and expiry before approval,
or create more specific IP/CIDR/user rules manually from Infrastructure -> Rate
Limits.
Step-Up Challenges
Browser mutations with medium risk should require Turnstile through the existing server verification path. The API returns a generic challenge-required response without exposing the exact scoring rules. API keys, CLI calls, cron jobs, webhooks, and native clients do not receive browser challenges; they rely on token, workspace, and API-key reputation instead.Local E2E Isolation
The Playwright rate-limit suite intentionally exhausts budgets and records 429 signals. ItsresetDbRateLimits() helper must clear both PostgREST rate-limit
counters and generated adaptive abuse state (abuse_activity_signals,
abuse_step_up_challenges, and abuse_reputation_subjects) before each spec.
Specs that intentionally hit authenticated route limits should use Redis and a
fresh local dev-session account per test/retry so Redis-backed user keys and
asynchronous adaptive reputation writes cannot bleed into the next case. Without
Redis, apps/web rate-limit and IP-block checks fail open and will not produce
the expected 429 responses.
Admin Investigation
Use Infrastructure -> Abuse Intelligence to inspect:- trusted, watched, and restricted subject counts
- recent signals and coarse reason codes
- challenge pass/fail trends
- risky users, sessions, API keys, IPs, and CIDRs
- active manual overrides
- Check whether the subject has recent rate-limit, auth-failure, or payload abuse signals.
- Compare the subject with related location and user-location entries.
- Review challenge outcomes and recent route diversity before granting trust.
- Add a time-bound override only when the activity is clearly organic.
- Revoke trust immediately if the subject later shows scripted behavior, account takeover indicators, or noisy API-key traffic.
identity.clientIp,
limit.retryAfterSeconds, and limit.policy fields to check Infrastructure ->
Abuse Intelligence for active IP blocks or abuse events with api_abuse or
password_login_failed. If the activity is confirmed organic, clear the active
false-positive block, reset affected counters, or add a time-bound IP/CIDR trust
or rate-limit uplift. Do not relax the per-email OTP cooldown or failed-attempt
protections.
If the copied details include limit.proxyBlockReason = ip-already-blocked, route-limit tuning is secondary. Clear the active block or
approve the user’s Rate Limit Appeal first, then decide whether the root cause
needs a trusted workspace, IP/CIDR uplift, or app-level batching fix.
Manual overrides require a reason and are written to the audit signal stream.
Prefer expiry-bound overrides for trust and watch decisions so stale operational
judgment does not become permanent policy.