Skip to content

Trade inquiry API accepts malformed email addresses #117

Description

@spizeck

Summary

POST /api/trade-inquiry validates that email is non-empty and within the length limit, but never checks its format. A malformed address is persisted to tradeLeads and passed verbatim to Resend as replyTo, where it fails — producing a guaranteed notification failure (and a Sentry alert) for a lead the owner cannot reply to anyway.

Evidence

  • app/api/trade-inquiry/route.ts:68 — required-field check is !email only; the length check (tradeLeadFieldTooLong) is the only other constraint.
  • lib/trade-leads.ts:65 — replyTo: input.email is sent unvalidated. Resend rejects an invalid replyTo, the send throws, and processTradeInquiry logs trade_inquiry.notification_failed (which forwards to Sentry in production via logError).
  • The lead is still persisted and the customer sees success — so the failure mode is: dead record in Firestore plus monitoring noise, with no way to reach the submitter unless they also provided a phone number.
  • Client-side type="email" validation does not help: the endpoint is public and bots/malformed requests bypass the form entirely.

Why existing tests did not catch it

Route/validation tests cover required fields, field-length limits, honeypot, and rate limiting — not field formats. There was no email-format rule to assert.

Recommended scope

Add a format check in the route before persistence — the same EMAIL_REGEX pattern already used by app/api/admin/users/route.ts and components/admin-access.tsx — returning 400 with a clear message. Optionally consider constraining venueType to the known option set as well (currently free-text up to 64 chars server-side), though that is lower value.

Acceptance criteria

  • A syntactically invalid email returns 400 and persists nothing.
  • Valid addresses continue to submit normally (including edge cases like + tags and subdomains).
  • Tests cover the format check alongside the existing required/length cases.

Overlap

None — #57/#59 covered persistence and retention policy, not input validation depth.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    priority: lowUseful improvement that can waittype: bugSomething is broken or behaving incorrectly

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions