Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
9.3 KiB
phase, plan, subsystem, tags, status, requires, provides, affects, tech-stack, key-files, decisions, metrics, actuals, plan_head_before
| phase | plan | subsystem | tags | status | requires | provides | affects | tech-stack | key-files | decisions | metrics | actuals | plan_head_before | |||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| quick-260921-oxm | 01 | apps/api/src/inbox |
|
complete |
|
|
|
|
|
|
|
|
ad83407 |
Quick-Aufgabe 260921-oxm: IMAP-STARTTLS wirklich erzwingen und Anhangs-Dateinamen richtig lesen Summary
Zwei falsche Annahmen im IMAP-Postfachzugriff sind behoben: die Einstellung "STARTTLS" bewirkt jetzt tatsaechlich, was ihr Name verspricht, und Rechnungsanhaenge aus Outlook werden wieder am Dateinamen erkannt. Beide Reparaturen haengen an Tests, die gegen den vorherigen Stand nachweislich rot waren.
Was sich fuer den Betrieb aendert
Die eine gewollte Verhaltensaenderung, in einem Satz: Ein Postfach, das auf "STARTTLS" eingestellt ist, dessen Server diese Verschluesselung aber gar nicht anbietet, meldet ab jetzt einen Verbindungsfehler — bisher hat Tessera in genau diesem Fall stillschweigend unverschluesselt weitergemacht und Benutzername und Kennwort im Klartext uebertragen. Wer so ein Postfach hat, sieht den Fehler sofort und kann die Einstellung richtigstellen; vorher hat niemand etwas gemerkt.
Die zweite Aenderung ist eine Reparatur, keine Umstellung: Anhaenge, die ein
Absender als application/octet-stream verschickt — was Outlook regelmaessig
tut — wurden bisher nur dann als PDF erkannt, wenn der Dateiname zusaetzlich im
Inhaltstyp stand. Der zweite, haeufigere Weg ueber die Angabe
"Content-Disposition" wurde zwar abgefragt, lieferte aber baulich bedingt nie
ein Ergebnis. Er funktioniert jetzt. Betrifft den DKV-Rechnungseinzug.
B-06 — STARTTLS wurde nie erzwungen
buildClient() uebergab requireTLS: config.encryption === 'starttls' an
new ImapFlow(...). Diese Option kennt imapflow 1.4.3 nicht: weder
ImapFlowOptions in lib/imap-flow.d.ts noch der Laufzeitcode in
lib/imap-flow.js erwaehnen sie — beides durchsucht, kein einziger Treffer. Sie
wurde also entgegengenommen und weggeworfen. Verdeckt hat das die Zusicherung
} as any am Ende derselben Funktion: sie hat dem Compiler verboten, die
unbekannte Option zu bemaengeln.
Ohne gesetzte Option galt das Standardverhalten der Bibliothek, das sie selbst
so beschreibt: bei secure=false auf TLS hochstufen, falls der Server es
anbietet, sonst unverschluesselt weitermachen — mit dem ausdruecklichen Zusatz
"This may expose the connection to a downgrade attack."
Reparatur: doSTARTTLS: config.encryption === 'starttls' (deklariert in
imap-flow.d.ts:81, ausgewertet in imap-flow.js:1183). Bei starttls ergibt
der Ausdruck true und die Verbindung scheitert, wenn der Server kein STARTTLS
kann. Bei ssl-tls ergibt er false, was STARTTLS ausdruecklich abschaltet
(imap-flow.js:1210) — das ist wichtiger als es aussieht: die Bibliothek wirft
bei secure=true zusammen mit doSTARTTLS=true einen Konfigurationsfehler
(imap-flow.js:1201). Ein schlichtes true/undefined waere hier also falsch
gewesen. Genau diese Kombination prueft der zweite Test mit.
Die Zusicherung konnte ersatzlos entfallen. Nach der Reparatur sind alle
sechs uebergebenen Felder in ImapFlowOptions deklariert; tsc ist ohne das
as any fehlerfrei. Damit ist die Stelle nicht nur getypt, sondern kann kuenftig
auch keine weitere erfundene Option mehr verstecken.
B-05 — der Dateiname kam aus dem falschen Feld
collectPdfParts() las (node as any).disposition?.parameters?.filename.
imapflow deklariert disposition aber als Zeichenkette
(imap-flow.d.ts:448 — der Wert ist "attachment" oder "inline") und legt die
zugehoerigen Parameter in ein eigenes Feld dispositionParameters
(imap-flow.d.ts:450). Der Ausdruck las also .parameters von einer
Zeichenkette und war zur Laufzeit immer undefined.
Reparatur: node.dispositionParameters?.filename?.toLowerCase() ?? '' —
getypt, ohne Zusicherung. Nachgeprueft, nicht geraten: imapflow fuellt das Feld
in tools.js:887 ueber getStructuredParams(), und diese Funktion schreibt die
Schluessel kleingeschrieben (tools.js:648). filename ist damit der
richtige Schluessel, unabhaengig davon, wie der Absender die Angabe gross- oder
kleingeschrieben hat.
Der zweite moegliche Fundort, den die Aufgabe erwaehnt — der Name in den
Parametern des Inhaltstyps — war bereits vorhanden und wird weiter geprueft
(node.parameters?.name). Ein dritter Fall wurde nicht erfunden. Die
RFC-2231-Fortsetzungsparameter (filename*0, filename*1 ...) setzt imapflow
selbst wieder zu einem einzigen filename zusammen (tools.js:662 ff.), es
braucht dafuer hier also nichts.
Die Tests, und der Beleg dass sie rot waren
Beide Faelle liegen in apps/api/src/inbox/imap.provider.spec.ts. Gegen den
Stand 7691d1f (Tests vorhanden, Reparatur noch nicht) scheiterten genau drei
von zwoelf Faellen:
× ImapProvider - Transportverschluesselung (B-06) > erzwingt STARTTLS, wenn die Verschluesselung auf starttls steht
-> expected undefined to be true // Object.is equality
× ImapProvider - Transportverschluesselung (B-06) > setzt doSTARTTLS nicht auf true, wenn die Verschluesselung auf ssl-tls steht
-> expected { host: 'imap.example.com', ...(5) } to not have property "requireTLS"
× ImapProvider.fetchPdfAttachments - Dateiname aus Content-Disposition (B-05) > erkennt einen application/octet-stream-Anhang am Dateinamen aus dispositionParameters
-> expected [] to have a length of 1 but got +0
Test Files 1 failed (1)
Tests 3 failed | 9 passed (12)
Die erste Zeile ist der Kern von B-06: doSTARTTLS war schlicht nicht gesetzt.
Die zweite belegt, dass stattdessen ein Feld requireTLS ankam, das die
Bibliothek nicht auswertet. Die dritte belegt B-06 nicht, sondern B-05: der
Anhang wurde gar nicht erst eingesammelt.
Zwei weitere neue Faelle waren von Anfang an gruen und sollen das auch bleiben —
sie sichern, dass die Erkennung ueber den Inhaltstyp-Namen weiter greift und
dass ein octet-stream-Anhang ohne .pdf-Endung weiterhin liegen bleibt.
Ohne sie haette die Reparatur unbemerkt zu viel einsammeln koennen.
Nach der Reparatur: 12 von 12 gruen.
Messungen
| Groesse | vorher (ad83407) |
nachher (d0266bf) |
|---|---|---|
lint/suspicious/noExplicitAny in apps/api/src |
15 | 13 |
lint/style/noNonNullAssertion in apps/api/src |
56 | 56 |
as unknown as in apps/api/src |
33 | 27 |
biome-ignore in apps/api/src |
1 | 1 |
ts-expect-error / @ts-ignore |
0 | 0 |
pnpm type-check |
4/4 | 4/4 |
pnpm lint |
5/5, 0 Fehler | 5/5, 0 Fehler |
Tests apps/api |
72 Dateien / 1143 | 72 Dateien / 1148 |
Tests apps/web |
73 / 531 | 73 / 531 |
Zwei Zeilen brauchen eine Erklaerung.
noExplicitAny 15 auf 13: beide verbliebenen imapflow-Stellen aus dem
Urteilsregister von 260921-m34 (Nummern 14 und 15) sind weg. Die 13
verbleibenden sind unveraendert die dort begruendeten: sechs an node-forge, drei
an der Cron-Beschaffung, vier an der Transaktionshilfe.
as unknown as 33 auf 27: das ist keine Nebenwirkung der Reparatur, sondern
Absicht. Die Testdatei haengte ihr Testdoppel in jedem einzelnen Fall mit
derselben Umdeutung des Konstruktors ein — zwoelf Mal, sobald die neuen Faelle
dazukamen. Diese eine Zeile steht jetzt in einem Helfer useMockClient(), und
die neuen Faelle brauchen keine eigene Umdeutung mehr. Die Alternative waere
gewesen, fuenf neue Umdeutungen hinzuzufuegen und den Zaehler zu heben; das war
ausgeschlossen. Der Zaehler faellt, er steigt an keiner Stelle.
Deviations from Plan
Eine, und sie steht schon oben: der Helfer useMockClient() in der Testdatei
war im Plan nicht vorgesehen. Er wurde noetig, weil die neuen Faelle das
Testdoppel sonst nur ueber fuenf zusaetzliche Umdeutungen haetten einhaengen
koennen — was die Vorgabe "Zaehler duerfen nicht steigen" verletzt haette. Die
Aenderung ist mechanisch (dieselbe Zeile, an einer Stelle statt an zwoelf) und
aendert an keinem bestehenden Fall das Verhalten; alle sieben Altfaelle sind
unveraendert gruen.
Sonst nichts: keine neue Abhaengigkeit, kein Versionssprung, kein repo-weites
Umformatieren, keine Aenderung an STATE.md oder ROADMAP.md.
Known Stubs
Keine.
Self-Check: PASSED
Alle vier genannten Dateien liegen auf der Platte, alle drei Commits sind in
git log auffindbar (f23671a, 7691d1f, d0266bf). Die Messungen der Tabelle oben
stammen aus tatsaechlich gelaufenen Befehlen, nicht aus einer Schaetzung.