← Zurück zum Blog
pullmdmarkitdownarchitecturesecuritycode-reviewclaude-code16. Juni 2026 · 8 Min. Lesezeit

PullMD Teil 9: Microsofts MarkItDown integriert — und drei Teile wieder rausgerissen

DOCS: SIDECAR · AUDIO/YT/VISION: OURS · NO SILENT EXTERNALS
Editorial-Diagramm: Dokumentformate (PDF, DOCX, PPTX, XLSX, EPUB) fließen in eine MarkItDown-Sidecar-Box, während drei eigene Pfade (Audio, YouTube, Vision) bewusst daran vorbei durch eigene Panels geführt werden; Warnschild 'No silent externals'Editorial-Diagramm: Dokumentformate (PDF, DOCX, PPTX, XLSX, EPUB) fließen in eine MarkItDown-Sidecar-Box, während drei eigene Pfade (Audio, YouTube, Vision) bewusst daran vorbei durch eigene Panels geführt werden; Warnschild 'No silent externals'

PullMD ist mein selbst gehostetes Tool, das Webseiten und Dateien in sauberen Text verwandelt. Die bisherigen Teile handelten vom Bauen, Härten und Veröffentlichen. Dieser Teil beginnt nicht mit einem Plan, sondern mit einem Juckreiz.

Der banale Juckreiz

Ich hatte ein paar HTML-Dateien auf der Festplatte, die ich Claude geben wollte. PullMD konnte schon Webseiten umwandeln. Warum also nicht auch lokale Dateien?

Die Lösung war ein kleines Feature: Datei hochladen, gleicher Ablauf wie bei einer Webseite. Die Schutzvorkehrungen drumherum kamen von Claude: keine Zwischenspeicherung für hochgeladene Dateien, der Dateiname so übertragen, dass er nicht in den Server-Protokollen auftaucht. Mein eigener Anteil war profaner: Ich habe schlicht keinen Sinn darin gesehen, eine Datei zu speichern, die man einmal per Drag-and-Drop umwandelt.

Beim Testen kam mir die naheliegende Frage:

„aber auf mobile könnte man ‚oder Datei öffnen’ daraus machen, oder? eine heruntergeladene html datei kann man auf mobile auch umwandeln wollen.“

Also wurde die Ablagefläche antippbar und öffnet seitdem einen Datei-Dialog. Desktop und Handy.

MarkItDown rein

Zwei Tage später stolperte ich über Microsofts MarkItDown. Eine Bibliothek unter der MIT-Lizenz (benannt nach der Hochschule, an der sie entstanden ist), die alle möglichen Dokumentformate in Text umwandelt: PDF, Word, PowerPoint, Excel, EPUB und mehr.

Wenn HTML-Dateien schon gehen, warum nicht auch diese Formate? Und warum nicht gleich YouTube-Transkripte? Wer einmal auf einer dieser überladenen Transkript-Seiten gelandet ist, weiß, warum man das lieber im eigenen Tool hätte.

Ich gab Claude das Projekt:

„Schau dir das folgende Repo an. Können wir es sinnvoll in PullMD integrieren?“

Der Plan leuchtete sofort ein. MarkItDown übernimmt nur die Dokumentformate, die PullMD noch nicht konnte. Für Webseiten wäre es ein Rückschritt gewesen.

Die Architektur: ein kleiner Hilfsdienst neben dem Hauptprogramm. In der Fachsprache heißt das Sidecar, wie ein Beiwagen am Motorrad. Er fährt mit, aber er lenkt nicht. PullMD ist ohnehin ein Verbund aus mehreren kleinen Diensten für verschiedene Aufgaben; der neue Sidecar springt ein, wenn ein Dokumentformat reinkommt, das die bestehende Verarbeitung nicht beherrscht.

So weit, so sauber. Eine fremde Bibliothek für die Fleißarbeit, denn Bürodokumente zu zerlegen ist niemandes Lieblingsbeschäftigung. Dann habe ich mir den Plan im Detail angesehen, und aus „rein damit“ wurde „rein, aber drei Teile davon nicht“.

Drei Teile wieder raus

Beim Lesen von Claudes Integrationsplan fielen mir zwei Dinge auf: Der YouTube-Teil fehlte kommentarlos, ausgerechnet der, der mich am meisten gereizt hatte. Und beim Audio stand „Privacy“ als Begründung, während der Plan trotzdem einen externen Dienst aufrief. Ich hakte nach:

„warum hast du YT-transcript weggelassen? … und was bedeutet ‚Privacy’, wenn du openai dennoch verwendest?“

