Identity and sessions
Browser sessions use random 256-bit opaque tokens. Only a SHA-256 digest is stored in PostgreSQL. Sessions have idle and absolute expiry, are revocable immediately, and are rechecked against active organization membership.
Encryption boundary
Organization data encryption keys are wrapped by a root key mounted as a server secret. User passwords do not wrap organization keys, so password recovery can revoke sessions and replace a credential without erasing accounting records.
Accounting integrity
Tenant context is set inside database transactions. Posted records and source events are guarded against ordinary updates or deletion. Period states, role permissions, and source-module ownership are designed as server-side controls.
Public demo
Every browser opens a separate short-lived session in the same encrypted synthetic organization, so visitors see one another’s changes. Sessions expire after 15 minutes idle or one hour total. At 04:15 America/Toronto, the reset process fences new work, revokes every demo session, deletes the shared organization’s mutable accounting data, reseeds it, verifies the baseline, and reopens access. Speculative browser requests cannot create a demo session.
Current release boundary
DEMO_LOGIN_ENABLED=true and DEMO_WRITES_ENABLED=true permit accounting changes only through a live demo-link session in the shared PUBLIC_DEMO organization. The demo accountant has the same in-app accounting permissions as a standard owner, including a clearly labeled demo-only privileged-action simulation. It still cannot administer key recovery, send real email, connect external banks, execute payments, file taxes, publish webhooks, or issue public MCP credentials.
Report a vulnerability
Use the repository’s private GitHub security advisory form. Do not include real customer or credential data in a public issue.