SECURITY — gate accounting-settings PATCH behind new accounting:config permission
The hole¶
PATCH /api/accounting/settings (pages/api/accounting/settings.ts) was only session?.user auth-checked — no role/permission gate. The accounting PAGE allows auditing_accountant + bookkeeper (DEFAULT_PAGE_RULES.accounting), so any bookkeeper could silently rewrite the statutory entityCalendar (incorporationDate / yearEndMonthDay / explicitPeriodEnds) that drives every HK profits-tax basis period via listBasisPeriods. Surfaced during the fiscal-period-UX investigation.
Fix (origin/main e89f0a5c)¶
- New permission
accounting:configin thePermissionunion, granted to super_admin / admin / auditing_accountant in bothROLE_PERMISSIONS(runtime enforcement) andinitializeSystemRoles(seed). NOT bookkeeper / project_admin. - PATCH branch now checks
canPerform(role, status, 'accounting:config')→ 403 on failure. GET stays open to any page-eligible user. - Extended all three exhaustive
Record<Permission,string>label maps (lib/rbac/types.ts, lib/rbac/permissions.ts, components/settings/SettingsApp.tsx) + the admin permission groups so the new permission renders in the role editor. - Regression test
__tests__/lib/rbac/accountingConfigPermission.test.tslocks the grant matrix.
Verified¶
tsc clean; 0 net-new lint (27 pre-existing any/unused in SettingsApp.tsx); regression test 2/2; end-to-end under RBAC on → bookkeeper/project_admin DENY, super_admin/admin/auditing_accountant ALLOW. Enforcement uses the hardcoded ROLE_PERMISSIONS map → effective on deploy, no Firestore re-seed needed.
Links¶
- Prereq for T-058 (fiscal-period selector + Fiscal-Year config tab) — the config tab will reuse
accounting:config.
Log¶
- 2026-06-16 DONE. Shipped to origin/main e89f0a5c. Closes the ungated-PATCH gap found while investigating the fiscal-period UX.