Claudes Antwort ging die Gründe am Code entlang durch. Daraus wurde die eigentliche Arbeit dieses Releases: nicht das Einbauen, sondern das gezielte Wieder-Herausnehmen.

Audio. MarkItDowns Standard schickt Audiodateien still an Googles Spracherkennung im Internet. Bei einem selbst gehosteten Tool gehört diese Entscheidung dem Betreiber, nicht dem Standard. Also ein eigener Weg mit wählbarem Anbieter: lokal auf dem eigenen Rechner oder extern, je nachdem, was man bewusst einstellt. Bewusste Wahl statt stiller Standard.

YouTube. MarkItDowns YouTube-Baustein flog komplett raus. Claudes Blick in den Code fand: ein einziges gesperrtes Video reißt den ganzen Ablauf mit hoch. Man verliert sogar Titel und Zusatzinfos, obwohl die im Seitenquelltext längst dastanden. An die Stelle kam ein eigener YouTube-Handler, der bei Problemen sanft weitermacht statt alles mitzureißen.

Bild-Beschreibung. Statt MarkItDowns eigenen KI-Aufruf zu nutzen, läuft die Bilderkennung direkt im Hauptprogramm. Null Kopplung an MarkItDown für den KI-Teil: ein Update der Bibliothek kann daran nichts kaputtmachen.

Das Prinzip ging als Satz ins Projekt-Gedächtnis: MarkItDown nur für Dateitypen, fragile und externe Pfade bauen wir selbst.

Am nächsten Vormittag stellte ich die offene Frage:

„bauen wir unseren eigenen markitdown-Ersatz? wäre das schlechtes Design?“

Claudes Antwort war nein, und es war die richtige. Nicht den ganzen Zoo an Dokumentformat-Zerlegern nachbauen; den will niemand selbst pflegen. Aber die KI-Schicht gehörte heraus. Bilderkennung und Sprache-zu-Text wanderten komplett aus dem Sidecar ins Hauptprogramm. Meine Begründung dafür:

„ich finde es nicht gut, wenn wir unsere LLM-Calls so von MarkItDown abhängig machen. Wenn jemand gar keine Dokumente braucht, muss er trotzdem MarkItDown mitlaufen lassen.“

Der Sidecar schrumpfte damit auf das, was er gut kann: Dokumente und YouTube.

Beinahe wäre noch ein viertes Stück dazugekommen: Docling, eine Tabellen-Engine von IBM, die lokal die besten Ergebnisse liefert. Ich war kurz begeistert, bis Claude die Zahlen nachreichte: riesiges Installationspaket, mehrere Gigabyte Arbeitsspeicher pro Anfrage, über eine Minute Anlaufzeit. Den Ausschlag gab dann keine technische Abwägung:

„wenn dann so ein ewig langer warmup kommt, bin ich frustriert und schiebe es auf ‚was für ein schlechtes projekt, dieses pullmd’.“

Docling ist jetzt nur als steckbarer Anknüpfungspunkt dokumentiert, nicht im Standardpaket. Der Leitsatz dahinter: leichte Tür für alle, schwere Tür nur für die, die sie bewusst aufmachen.

Am Ende blieb genau ein echter Bruch mit der alten Version. Der umgewandelte Text enthält jetzt nur noch Titel und Inhalt. Quelle und Abrufdatum stehen ausschließlich im sogenannten YAML-Frontmatter. YAML ist ein einfaches Format für Metadaten, wie ein Etikett auf einem Aktenordner: Infos über den Inhalt, getrennt vom Inhalt selbst. Nichts ist mehr doppelt, KI-Assistenten lesen weniger. Wer das alte Format braucht, kann es per Einstellung zurückholen. Das machte aus dem Release v3.0.0.

Das neue Modell als Türsteher

Am Abend, als v3 fertig war und der Release-Tag geplant, erschien Fable 5: Anthropics neues Spitzenmodell.

Ein brandneues Modell, dessen Klasse fürs Schwachstellen-Finden gebaut ist, am Abend vor einem Release, das gerade einen öffentlichen Upload-Pfad dazubekommen hatte. Die naheliegendste erste Aufgabe konnte nur eine sein:

„mach bitte ein code review und finde bugs und sicherheitsprobleme.“

Code-Review bedeutet: jemand (oder in diesem Fall etwas) liest den gesamten neuen Code und sucht nach Fehlern und Schwachstellen.

Sieben echte Befunde, ein falscher Alarm. Die zwei, die mir am meisten zu denken gaben, betrafen Sicherheit:

