← Ataski · Trust & security · DPA · Subprocessors

This is the canonical English version. Native translations for DE / ES / FR / PT / RU are pending counsel sign-off.

Approved 2026-10-08

Access Control Policy

Owner: the founder. Approved: 2026-10-08. Review: quarterly (access list) and annually (this document).

1. Principle

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.

2. What can reach production

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.

3. Authentication

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.

4. Authorization inside a workspace

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.

5. Ataski staff access into a customer workspace

Support access is a stronger power than the operator dashboard and is treated as such (src/ataski/support_session.py):

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?".

6. Access review

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.

7. Credential rotation

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

8. Before the first hire

None of the following exists yet, and all of it is required before anyone but the founder holds production access:

  1. a joiner checklist — identity verification, policy acknowledgement, the minimum grant that lets them work, and nothing more;
  2. secrets moved off shared .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;
  3. a leaver checklist that mirrors the joiner one in reverse, executed the same day;
  4. named backup for incident command, because a one-person rota is not a rota.