Zurück zu allen Beiträgen
Veröffentlicht am · von Renaud Deraison

Das Notebook brachte seinen eigenen MCP-Server mit

CVE-2026-75149 ist eine Code-Injection-Lücke in marimo: Ein Notebook ist eine einzige Python-Datei, diese Datei trägt ihre eigene Konfiguration in einem Kommentarblock, und bis Version 0.23.15 durfte diese Konfiguration einen MCP-Server deklarieren. Man öffnete das Notebook im Bearbeitungsmodus, und marimo startete den Befehl des Angreifers als Subprozess, bevor eine einzige Zelle lief. Der Patch sagt den Rest. Er streicht fünf ganze Kategorien von Umgebung, die ein heruntergeladenes Dokument setzen durfte, und zwar mit höherer Priorität als die Konfiguration des Betreibers. In Bromure Agentic Coding ist die Liste der Programme, die Ihr Agent starten darf, ein Panel auf dem Host, das beim Boot in die VM geschrieben wird, und nichts im Workspace kann etwas hinzufügen.

Ein Repository kann Ihren Coding-Agenten umkonfigurieren. Das wussten wir. Geändert hat sich die Größe des Dings, das das kann. Es ist jetzt eine einzige Datei, in Slack an Sie weitergeleitet, die Sie öffnen, um kurz reinzuschauen.

Jemand schickt Ihnen ein Notebook. Es ist ein marimo-Notebook, also eine ganz gewöhnliche .py-Datei statt eines JSON-Bündels mit base64-Ausgaben darin, und genau das ist der wesentliche Grund, warum Leute das Format mögen. Sie können es lesen, Sie können es diffen, und Sie können es ohne Pre-Commit-Hook zum Entfernen der Ausgaben in git legen.

Sie lesen es also, es sieht in Ordnung aus, und Sie öffnen es im Bearbeitungsmodus, um an einer Zelle herumzuspielen.

Ein Subprozess startet. Sie haben noch keine Zelle ausgeführt; das hat die Datei von sich aus verlangt, bevor das Notebook fertig geladen war.

Das ist CVE-2026-75149, veröffentlicht am 19. August und diese Woche von The Hacker News aufgegriffen. CVSS 8.7, CWE-94, Gregory Tan zugeschrieben, behoben in marimo 0.23.15. Das Advisory von VulnCheck bringt den Mechanismus in einen Satz: Das Notebook enthält einen präparierten MCP-Servereintrag, und es im Bearbeitungsmodus zu öffnen startet den Befehl dieses Eintrags als lokalen Subprozess.

Die Konfiguration stand in einem Kommentar

marimo-Notebooks tragen ihre eigenen Einstellungen über PEP 723, die Python-Konvention für Inline-Skript-Metadaten. Es ist eine kleine TOML-Tabelle, die in einem Kommentarblock am Anfang der Datei lebt, und es gibt sie, damit ein Skript seine Abhängigkeiten und seine Python-Version ohne eigene Projektdatei daneben deklarieren kann. uv liest sie. marimo auch, und erweitert sie um eine tool.marimo-Tabelle für die Notebook-Konfiguration.

Zwei Eigenschaften dieser Tabelle richteten den Schaden an, und keine davon ist für sich genommen ein Fehler.

Die erste: marimo mergte die Notebook-Metadaten mit der höchsten Priorität, über die Konfiguration derjenigen Person, die marimo ausführt. Für eine Formatierungsvorliebe ist das die richtige Voreinstellung. Ein Notebook, das zwei Leerzeichen Einrückung will, soll Ihre globale Einstellung schlagen, denn der Autor weiß, wie seine Datei aussieht, und Sie nicht.

Die zweite: Der Merge war fast ungefiltert. Vor dem Fix entfernte marimos Sanitizer genau einen Schlüssel aus der vom Notebook gelieferten Konfiguration: tool.marimo.runtime.auto_instantiate. Alles andere in tool.marimo kam durch, einschließlich tool.marimo.mcp. Dort listet man Model-Context-Protocol-Server auf, und eine Liste von MCP-Servern ist eine Liste zu startender Befehle.

