From 652952379b5d1d8350dc17096b3125919add4884 Mon Sep 17 00:00:00 2001 From: shuaiplus <2327005759@qq.com> Date: Sun, 26 Jul 2026 15:48:56 +0800 Subject: [PATCH] fix: stop accepting backup blob credentials from the URL --- src/handlers/backup.ts | 21 ++++++++------------- src/router-admin-backup.ts | 19 +++++++++++++++++-- 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/src/handlers/backup.ts b/src/handlers/backup.ts index 407d444..2b187ae 100644 --- a/src/handlers/backup.ts +++ b/src/handlers/backup.ts @@ -1247,19 +1247,14 @@ export async function handleDownloadAdminBackupAttachment(request: Request, env: if (!isAdmin(actorUser)) return errorResponse('Forbidden', 403); try { - const url = new URL(request.url); - let input: { blobName?: unknown; masterPasswordHash?: unknown } = {}; - if (request.method === 'POST') { - try { - input = await request.json<{ blobName?: unknown; masterPasswordHash?: unknown }>(); - } catch { - return errorResponse('Backup attachment download payload is invalid', 400); - } - } else { - input = { - blobName: url.searchParams.get('blobName') || '', - masterPasswordHash: url.searchParams.get('masterPasswordHash') || '', - }; + // Read the request body only. Accepting these fields from the query string + // would put the master-password authentication hash in the URL, where it is + // captured by request logs, browser history and Referer headers. + let input: { blobName?: unknown; masterPasswordHash?: unknown }; + try { + input = await request.json<{ blobName?: unknown; masterPasswordHash?: unknown }>(); + } catch { + return errorResponse('Backup attachment download payload is invalid', 400); } const verificationError = await requireBackupUserVerification( diff --git a/src/router-admin-backup.ts b/src/router-admin-backup.ts index d076fe4..54d4af4 100644 --- a/src/router-admin-backup.ts +++ b/src/router-admin-backup.ts @@ -14,6 +14,7 @@ import { handleRunAdminConfiguredBackup, handleUpdateAdminBackupSettings, } from './handlers/backup'; +import { errorResponse } from './utils/response'; export async function handleAdminBackupRoute( request: Request, @@ -26,8 +27,22 @@ export async function handleAdminBackupRoute( return handleAdminExportBackup(request, env, actorUser); } - if (path === '/api/admin/backup/blob' && (method === 'GET' || method === 'POST')) { - return handleDownloadAdminBackupAttachment(request, env, actorUser); + if (path === '/api/admin/backup/blob') { + // POST only: this endpoint requires master-password verification, and a GET + // could only carry that credential in the query string, where it would leak + // into request logs, proxy logs, browser history and Referer headers. + // The credential is the same value clients send to /identity/connect/token, + // so a leaked copy is enough to sign in as this admin. + if (method === 'POST') { + return handleDownloadAdminBackupAttachment(request, env, actorUser); + } + if (method === 'GET') { + return errorResponse( + 'Use POST with a JSON body for this endpoint. Credentials must not be sent in the URL.', + 405 + ); + } + return null; } if (path === '/api/admin/backup/settings') {