From 956245b064b55ffff702fb481895ee622f638f85 Mon Sep 17 00:00:00 2001 From: Dustin Healy <54083382+dustinhealy@users.noreply.github.com> Date: Mon, 1 Jun 2026 22:01:46 -0700 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=92=20fix:=20require=20ACCESS=5FADMIN?= =?UTF-8?q?=20on=20audit-log=20routes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every other admin router (config, grants, users, roles, groups, auth) enforces requireJwtAuth followed by requireCapability(ACCESS_ADMIN) before any feature-specific capability check. The audit-log router only required READ_AUDIT_LOG, which is independent of ACCESS_ADMIN in CapabilityImplications, so a role delegated only READ_AUDIT_LOG without ACCESS_ADMIN could read or CSV-export the audit trail and bypass the admin boundary. Aligned the middleware chain with the rest of the admin surface so ACCESS_ADMIN gates entry and READ_AUDIT_LOG gates the feature within it. --- api/server/routes/admin/audit.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/api/server/routes/admin/audit.js b/api/server/routes/admin/audit.js index 4e03023e1e..588271463b 100644 --- a/api/server/routes/admin/audit.js +++ b/api/server/routes/admin/audit.js @@ -7,6 +7,7 @@ const db = require('~/models'); const router = express.Router(); +const requireAdminAccess = requireCapability(SystemCapabilities.ACCESS_ADMIN); const requireAuditLogRead = requireCapability(SystemCapabilities.READ_AUDIT_LOG); const handlers = createAdminAuditLogHandlers({ @@ -15,7 +16,14 @@ const handlers = createAdminAuditLogHandlers({ streamAuditLogEntries: db.streamAuditLogEntries, }); -router.use(requireJwtAuth, requireAuditLogRead); +/** + * `ACCESS_ADMIN` gates entry to the admin surface; `READ_AUDIT_LOG` then gates + * this specific feature within that surface. The two capabilities are + * independent in `CapabilityImplications`, so a role delegated only + * `READ_AUDIT_LOG` without `ACCESS_ADMIN` would otherwise bypass the admin + * boundary on this router — every other admin router enforces the same pair. + */ +router.use(requireJwtAuth, requireAdminAccess, requireAuditLogRead); router.get('/', handlers.listAuditLog); /** `/export.csv` MUST precede `/:id` so it isn't matched as `{ id: 'export.csv' }`. */