Skip to content

fix(auth): allow localhost URLs in email-confirm next param#327

Open
drankou wants to merge 1 commit into
mainfrom
investigate-gh-241-zod-validation-error-eng-3979
Open

fix(auth): allow localhost URLs in email-confirm next param#327
drankou wants to merge 1 commit into
mainfrom
investigate-gh-241-zod-validation-error-eng-3979

Conversation

@drankou
Copy link
Copy Markdown

@drankou drankou commented May 14, 2026

Summary

Fixes #241: Zod validation error blocks email verification on local-dev Supabase setups.

  • Replaces z.httpUrl() with a shared httpUrlSchema that uses z.url({ protocol: /^https?$/ }) — still requires http/https, but accepts http://localhost:3000/....
  • Schema lives in src/core/shared/schemas/url.ts alongside the existing relativeUrlSchema, and both the auth confirm route handler and ConfirmEmailInputSchema consume it.
  • Webhook URL validation (src/core/server/functions/webhooks/schema.ts) intentionally kept on z.httpUrl() — webhooks should not accept localhost.

Files changed

  • src/core/shared/schemas/url.ts — adds httpUrlSchema alongside existing relativeUrlSchema
  • src/core/modules/auth/models.tsConfirmEmailInputSchema.next uses the shared schema
  • src/app/api/auth/confirm/route.ts — same for the route's inline confirmSchema
  • tests/unit/url-schema.test.ts (new) — 12 unit tests covering accept/reject cases

Tests

bun run test:unit
bun run lint

Credits

Adapted from #249 by @taheerahmed

@drankou drankou requested a review from ben-fornefeld as a code owner May 14, 2026 15:04
@cla-bot cla-bot Bot added the cla-signed label May 14, 2026
@linear-code
Copy link
Copy Markdown

linear-code Bot commented May 14, 2026

ENG-3979

@vercel
Copy link
Copy Markdown

vercel Bot commented May 14, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
web Ready Ready Preview, Comment May 14, 2026 3:04pm
web-juliett Ready Ready Preview, Comment May 14, 2026 3:04pm

Request Review

* 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?$/ })
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 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.

Copy link
Copy Markdown

@claude claude Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Zod validation error

1 participant