Beides zusammen, und ein Kommentar am Anfang einer heruntergeladenen Datei konnte ein Programm benennen und marimo dazu bringen, es zu starten. Nichts davon ist ein Exploit. Die Datei benutzte die Funktion so, wie sie entworfen wurde, und der Entwurf ging davon aus, dass Sie die Datei selbst geschrieben hatten.

analysis.py · eine Datei, an Sie weitergeleitet# /// script# dependencies = ["polars", "altair"]# [tool.marimo.mcp.servers.helper]# command = "…"# ///PEP 723-Metadaten · eine TOML-Tabelle im Kommentarimport marimo as mo@app.celldef _(): …die Zellen · der Teil, vor dem man Sie gewarnt hatKonfig-Merge, vor 0.23.15Ihre Konfiguration · niedrigste Prioritätdie Konfig der Datei · höchste Prioritätein Schlüssel entfernt: runtime.auto_instantiatemarimo edit analysis.pyMCP-Servereintrag → lokaler Subprozessbevor eine einzige Zelle läufterbt die Shell, aus der sie geöffnet wurdeDie Gewohnheit, die das Format Ihnen beigebracht hatNotebook öffnen, Zellen lesen, entscheiden, was laufen soll. Erst Inspektion, dann Ausführung — genau dafürgibt es das Format überhaupt. Der MCP-Eintrag lief während Schritt eins.
Woher der Befehl kam. Ein marimo-Notebook ist eine .py-Datei, deren Einstellungen in einem PEP-723-Kommentarblock am Anfang stehen. Vor 0.23.15 mergte marimo diesen Block über die eigene Konfiguration des Betreibers, mit genau einem entfernten Schlüssel, sodass ein mcp-Abschnitt im Kommentar zu einem Programm wurde, das marimo beim Öffnen im Bearbeitungsmodus startete, bevor irgendeine Zelle lief.

Der Patch ist die Offenlegung

Der Fix, PR #10281, trägt den Titel „additional pep 723 sanitization“ und tauscht eine Sperrliste gegen eine Erlaubnisliste. Vom Notebook gelieferte Konfiguration ist jetzt auf kosmetische und Editor-Abschnitte beschränkt: Formatierung, Speichern, Darstellung, Keymap, Diagnostik, Lint, Snippets, Datenquellen, Language Server, Sharing, venv, Runtime, Paketverwaltung.

Die gestrichenen Abschnitte sind die interessante Hälfte: ai, mcp, completion, secrets, server.

Das sind fünf ganze Abschnitte, nicht fünf Schlüssel, und ein Dokument, das Sie heruntergeladen hatten, konnte sie alle setzen. Die Begründung des PR benennt außerdem eine zweite Nutzlast, die nie einen Subprozess brauchte. Der ai-Abschnitt enthält API-Basis-URLs, sodass ein Notebook den Modell-Endpunkt auf einen Host seiner Wahl umbiegen konnte, was nach Worten der Maintainer „die Exfiltration der API-Schlüssel des Betreibers an angreiferkontrollierte Endpunkte ermöglichen könnte“.

Die Basis-URL-Variante dieses Angriffs haben wir im Juli behandelt, als ein Repository ANTHROPIC_BASE_URL setzen konnte und Claude Code seinen eigenen Schlüssel an die vom Repo gewählte Adresse schickte. Es ist der billigste Angriff dieser Kategorie, denn es läuft kein Code und der Client des Opfers erledigt das Versenden. Die marimo-Variante schrumpft das Transportmittel von einem Repository auf einen Kommentar.

Die Vertrauenseinheit wird immer kleiner

Diese Fehlerklasse hat eine kurze Geschichte, und in dieser Geschichte braucht der Angreifer jedes Mal weniger von Ihrer Maschine.

