Technical and organisational measures
This page is a draft, written from the code and server configuration on 14 October 2026. It has not been reviewed by a lawyer yet and is not ready to sign. It describes what Clubkit actually does today; open gaps are marked open.
Annex 1 to the data processing agreement: how Clubkit protects your club's data today, including the gaps that are still open.
Provider: Mac Beagan – Web Development, Isartalstr. 16, 80469 München, Germany Platform: Clubkit (clubkit.team)
1. Overview: how Clubkit is built
- One virtual server at Hetzner Online GmbH in a data centre in Germany. It runs, in Docker containers: the database (PostgreSQL 16), the sync service (PowerSync, self-hosted), the API server, the public club websites and the Caddy web server.
- Local-first: the app and the web admin keep a copy of the data the signed-in person may see on the device. Only the server decides which data that is (see 2.3).
- No analytics or tracking tools, no advertising, no third-party error reporting in the app, web admin or club website.
2. Confidentiality
2.1 Physical access (data centre)
Physical access is Hetzner's responsibility (access control, video surveillance, on-site staff according to Hetzner). The Provider runs no server rooms of its own.
2.2 System access
- Only ports 80 and 443 of the web server are open to the internet. The database has no public port; it is reachable only inside the Docker network.
- Only the owner has administrative access to the server, via SSH. (Open: record that only key login is allowed and document the firewall rules.)
- Service credentials (database, email, DNS) are kept only in a configuration file on the server, not in the source code.
User sign-in:
- Email and password. Passwords are stored only as a hash (scrypt, Better Auth default).
- Sign-in attempts are limited per IP address: 30 per minute for sign-in and sign-up, 10 per minute for "forgot password".
- A password reset ends all other sessions and sends a notice email.
- Sessions are stored server-side. In the browser the session cookie is limited to its own host (never the public club websites). In the app the session token is kept in the operating system's protected storage (iOS Keychain, Android Keystore).
- The sync service only accepts short-lived tokens (5 minutes) that contain only the user id. Roles are never read from the token.
- Open: no two-factor sign-in; email addresses are not verified at sign-up.
2.3 Access control (who sees what)
Club roles: owner, admin, coach, member, fan; plus guardians (linked to a child) and individual extra permissions granted by club admins.
Reading: what reaches a device is decided by the server's sync rules, not by the app's screens. A field that isn't shown isn't merely hidden; it is never sent. In detail:
- Members get the member directory with a few columns only (name, role, team, position and similar). They do not receive other members' phone numbers, emergency contacts, medical notes, nationality or fee data.
- Members see other members' profile photos only if the pictured person consented to internal use.
- Coaches get contact data, emergency contacts, medical notes and nationality only for members of their own teams.
- Coaches get the replies and attendance of every event in the club, including other teams' events. They record attendance for events of the teams they coach (for an event with several teams, coaching one of them is enough) and for club-wide events.
- Invite-only events (and their replies) reach only the invitees and club admins.
- Fans get only public match data (club branding, teams, fixtures, results) and their own account data.
- Guardians get their own children's data.
- Members, guardians and fans receive events, replies and matches from the last 12 months only. Club admins and coaches keep the full history.
- Emails and push notifications sent by the server follow the same rules: whoever doesn't get something synced doesn't get it by email or push either.
Writing: every change from an app or the web admin goes to a single server endpoint. On every request the server checks in the database that the person is active in this club and holds the required role. The server sets the club id and the timestamp itself and ignores what the device sends. Disallowed changes are not applied and are reported back to the device as rejected. Match scores are computed only by the server from the event log.
Checks: the sync rules are covered by automated tests that check, per role, which rows and columns arrive. After every change to the rules this test runs before release, and a spot check runs on production.
2.4 Separation
All clubs share one database. Every club-related table has a club id; reading and writing are always tied to the person's membership in that specific club (see 2.3). The server, never the device, sets a record's club id.
2.5 Pseudonymisation and encryption
- All connections use TLS (HTTPS, automatic certificates): app, web admin, API, sync and club websites.
- Deleted accounts are anonymised (name replaced by a placeholder, personal fields cleared) so the club's history stays without reference to the person (details in retention.en.md).
- Open: the database and uploaded images are not additionally encrypted on the server (no application-level encryption). Backups are not encrypted.
- Open: data on devices (local database) is not additionally encrypted by the app; the operating system's device encryption applies. Signing out deletes the local database.
3. Integrity
3.1 Transfer control
- Transfers only over TLS.
- Emails are sent through Resend, one message per recipient (no shared recipient lists).
- Push notifications go through Expo's push service to Apple or Google. They contain the notification text (e.g. the title of an event or announcement).
- People appear on the public club website (e.g. in a squad) only with their website consent.
- Uploaded images get random file names (UUID); only JPG, PNG and WebP up to 5 MB are accepted. Open: image files can be fetched by anyone who knows the (unguessable) URL; there is no access check on download. Whether image metadata (e.g. location) is removed still needs checking.
3.2 Input control
- Every row carries a server-set modification time.
- Consents are only appended, never overwritten: each decision with its time and the person who made it.
- Match events record who entered them.
- Open: there is no general change log (who changed which field when).
4. Availability and resilience
- Containers restart automatically after a crash or reboot.
- The apps keep working offline and sync changes later.
- Backups today: a script backs up the database (pg_dump) and uploaded images and deletes backups after 30 days. Today backups are kept only on the same server; a second (offsite) location is not set up yet. A backup is taken before every release that changes the database.
- Open: set up encrypted offsite backups; confirm scheduled backups; test and document a restore.
- Open: no external uptime monitoring.
5. Regular review
- Automated tests (over 1,000) run before every release, including per-role access tests and end-to-end tests of the sync privacy rules.
- Changes to the sync rules are released on their own and spot-checked per role afterwards.
- New data fields follow fixed rules (additive only, unknown values are rejected, personal data never in tables sent in full to all members).
- Logs: the API server logs method, path, status code and duration per request, no IP addresses. The web server writes no access log. Open: set a retention period for container logs.
- Open: write down the breach procedure (who notifies whom and when, see DPA clause 10).
6. Processor control
- Sub-processors are listed in subprocessors.en.md; each has a data processing agreement or its data processing terms apply. (Open: check and file the agreement for each service.)
- The Provider processes club data only to run Clubkit, not for its own purposes.
- The Provider holds no certifications (e.g. ISO 27001). Hetzner holds its own ISO 27001 certification. (Open: file Hetzner's current certificate.)