Skip to content

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:config in the Permission union, granted to super_admin / admin / auditing_accountant in both ROLE_PERMISSIONS (runtime enforcement) and initializeSystemRoles (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.ts locks 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.

  • 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.