From 2682d70dff63242b05fbdc8d1dac5624e68edb32 Mon Sep 17 00:00:00 2001 From: Dustin Healy <54083382+dustinhealy@users.noreply.github.com> Date: Thu, 14 May 2026 11:46:43 -0700 Subject: [PATCH] =?UTF-8?q?=F0=9F=9B=A1=EF=B8=8F=20feat:=20Gate=20audit-lo?= =?UTF-8?q?g=20routes=20on=20a=20dedicated=20`READ=5FAUDIT=5FLOG`=20capabi?= =?UTF-8?q?lity?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The audit-log routes were gated on `ACCESS_ADMIN`, which conflates "can log into the admin panel" with "can see who granted what to whom." Anyone with `ACCESS_ADMIN + READ_CONFIGS` (a config reviewer with no people-management authority) could read the grant history of every user, group, and role — information they have no need to know. `READ_AUDIT_LOG` ('read:audit_log') is now an explicit, separately grantable read capability with no MANAGE counterpart, matching the append-only nature of the collection. `seedSystemGrants` iterates `Object.values(SystemCapabilities)` so existing ADMIN-role seeds pick it up automatically on next startup. This also makes an "auditor" persona possible: hold `ACCESS_ADMIN + READ_AUDIT_LOG` without any MANAGE_* grants and you can review history without modifying anything. --- api/server/routes/admin/audit.js | 4 ++-- packages/data-schemas/src/admin/capabilities.ts | 12 +++++++++++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/api/server/routes/admin/audit.js b/api/server/routes/admin/audit.js index b21287e7ac..e94867631e 100644 --- a/api/server/routes/admin/audit.js +++ b/api/server/routes/admin/audit.js @@ -7,7 +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({ listAuditLogPage: db.listAuditLogPage, @@ -15,7 +15,7 @@ const handlers = createAdminAuditLogHandlers({ streamAuditLogEntries: db.streamAuditLogEntries, }); -router.use(requireJwtAuth, requireAdminAccess); +router.use(requireJwtAuth, requireAuditLogRead); router.get('/', handlers.listAuditLog); router.get('/export.csv', handlers.exportAuditLogCsv); diff --git a/packages/data-schemas/src/admin/capabilities.ts b/packages/data-schemas/src/admin/capabilities.ts index 7a966c0062..33a6c2ecf9 100644 --- a/packages/data-schemas/src/admin/capabilities.ts +++ b/packages/data-schemas/src/admin/capabilities.ts @@ -42,6 +42,12 @@ export const SystemCapabilities = { /** Reserved — not yet enforced by any middleware. */ READ_ASSISTANTS: 'read:assistants', MANAGE_ASSISTANTS: 'manage:assistants', + /** + * Required to list, view, and CSV-export the SystemGrant audit log. Append-only + * by design, so there is no MANAGE counterpart — modifying historical entries + * would defeat the forensic guarantee. + */ + READ_AUDIT_LOG: 'read:audit_log', } as const; /** @@ -224,6 +230,10 @@ export const CAPABILITY_CATEGORIES: CapabilityCategory[] = [ { key: 'system', labelKey: 'com_cap_cat_system', - capabilities: [SystemCapabilities.ACCESS_ADMIN, SystemCapabilities.READ_USAGE], + capabilities: [ + SystemCapabilities.ACCESS_ADMIN, + SystemCapabilities.READ_USAGE, + SystemCapabilities.READ_AUDIT_LOG, + ], }, ];