docs(quick-260921-oxm): Akte - beide IMAP-Befunde behoben, Rot-Nachweis festgehalten
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
This commit is contained in:
+194
@@ -0,0 +1,194 @@
|
||||
---
|
||||
phase: quick-260921-oxm
|
||||
plan: 01
|
||||
subsystem: apps/api/src/inbox
|
||||
tags: [imap, imapflow, starttls, sicherheit, anhaenge, dkv, tdd]
|
||||
status: complete
|
||||
requires:
|
||||
- "260921-m34 (Befunde B-05 und B-06)"
|
||||
provides:
|
||||
- "ImapProvider erzwingt STARTTLS ueber die Option, die imapflow wirklich kennt"
|
||||
- "ImapProvider erkennt Anhangs-Dateinamen aus Content-Disposition"
|
||||
affects:
|
||||
- "apps/api/src/inbox/imap.provider.ts"
|
||||
- "apps/api/src/inbox/imap.provider.spec.ts"
|
||||
tech-stack:
|
||||
added: []
|
||||
patterns:
|
||||
- "Konstruktoroptionen einer Fremdbibliothek im Test gegen die uebergebenen Werte pruefen, nicht gegen eine echte Verbindung"
|
||||
key-files:
|
||||
created: []
|
||||
modified:
|
||||
- apps/api/src/inbox/imap.provider.ts
|
||||
- apps/api/src/inbox/imap.provider.spec.ts
|
||||
decisions:
|
||||
- "doSTARTTLS bei ssl-tls auf false statt weglassen: schliesst die Unvertraeglichkeit secure=true + doSTARTTLS=true aus und schaltet STARTTLS dort ausdruecklich ab"
|
||||
- "Einhaengen des Testdoppels in einen Helfer gezogen, damit die neuen Faelle ohne eigene Umdeutung auskommen"
|
||||
metrics:
|
||||
duration: "35 min (17:55 bis 18:30 Uhr, 21.09.2026)"
|
||||
completed: 2026-09-21
|
||||
actuals:
|
||||
tokens: 31000
|
||||
tasks: 2
|
||||
commits: 4
|
||||
plan_head_before: 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.
|
||||
Reference in New Issue
Block a user