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
Each browser receives a server-selected, independently encrypted organization containing synthetic data. A separate host-only claim returns that browser to the same sandbox after logout or session expiry without sharing its data. Sessions expire after 15 minutes idle or one hour total; the claim and sandbox are invalidated only by the 04:15 America/Toronto nightly reconciliation. 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 a sandbox organization. The demo owner has the same in-app accounting permissions as a standard owner, including a clearly labeled sandbox-only privileged-action simulation. It still cannot administer key recovery, send real email, connect 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.