Kai Ole Hartwig
s3mail · Dokumentation

Zugang, Verschlüsselung, Zustand

s3mail bindet auf 127.0.0.1 und kennt keine Benutzer. Was den Zugang schützt, sind drei Prüfungen bei jeder Anfrage; was die Daten schützt, ist der Bucket selbst, und der Zustand liegt daneben, geteilt zwischen Rechnern ohne Sperre.

  • Token gegen Mitleser, Host gegen DNS-Rebinding, Origin gegen CSRF
  • Verschlüsselung: SSE-S3/SSE-KMS transparent, client-seitige KMS-Umschläge werden aufgemacht
  • Zustand: jede Änderung ein eigenes Objekt, kein If-Match, kein 412

Wer darf ran

Wer darf ran

s3mail bindet auf 127.0.0.1 und kennt keine Benutzer. Was den Zugang schützt, sind drei Prüfungen bei jeder Anfrage, jede gegen einen anderen Angriff:

Was das nicht ist: eine Anmeldung. Wer die Adresse mit dem Token hat, sieht das ganze Postfach. Das genügt für ein Programm, das neben dem Browser auf dem eigenen Rechner läuft, und trägt nicht weiter. --host auf eine öffentliche Adresse zu legen heißt, das Postfach ins Netz zu stellen; dann gehört ein Reverse-Proxy mit richtiger Auth davor. Und die Host-Prüfung schaltet sich dann selbst ab, sie ergibt nur auf Loopback Sinn.

Die Zugangsdaten zu AWS liegen als benanntes Profil in ~/.aws/credentials (chmod 600), nie in s3mails eigener Konfiguration.

Verschlüsselte Buckets

Es gibt zwei Sorten Verschlüsselung, und nur eine davon macht Arbeit.

Serverseitig (SSE-S3 / SSE-KMS) ist die Standardverschlüsselung des Buckets. S3 entschlüsselt beim GetObject selbst, s3mail merkt davon nichts. Bei SSE-KMS braucht der Zugang zusätzlich kms:Decrypt auf dem Schlüssel und kms:GenerateDataKey zum Schreiben. Beim Verschieben liest s3mail die Verschlüsselungseinstellung des Originals per HeadObject und gibt sie an CopyObject weiter; die Kopie landet also nicht versehentlich unter dem Standardschlüssel des Buckets. Die Speicherklasse reist genauso mit.

Client-seitig (die KMS-Option in der SES-Receipt-Rule): hier verschlüsselt SES die Mail, bevor sie in S3 landet. Im Bucket liegt dann kein MIME, sondern ein Umschlag: der Datenschlüssel steckt von KMS verpackt in den Objekt-Metadaten, der Inhalt ist mit AES-256 verschlüsselt. Ein normales GetObject liefert Kauderwelsch. s3mail erkennt das an den Metadaten und macht es auf: kms:Decrypt mit dem Encryption Context aus x-amz-matdesc, sonst lehnt KMS ab, dann AES-GCM oder AES-CBC.

Zwei Folgen im Betrieb: der Index kann keine Teilstücke mehr per Range-GET holen, ein halbes Chiffrat lässt sich nicht entschlüsseln, also lädt s3mail Objekte komplett. Und beim Verschieben bleibt der Umschlag unangetastet. Ein gemischtes Postfach ist kein Problem, das wird pro Objekt entschieden; fehlt kms:Decrypt, fällt nur die betroffene Mail als „nicht lesbar“ aus.

Auf der Platte

Was s3mail zwischenspeichert, ist mit AES-256-GCM verschlüsselt, mit einem Schlüssel, der nicht im Heimatverzeichnis steht, sondern im Schlüsselbund des Systems: der Anmeldeschlüsselbund unter macOS, DPAPI an das Windows-Konto gebunden, der Secret Service unter Linux. Ein Backup, ein Ordnersync oder eine Platte ohne FileVault nützt damit niemandem etwas. Gibt es keinen Schlüsselbund, startet s3mail nicht, sondern verlangt eine Entscheidung: --no-cache oder --cache-plaintext. Ein stiller Klartext-Cache würde genau die Zusage aufheben, die ein verschlüsselter Bucket gibt.

Zustand: geteilt, ohne Sperre

Tags, gelesen/ungelesen, Stern und Regeln stehen im Bucket, damit mehrere Rechner denselben Stand sehen. Geschrieben wird nicht das ganze Dokument, sondern die einzelne Änderung: jeder Schreibvorgang legt ein kleines Objekt unter <prefix>.s3mail-state/ ab, auf dessen Schlüssel nur er selbst schreibt.

 

mail/.s3mail-state.json                              ← Snapshot, selten geschrieben
mail/.s3mail-state/20260820T2131...-0001-a7f3.json   {"ops":[{"t":"flags",…}]}
mail/.s3mail-state/20260820T2131...-0002-b1c9.json   {"ops":[{"t":"tags",…}]}

 

Zwei Rechner können sich dabei nicht ins Gehege kommen: es gibt keinen gemeinsamen Schlüssel, auf den beide zeigen, und damit weder Sperre noch If-Match noch 412. Eine Mail als gelesen zu markieren kostet ein paar hundert Byte statt des ganzen Postfachs.

Gelesen wird der Snapshot plus alle Änderungen, die neuer sind als sein Wasserstand, in Schlüsselreihenfolge; die Schlüssel beginnen mit dem Zeitstempel. Ab 50 offenen Änderungen wird zusammengefasst. Bleibt beim Aufräumen ein Objekt liegen, weil das Löschen scheitert, wird es beim nächsten Laden übersprungen statt ein zweites Mal angewandt: Löschen ist Müllabfuhr, keine Buchhaltung.

Zwei Dinge, die du wissen solltest: die Uhr entscheidet die Reihenfolge, was nur bei Änderungen zählt, die aufeinander aufbauen; und alle Rechner sollten dieselbe Version fahren, eine ältere liest nur den Snapshot. Fehlt das Schreibrecht ganz, fällt s3mail auf einen lokalen Zustand zurück und zeigt das an.

Weiter

Zurück zu s3mail

Die Übersicht: was s3mail ausmacht, Loslegen, alle Kapitel.

s3mail →
Zurück zu s3mail

MCP, iPhone, Grenzen

Für ein Modell erreichbar, ohne Senden; die iOS-App; was s3mail nicht tut.

Lesen →
MCP, iPhone, Grenzen