diff options
| author | Mohamed Bassem <me@mbassem.com> | 2026-02-04 13:44:39 +0000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-02-04 13:44:39 +0000 |
| commit | 93ad2e2001eb7070df50b0ab51dfd3e1ab377629 (patch) | |
| tree | 26cefb449ec3769d1b19569a8c100d49fc7f8cc1 | |
| parent | d9329e89adc6ca579a299d42d115c850fc9305dd (diff) | |
| download | karakeep-93ad2e2001eb7070df50b0ab51dfd3e1ab377629.tar.zst | |
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 <noreply@anthropic.com>
| -rw-r--r-- | apps/workers/workers/importWorker.ts | 27 |
1 files changed, 26 insertions, 1 deletions
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<void> { 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)); |
