nextjs - ✅(Solved) Fix HTTP 500 on malformed request body for server action (JSON parse SyntaxError) [5 pull requests, 9 comments, 8 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
vercel/next.js#86945Fetched 2026-04-08 02:08:19
View on GitHub
Comments
9
Participants
8
Timeline
26
Reactions
9
Timeline (top)
commented ×9cross-referenced ×7subscribed ×3labeled ×2

Fix Action

Fixed

PR fix notes

PR #90109: fix: return 400 instead of 500 for malformed server action body

Description (problem / solution / changelog)

What?

Return 400 Bad Request instead of 500 Internal Server Error when a server action request has a malformed body.

Why?

When a POST request includes a valid Next-Action header but the body is malformed (e.g. invalid JSON like [ instead of []), decodeReply throws a SyntaxError. This error was uncaught, resulting in a 500 response.

This is a widespread issue affecting production apps — vulnerability scanners (Assetnote, Shannon, etc.) send malformed requests that trigger 500s, which pollute error monitoring dashboards. Multiple users reported this in #86945 across Next.js 14.x, 15.x, and 16.x.

Reproduction scenarios covered by this fix:

  • text/plain body with invalid JSON (e.g. [zxc], [)
  • text/x-component body with malformed payload (same code path)

Related PRs:

  • #90101 fixes a related issue where multipart POST without a valid action ID returned 500 instead of 404

How?

Wrapped the decodeReply calls for text/plain action bodies in try/catch blocks (both edge and node runtime paths). On failure, the server now returns a 400 Bad Request with a text/plain body instead of crashing with a 500.

The fix is scoped to text/plain body paths only — multipart/form-data bodies are parsed by the platform (FormData API / busboy) and have their own error handling.

Test plan

Manual: send a POST with Next-Action: <valid-id> header and malformed body ([) → now returns 400 instead of 500.

Fixes #86945

  • Related issues linked using fixes #number
  • Errors have a helpful link attached, see contributing.md

Changed files

  • packages/next/src/server/app-render/action-handler.ts (modified, +32/-10)

PR #53: 🔒 Sentinel: [Security Fix] Add Zod input validation to quiz endpoints

Description (problem / solution / changelog)

🔒 Fix: Add input validation for quiz endpoints

🎯 What: Added Zod validation for request body payloads on /api/quiz/[id]/save and /api/quiz/[id]/start endpoints.

⚠️ Risk: These endpoints previously trusted raw input via request.json() which could lead to missing data logic errors or unexpected NoSQL-like JSON mutations when saving the payload to the database.

🛡️ Solution: Implemented z.object() schemas using zod to strictly validate enrollmentId and answers structures before processing the request, rejecting malformed data with a 400 Bad Request status.


PR created automatically by Jules for task 2489908183541928530 started by @thymosian

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

Summary by CodeRabbit

  • Bug Fixes
    • Improved input validation for quiz operations (save and start)
    • Enhanced error responses with clearer feedback when invalid data is submitted
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Changed files

  • .jules/bolt.md (modified, +8/-0)
  • package.json (modified, +2/-0)
  • src/app/actions/course.ts (modified, +9/-19)
  • src/app/actions/enrollment.ts (modified, +1/-1)
  • src/app/actions/organization.ts (modified, +2/-1)
  • src/app/actions/staff.ts (modified, +2/-0)
  • src/app/api/courses/[id]/learn/route.ts (modified, +21/-3)
  • src/app/api/quiz/[id]/save/route.ts (modified, +19/-3)
  • src/app/api/quiz/[id]/start/route.ts (modified, +13/-3)
  • src/app/api/quiz/[id]/submit/route.ts (modified, +23/-5)
  • src/app/dashboard/(main)/training/courses/[id]/results/[enrollmentId]/page.tsx (modified, +1/-0)
  • src/app/learn/[id]/page.tsx (modified, +9/-4)
  • src/components/courses/AdminLessonEditor.tsx (modified, +7/-4)
  • src/components/courses/CourseSlide.tsx (modified, +2/-1)
  • src/components/dashboard/courses/CourseWizard.tsx (modified, +15/-26)
  • src/components/dashboard/courses/steps/Step1Category.tsx (removed, +0/-51)
  • src/components/dashboard/courses/steps/Step5Review.tsx (modified, +2/-2)
  • src/components/dashboard/learner/WorkerWelcomeModal.tsx (modified, +3/-1)
  • src/components/dashboard/staff/StaffProfileClient.tsx (modified, +2/-0)
  • src/components/dashboard/training/QuizResults.tsx (modified, +3/-1)
  • src/components/ui/Button.module.css (modified, +17/-0)
  • src/components/ui/Button.tsx (modified, +9/-5)
  • src/components/ui/SlideContentFitter.tsx (modified, +2/-1)
  • src/lib/ai-client.test.ts (modified, +140/-16)
  • src/lib/documents/versioning.test.ts (added, +44/-0)
  • src/lib/file-parser.ts (modified, +0/-3)
  • src/lib/sanitize.ts (added, +13/-0)
  • src/types/course.ts (modified, +0/-3)

PR #185: feat: Add Jobs Report feature

Description (problem / solution / changelog)

This pull request introduces a new "Jobs Report" feature, including its database schema, backend API endpoints, and PDF/email generation logic. The main changes are the creation of new database tables and types for jobs reports and their lines, Prisma schema updates, and three new API endpoints for finalizing, generating a PDF, and emailing a jobs report to a driver.

Database and Schema Changes:

  • Added new JobsReport and JobsReportLine tables, including relevant fields, indexes, and foreign keys. Also introduced the JobsReportStatus enum and added a driverCharge field to JobsReportLine. [1] [2]
  • Updated the Prisma schema to define the new models (JobsReport, JobsReportLine), relationships, and the JobsReportStatus enum. Linked jobs reports to drivers. [1] [2] [3]

API Endpoints for Jobs Reports:

  • Added POST /api/jobs-report/[id]/finalize endpoint to finalize (lock) a jobs report, with validation to ensure only draft reports with at least one line can be finalized. (src/app/api/jobs-report/[id]/finalize/route.tsR1-R83)
  • Added GET /api/jobs-report/[id]/pdf endpoint to generate and return a PDF of a jobs report, including company branding and driver/job details. (src/app/api/jobs-report/[id]/pdf/route.tsR1-R162)
  • Added POST /api/jobs-report/[id]/email endpoint to generate a PDF and email it to the driver, with company settings and logo handling, and updates the report's sentAt timestamp. (src/app/api/jobs-report/[id]/email/route.tsR1-R324)
<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

Summary by CodeRabbit

  • New Features

    • Jobs Report system: create/manage weekly driver reports (by week or by driver), finalize/unfinalise reports, edit draft notes.
    • Email finalized reports to drivers and download PDF exports (with company logo and totals).
    • Filter/report list by driver, date range, week/month view, and status; track charged hours and driver charges per job.
  • Replaced Features

    • Payroll section replaced by Jobs Report management (navigation, permissions, and UI updated).
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Changed files

  • prisma/migrations/20260322095414_add_jobs_report/migration.sql (added, +61/-0)
  • prisma/migrations/20260322104310_add_driver_charge_to_jobs_report_line/migration.sql (added, +2/-0)
  • prisma/migrations/20260323082600_add_jobs_report_driver_week_unique/migration.sql (added, +2/-0)
  • prisma/schema.prisma (modified, +46/-0)
  • src/app/api/jobs-report/[id]/email/route.ts (added, +433/-0)
  • src/app/api/jobs-report/[id]/finalize/route.ts (added, +83/-0)
  • src/app/api/jobs-report/[id]/pdf/route.ts (added, +162/-0)
  • src/app/api/jobs-report/[id]/route.ts (added, +236/-0)
  • src/app/api/jobs-report/[id]/unfinalise/route.ts (added, +83/-0)
  • src/app/api/jobs-report/route.ts (added, +489/-0)
  • src/app/api/rcti/[id]/lines/[lineId]/route.ts (modified, +2/-2)
  • src/app/jobs-report/page.tsx (added, +1813/-0)
  • src/app/overview/page.tsx (modified, +3/-3)
  • src/app/payroll/page.tsx (removed, +0/-150)
  • src/app/rcti/page.tsx (modified, +71/-375)
  • src/components/brand/icon-logo.tsx (modified, +7/-7)
  • src/components/jobs-report/email-jobs-report-dialog.tsx (added, +345/-0)
  • src/components/jobs-report/jobs-report-pdf-template.tsx (added, +538/-0)
  • src/components/layout/app-sidebar.tsx (modified, +4/-4)
  • src/components/layout/page-controls.tsx (modified, +185/-17)
  • src/components/rcti/rcti-by-driver-view.tsx (modified, +4/-29)
  • src/components/shared/driver-filter-popover.tsx (added, +168/-0)
  • src/components/shared/sent-badge.tsx (added, +14/-0)
  • src/components/shared/status-badge.tsx (added, +21/-0)
  • src/components/shared/status-filter-popover.tsx (added, +125/-0)
  • src/components/shared/summary-stat-card.tsx (added, +26/-0)
  • src/lib/jobs-report-email-utils.ts (added, +199/-0)
  • src/lib/permissions-client.ts (modified, +69/-29)
  • src/lib/permissions.ts (modified, +108/-66)
  • src/lib/types.ts (modified, +34/-0)
  • src/middleware.ts (modified, +3/-3)
  • tests/fixtures/golden-data.ts (modified, +28/-28)
  • tests/unit/app/api/rcti/line-deletion.test.ts (modified, +5/-7)
  • tests/unit/components/auth/protected-route.test.tsx (modified, +7/-7)
  • tests/unit/components/layout/app-sidebar.test.tsx (modified, +11/-12)
  • tests/unit/contexts/permissions-context.test.tsx (modified, +2/-3)
  • tests/unit/middleware/role-checking.test.ts (modified, +4/-4)

PR #435: Harden API routes and signal server for launch readiness

Description (problem / solution / changelog)

  • Add per-socket rate limiting to signal server (join, signaling, voice state events)
  • Add peer-in-room validation for WebRTC offer/answer/ice-candidate relay
  • Add displayName/avatarUrl length validation on socket join
  • Wrap invites, webhooks, push, DM messages, tasks, and timeout routes in try/catch
  • Add null checks on all .single() query results
  • Add error checks on DELETE operations (push, DM messages)
  • Add input type validation for timeout duration_seconds and task PATCH fields
  • Add IP-based rate limiting (20/15min) to login endpoint
  • Replace raw error.message responses with generic error strings

https://claude.ai/code/session_01LP8a2BuPMQHhi9PtuqK7jh

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

Summary by CodeRabbit

  • New Features

    • Broad rate limiting added across APIs, media/search/gif/meme/sticker endpoints, webhooks, and realtime connections to reduce abuse.
    • Faster realtime reliability via session re-validation caching and stricter peer/membership checks.
  • Bug Fixes

    • Standardized 500 responses to avoid leaking internal errors.
    • Many routes now validate inputs, wrap handlers to return safe errors, and treat rate-limiter failures as non-blocking for resilience.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Changed files

  • apps/signal/src/index.ts (modified, +496/-132)
  • apps/web/app/api/appeals/[appealId]/route.ts (modified, +3/-3)
  • apps/web/app/api/appeals/route.ts (modified, +5/-5)
  • apps/web/app/api/apps/discover/route.ts (modified, +2/-2)
  • apps/web/app/api/attachments/[attachmentId]/download/route.ts (modified, +1/-1)
  • apps/web/app/api/auth/account/route.ts (modified, +1/-1)
  • apps/web/app/api/auth/login/route.ts (modified, +14/-1)
  • apps/web/app/api/auth/mfa/disable/route.ts (modified, +1/-1)
  • apps/web/app/api/auth/passkeys/login/options/route.ts (modified, +110/-50)
  • apps/web/app/api/auth/passkeys/login/verify/route.ts (modified, +131/-116)
  • apps/web/app/api/auth/recovery-codes/redeem/route.ts (modified, +100/-84)
  • apps/web/app/api/auth/recovery-codes/route.ts (modified, +2/-2)
  • apps/web/app/api/auth/sessions/[sessionId]/route.ts (modified, +2/-2)
  • apps/web/app/api/channels/[channelId]/docs/route.ts (modified, +3/-3)
  • apps/web/app/api/channels/[channelId]/permissions/route.ts (modified, +3/-3)
  • apps/web/app/api/channels/[channelId]/tasks/route.ts (modified, +4/-4)
  • apps/web/app/api/cron/thread-auto-archive/route.ts (modified, +1/-1)
  • apps/web/app/api/dm/channels/[channelId]/keys/route.ts (modified, +5/-5)
  • apps/web/app/api/dm/channels/[channelId]/members/route.ts (modified, +2/-2)
  • apps/web/app/api/dm/channels/[channelId]/messages/[messageId]/route.ts (modified, +46/-32)
  • apps/web/app/api/dm/channels/[channelId]/messages/route.ts (modified, +1/-1)
  • apps/web/app/api/dm/channels/[channelId]/route.ts (modified, +5/-5)
  • apps/web/app/api/dm/channels/route.ts (modified, +6/-6)
  • apps/web/app/api/dm/keys/device/route.ts (modified, +2/-2)
  • apps/web/app/api/dm/route.ts (modified, +2/-2)
  • apps/web/app/api/docs/[docId]/route.ts (modified, +2/-2)
  • apps/web/app/api/friends/route.ts (modified, +8/-8)
  • apps/web/app/api/friends/status/route.ts (modified, +1/-1)
  • apps/web/app/api/friends/suggestions/route.ts (modified, +2/-2)
  • apps/web/app/api/gif/search/route.ts (modified, +10/-0)
  • apps/web/app/api/gif/suggestions/route.ts (modified, +10/-0)
  • apps/web/app/api/gif/trending/route.ts (modified, +12/-2)
  • apps/web/app/api/invites/[code]/route.ts (modified, +59/-51)
  • apps/web/app/api/meme/search/route.ts (modified, +5/-1)
  • apps/web/app/api/meme/trending/route.ts (modified, +7/-3)
  • apps/web/app/api/messages/[messageId]/pin/route.ts (modified, +2/-2)
  • apps/web/app/api/messages/[messageId]/reactions/route.ts (modified, +4/-4)
  • apps/web/app/api/messages/[messageId]/task/route.ts (modified, +1/-1)
  • apps/web/app/api/messages/route.ts (modified, +1/-1)
  • apps/web/app/api/oembed/route.ts (modified, +13/-0)
  • apps/web/app/api/push/route.ts (modified, +51/-39)
  • apps/web/app/api/reports/route.ts (modified, +4/-4)
  • apps/web/app/api/search/route.ts (modified, +5/-0)
  • apps/web/app/api/servers/[serverId]/admin/activity/route.ts (modified, +1/-1)
  • apps/web/app/api/servers/[serverId]/appeal-templates/route.ts (modified, +2/-2)
  • apps/web/app/api/servers/[serverId]/appeals/route.ts (modified, +1/-1)
  • apps/web/app/api/servers/[serverId]/apps/giveaway/route.ts (modified, +1/-1)
  • apps/web/app/api/servers/[serverId]/apps/incidents/route.ts (modified, +2/-2)
  • apps/web/app/api/servers/[serverId]/apps/reminder/route.ts (modified, +4/-4)
  • apps/web/app/api/servers/[serverId]/apps/route.ts (modified, +3/-3)
  • apps/web/app/api/servers/[serverId]/apps/standup/route.ts (modified, +4/-4)
  • apps/web/app/api/servers/[serverId]/audit-log/route.ts (modified, +1/-1)
  • apps/web/app/api/servers/[serverId]/automod/route.ts (modified, +2/-2)
  • apps/web/app/api/servers/[serverId]/bans/route.ts (modified, +4/-4)
  • apps/web/app/api/servers/[serverId]/channels/[channelId]/messages/[messageId]/route.ts (modified, +2/-2)
  • apps/web/app/api/servers/[serverId]/channels/route.ts (modified, +3/-3)
  • apps/web/app/api/servers/[serverId]/emojis/route.ts (modified, +5/-5)
  • apps/web/app/api/servers/[serverId]/events/[eventId]/route.ts (modified, +3/-3)
  • apps/web/app/api/servers/[serverId]/events/[eventId]/rsvp/route.ts (modified, +1/-1)
  • apps/web/app/api/servers/[serverId]/events/ical/route.ts (modified, +1/-1)
  • apps/web/app/api/servers/[serverId]/events/route.ts (modified, +2/-2)
  • apps/web/app/api/servers/[serverId]/invites/route.ts (modified, +3/-3)
  • apps/web/app/api/servers/[serverId]/members/[userId]/roles/route.ts (modified, +3/-3)
  • apps/web/app/api/servers/[serverId]/members/[userId]/route.ts (modified, +1/-1)
  • apps/web/app/api/servers/[serverId]/members/[userId]/timeout/route.ts (modified, +88/-76)
  • apps/web/app/api/servers/[serverId]/members/route.ts (modified, +3/-3)
  • apps/web/app/api/servers/[serverId]/moderation/timeline/route.ts (modified, +1/-1)
  • apps/web/app/api/servers/[serverId]/roles/reorder/route.ts (modified, +1/-1)
  • apps/web/app/api/servers/[serverId]/roles/route.ts (modified, +2/-2)
  • apps/web/app/api/servers/[serverId]/screening/accept/route.ts (modified, +1/-1)
  • apps/web/app/api/servers/[serverId]/screening/route.ts (modified, +3/-3)
  • apps/web/app/api/servers/[serverId]/voice-intelligence-policy/route.ts (modified, +1/-1)
  • apps/web/app/api/servers/[serverId]/webhooks/route.ts (modified, +3/-3)
  • apps/web/app/api/servers/discover/route.ts (modified, +10/-9)
  • apps/web/app/api/sticker/search/route.ts (modified, +5/-1)
  • apps/web/app/api/sticker/trending/route.ts (modified, +7/-3)
  • apps/web/app/api/tasks/[taskId]/route.ts (modified, +44/-36)
  • apps/web/app/api/threads/[threadId]/members/route.ts (modified, +3/-3)
  • apps/web/app/api/threads/[threadId]/messages/route.ts (modified, +1/-1)
  • apps/web/app/api/threads/[threadId]/route.ts (modified, +3/-3)
  • apps/web/app/api/threads/counts/route.ts (modified, +2/-2)
  • apps/web/app/api/threads/route.ts (modified, +3/-3)
  • apps/web/app/api/users/activity/route.ts (modified, +2/-2)
  • apps/web/app/api/voice/sessions/[id]/consent/route.ts (modified, +1/-1)
  • apps/web/app/api/voice/sessions/[id]/subtitle-preferences/route.ts (modified, +1/-1)
  • apps/web/app/api/voice/sessions/[id]/summary/route.ts (modified, +1/-1)
  • apps/web/app/api/voice/sessions/[id]/transcript/route.ts (modified, +2/-2)
  • apps/web/app/api/voice/sessions/route.ts (modified, +1/-1)
  • apps/web/app/api/webhooks/[token]/route.ts (modified, +150/-86)
  • apps/web/lib/attachment-validation.ts (modified, +2/-1)
  • apps/web/lib/auth/passkeys.ts (modified, +2/-0)
  • apps/web/lib/utils/api-helpers.ts (modified, +19/-2)
  • packages/shared/src/index.ts (modified, +29/-0)

PR #92051: fix: return 400 instead of 500 for malformed server action request body

Description (problem / solution / changelog)

Problem

Fixes #86945

When a malformed request body is sent to a server action endpoint, decodeReply() throws a SyntaxError that bubbles up to the generic catch handler, returning HTTP 500 Internal Server Error. This should be HTTP 400 Bad Request since the error is in the client's request, not the server.

This is being actively exploited by vulnerability scanners (CVE-2025-55182 context) that probe server action endpoints with malformed payloads, generating noisy 500 alerts in production monitoring.

Root Cause

In action-handler.ts, the two text body paths (edge runtime at line 855, node runtime at line 1064) call decodeReply() on the raw body string without catching SyntaxError. When the body is malformed JSON (e.g. [ instead of []), the error propagates to the outer catch at line 1256 which sets statusCode = 500.

Fix

Wrap both decodeReply() call sites in try/catch for SyntaxError:

  • If SyntaxError: set res.statusCode = 400 and return (client error, not server error)
  • All other errors: re-throw to preserve existing behavior

Applied to both the edge/web runtime path and the node runtime path identically.

Note on PR #90109

PR #90109 addresses the same issue but has been open with 0 reviews since February. This PR takes a similar approach with a cleaner implementation.

Changed files

  • packages/next/src/server/app-render/action-handler.ts (modified, +30/-10)

Code Example

NextJS version: 15.3.6 & 16.0.1.
RAW_BUFFERClick to expand / collapse

Link to the code that reproduces this issue

https://github.com/artemzhdev/next-js-server-action-500

To Reproduce

  1. Run your application and trigger any server action from the client (browser)
  2. Find POST request related to the server action in the NETWORK tab and copy it as curl
  3. Slightly modify it to make the request body to contain any mailformed JSON (like "["). You can just replace " --data-raw '[]'" with " --data-raw '[' -v" at the end.
  4. Try to run this request using terminal

Current vs. Expected behavior

Current behavior: HTTP 500. Expected behavior: HTTP 400 (Bad Request)

Provide environment information

NextJS version: 15.3.6 & 16.0.1.

Which area(s) are affected? (Select all that apply)

Server Actions

Which stage(s) are affected? (Select all that apply)

next start (local)

Additional context

My site is being scanned by some vulnerability scanner. It's not an issue. But I use monitoring to track all 5xx errors. Such responses are tracked as high priority bugs. Unfortunately, this happens for bad requests produced by vulnerability scanners. My server action code is not even called. And I cannot find a way to catch it in the middleware.

extent analysis

TL;DR

  • Modify the NextJS server action to handle malformed JSON requests and return a 400 Bad Request response instead of a 500 Internal Server Error.

Guidance

  • Investigate the NextJS error handling mechanism to catch and handle malformed JSON requests before they reach the server action code.
  • Check if there are any existing middleware or libraries that can be used to validate and sanitize incoming request data.
  • Consider adding a custom error handler to catch and handle JSON parsing errors, returning a 400 Bad Request response.
  • Review the NextJS documentation for any built-in features or configuration options that can help handle malformed requests.

Example

  • No example code is provided as it would require more information about the specific NextJS setup and server action code.

Notes

  • The issue seems to be related to the NextJS framework's built-in error handling mechanism, which returns a 500 Internal Server Error for malformed JSON requests.
  • The provided information suggests that the server action code is not being called, indicating that the error occurs before the code is executed.

Recommendation

  • Apply workaround: Implement a custom error handler or middleware to catch and handle malformed JSON requests, returning a 400 Bad Request response, as this will allow for more control over error handling and provide a more accurate response to clients.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING