Invite codes are brute-forceable (insufficient entropy) #2
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Problem
Invite codes are 8-character alphanumeric strings (
[A-Z0-9]), giving only ~41.4 bits of entropy (36^8 ≈ 2.8 trillion). Combined with no rate limiting on the accept endpoint, this is brute-forceable.Affected files
HouseholdService.java:175-180—generateInviteCode()methodHouseholdService.java:40—CODE_CHARSconstantAttack scenario
At 1000 requests/second with no rate limiting, an attacker could enumerate the code space in a feasible timeframe. Multiple active invites increase the probability of a hit.
Recommended fix
Replace the 8-char alphanumeric code with UUIDv4 (122 bits of entropy) as the invite code. The spec already calls for "UUIDv4 minimum." This also requires updating the
invite_codecolumn length constraint.Rate limiting on the accept endpoint (see separate issue) provides defense-in-depth.
Severity
Critical — invite codes can be guessed, granting unauthorized household access.
👨💻 Kai — Frontend Engineer
This is entirely a backend change, but the invite acceptance flow touches frontend routing and I want to flag a few things.
Frontend impact of switching to UUIDv4 invite codes:
/invites/ABC12345/accept. A UUIDv4 code produces/invites/550e8400-e29b-41d4-a716-446655440000/accept. The URL is longer but still shareable as a link — this is fine. I just need to update any route parameter validation in the SvelteKit route if it currently enforces a length/pattern.Questions:
invite_codecolumn have a length constraint that needs a migration when we switch fromvarchar(8)tovarchar(36)? Kai can't handle DB migrations, but I need to know if this is a breaking change for the/invites/{code}/acceptroute parameter.No blockers on my end — the UUIDv4 switch is transparent to the frontend as long as the route parameter accepts the new format.
🏗️ Backend Engineer — Spring Boot / PostgreSQL Specialist
Agreed on the fix — UUIDv4 is the right call. The implementation details matter here.
Implementation specifics:
Code generation: Replace the custom
generateInviteCode()withUUID.randomUUID().toString(). That's it. No custom alphabet, no length calculation — just use the standard library.DB column migration: The
invite_codecolumn is almost certainlyvarchar(8)or similar. A Flyway migration is needed to widen it tovarchar(36)(oruuidtype). Using the PostgreSQL nativeuuidtype is cleaner:ALTER TABLE invites ALTER COLUMN invite_code TYPE uuid USING invite_code::uuid. This also gives us free format validation at the DB level.Application layer: If
invite_codechanges touuidtype in PostgreSQL, the JPA mapping should useUUID(Java type), notString. Spring Data JPA handlesUUID↔ PostgreSQLuuidnatively.CODE_CHARSconstant: TheHouseholdService.java:40CODE_CHARSconstant can be deleted entirely — no longer needed.Existing data: Any existing 8-char invite codes in the database become invalid after migration. Is this acceptable? If there are outstanding invites, they'll need to be regenerated or expired first. For v1 / development, this is probably fine — just worth calling out.
Questions:
invite_codecolumn currentlyvarchar(n)or something else? I want to confirm the migration path.UNIQUEindex for fast lookup — and if not, that needs to be added in the same migration.Defense in depth: Rate limiting on the accept endpoint (issue #1) should be implemented alongside this — the two fixes together close the brute-force attack. Either fix alone is incomplete.
🧪 QA Engineer
Here's the full test coverage I'd want for this fix, including the migration path.
Unit tests for
HouseholdService.generateInviteCode()(or its replacement):Integration tests for invite acceptance:
POST /v1/invites/{validUUID}/acceptwith valid, unexpired code →200, user added to householdPOST /v1/invites/{validUUID}/acceptwith expired code →410(Gone) or404— what's the intended status?POST /v1/invites/{validUUID}/acceptwith already-used code →409or404POST /v1/invites/{invalidFormat}/accept— e.g.,"ABC12345"(old format) or"not-a-uuid"→400POST /v1/invites/{nonExistentUUID}/accept— valid UUID format but doesn't exist →404401409?Migration test:
UNIQUEconstraint oninvite_codeis present after migrationEdge cases:
UNIQUEconstraint should catch it and the service should handle the constraint violation gracefully — not return a 500)uuidtype normalizes case, butvarchardoes not. This matters if the code is passed via URL and the case differs.Note: These tests should be coupled with rate limiting tests from issue #1 — the combination of UUIDv4 + rate limiting is what makes the fix complete.
🔒 Sable — Security Engineer
This is correctly rated Critical. The math in the issue description undersells the risk slightly — let me sharpen it.
Why 41.4 bits is insufficient:
The issue says ~2.8 trillion combinations. That sounds large, but the relevant metric is how many guesses an attacker needs to achieve a given probability of success. With multiple active invite codes (common in a household app where several people are invited simultaneously), the probability of a hit per request scales linearly with the number of active codes. With no rate limiting, even a modest botnet can enumerate at millions of requests per second.
Security requirements for the fix:
UUID.randomUUID()(Java'sSecureRandombacked) is correct. Do not useMath.random()or any non-cryptographically-secure random source for invite code generation./v1/invites/*/acceptis still valuable defense-in-depth (issue #1 covers this). These two fixes are complementary.Atomicity concern: The current
HouseholdService.java— is the "check code valid → accept → mark used" operation wrapped in a single@Transactionalboundary? If not, two concurrent requests with the same code could both pass the validity check before either marks it used. The DBUNIQUEconstraint on the code is not sufficient here — we need the update to be atomic with the lookup.🎨 Atlas — UI/UX Designer
This is a backend security fix, but the invite flow has clear UX implications that I want to think through while it's being touched.
UX impact of switching to UUIDv4:
Invite UX states I want to design (if not already done):
Questions: