Owner: the founder. Approved: 2026-10-08. Review: quarterly (access list) and annually (this document).
Least privilege, and every access leaves a trace. Access that cannot be listed cannot be reviewed, so the list below is the policy's real content.
| System | Who | How access is held | How it is removed |
|---|---|---|---|
| Hetzner node (SSH) | founder | key-only; PermitRootLogin no, PasswordAuthentication no, MaxAuthTries 3 (infrastructure/harden_v2.py) |
remove the key from authorized_keys |
Application admin (/admin) |
founder | WorkOS login plus e-mail in settings.platform_admin_emails; a static token path remains for cron |
edit the env allowlist, restart |
| Neon Postgres | founder (owner role); the app (ataski_runtime) |
owner via the Neon console; runtime role provisioned by infrastructure/bootstrap_role.py, no BYPASSRLS |
rotate the password through the same script |
Production secrets (the server .env) |
founder, via SSH | readable by its owning service account only, mode 0600 — the deploy refuses to run when the mode is wider |
covered by the SSH row; then rotate what the person could read (docs/security/secrets.md) |
| Cloudflare, WorkOS, Stripe, Postmark, and the other vendor consoles | founder | vendor account credentials | vendor console |
| GitHub | founder | account; deploys read origin/main |
collaborator removal |
Who can change what ships. Anyone with write access to the GitHub
repository can push to main directly: GitHub branch protection is not
available for a private repository on the free plan, and there are no
server-side required checks (ADR-0119 removed CI). Today that is one account,
the founder's. Production follows only the whole origin/main ref
(deploy.py refuses anything else) and only from a machine holding the SSH
key, so write access to the repository plus that key is what amounts to
production change access — both are rows in the table above. Adding a GitHub
collaborator is therefore a production-access grant, and is treated as one.
Customer users reach only their own workspace. Tenant isolation is a database
property, not an application convention: set_current_tenant binds the session
variable and Postgres RLS refuses anything outside it.
Delegated to WorkOS: e-mail / passwordless, MFA, SSO/SAML for customers who want
it. Sessions ride a sealed __Host- cookie, encrypted and MAC'd, with a refresh
grace window. Interactive WorkOS steps (MFA challenge, SSO required, e-mail
verification) are honoured, never bypassed.
Rule: MFA is required on every account the founder holds that can reach production or a vendor console. Status: to be enforced in the WorkOS dashboard and evidenced by screenshot — see the readiness map, CC6.1.
Route-level role gates guard the high-stakes actions — DPA signature, board-pack
approval, monthly investor-update send — and require founder or finance_lead
on that workspace. The RLS-scoped query underneath is the second gate; both must
agree. A new high-stakes surface copies the existing gate rather than inventing
its own, so a future change to the role model moves everything in lockstep.
Support access is a stronger power than the operator dashboard and is treated as
such (src/ataski/support_session.py):
platform_support actor type, so it appears in
the customer's own audit export rather than only in ours;Who may start one is the same fail-closed env allowlist that gates /admin. A
support session must never have a weaker or a different source of truth for "is
this the founder?".
Quarterly. Walk the table in §2, one row at a time, and record for each: who
holds it today, whether that is still correct, and what was removed. Keep the
signed result in docs/security/evidence-checklist.md §2. The whole review is
under an hour precisely because the estate is small; there is no excuse for
skipping it.
Trigger an out-of-cycle review immediately on: a suspected credential compromise, a departure, or any incident classified SEV-1 or SEV-2.
Rotate yearly (the schedule in docs/security/secrets.md §3), on suspicion, on
vendor advisory, and on any departure. No rotation has been performed yet; the
ledger is docs/security/evidence/secret-rotations.md.
infrastructure/bootstrap_role.py rotates the database runtime role; every
other credential is rotated in its vendor console and written into the server
.env, followed by a restart (docs/runbooks/secret-rotation.md).
None of the following exists yet, and all of it is required before anyone but the founder holds production access:
.env files onto a managed store (Doppler is the
named choice, deferred until exactly this moment) so access can be granted
and revoked without handing out SSH;