FormslySicherheitArchitektur
Whitepaper · Sicherheitsarchitektur

Wie wir Schweizer Daten technisch schützen.

Diese Seite ist die öffentliche Zusammenfassung unseres internen Sicherheitsdokuments. Sie beschreibt jede Schicht im Detail, vom Browser des Ausfüllenden bis zum verschlüsselten Backup in Zürich. Keine Marketingformeln, kein Wischiwaschi: echte Architektur-Entscheidungen, dokumentierte Restrisiken, prüffähig.

Datenstandort Zürich DSG / nDSG-konform AES-256-GCM + ECIES P-256
Grundprinzipien

Vier Entscheidungen, die alles andere bestimmen.

Eine Sicherheitsarchitektur ist die Summe ihrer Voreinstellungen. Unsere vier Default-Entscheidungen. Und wie wir sie technisch erzwingen.

01Default

Daten verlassen die Schweiz nicht

Alle VMs, Backups und das Object-Storage liegen in Exoscale Zürich (eu-central-2). Keine Replikation, kein CDN-Spiegel ausserhalb der CH.

02Default

Server kennt den Inhalt nicht (wenn E2EE aktiv)

Im Pro-Plan werden Antworten bereits im Browser des Ausfüllenden mit ECIES + AES-256-GCM verschlüsselt. Private Schlüssel verlassen den Browser nie.

03Default

Mandantentrennung auf DB-Ebene

Row-Level Security in PostgreSQL ist auf jeder Tabelle aktiv. Auch wir können nicht versehentlich Mandanten mischen, selbst via direktem SQL nicht.

04Default

Restrisiken werden dokumentiert

Was nicht eliminiert werden kann, wird benannt: in known-security-risks.md, mit Bewertung und Kompensationsmassnahme.

02 · Datenfluss

Vom Klick auf „Absenden" bis ins Dashboard.

Der Pfad jeder Antwort, mit Verschlüsselungs-Schritt, Zwischenhalt und Speicherort. Bei E2EE-Formularen passiert der Klartext-Schritt ausschliesslich im Browser des Ausfüllenden und im Browser des Admins. Nie auf unserem Server.

verschlüsselt Klartext (lokal) in der Schweiz
Pfad einer Einreichung · E2EE-Modus
01lokal
Browser · Ausfüllende:r

Antworten werden lokal mit ECIES (P-256) + AES-256-GCM verschlüsselt. Private Key verbleibt nie auf dem Gerät.

KlartextWeb Crypto API
TLS 1.3
HSTS · 12 Monate
02
Edge · formsly.ch

Nginx mit Nonce-CSP und ALTCHA-Bot-Check. Keine eigene Logik auf Antwortinhalten.

ALTCHACSP nonce
HTTP intern
Loopback
03Server
App · Zürich (CH)

Speichert ausschliesslich Ciphertext und gewrappte Schlüssel. Bei Nicht-E2EE: zusätzlich ALE mit ENCRYPTION_KEY auf Server-Ebene.

AES-256-GCMALE
04
DB · Supabase RLS

PostgreSQL mit Row-Level Security auf jeder Tabelle. Mandantenübergreifende Reads sind technisch ausgeschlossen.

RLSmandantengetrennt
Snapshot
täglich, 30 Tage
05
Backup · Object Storage

Tägliche Snapshots, AES-256 verschlüsselt, Aufbewahrung 30 Tage, dann automatische und unwiderrufliche Löschung.

AES-25630 Tage
Passkey · 2FA
Login Admin
06lokal
Dashboard des Admin

Entschlüsselung lokal im Admin-Browser. Auto-Lock nach 30 Minuten ohne Aktivität.

E2E openAuto-Lock 30m
0
US-Subunternehmer im Datenpfad
200'000
PBKDF2-Iterationen für Master-Pwd
12 B
zufällige IV pro AES-GCM-Block
< 10 min
Bekanntwerden bei Ausfall (Uptime Robot)
03 · Schutz-Schichten

Fünf Schichten. Jede für sich allein wirksam.

Defense in Depth heisst nicht „eine Wand pro Risiko". Es heisst: selbst wenn eine Schicht versagt, schützen die übrigen vier. Wir konstruieren die App so, als wären die anderen Schichten bereits kompromittiert.

Weiter zur Verschlüsselung
01

Netzwerk

TLS 1.2/1.3 erzwungen, HSTS preload, Nginx-Reverse-Proxy, keine HTTP-Fallbacks.

02

Anwendung

Nonce-basierte CSP mit strict-dynamic, script-src-attr 'none', Zod-Validierung auf jedem API-Endpoint, HMAC-signierte Webhooks.

03

Verschlüsselung

Application-Level Encryption (AES-256-GCM) für jede Submission. E2EE auf Wunsch, der Server kennt den Inhalt dann nie.

04

Datenbank

Row-Level Security auf jeder Tabelle. Service-Role-Key nur serverseitig, nie im Browser. Direkter SQL-Zugriff nur via SSH-Tunnel.

05

Infrastruktur

