Anonymous dynamic client registration is capped at 50 rows (MAX_OAUTH_CLIENTS in src/server/auth.ts). That is a fine default for the intended shape of this product — one person, or a small set of servers — and no change is needed today. Filed so the ceiling is a known decision rather than a surprise.
What the cap does, and why it is not redundant
Two limits guard /oauth2/register, and they bound different things:
- Better Auth rate limit — 20 per hour. Caps the rate, and resets.
MAX_OAUTH_CLIENTS — 50. Caps the total, and does not reset, except that unused clients older than 24h are pruned before the count.
Twenty an hour sustained is still unbounded accumulation — roughly 480 a day — on a volume that also holds the vault, with a 5 GB ceiling on Railway Hobby. The rate limiter only ever asks how fast, never how many.
When 50 might not be enough
- Many connectors across many clients, each leaving throwaway registrations behind (a failed login leaves one)
- Someone pointing a fleet at one instance, which the single-user design does not target but does not prevent
The failure mode is self-denial rather than a security hole: once the table is full nobody can register, including the operator adding a legitimate connector. Pruning only helps for clients that are both unused and over a day old, so a burst of throwaways in one afternoon is not immediately recoverable.
Shape of the change
Read an optional override, keeping 50 as the default:
const MAX_OAUTH_CLIENTS = Number(process.env.MAX_OAUTH_CLIENTS) || 50;
Worth deciding at the same time:
- A sane floor, so a typo cannot set it to 0 and lock out registration entirely
- Whether the Security tab should show the configured value — it already reports
maxOAuthClients via getSecurityPage
- Whether an operator hitting the cap should see something better than a 429, since the current message does not say the fix is deleting unused connectors
Tests
tests/http/client-cap.test.ts covers the cap and reads MAX_OAUTH_CLIENTS from the module, so it follows an override without editing. Note the trap it documents: testing this over HTTP by looping is meaningless, because Better Auth refuses at 20 with its own "Too many requests" long before the cap of 50 is reached. The test seeds the table directly for that reason.
Anonymous dynamic client registration is capped at 50 rows (
MAX_OAUTH_CLIENTSinsrc/server/auth.ts). That is a fine default for the intended shape of this product — one person, or a small set of servers — and no change is needed today. Filed so the ceiling is a known decision rather than a surprise.What the cap does, and why it is not redundant
Two limits guard
/oauth2/register, and they bound different things:MAX_OAUTH_CLIENTS— 50. Caps the total, and does not reset, except that unused clients older than 24h are pruned before the count.Twenty an hour sustained is still unbounded accumulation — roughly 480 a day — on a volume that also holds the vault, with a 5 GB ceiling on Railway Hobby. The rate limiter only ever asks how fast, never how many.
When 50 might not be enough
The failure mode is self-denial rather than a security hole: once the table is full nobody can register, including the operator adding a legitimate connector. Pruning only helps for clients that are both unused and over a day old, so a burst of throwaways in one afternoon is not immediately recoverable.
Shape of the change
Read an optional override, keeping 50 as the default:
Worth deciding at the same time:
maxOAuthClientsviagetSecurityPageTests
tests/http/client-cap.test.tscovers the cap and readsMAX_OAUTH_CLIENTSfrom the module, so it follows an override without editing. Note the trap it documents: testing this over HTTP by looping is meaningless, because Better Auth refuses at 20 with its own "Too many requests" long before the cap of 50 is reached. The test seeds the table directly for that reason.