From 1cf82d81a7935ca106b36521cbf02f63cd2e14b6 Mon Sep 17 00:00:00 2001 From: Paul Nothaft <53005142+the-luap@users.noreply.github.com> Date: Thu, 16 Jul 2026 10:55:10 +0200 Subject: [PATCH] fix(security): preserve current admin on .picpeak restore (GHSA-qxfx-4493-4v8f) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit adminAuth populates req.admin, not req.user, so currentAdminId was always undefined in the /api/admin/picpeak/import handler. reinjectCurrentAdmin() then had no account to preserve and the admin_users table was fully replaced by the uploaded backup — a crafted .picpeak let any admin with backup.restore take over every admin account (critical). One-line fix: pass req.admin.id. Closes GHSA-qxfx-4493-4v8f and its duplicate GHSA-pjp6-jcrj-3cr5. --- backend/src/routes/adminBackup.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/backend/src/routes/adminBackup.js b/backend/src/routes/adminBackup.js index 67d1479c..b1ee0e97 100644 --- a/backend/src/routes/adminBackup.js +++ b/backend/src/routes/adminBackup.js @@ -178,7 +178,11 @@ router.post('/picpeak/import', adminAuth, requirePermission('backup.restore'), p const picpeakPath = req.file.path; try { const { importFromPicpeak } = require('../services/picpeakImportService'); - const result = await importFromPicpeak({ picpeakPath, currentAdminId: req.user && req.user.id }); + // adminAuth populates req.admin, not req.user. Passing req.user.id here + // left currentAdminId undefined, so reinjectCurrentAdmin() had no account + // to preserve and the admin_users table was fully replaced by the backup — + // letting a crafted .picpeak take over every admin account (GHSA-qxfx-4493-4v8f). + const result = await importFromPicpeak({ picpeakPath, currentAdminId: req.admin && req.admin.id }); res.json({ success: true, tables: result.tables,