Exoscale Zürich, ISO 27001/27017/27018. Docker non-root (UID 1001). SSH nur mit Ed25519-Key + Passphrase, kein Passwort-Login.

04 · Verschlüsselung

Zwei Schichten. Eine immer aktiv. Die zweite optional auf Knopfdruck.

Application-Level Encryption (ALE) ist auf jedem Formular aktiv, ohne Konfiguration, ohne Plan-Upgrade. Wer Antworten zusätzlich vor uns selbst verbergen will, aktiviert Ende-zu-Ende-Verschlüsselung (E2EE). Der entscheidende Unterschied: wer den Schlüssel besitzt.

Implementierung ALElib/encryption.ts
Implementierung E2EElib/e2e-encryption.ts
Format-Headerv1:base64(iv):base64(authTag):base64(ciphertext)
Eigenschaft
ALE · Standard
E2EE · ab Starter
Wer kann entschlüsseln?
Formsly-Server (mit ENCRYPTION_KEY)
Nur der eingeloggte Admin im eigenen Browser
Algorithmus Antwortinhalt
AES-256-GCM
ECDH P-256 + ECIES + AES-256-GCM
Schlüsselableitung
Statischer ENCRYPTION_KEY (Server-Umgebung)
PBKDF2-SHA-256, 200'000 Iterationen aus Master-Passwort
IV / Nonce
12 Byte zufällig pro Operation
12 Byte zufällig pro Operation
Wo entsteht der Schlüssel?
Serverseitig (Env-Variable .env.local)
Browser des Nutzers, Web Crypto API
Was passiert bei DB-Dump?
Ohne ENCRYPTION_KEY ist Ciphertext unleserlich
Ohne Master-Passwort des Nutzers ist Ciphertext unleserlich
2FA-Voraussetzung
Empfohlen
Technisch erzwungen — ohne TOTP keine E2EE-Aktivierung
Auto-Lock
n/a
30 Minuten Inaktivität → Private Key wird aus dem Browser-Speicher gelöscht
Verfügbar in Plan
Standard (alle Pläne)
Alle Pläne — bereits ab Starter aktivierbar
Vollständige technische Spezifikation: docs/compliance/e2e-encryption-spec.md
05 · Zugriff und Mandanten

Nichts ist „nur Application-Logik".

Jede Autorisierungsentscheidung ist doppelt verankert: einmal in der API-Route, einmal in der Datenbank selbst. Ein Bug in einer Schicht kompromittiert nicht die andere.

Endnutzer

Authentifizierung der Formsly-Nutzer

  • E-Mail + Passwort über Supabase Auth, bcrypt mit Salt pro User
  • TOTP-2FA optional — für E2EE technisch erzwungen
  • Keine geteilten Accounts; jeder Zugriff ist einem User zuordenbar
  • Edit-Token für Einreichungen: SHA-256-Hash in DB, Klartext nur im E-Mail-Link
Datenbank

Row-Level Security (RLS) auf jeder Tabelle

  • profiles, forms, submissions, webhooks, form_key_shares, consent_audit, e2e_access_log — alle mit RLS-Policies
  • Authentifizierter User sieht ausschliesslich eigene Zeilen — auch via direktem SQL
  • Mandantenübergreifende Reads sind auf DB-Ebene technisch ausgeschlossen
  • Service-Role-Key umgeht RLS — wird ausschliesslich serverseitig in API-Routes verwendet, nie im Browser
Betreiber

Authentifizierung der Infrastruktur

  • SSH ausschliesslich mit Ed25519-Key + Passphrase, kein Passwort-Login
  • Key auf verschlüsseltem USB-Stick — nicht in Cloud-Diensten
  • GitHub, Google, Infomaniak, Stripe: 2FA mit YubiKey (Primary + Backup)
  • Supabase-Dashboard nicht öffentlich exponiert — nur via SSH-Tunnel erreichbar
06 · Standort

Ein Datacenter. Eine Region. Eine Rechtsordnung.

Wir betreiben Formsly auf dedizierten virtuellen Maschinen bei Exoscale im Rechenzentrum Zürich. Region eu-central-2. ISO 27001, 27017 und 27018 zertifiziert. Es gibt keine sekundäre Region, kein CDN-Edge ausserhalb der Schweiz, keine asynchrone Replikation in andere Rechtsordnungen. Eine bewusste Architektur-Entscheidung.

Region
eu-central-2
Datacenter
Exoscale Zürich, CH
Zertifizierungen
ISO 27001 / 27017 / 27018
CLOUD-Act-Exposure
Nein · keine US-Subunternehmer im Datenpfad
Datenresidenz · liveoperativ
Zürich
47.37° N · 8.55° E
0
andere Regionen
3
ISO-Zertifikate
100%
CH-Datenresidenz
07 · Subunternehmer

Vier Subunternehmer. Drei in der Schweiz.

Stripe ist die einzige Abhängigkeit ausserhalb der Schweiz. Es sieht ausschliesslich Abrechnungsdaten, nie Formular-Antworten. Neue Subunternehmer durchlaufen Datenschutz- und Sicherheitsprüfung und werden in anbieter-und-transfers.md dokumentiert, bevor sie produktiv gehen.

