Skip to content

Keep raw database error text out of production 500 responses #413

Description

@HMarzban

Summary

handlePrismaError wraps the original error message in a DatabaseError, and getErrorResponse returns that message to the client in every environment. Only details is hidden outside development. So production 500 responses can carry Prisma messages, constraint names, host and port text, and non-Prisma messages such as a Supabase RPC error.

  • Severity: Low (information leak)
  • Area: apps/hocuspocus.server
  • Source: security review of 2026-10-06

Where

  • handlePrismaError: apps/hocuspocus.server/src/lib/errors.ts:94-101 puts error.message into DatabaseError (an AppError).
  • getErrorResponse: apps/hocuspocus.server/src/lib/errors.ts:104-115 returns AppError.message in every environment.
  • Non-Prisma example: permanentlyDeleteDocument (apps/hocuspocus.server/src/api/services/documents.service.ts:950-953) passes "Footprint purge RPC failed: ".
  • The house envelope lives in apps/hocuspocus.server/src/http/envelope.ts (ok / fail); keep using it (see apps/hocuspocus.server/CLAUDE.md §HTTP Modules).

Fix plan

  1. In handlePrismaError, give DatabaseError a fixed public message, for example "A database error occurred.", and keep the original as cause.
  2. In getErrorResponse, outside development, return the fixed message for DatabaseError and for any non-AppError. Keep the specific message for 4xx AppErrors that are written for users (validation, not found, forbidden).
  3. Log the original message and stack with the requestId on the server, so support can still trace it.
  4. Check callers that build messages from upstream errors (grep RPC failed:) and move the upstream text into cause.

Acceptance criteria

  • In production mode, a forced Prisma error returns a 500 with the fixed message and a requestId, and no driver text.
  • The server log has the full original error for that requestId.
  • 4xx validation messages are unchanged.
  • Existing tests in apps/hocuspocus.server pass.

Related


Generated by Claude Code

Activity

  1. added theissue type on Oct 6, 2026
  2. changed the title [-]Raw database and Prisma error text appears in production 500 responses[/-] [+]Keep raw database error text out of production 500 responses[/+] on Oct 6, 2026
  3. HMarzban commented on Oct 6, 2026

    @HMarzban
    CollaboratorAuthor

    Status: fixed in 355502b on claude/youthful-lovelace-3nvsc6, at the response layer instead of in handlePrismaError. Not merged or deployed yet.

    What shipped: outside development, getErrorResponse in apps/hocuspocus.server/src/lib/errors.ts returns Internal server error for every AppError with statusCode >= 500. The code is unchanged. A non-AppError already got that message. 4xx messages are unchanged. apps/hocuspocus.server/API.md documents the rule, and tests/integration/documents.test.ts asserts it.

    Differences from the plan:

    • Steps 1 and 4 were not done. handlePrismaError and the RPC failed: callers still put upstream text into the message. The response layer now hides it, so moving it into cause is not needed.
    • Step 3 was already in place. app.onError in src/index.ts logs the original error with requestId. handleError in documents.controller.ts logs it without a requestId field; the request log line for the same request has it.
    • The response body has no requestId. Hono's requestId() middleware sends it in the X-Request-Id response header.
    • Handlers with their own error shape do not call getErrorResponse, so this fix does not cover them. The OpenAPI notes list them: email handlers, admin middleware, media-upload guards and link metadata.
    • Module handlers under src/modules/ call fail() from src/http/envelope.ts, which does not mask. Their 5xx messages are fixed strings, such as the persist-failed text, so they carry no driver text. The API.md line "every 5xx error.message is Internal server error" covers getErrorResponse only.

    Verify: cd apps/hocuspocus.server && bun test tests/integration/documents.test.ts.


    Generated by Claude Code

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

    DevOpsSecuritySecurity, access control, and data exposurebugSomething isn't working

    Type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions