diff --git a/packages/api/src/admin/auditLog.spec.ts b/packages/api/src/admin/auditLog.spec.ts index 1bf55aba42..c85b6a5870 100644 --- a/packages/api/src/admin/auditLog.spec.ts +++ b/packages/api/src/admin/auditLog.spec.ts @@ -423,7 +423,7 @@ describe('createAdminAuditLogHandlers', () => { await handlers.exportAuditLogCsv(ctx.req, ctx.res); - expect(ctx.chunks[0]).toBe(''); + expect(ctx.chunks[0]).toBe('\uFEFF'); expect(ctx.chunks).toContain('\r\n'); expect(ctx.endCalled()).toBe(true); }); diff --git a/packages/api/src/admin/auditLog.ts b/packages/api/src/admin/auditLog.ts index 40d1ccea48..6aa766a81b 100644 --- a/packages/api/src/admin/auditLog.ts +++ b/packages/api/src/admin/auditLog.ts @@ -15,7 +15,10 @@ import type { Response } from 'express'; import type { ServerRequest } from '~/types/http'; const FORMULA_PREFIX = /^[=+\-@\t\r]/; -const CSV_BOM = ''; +/** UTF-8 BOM, written first so Excel recognizes the encoding on import. Spelled + * as a Unicode escape so readers can see the constant in editors that hide + * the zero-width glyph. */ +const CSV_BOM = '\uFEFF'; const VALID_ACTIONS = new Set(AUDIT_ACTIONS); const VALID_PRINCIPAL_TYPES = new Set(Object.values(PrincipalType)); diff --git a/packages/api/src/admin/grants.ts b/packages/api/src/admin/grants.ts index b85cf200eb..1119096f8c 100644 --- a/packages/api/src/admin/grants.ts +++ b/packages/api/src/admin/grants.ts @@ -450,7 +450,7 @@ export function createAdminGrantsHandlers(deps: AdminGrantsDeps): { // Only emit the audit entry when a grant document was actually deleted — // a no-op revoke (the grant never existed) must not appear in the audit // trail or the forensic record becomes misleading. - if (revokeResult?.deletedCount > 0) { + if (revokeResult.deletedCount > 0) { await emitAudit({ action: 'grant_removed', caller,