Eine Lücke in der Datenaufbereitung. Die Funktion, die Text für den Kopfbereich vorbereitet, übersah ein bestimmtes unsichtbares Zeichen. YAML behandelt dieses Zeichen als Zeilenumbruch: ein Angreifer hätte über einen manipulierten Seitentitel eigene Daten einschleusen können. Der Fix war ein einzelnes Zeichen mehr in der Suchregel. Ein Ein-Zeichen-Loch. Geschrieben hatte den Code Claude, und durch jedes Review davor war es trotzdem gerutscht.

Eine Überlastungs-Attacke auf den Upload-Pfad. Beliebige Dokumente jagten ungeschützt durch den Verarbeiter, und der Sidecar war intern nicht abgesichert. DoS (Denial of Service, Dienstverweigerung) nennt man das: jemand schickt absichtlich so viel oder so Schwieriges, dass der Server für alle anderen aufhört zu funktionieren. Der Fix: jede Umwandlung läuft jetzt in einem Wegwerf-Prozess mit Zeitlimit und Speicherdeckel. Ein öffentlicher Upload-Pfad ohne solche Grenzen ist keine Sache, die man auf später verschiebt.

Dazu die Bugs: ein fehlender Schutz, der statt einer sauberen Fehlermeldung den ganzen Dienst zum Stolpern gebracht hätte. Ein Zwischenspeicher-Treffer, der bestimmte Zusatzinformationen verlor. Ein Werkzeug im MCP-Protokoll (Model Context Protocol, die Schnittstelle, über die KI-Assistenten das Tool direkt ansprechen können), das von der normalen Schnittstelle abgedriftet war. Und ein falscher Alarm: ein gemeldetes Datenleck, das keines war, weil die betroffenen Daten verworfen werden, bevor sie nach außen gelangen.

Auch das Lesen des Reviews bleibt Pflicht; ein Befund ist eine Hypothese, kein Urteil.

Am nächsten Morgen ein einzelnes kaputtes Bild auf einer Teilen-Seite. Ich zog daran, und es kam ein ganzer Schwung mit. Relative Bildadressen wurden nie gegen die Quellseite aufgelöst. Ein Zugangsschlüssel war seit einer früheren Version in keiner Konfigurationsdatei durchgeleitet. Und die Einstellung, mit der man den Formatbruch rückgängig machen konnte, war über keinen Weg erreichbar. Die wichtigste Lücke: ein Bruch ohne erreichbare Rückfalloption ist einfach nur ein Bruch.

Nach all dem standen 721 Tests plus 4 Sidecar-Schutztests auf Grün, von 644 beim Start des Arbeitsabschnitts.

Schaubild: Dokumentformate fließen links rein, das Hauptprogramm verteilt sie an verschiedene Hilfsdienste, rechts stehen optionale KI-Dienste, die nur aktiv werden, wenn der Betreiber sie bewusst einschaltet Schaubild: Dokumentformate fließen links rein, das Hauptprogramm verteilt sie an verschiedene Hilfsdienste, rechts stehen optionale KI-Dienste, die nur aktiv werden, wenn der Betreiber sie bewusst einschaltet
Die fertige v3-Architektur: alles in der Box bleibt in der Box. Die KI-Dienste rechts existieren nur, wenn der Betreiber sie bewusst einsteckt.

Draußen

Zusammenführen, Versionsnummer, Veröffentlichung. Minuten später der Beitrag auf Reddit. Kein Triumphton: das Ding ist draußen, fertig. Vor dem Hochladen lief noch ein Privatsphäre- und Geheimnisse-Scan über den ganzen Arbeitsabschnitt; das war mir wichtiger als saubere Ästhetik, aus naheliegenden Gründen, die in dieser Serie schon einmal ein eigenes Thema waren.

Die Aufteilung war über den ganzen Bogen dieselbe: ich habe die Entscheidungen getroffen, Claude hat analysiert und gebaut. Was diesen Teil von den vorigen unterscheidet, ist, dass die wichtigsten Entscheidungen nicht das Einbauen waren, sondern das bewusste Wieder-Herausnehmen. Die langweilige Hälfte habe ich abgegeben. Die heikle Hälfte habe ich behalten.

Eine fremde Bibliothek spart einem den ganzen Zoo an Dokumentformat-Zerlegern. Aber jeden Pfad, der entweder fragil ist oder still Daten nach außen schickt, übernimmt man besser nicht ungeprüft: den baut man selbst, oder man macht ihn wenigstens zur bewussten Wahl statt zum stillen Standard.


Teil 9 von 9 der PullMD Serie.

PullMD Teil 8: Eine alte Regel