Exoscale
Schweiz · Zürich
Hosting, VMs, Block Storage, Backups
Physischer Serverstandort. Kein App-Zugriff.
exoscale-dpa-v3.pdf
Supabase
Self-Hosted · Zürich
Auth, PostgreSQL, Storage Backend
Selbst gehostet auf eigenen VMs. Kein Drittanbieter.
Entfällt
Infomaniak
Schweiz · Genf
Transaktions-E-Mails (Bestätigungen, Edit-Links)
Nur Metadaten der Zustellung. Keine Antwortinhalte.
Infomaniak-Portal
Stripe
USA · global
Zahlungsabwicklung (Abonnement-Kunden)
Nur Abrechnungsdaten des Kunden. Keine Submissions.
Stripe DPA + SCCs
08 · Betrieb

Sicherheit ist ein Lebenszyklus, kein Launch-Event.

Patches, Backups, Monitoring und Incident Response sind im Tages- und Wochenrhythmus verankert. Jede Zahl unten ist im internen Dokument einer Person und einem Prozess zugeordnet.

Operativ
7Tage

Patch-Management

Sicherheits-Updates innerhalb von 7 Tagen nach Bekanntwerden via pnpm audit und Watchlist.

Operativ
BranchProtection

Deployment

Kein Direkt-Push auf main. GitHub Actions baut den Container nach jedem PR-Merge.

Operativ
< 10 minAlert

Monitoring

Uptime Robot prüft formsly.ch und admin.formsly.ch alle 5 Minuten. E-Mail-Alert bei Ausfall.

Operativ
30 dAufbewahrung

Backups

Tägliche Snapshots, AES-256 verschlüsselt, in Exoscale Object Storage Zürich. Danach automatische Löschung.

Operativ
RTO 24hRPO 24h

Wiederherstellung

Recovery-Tests jährlich oder nach Architekturwechseln. Letzter Test: 23. März 2026, erfolgreich.

Operativ
72 hMeldefrist

Incident Response

Meldung an EDÖB nach Art. 24 nDSG, an Betroffene nach Art. 24 Abs. 2. Plan dokumentiert in incident-response-plan.md.

09 · Bekannte Restrisiken

Was nicht eliminierbar ist, wird benannt.

Jedes ausgereifte System hat Restrisiken. Wir dokumentieren sie öffentlich, mit Bewertung und Kompensationsmassnahme. Die vollständige Liste liegt in known-security-risks.md im Repository.

Zur Dokumenten-Übersicht
01Metadaten von Submissions liegen unverschlüsselt in der DB

Felder wie submitted_at, completion_time_seconds, variant_id, consent_given und utm_params werden im Klartext gespeichert. Bewusste Designentscheidung: Diese Felder enthalten für sich genommen keine Identifikatoren. Ein Angreifer mit reinem DB-Zugriff sieht Zeitstempel und Zahlen, aber nicht, wer was eingereicht hat. Verschlüsselung dieser Felder würde Dashboards, Löschfristen und Analysen verunmöglichen, ohne wesentlichen Sicherheitsgewinn.

02At-Rest-Verschlüsselung auf Infrastruktur-Ebene nicht garantiert

Exoscale verschlüsselt VM-Disks und Block Storage nicht automatisch. Kompensationsmassnahme: alle personenbezogenen Daten werden auf Applikationsebene mit AES-256-GCM verschlüsselt (ALE), bevor sie die Datenbank erreichen. Bei E2EE-Formularen zusätzlich vorher im Browser.

03Stripe als US-Subunternehmer im Bezahlflow

Stripe sieht ausschliesslich Abrechnungsdaten zahlender Kunden, niemals Formularantworten oder Daten von Endnutzern. Übertragung auf Basis von Standardvertragsklauseln (SCCs); DPA via Stripe. Dokumentiert in anbieter-und-transfers.md.

04Browser-Kompromittierung kompromittiert E2EE

E2EE schützt vor Server-Kompromittierung, nicht vor einem kompromittierten Endgerät. Wenn der Browser des Admins infiziert ist, kann der lokal entschlüsselte Klartext extrahiert werden. Kompensation: Auto-Lock nach 30 Minuten, 2FA technisch erzwungen, CSP mit nonce-basiertem strict-dynamic.

05Wer den ENCRYPTION_KEY besitzt, kann alle ALE-Antworten entschlüsseln

ALE schützt vor reinem DB-Zugriff (z. B. Backup-Diebstahl), nicht vor einem kompromittierten Server. Daher die Option auf E2EE für vertrauliche Daten. Dort wird der Klartext auf dem Server nie sichtbar, selbst nicht für uns.

Sicherheits-Kontakt

Eine Frage, eine Meldung, ein Audit-Termin?

Responsible-Disclosure-Meldungen und Audit-Anfragen gehen direkt an die Person, die diese Seite verantwortet, nicht an einen Bot. Antwort üblicherweise innert 24 Stunden, an Werktagen schneller.

team@formsly.ch

PGP-Fingerprint auf Anfrage