fix(auth): allow localhost URLs in email-confirm next param#327
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
| * route handlers, which reconstruct the redirect using the dashboard's own | ||
| * origin and preserve only `pathname` + `searchParams` from the input. | ||
| */ | ||
| export const httpUrlSchema = z.url({ protocol: /^https?$/ }) |
There was a problem hiding this comment.
🔒 Agentic Security Review
Severity: MEDIUM
Allowing unrestricted HTTP(S) hosts here (including loopback) expands the auth confirm redirect surface to attacker-controlled local targets when next is later passed through confirmation flows.
Impact: Crafted confirmation links can be used as a trusted redirect primitive toward victim-local services (localhost/127.0.0.1), increasing risk of browser-driven local service interaction.
There was a problem hiding this comment.
LGTM — small, well-tested schema fix to unblock local-dev email verification.
Extended reasoning...
Overview
The PR swaps two uses of z.httpUrl() (in src/app/api/auth/confirm/route.ts and src/core/modules/auth/models.ts) for a new shared httpUrlSchema defined as z.url({ protocol: /^https?$/ }). The new schema lives next to the existing relativeUrlSchema in src/core/shared/schemas/url.ts and is covered by a new 12-case unit-test file. Webhook URL validation in src/core/server/functions/webhooks/schema.ts is intentionally left on z.httpUrl().
Security risks
This is auth-adjacent code, but the schema only governs structural validation of the next query param — redirect-safety is enforced separately by isExternalOrigin() (origin comparison) and the buildRedirectUrl logic that reconstructs the URL from the dashboard's own origin. The new schema continues to reject mailto:, ftp:, file:, and javascript: URLs (verified by tests), so the open-redirect surface is unchanged versus the previous schema. The only behavior change is now accepting http://localhost / http://127.0.0.1 URLs as well-formed — which, downstream, would be treated as a different origin and routed through the Supabase verify flow as intended for the local-dev case.
Level of scrutiny
Low. The diff is mechanical (two call-site swaps + a one-line schema + tests + comment), the regex is correct, and the safety rationale is documented inline. No CODEOWNER-sensitive changes beyond the schema swap, and the bug hunting system flagged nothing.
Other factors
The PR explicitly preserves the stricter z.httpUrl() for webhook URLs, which is the correct boundary. Tests cover all the cases I'd want covered (accepted https/localhost/127.0.0.1, rejected mailto/ftp/file/javascript/empty/plain-string), and the Vercel preview builds pass.
Summary
Fixes #241: Zod validation error blocks email verification on local-dev Supabase setups.
z.httpUrl()with a sharedhttpUrlSchemathat usesz.url({ protocol: /^https?$/ })— still requires http/https, but acceptshttp://localhost:3000/....src/core/shared/schemas/url.tsalongside the existingrelativeUrlSchema, and both the auth confirm route handler andConfirmEmailInputSchemaconsume it.src/core/server/functions/webhooks/schema.ts) intentionally kept onz.httpUrl()— webhooks should not accept localhost.Files changed
src/core/shared/schemas/url.ts— addshttpUrlSchemaalongside existingrelativeUrlSchemasrc/core/modules/auth/models.ts—ConfirmEmailInputSchema.nextuses the shared schemasrc/app/api/auth/confirm/route.ts— same for the route's inlineconfirmSchematests/unit/url-schema.test.ts(new) — 12 unit tests covering accept/reject casesTests
Credits
Adapted from #249 by @taheerahmed