Access control policy
Last updated 2026-09-25. Owner: founder. Review: yearly.
Covers (1) access by CheckTrail personnel to production systems and (2) access by customer users inside the app.
1. Principles
- Least privilege. Every person and every machine identity gets only what its job needs.
- MFA everywhere. Every account that can reach production or customer data uses multi-factor authentication. Hardware security keys or an authenticator app; SMS only where nothing else is offered.
- No shared accounts. Each person has their own login. Machine credentials are separate from human ones.
- Deactivate, don't delete. App users are deactivated so their history stays attributable.
- Everything logged. App actions go to the hash-chained audit log; provider consoles keep their own logs.
2. CheckTrail personnel and production access
Today CheckTrail has one person, the founder, who is the only person with production access.
| System | Who | How | MFA | Status |
|---|---|---|---|---|
| AWS (CheckTrail account) | Founder | IAM Identity Center or IAM user with an admin role; root user not used day to day, root has MFA and no access keys | Required | To be set up with the Terraform apply |
| Neon | Founder | Neon console / owner role for migrations | Required | Confirm MFA enabled |
| Netlify | Founder | Console; holds production secrets | Required | Confirm MFA enabled |
| GitHub | Founder | Repository admin | Required | Confirm |
| Stripe, Resend, Sentry | Founder | Consoles | Required | Confirm on each |
| Google Workspace (email) | Founder | Admin | Required | Confirm |
| CheckTrail app (customer data) | Founder does not have a user in customer firms | Support access only at a customer's written request, through a time-limited account the firm creates, audited like any other user | App 2FA | Policy |
Rules:
- Production data is accessed only to operate the service, fix a problem, or at a customer's request. Direct database queries against production are avoided; when needed, they are read-only and noted in an operations log (date, reason, what was queried).
- Being the AWS account administrator technically allows decrypting stored images. This is a known concentration of access for a one-person company; it is controlled by MFA, by CloudTrail logging (to be enabled), and by this policy, not by technical separation.
- The founder's laptop: full-disk encryption, screen lock, OS updates on, no customer data stored locally, development uses fake data only.
- Secrets live in Netlify environment variables and the founder's password manager, never in the
repository (
.env*is git-ignored).
When a second person joins: named accounts, the narrowest role that works, MFA from day one, a signed confidentiality agreement, and removal of all access on their last day (checklist in the quarterly review).
3. Machine identities
| Identity | Can | Cannot |
|---|---|---|
Postgres role checktrail_app (used by the app) |
SELECT/INSERT on record tables; SELECT/INSERT/UPDATE on state tables; DELETE only on rate-limit counters, holidays and branch assignments | UPDATE/DELETE records; alter schema; drop triggers |
| Postgres owner role (migrations only) | Run migrations | Used by the app at runtime (never) |
AWS IAM user checktrail-app |
PutObject / PutObjectRetention / GetObject on the records bucket; Encrypt/Decrypt/GenerateDataKey on the records key; Textract AnalyzeDocument in one region | List, delete, change bucket or key settings (explicit deny) |
| Stripe, Resend, Sentry API keys | Their own service only | — |
CRON_SECRET |
Trigger the 15-minute job | Anything else |
Production setup check: the checktrail_app role must be created before running migrations
(otherwise grants are skipped). After every migration, confirm
SELECT has_table_privilege('checktrail_app', '"AuditLog"', 'UPDATE') returns false.
Machine credentials are rotated at least yearly and immediately after any suspected exposure.
4. Customer users inside the app
Firms manage their own users. Enforcement is server-side on every action (src/lib/server/rbac.ts).
| Permission | Rep / assistant | Supervising principal | Home office compliance | Firm admin |
|---|---|---|---|---|
| See check data | Own branch | Assigned branches | All branches | No (unless also compliance) |
| Log checks, record forwarding/holds, correct fields | ✓ | ✓ | ✓ | |
| Review, approve, comment, resolve exceptions | ✓ | ✓ | ||
| Confirm receipt manually | ✓ | ✓ | ||
| Import deposit reports (CSV) | ✓ | |||
| Exam exports | ✓ (own exports, own branches) | ✓ | ||
| Examiner links | ✓ | |||
| Firm settings and rules | ✓ | ✓ | ||
| Invite / deactivate users, change roles | ✓ | ✓ | ||
| Grant the firm admin role | ✓ only | |||
| Branches | ✓ | |||
| Billing, SSO | ✓ | |||
| View audit log | ✓ | ✓ | ||
| Examiner (link only) | One export package, read-only, ≤ 30 days, revocable |
Other rules enforced by the app:
- Every query is limited to the user's firm; tests prove no cross-firm access.
- A user can hold several roles. Reps belong to exactly one branch; principals can cover several.
- 2FA is required for every password sign-in. Firms using SSO rely on their identity provider's MFA; a firm can make SSO mandatory for all its users.
- Lockout after 5 failed attempts for 15 minutes; sessions end after 30 minutes idle or 12 hours.
- Deactivating a user ends their sessions at once. Users are never deleted.
- When a subscription lapses, all users become read-only; exports still work.
- Every role change, invite, deactivation and settings change is audited.
5. Access reviews (quarterly)
Every quarter, the founder:
- Lists every account with access to AWS, Neon, Netlify, GitHub, Stripe, Resend, Sentry, Google Workspace; confirms each is needed and has MFA on.
- Lists IAM users/roles and access keys in AWS; confirms the app key is the only access key and is under a year old.
- Confirms
checktrail_appgrants are unchanged (query above) and triggers are present. - Reviews the operations log of any direct production data access.
- Reminds each customer's compliance contact to review their own users (the app lists users, roles, branches and last sign-in).
- Records the review: date, what was checked, what changed. Keep records 6 years.
No quarterly review has been done yet. The first is due at go-live.
6. Break-glass
For when the founder is unavailable and the service needs urgent action, or normal MFA is lost.
- MFA loss: recovery codes for every production account are kept in the password manager and a printed copy in a sealed envelope in a secure place at home.
- Founder unavailable (planned, not in place): a named trusted person gets, in a sealed envelope, instructions to (a) contact customers using a prepared message, (b) contact counsel, and (c) put the site into maintenance mode using a limited Netlify account. They do not get access to customer data. Nobody has been named yet.
- Any use of break-glass is logged, the credentials used are rotated afterwards, and customers are told if their data was touched.