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

  1. CLI verschlüsselt das Secret lokal mit einem zufälligen AES-256-GCM -Schlüssel
  2. Server speichert nur den Ciphertext mit einer öffentlichen ID
  3. Link wird erzeugt: once.mathis-adler.dev/s/<id>#<key> — der Schlüssel im Fragment verlässt nie den Client
  4. Empfänger öffnet den Link, klickt auf “Anzeigen”, der Browser holt den Ciphertext und entschlüsselt ihn per WebCrypto
  5. 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 explizite POST /api/secrets/:id/consume gibt den Ciphertext zurück.
  • Atomarer Consume — SQLite’s UPDATE ... RETURNING stellt 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 ... RETURNING wäre ein Race-Condition-Window, in dem zwei Requests gleichzeitig den Ciphertext erhalten.

Glossar

Ende-zu-Ende-Verschlüsselung
Ein Kommunikationsprinzip, bei dem nur Sender und Empfänger die Nachricht lesen können. Der Transportweg — einschließlich des Servers — sieht nur verschlüsselte Daten und hat keinen Zugriff auf den Schlüssel.
AES-256-GCM
Ein authentifizierter Verschlüsselungsmodus, der AES mit 256-Bit-Schlüssel und Galois/Counter Mode kombiniert. Bietet gleichzeitig Vertraulichkeit und Integritätsschutz — manipulierte Ciphertexte werden bei der Entschlüsselung erkannt.
URL-Fragment
Der Teil einer URL nach dem #-Zeichen. Wird laut HTTP-Spezifikation nie an den Server gesendet und nur clientseitig vom Browser verarbeitet. Eignet sich dadurch als Kanal für Daten, die der Server nicht sehen soll.
Base64url
Eine URL-sichere Variante der Base64-Kodierung, die + durch - und / durch _ ersetzt. Kann ohne URL-Encoding direkt in URLs verwendet werden.
WebCrypto API
Eine Browser-API für kryptografische Operationen wie Verschlüsselung, Hashing und Schlüsselgenerierung. Läuft nativ im Browser ohne externe Libraries und unterstützt unter anderem AES-GCM, RSA und ECDSA.
Atomare Operation
Eine Operation, die entweder vollständig oder gar nicht ausgeführt wird — ohne Zwischenzustände. In Datenbanken verhindert Atomarität, dass parallele Requests inkonsistente Ergebnisse erzeugen.
Content Security Policy
Ein HTTP-Header, der dem Browser vorschreibt, welche Ressourcen eine Seite laden darf. script-src 'self' erlaubt nur Scripts von der eigenen Domain und blockiert damit eingeschleusten Code (XSS).
Rate Limiting
Ein Schutzmechanismus, der die Anzahl der Anfragen pro Zeiteinheit begrenzt. Verhindert Brute-Force-Angriffe und Token-Enumeration durch automatisierte Abfragen.