From 93ad2e2001eb7070df50b0ab51dfd3e1ab377629 Mon Sep 17 00:00:00 2001 From: Mohamed Bassem Date: Wed, 4 Feb 2026 13:44:39 +0000 Subject: fix(import): sanitize error messages to prevent backend detail leakage (#2455) The catch block in processOneBookmark was storing raw error strings via String(error) in the resultReason field, which is exposed to users through the getImportSessionResults tRPC route. This could leak internal details like database constraint errors, file paths, stack traces, or connection strings. Replace String(error) with getSafeErrorMessage() that only allows through: - TRPCError client errors (designed to be user-facing) - Known safe validation messages from the import worker - A generic fallback for all other errors The full error is still logged server-side for debugging. https://claude.ai/code/session_01F1NHE9dqio5LJ177vmSCvt Co-authored-by: Claude --- apps/workers/workers/importWorker.ts | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/apps/workers/workers/importWorker.ts b/apps/workers/workers/importWorker.ts index 2acbdcc1..047a8e39 100644 --- a/apps/workers/workers/importWorker.ts +++ b/apps/workers/workers/importWorker.ts @@ -10,6 +10,7 @@ import { or, } from "drizzle-orm"; import { Counter, Gauge, Histogram } from "prom-client"; +import { TRPCError } from "@trpc/server"; import { buildImpersonatingTRPCClient } from "trpc"; import { db } from "@karakeep/db"; @@ -71,6 +72,30 @@ function sleep(ms: number): Promise { return new Promise((resolve) => setTimeout(resolve, ms)); } +/** + * Extract a safe, user-facing error message from an error. + * Avoids leaking internal details like database errors, stack traces, or file paths. + */ +function getSafeErrorMessage(error: unknown): string { + // TRPCError client errors are designed to be user-facing + if (error instanceof TRPCError && error.code !== "INTERNAL_SERVER_ERROR") { + return error.message; + } + + // Known safe validation errors thrown within the import worker + if (error instanceof Error) { + const safeMessages = [ + "URL is required for link bookmarks", + "Content is required for text bookmarks", + ]; + if (safeMessages.includes(error.message)) { + return error.message; + } + } + + return "An unexpected error occurred while processing the bookmark"; +} + export class ImportWorker { private running = false; private pollIntervalMs = 5000; @@ -441,7 +466,7 @@ export class ImportWorker { .set({ status: "failed", result: "rejected", - resultReason: String(error), + resultReason: getSafeErrorMessage(error), completedAt: new Date(), }) .where(eq(importStagingBookmarks.id, staged.id)); -- cgit v1.2.3-70-g09d2