Back to home

Security & Trust

Last updated: June 12, 2026

This is the honest current-state account of how osocios.club protects your club's data. We name what we do, what we don't do, and what is next on the roadmap. We don't claim field-level encryption we haven't shipped, and we mark every limitation explicitly.

1. Where your data lives

  • Database & uploads: Supabase (Postgres + Storage), region eu-west-1 (Ireland). Disk-level AES-256 at rest.
  • Application hosting: Vercel, EU region.
  • In transit: TLS 1.2+ on every request, HSTS on the apex domain.

2. Encryption — what we actually do

  • At rest: Supabase disk-level AES-256 for the entire database and storage buckets. Standard managed-cloud encryption.
  • In transit: TLS 1.2+ on every request.
  • Stored credentials (S3 keys you connect, BYOK AI keys): encrypted with AES-256-GCM in the database via lib/crypto/secret-box.ts. We hold the wrapping key; you hold the underlying credential.
  • Auth tokens & password-reset tokens: stored as SHA-256 hashes, never in plain text.
  • Member PIN hashes (staff accounts): bcrypt.

What we do not do: we do not app-encrypt finance amounts (sales, balances, expenses, reconciliations). The server has to compute them to do its job, so the server would hold the key either way — encrypting them would be security theater, not protection. Our finance protections are different (operator write-deny, tamper-evident anchors, dual control — see sections 3 and 5).

3. Who can read your data — the operator policy

Two of us run the platform: Jeff and Miki (allowlisted via the PLATFORM_ADMIN_EMAILS environment variable on Vercel, nowhere else). The honest current state:

  • We can read your club's data in the support panel (members, settings, content, ops, finance ledger). This is so we can actually help when you ask.
  • We cannot write to your finances. Every finance-mutating server action (sales, voids, top-ups, expenses, reconciliations, balance adjustments, referral payouts, paid membership renewals) calls assertNotElevated and throws before touching the database when the actor is a platform-operator elevation. Build-failing test tests/security/finance-elevated-guard-coverage.test.ts pins the deny-list — adding a new finance action without the guard fails CI.
  • We don't silently impersonate you. When an operator acts, the activity-log row is attributed [platform:operator@email] in the details column, with actor_owner_id nulled. The audit trail you export reflects who actually did the action.
  • What we do not yet do — and what is coming: today, operator reads are not logged, and an operator does not need your explicit consent to enter the support panel. Trust Phase 6 adds (a) every operator entry creates a platform_access activity-log row visible to you, and (b) entry requires either an owner-granted support window (24/48/72h) or a break-glass with a mandatory reason, your email notification, and an automatic 4-hour expiry. We're shipping this last, deliberately, because it is the biggest change and we want the rest of the trust story to be solid first.

4. Your data is yours — self-service export

From Admin → Settings → Data & Privacy you can download:

  • Members CSV — every member row, every column we hold: codes, names, contact, identity number, residency, membership status, dates, internal balances.
  • Activity log CSV — every administrative action ever recorded for your club, including operator entries tagged as [platform:...].
  • Finance ledger CSV — the existing date-range exporter on the Finance tab.

Each button is gated by requireRealOwnerFromClerk — a platform operator cannot trigger it, and there is no operator-facing export path in code or in support. Build-failing test tests/security/no-operator-data-export.test.ts enforces this.

5. Sensitive member documents (BYOS)

Identity documents and the signed membership PDF can be offloaded to a storage bucket you own (S3-compatible: AWS, Cloudflare R2, MinIO, etc.). When you connect a bucket:

  • We encrypt your bucket credentials with AES-256-GCM before storing them.
  • Newly-uploaded sensitive files land in your bucket, not ours. The legacy plaintext columns on existing members are kept until you ask us to backfill (deferred work).
  • Members' on-screen previews are served via short-lived signed URLs.

6. Tamper-evident finance ledger

Every night, we compute a SHA-256 over your day's club_money_transactions + sales rows, chain it onto the previous day's digest, and store the chain in our database and mirror it as a JSON file to {clubId}/finance/anchors/YYYY-MM-DD.json in your own bucket.

The point is the copy you hold. If anyone — us, an operator, a compromised account — silently rewrote a past transaction, the digest we wrote that day would no longer match the recomputed digest, and your bucket would prove what the row used to be.

  • Verify in-app: the “Verify last 30 days” button in Data & Privacy recomputes every anchor and shows green/red per day.
  • Verify independently: scripts/verify-finance-anchors.mjs in the repo. Point it at a directory of anchor JSON files downloaded from your bucket. Pure Node, no osocios code required — an auditor can run it offline.
  • Honest limitation: same-day rows (today, before the 03:30 UTC cron runs) are not yet anchored. The chain pins historical rows; live writes are by definition not historical yet.

7. Retention — your bucket, your timeline

In Data & Privacy you can set a retention window: off, after 1 week, or after 1 month. When the window is set, the same nightly cron archives every finance table for any day older than the window to {clubId}/finance/archive/YYYY-MM-DD/ in your bucket as CSV + JSON, HEAD-verifies the manifest object exists, then hard-deletes those rows from our database.

From that point on, your bucket is the canonical copy. We do not keep a shadow. Reports inside the app for archived ranges show a banner pointing you to your bucket. Each per-day archive + delete is a single transaction; if anything fails, the whole day rolls back and the real owner gets an email so the next night's run can retry cleanly.

8. On the roadmap (we'll update this page as we ship)

  • Dual control for finance ops above a configurable threshold (voids, adjustments, large expenses) — second approver required.
  • Operator-access logging + grants (Phase 6 above).

9. Vulnerability reports

Found something? Email security@osocios.club. We respond in plain English, no boilerplate.