Skip to content

getBody() returns 500 instead of 400 for malformed JSON request bodies #107

@allandelmare

Description

@allandelmare

Description

When a request with malformed JSON is sent to a better-call endpoint, the server returns 500 Internal Server Error instead of 400 Bad Request. This occurs because getBody() in packages/better-call/src/utils.ts does not catch SyntaxError thrown by request.json().

Current Behavior

  • Malformed JSON (e.g., invalid escape sequences like \x, \z) triggers SyntaxError in request.json()
  • Error bubbles up to router's catch-all handler in router.ts
  • Returns 500 Internal Server Error with stack trace logged to console
  • Makes it impossible to distinguish client input errors from actual server errors in monitoring/logs

Expected Behavior

  • Malformed JSON should return 400 Bad Request
  • Error message should clearly indicate invalid JSON
  • No stack trace should be logged for client-side input errors

Reproduction

curl -X POST https://example.com/api/auth/sign-up/email \
  -H "Content-Type: application/json" \
  -d '{"email": "test@example.com\x", "password": "test1234"}'

Current response: 500 Internal Server Error
Expected response: 400 Bad Request

Root Cause

In packages/better-call/src/utils.ts, the getBody() function:

if (jsonContentTypeRegex.test(normalizedContentType)) {
  return await request.json(); // ← NO TRY/CATCH
}

The outer catch in router.ts treats this as a server error because it's not an APIError:

// For non-APIError (including JSON.parse SyntaxError):
console.error(`# SERVER_ERROR: `, error);
return new Response(null, { status: 500, statusText: "Internal Server Error" });

Proposed Solution

Wrap request.json() in try/catch and throw APIError for malformed input:

if (jsonContentTypeRegex.test(normalizedContentType)) {
  try {
    return await request.json();
  } catch (error) {
    throw new APIError(400, {
      message: error instanceof SyntaxError 
        ? `Invalid JSON: ${error.message}`
        : "Malformed request body",
      code: "INVALID_JSON",
    });
  }
}

This follows the existing pattern — getBody() already throws APIError(415) for unsupported content types.

Environment

  • better-call version: via better-auth ^1.4.18
  • Runtime: Node.js (Next.js API routes)
  • Discovery: Automated security assessment (malformed JSON payloads)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions