Skip to content

Comments

Harden account API routes with CSRF origin checks#14

Merged
schartrand77 merged 1 commit intomainfrom
codex/ensure-api-safety-and-security
Feb 24, 2026
Merged

Harden account API routes with CSRF origin checks#14
schartrand77 merged 1 commit intomainfrom
codex/ensure-api-safety-and-security

Conversation

@schartrand77
Copy link
Owner

@schartrand77 schartrand77 commented Feb 24, 2026

Motivation

  • Protect cookie-authenticated, state-changing account endpoints from cross-site request forgery by enforcing same-origin checks before performing sensitive updates.

Description

  • Import and use isSameOriginRequest from lib/csrf and return 403 when the origin is invalid for PATCH /api/account/email.
  • Import and use isSameOriginRequest from lib/csrf and return 403 when the origin is invalid for POST /api/account/email/request.
  • Import and use isSameOriginRequest from lib/csrf and return 403 when the origin is invalid for PATCH /api/profile.

Testing

  • Ran lint on the changed files with npm run lint -- app/api/account/email/route.ts app/api/account/email/request/route.ts app/api/profile/route.ts and it completed (repo contains unrelated lint warnings but no new errors for these changes).
  • Ran static type checks with npm run typecheck and it succeeded.

Codex Task

Summary by Sourcery

Bug Fixes:

  • Reject cross-site requests to account email update, email verification request, and profile update endpoints by validating request origin before processing.

@sourcery-ai
Copy link

sourcery-ai bot commented Feb 24, 2026

Reviewer's guide (collapsed on small PRs)

Reviewer's Guide

Adds CSRF hardening to sensitive account- and profile-related API routes by enforcing same-origin checks using a shared helper before executing authenticated PATCH/POST handlers.

Sequence diagram for CSRF-hardened protected API route

sequenceDiagram
  actor Client
  participant NextAPI as NextAPI_Route
  participant CSRF as CSRF_Helper
  participant Auth as Auth_Helper
  participant DB as Database

  Client->>NextAPI: HTTP PATCH/POST request with cookies and headers
  NextAPI->>CSRF: isSameOriginRequest(req)
  CSRF-->>NextAPI: true or false
  alt invalid_origin
    NextAPI-->>Client: 403 JSON { error: Invalid CSRF origin }
  else valid_origin
    NextAPI->>Auth: getUserIdFromCookie()
    Auth-->>NextAPI: userId or null
    alt no_user
      NextAPI-->>Client: 401 JSON { error: Unauthorized }
    else authenticated
      NextAPI->>DB: Perform protected update
      DB-->>NextAPI: Update result
      NextAPI-->>Client: 200 JSON success response
    end
  end
Loading

Flow diagram for CSRF and auth checks in PATCH_profile_handler

flowchart TD
  A[PATCH /api/profile handler invoked] --> B[Call isSameOriginRequest req]
  B -->|false| C[Return 403 - error: Invalid CSRF origin]
  B -->|true| D[Call getUserIdFromCookie]
  D -->|no userId| E[Return 401 - error: Unauthorized]
  D -->|userId present| F[Proceed with profile update logic]
  F --> G[Update database and filesystem]
  G --> H[Return 200 success response]
Loading

Flow diagram for CSRF and auth checks in account_email_handlers

flowchart TD
  A[POST or PATCH /api/account/email handler invoked] --> B[Call isSameOriginRequest req]
  B -->|false| C["Return 403 { error: Invalid CSRF origin }"]
  B -->|true| D[Call getUserIdFromCookie]
  D -->|no userId| E["Return 401 { error: Unauthorized }"]
  D -->|userId present| F[Execute email-specific logic]
  F --> G[For POST: create token and send verification email]
  F --> H[For PATCH: update email in database]
  G --> I[Return 200 verification requested]
  H --> J[Return 200 email updated]
Loading

File-Level Changes

Change Details Files
Enforce CSRF same-origin checks on email verification request endpoint before processing authenticated POST logic.
  • Import CSRF helper function used to validate request origin.
  • Add early-return guard that rejects requests with invalid origin using a 403 JSON response payload.
  • Ensure the CSRF check runs before retrieving the user from the auth cookie or performing any email verification work.
app/api/account/email/request/route.ts
Enforce CSRF same-origin checks on email update endpoint before processing authenticated PATCH logic.
  • Import CSRF helper function to validate the origin of incoming PATCH requests.
  • Insert an early-origin check that responds with 403 and an error JSON body when the origin is invalid.
  • Place the CSRF guard ahead of cookie-based authentication and database update logic to avoid side effects on failed checks.
app/api/account/email/route.ts
Enforce CSRF same-origin checks on profile update endpoint before processing authenticated PATCH logic.
  • Import CSRF helper function alongside existing profile-related utilities.
  • Add a precondition in the PATCH handler that validates the request origin and returns 403 with an error message if invalid.
  • Ensure the CSRF validation precedes user authentication and any filesystem/profile mutation to prevent CSRF-driven changes.
app/api/profile/route.ts

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@schartrand77 schartrand77 merged commit 027eb31 into main Feb 24, 2026
6 checks passed
Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 1 issue, and left some high level feedback:

  • Consider using a shared helper or constant for the CSRF failure response so the error shape and message are consistent across routes and easier to update in one place.
  • You may want to align the error semantics of the CSRF failure (403 with a specific message) with your existing auth errors (e.g., 401/403 messaging) to avoid leaking whether a request failed due to origin vs. authentication.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Consider using a shared helper or constant for the CSRF failure response so the error shape and message are consistent across routes and easier to update in one place.
- You may want to align the error semantics of the CSRF failure (403 with a specific message) with your existing auth errors (e.g., 401/403 messaging) to avoid leaking whether a request failed due to origin vs. authentication.

## Individual Comments

### Comment 1
<location path="app/api/account/email/request/route.ts" line_range="11-13" />
<code_context>
 const schema = z.object({ email: z.string().email() })

 export async function POST(req: NextRequest) {
+  if (!isSameOriginRequest(req)) return NextResponse.json({ error: 'Invalid CSRF origin' }, { status: 403 })
   const userId = await getUserIdFromCookie()
   if (!userId) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
</code_context>
<issue_to_address>
**🚨 suggestion (security):** Consider using a more generic error message for CSRF failures to avoid leaking implementation details.

A specific message like `'Invalid CSRF origin'` reveals which validation step failed, which can help an attacker probe your CSRF protections. Prefer a generic body (e.g. `{ error: 'Forbidden' }` with status 403) and, if needed, log the detailed reason server-side instead.

```suggestion
export async function POST(req: NextRequest) {
  if (!isSameOriginRequest(req)) {
    console.warn('CSRF validation failed for email verification request', {
      origin: req.headers.get('origin'),
      referer: req.headers.get('referer'),
    })
    return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
  }
  const userId = await getUserIdFromCookie()
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines 11 to 13
export async function POST(req: NextRequest) {
if (!isSameOriginRequest(req)) return NextResponse.json({ error: 'Invalid CSRF origin' }, { status: 403 })
const userId = await getUserIdFromCookie()
Copy link

Choose a reason for hiding this comment

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

🚨 suggestion (security): Consider using a more generic error message for CSRF failures to avoid leaking implementation details.

A specific message like 'Invalid CSRF origin' reveals which validation step failed, which can help an attacker probe your CSRF protections. Prefer a generic body (e.g. { error: 'Forbidden' } with status 403) and, if needed, log the detailed reason server-side instead.

Suggested change
export async function POST(req: NextRequest) {
if (!isSameOriginRequest(req)) return NextResponse.json({ error: 'Invalid CSRF origin' }, { status: 403 })
const userId = await getUserIdFromCookie()
export async function POST(req: NextRequest) {
if (!isSameOriginRequest(req)) {
console.warn('CSRF validation failed for email verification request', {
origin: req.headers.get('origin'),
referer: req.headers.get('referer'),
})
return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
}
const userId = await getUserIdFromCookie()

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant