Compare commits

..
1 Commits
Author SHA1 Message Date
shuaiplus 652952379b fix: stop accepting backup blob credentials from the URL 2026-07-26 16:00:39 +08:00
2 changed files with 25 additions and 15 deletions
+4 -9
View File
@@ -1247,20 +1247,15 @@ 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') {
// 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);
}
} else {
input = {
blobName: url.searchParams.get('blobName') || '',
masterPasswordHash: url.searchParams.get('masterPasswordHash') || '',
};
}
const verificationError = await requireBackupUserVerification(
actorUser,
+16 -1
View File
@@ -14,6 +14,7 @@ import {
handleRunAdminConfiguredBackup,
handleUpdateAdminBackupSettings,
} from './handlers/backup';
import { errorResponse } from './utils/response';
export async function handleAdminBackupRoute(
request: Request,
@@ -26,9 +27,23 @@ export async function handleAdminBackupRoute(
return handleAdminExportBackup(request, env, actorUser);
}
if (path === '/api/admin/backup/blob' && (method === 'GET' || method === 'POST')) {
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') {
if (method === 'GET') return handleGetAdminBackupSettings(request, env, actorUser);