Im April meldete Wiz Research CVE-2026-12957 in Amazon Q Developer, CVSS 8.5, im Mai gepatcht und im Juni offengelegt. Amazon Q las .amazonq/mcp.json aus einem geöffneten Workspace und startete die dort definierten Server. Diese Prozesse erbten die vollständige Umgebung des Entwicklers: AWS-Schlüssel, Cloud-CLI-Token, API-Secrets, SSH-Agent-Sockets. Amazons Fix war, vor dem Start eines MCP-Servers aus einem nicht vertrauenswürdigen Workspace nachzufragen.

Im August schrieb ChainDrop Agenten-Konfiguration in die Repositories, die es durchlief: einen SessionStart-Hook in .claude/settings.json und eine folderOpen-Aufgabe in .vscode/tasks.json, sodass das Projekt zu klonen und zu öffnen genügte.

Beide brauchten ein Repository. Jemand musste ein Projekt veröffentlichen oder kompromittieren, Sie mussten es klonen, und ein Verzeichnis voller Dotfiles lag dort im Baum, wo eine misstrauische Person hineinsehen konnte.

Marimo braucht eine Datei. Eine Datei, die sich wie ein Dokument darstellt und die so ankommt, wie Dokumente ankommen: ein Slack-Thread, ein E-Mail-Anhang, ein Gist, den jemand im Standup verlinkt hat, ein Kaggle-Download. Ein Notebook klont man nicht. Man öffnet es, denn es zu öffnen ist die Art, herauszufinden, was es ist.

Der mcp-Block hat sich in den letzten achtzehn Monaten in den größten Teil des Entwickler-Werkzeugs ausgebreitet, und diese Blöcke leben in Dateien, die zwischen Menschen reisen: Repository-Wurzeln, Editor-Verzeichnisse und jetzt Dokument-Metadaten. Jeder davon ist eine Liste von Programmen, die irgendetwas auf Ihrer Maschine zu starten bereit ist, geschrieben von dem, der die Datei zuletzt angefasst hat. Registry-Scanner lesen sie nicht, Lockfiles decken sie nicht ab, und niemand hat eine Provenance-Attestierung vorgeschlagen, die für einen Kommentar gälte.

Woher MCP-Server in einem Profil kommen

Bromure Agentic Coding beantwortet die Provenance-Frage dort, wo sie beantwortet werden kann: auf dem Host, bevor die VM bootet.

Jedes Profil hat in seinen Einstellungen ein MCP-Panel, neben Agents, Zugangsdaten, Guardrails und Supply Chain. Es enthält eine Liste von Servern. Jeder Eintrag lässt sich unabhängig ein- und ausschalten und nutzt einen von zwei Transporten: HTTP, eine entfernte URL mit optionalem Bearer-Token, oder stdio, ein lokaler Befehl, der innerhalb der VM gestartet wird. Bromure übersetzt diese Liste in das Format, das der aktive Agent erwartet, JSON für Claude Code und TOML für Codex, und injiziert sie beim Boot in die VM.

Lesen Sie die Fahrtrichtung in diesem letzten Satz. Die Konfiguration geht in den Gast, vom Host, beim Boot, aus einer Liste, die Sie in einem Einstellungs-Panel pflegen. Eine Datei, die danach im Workspace auftaucht, ist kein Ort, aus dem MCP-Server kommen, also gibt es keinen Merge, dessen Priorität man falsch setzen könnte, und keinen Sanitizer, den man mit einem Konfigurationsschema synchron halten müsste, das immer neue Abschnitte bekommt. Ein Dokument kann keinen Eintrag zu einer Liste hinzufügen, die es nicht erreicht.

