From fdac9e251780a9a47b3da257cfade6f27f42e37b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Sun, 10 May 2026 00:23:33 +0200 Subject: [PATCH] fix(admin): scrub raw Prisma error from restore-failed response (v1.4.15 H1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The catch block around the restore transaction echoed the raw error message back to the admin client (`Restore transaction failed: `). Even on an admin-only endpoint that leaks column names, constraint names and query fragments to anyone with admin access — lower than CRITICAL but unnecessary surface for a future supply-chain or impersonation scenario. Response now returns a stable "Restore failed" 500. The verbose text still lands in the audit-log row (admin-readable, forensic) and in the Wide Event under `meta.restoreFailReason` (operator-readable), so root-cause investigation is unaffected. --- src/app/api/admin/backups/[id]/restore/route.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/app/api/admin/backups/[id]/restore/route.ts b/src/app/api/admin/backups/[id]/restore/route.ts index 2c7e17211..f95466b2c 100644 --- a/src/app/api/admin/backups/[id]/restore/route.ts +++ b/src/app/api/admin/backups/[id]/restore/route.ts @@ -376,6 +376,13 @@ const handler = apiHandler( }; }); } catch (err) { + // Scrub the raw Prisma / driver error from the wire response — + // even on an admin endpoint, leaking column names, constraint + // names or query fragments lowers the cost of a future supply- + // chain attack against this surface. The verbose text still + // lands in the audit row (admin-readable) and the Wide Event + // (operator-readable), so root-cause investigation is unaffected. + const verbose = err instanceof Error ? err.message : String(err); await auditLog("admin.backups.restore.failed", { userId: admin.id, ipAddress: getClientIp(request), @@ -383,13 +390,11 @@ const handler = apiHandler( backupId: id, ownerId, reason: "transaction_failed", - message: err instanceof Error ? err.message : String(err), + message: verbose, }, }); - return apiError( - `Restore transaction failed: ${err instanceof Error ? err.message : "unknown"}`, - 500, - ); + annotate({ meta: { restoreFailReason: verbose } }); + return apiError("Restore failed", 500); } const summary = summarizeBackup(payload);