Was ist das?
Ein Service zum einmaligen Teilen von Geheimnissen — Passwörter, API-Keys, private Nachrichten. Statt Secrets im Klartext per Chat oder E-Mail zu verschicken, erzeugt Once einen Link, der genau einmal geöffnet werden kann. Danach ist das Geheimnis unwiderruflich gelöscht.
Das Besondere: Die Ende-zu-Ende-Verschlüsselung stellt sicher, dass der Server zu keinem Zeitpunkt Zugriff auf den Klartext hat. Der Verschlüsselungsschlüssel steckt im URL-Fragment (#key) und wird nie an den Server gesendet.
Wie es funktioniert
- CLI verschlüsselt das Secret lokal mit einem zufälligen AES-256-GCM -Schlüssel
- Server speichert nur den Ciphertext mit einer öffentlichen ID
- Link wird erzeugt:
once.mathis-adler.dev/s/<id>#<key>— der Schlüssel im Fragment verlässt nie den Client - Empfänger öffnet den Link, klickt auf “Anzeigen”, der Browser holt den Ciphertext und entschlüsselt ihn per WebCrypto
- Secret wird konsumiert — atomar beim ersten Abruf, danach 410 Gone
Sicherheitsentscheidungen
- Zero-Knowledge-Architektur — Der Server sieht nie den Schlüssel oder Klartext. Selbst bei einer Server-Kompromittierung sind die Secrets geschützt.
- POST statt GET zum Konsumieren — Unfurl-Bots in Slack, Teams oder Discord machen nur GET-Requests. Die Reveal-Seite (
GET /s/:id) zeigt nur den “Anzeigen”-Button, ohne das Secret zu konsumieren. Erst der explizitePOST /api/secrets/:id/consumegibt den Ciphertext zurück. - Atomarer Consume — SQLite’s
UPDATE ... RETURNINGstellt sicher, dass bei gleichzeitigen Requests nur der erste den Ciphertext erhält. - Content Security Policy —
script-src 'self'verhindert, dass eingeschleuster Code den Schlüssel aus dem URL-Fragment exfiltrieren kann. - 128-Bit IDs — 22-Zeichen Base64url (~2^128 Kombinationen) machen Brute-Force-Angriffe auf IDs unmöglich.
- Zweistufiges Rate Limiting (Nginx + Express) — verschärft für Consume-Requests und ungültige IDs.
- Automatische Bereinigung — Abgelaufene Secrets werden sofort gelöscht, konsumierte nach einer Stunde Grace Period.
Was ich gelernt habe
- URL-Fragmente (
#...) werden vom Browser nie an den Server gesendet — das macht sie ideal für clientseitige Schlüssel. Das ist kein Hack, sondern Teil der HTTP-Spezifikation (RFC 3986). - Die WebCrypto API ist mächtig, aber ihre Ergonomie erfordert Sorgfalt: Key-Import, IV-Handling und die korrekte Trennung von IV, Ciphertext und Auth-Tag müssen exakt stimmen.
- “Einmal lesen, dann löschen” klingt einfach, aber die Atomarität ist entscheidend. Ohne
UPDATE ... RETURNINGwäre ein Race-Condition-Window, in dem zwei Requests gleichzeitig den Ciphertext erhalten.