Owner: the founder. Approved: 2026-10-08. Review: annually and after any incident. Applies to: everyone with access to Ataski systems or customer data — today, one person.
Customers hand Ataski their CRM, their accounting ledger, their contracts and their support inbox. Everything below exists so that data stays theirs, stays where they put it, and can be got back or destroyed on request.
This is not a template. Every rule names the mechanism that enforces it, so a rule that stops being true is a rule whose mechanism can be checked.
The production system: the Hetzner application node, the Neon Postgres database,
Redis, Cloudflare R2 object storage, and the vendor accounts listed on
/legal/subprocessors. Also the founder's workstation, because it holds the SSH
key and the development .env.local.
Out of scope: nothing. There is no test estate holding real customer data —
.env.local points at the production Neon instance, which is a known hazard and
is why the test-tenant guard exists.
tenant_id lacks a policy
(tests/test_rls_coverage_all_tenant_tables.py).migrations/versions/0177_audit_log_append_only_grants.py).
Retention is discharged by scrubbing personal columns, never by deleting rows.gitleaks runs on the staged diff in the commit
hook (scripts/git-hooks/pre-commit) and over the branch and the whole
history in the pre-push gate, which refuses the push on a finding. Both run on
the founder's machine and nothing on GitHub re-checks; a finding is never
pushed past with --no-verify — the credential is rotated instead.
Production values live only in the server's .env, which the deploy script
never writes and refuses to deploy with unless its mode is 0600 or tighter.ataski_runtime, a role provisioned without BYPASSRLS and without DELETE
on most tables. Destructive operations (the offboarding purge) require the
owner connection deliberately.There is one role: the founder, who is simultaneously the security officer, the
incident commander, the change approver and the only production operator. The
absence of segregation of duties is disclosed rather than papered over; the
compensating controls are that a change reaches production only as the whole
origin/main ref (the deploy refuses anything else), merged through a pull
request whose head passed the pre-push gate, and that every administrative
action lands in the append-only audit log. The gate is not an independent
witness: it runs on the author's machine, --no-verify skips it, and GitHub
enforces nothing — there are no required status checks, and branch protection
is not available for a private repository on the free plan (ADR-0119).
Two classes are enough at this size.
Credentials are a third thing: they are Customer Data that is never readable by
anyone, including the customer's own export
(src/ataski/data_classification.py marks them secret_columns).
An exception to any rule above requires an ADR in docs/adrs/ that states what
is being weakened, for how long, and what compensates. "We will fix it later" is
not an exception; it is an undocumented gap, and the readiness map
(docs/security/soc2-readiness.md) is where those are listed honestly instead.
The git hooks are the enforcement mechanism for the technical rules: the commit
hook runs the fast checks on the staged files, and the pre-push gate
(scripts/check.py, ADR-0119) runs everything — ruff including the S
security rules, mypy --strict, the test suite, gitleaks over the branch and
the whole history, trivy fs with CRITICAL blocking, the migration chain and
the live-schema suites, and the Schemathesis API fuzz. Both are installed once
per clone with scripts/install_git_hooks.sh. For the human rules the
mechanism is the quarterly self-review in
docs/security/evidence-checklist.md: if a rule here is not observable in that
review, it is decoration and should be deleted rather than kept for show.