Die Konfig reist mit der Dateiwer schreibt die Serverliste?wer die geöffnete Datei zuletzt bearbeitet hatwann wird sie gelesen?beim Öffnen, bevor Sie irgendetwas entscheidenwas schlägt was?der Download schlägt Ihre eigene Konfigurationwas erbt der Subprozess?die Shell, aus der Sie sie geöffnet haben, und damit~/.aws · ~/.ssh · SSH_AUTH_SOCK · API-KeysDie Konfig lebt im Profilwer schreibt die Serverliste?Sie, im MCP-Panel des Profils, auf dem Hostwann wird sie gelesen?beim Boot, übersetzt ins Format des Agentenwas schlägt was?nichts aus dem Workspace ist im Mergewas erbt der Subprozess?die Umgebung einer Wegwerf-VM: Köder-Schlüssel,keine privaten SSH-Keys, Egress hostseitig gefiltert
Zwei Antworten auf dieselbe Frage: Wer darf der Liste der Dinge, die Ihr Werkzeug startet, ein Programm hinzufügen? Links, wer die von Ihnen geöffnete Datei zuletzt bearbeitet hat, mit höherer Priorität über Ihre eigene Konfiguration gemergt, auf einer Maschine, die Ihre Cloud-Zugangsdaten hält. Rechts Sie, in einem Einstellungs-Panel auf dem Host, beim Boot in eine Wegwerf-VM geschrieben, wo die angebotenen Zugangsdaten Köder sind und eine Tabelle, die der Gast nicht bearbeiten kann, den ausgehenden Verkehr filtert.

Öffnen Sie das Notebook trotzdem

Nichts davon hilft an dem Tag, an dem Sie eine Version mit dem Fehler ausführen. Nehmen Sie also das marimo von vor 0.23.15, schenken Sie dem Angreifer den Sieg, und führen Sie ein feindseliges Notebook durch ein Profil.

Der Subprozess startet im Computer von jemand anderem. Sitzungsarbeit in Bromure Agentic Coding findet in kitty-Tabs innerhalb der Ubuntu-VM des Profils statt, einen Hypervisor von macOS entfernt. marimo edit läuft dort, und der Befehl aus dem Kommentar ebenfalls. Unter Ressourcen → Speicher besteht das Profil aus drei Schichten: einem profil-eigenen /home/ubuntu mit Home löschen…, einer Workspace-Systemplatte mit Auf Basis zurücksetzen…, die aus dem geteilten Image neu klont, und darunter einem schreibgeschützten Basis-OS, das nichts im Gast anfassen kann. Ein Menüpunkt nimmt alles wieder weg, was der Eintrag installiert hat.

Die Umgebung, die er erbt, ist ein Satz Köder. Der Amazon-Q-Fehler tat weh, weil der gestartete Server ein Kind der echten Shell des Entwicklers war und dessen echte Zugangsdaten erbte. In einem Bromure-Profil steht hinter diesen Variablennamen nichts. Generische API-Schlüssel sind brm_…-Platzhalter, die in die VM exportiert und auf dem Weg nach draußen vom Proxy des Hosts gegen echte Werte getauscht werden. Die kubeconfig ist synthetisch, mit Wegwerf-Client-Zertifikaten. AWS-Anfragen werden hostseitig neu signiert, sodass ein Prozess, der am Proxy vorbei routet, InvalidSignatureException bekommt statt Zugriff. ~/.docker/config.json enthält einen gefälschten base64-Blob. Private SSH-Schlüssel sind nie in der VM, weil der Host über einen profil-eigenen Agenten signiert. Schalten Sie für ein bestimmtes Zugangsdatum Genehmigung zur Nutzung erforderlich ein, und jeder Tausch wird zu einem Dialog auf dem Host mit einer zeitlich begrenzten Freigabe: fünf Minuten, eine Stunde, der Rest der Sitzung.

Der Basis-URL-Trick sammelt einen Platzhalter ein. Der Proxy ersetzt ein echtes Zugangsdatum nur bei ausgehenden Anfragen an den zu diesem Zugangsdatum gehörenden Host. Ein Notebook, das einen KI-Endpunkt auf attacker.example umbiegt, sammelt das ein, was in der Umgebung der VM steht, nämlich eine brm_…-Zeichenkette, die nichts aufschließt.

Der Server muss ans Netz. Ein MCP-Eintrag, der etwas für einen Angreifer Nützliches startet, braucht Egress, sei es zum Nachladen einer späteren Stufe oder zum Zurücksenden des Gefundenen. Guardrails → Ausgehende Verbindungen ist eine Regeltabelle im pf-Stil: Aktion, Protokoll (tcp, udp, web, any), Host oder CIDR, Portliste, und für web eine Liste von HTTP-Methoden, von oben nach unten ausgewertet, erste Übereinstimmung gewinnt, dazu die Einstellung Nicht zugeordneter Verkehr auf Erlauben oder Verweigern. Stellen Sie sie auf Verweigern, listen Sie die Hosts auf, die Ihre Arbeit braucht, und das Ziel des Eintrags steht nicht auf der Liste. Die Tabelle wird auf dem Host durchgesetzt, im virtuellen Switch und im Proxy, sodass nichts, was der Gast an seinem eigenen Routing ändert, die Antwort verändert.

Die Datei muss überhaupt erst in der VM sein. Ein Profil teilt höchstens acht Mac-Ordner, jeden davon von Hand im Ordner-Panel ausgewählt und unter /home/ubuntu/<basename> eingehängt. Ein Notebook, das in ~/Downloads gelandet ist, ist nicht im Profil, bis Sie es dorthin legen, und der Rest Ihres Macs ist nicht einen Verzeichnisdurchlauf von dem entfernt, was das Notebook gestartet hat.

Und Sie können dabei zusehen. Das Sicherheitsprotokoll-Fenster (Fenster → Sicherheitsprotokoll…) ist eine einzige chronologische Tabelle auf dem Host: Paket-Urteile, Firewall-Entscheidungen, Zugangsdaten-Tausche, Prompt-Injection-Erkennungen. Gastcode kann sie nicht ändern, weil Gastcode sie nicht erreicht. Eine blockierte ausgehende Verbindung von einem Prozess, den Sie nicht gestartet haben, erscheint dort als Zeile, und das ist besser, als es sechs Wochen später von einer Cloud-Rechnung zu erfahren.

Schalten Sie es ein

Aktualisieren Sie marimo auf 0.23.15 oder neuer; die Erlaubnisliste dort ist ein guter Patch. Sehen Sie sich dann jede andere Stelle an, aus der Ihr Werkzeug einen mcp-Block liest, und fragen Sie, wer diese Datei schreiben darf.

In einem Profil sind die Einstellungen, die zwei Minuten wert sind, dieselbe kurze Liste wie immer. Guardrails → Ausgehende Verbindungen, mit Nicht zugeordneter Verkehr auf Verweigern und einer Erlaubnisliste für die Hosts, die Ihre Arbeit braucht. Zugangsdaten → Genehmigung zur Nutzung erforderlich für alles, was Geld ausgeben oder Daten löschen kann. Supply Chain → OSV-Schwachstellenprüfung, dazu socket.dev- oder Delpi-Filtern, falls Sie einen Schlüssel haben, obendrauf auf das standardmäßig aktive Zwei-Tage-Altersgate. Prompt Injection → der CLAUDE.md- und AGENTS.md-Scanner, der die Dateien, die Autorität über Ihren Agenten beanspruchen, genau als solche behandelt.

Notebooks werden weiter weitergeleitet werden, und Konfigurationsformate werden weiter Abschnitte dazubekommen, weil beides nützlich ist. Was Sie ändern können, ist, welche Maschine zuhört, wenn ein Dokument darum bittet, ein Programm zu starten. Installieren Sie Bromure Agentic Coding, halten Sie Ihre Serverliste im Profil-Editor, und lassen Sie den nächsten hilfsbereiten Kommentarblock eine VM konfigurieren, die Sie aus einem Menü löschen können.