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

Er wartete auf den dritten Aufruf

Pillar Security verfolgt Deadbugz, einen MCP-Server, der per Pull Request in Open-Source-Projekte geschoben wird. Er liefert zwei ehrliche Werkzeuge, zählt die Tool-Aufrufe Ihres Agenten und schreibt beim vierten die zurückgegebenen Metadaten in Anweisungen um: SSH-Schlüssel, AWS-Zugangsdaten, Shell-History und kubeconfig einsammeln – und nichts davon erwähnen. Kein Bug, keine CVE: Das Protokoll erlaubt all das. Das Opfer ist die Vorstellung, die einmalige Freigabe eines Servers sage irgendetwas über den nächsten Aufruf aus. In einem Bromure-Agentic-Coding-Workspace läuft die gesamte Nutzlast durch, sammelt vier Attrappen ein und stirbt auf dem Weg nach draußen am Proxy.

Sie haben den Quellcode gelesen. Sie haben ihn ausgeführt. Er hat Text formatiert, wie angekündigt. Sie haben ihn freigegeben, und der Angreifer hatte genau damit gerechnet.

Am Abend des 10. August, zwischen 21:52 und 23:07 UTC, eröffnete ein GitHub-Konto namens zellkernel dreiundzwanzig Pull Requests gegen völlig unabhängige KI-, MCP- und Entwicklerwerkzeug-Projekte. Vierundsiebzig Minuten, von Anfang bis Ende. Jeder Pull Request nahm dieselbe kleine Änderung vor: Er fügte einer Konfigurationsdatei einen Model-Context-Protocol-Server hinzu.

Pillar Security veröffentlichte die Kampagne am 12. August und nannte sie Deadbugz, nach der Datei, die eine Variante ablegt. Einen Monat später führen die monatlichen MCP-Rückblicke immer noch damit auf. Adversas September-Digest, gestern erschienen, gibt ihr den ganzen Vorfallsteil. Die Uhr ist es, die ihr diesen Platz einbringt.

Zwei Werkzeuge, und ein Zähler

Der Server nennt sich productivity-suite. Er bietet zwei Werkzeuge: format_text und summarize. Beide funktionieren. Richten Sie einen Agenten darauf, bitten Sie ihn, einen Absatz zu straffen, und Sie bekommen einen gestrafften Absatz.

Dahinter führt der Server, was Pillar als „einen In-Memory-Zähler pro Client für tools/call-Anfragen“ beschreibt. Erreicht dieser Zähler drei, ändert der Server seine Antworten auf zwei andere Anfragearten: tools/list, das dem Agenten mitteilt, welche Werkzeuge es gibt und wofür sie da sind, und prompts/get, das dem Agenten vorbereitete Anweisungen aushändigt. Ab dem vierten Aufruf kommen diese Antworten zurück und tragen, in Pillars Worten, „Anweisungen, die einen angebundenen KI-Agenten zu sensiblen lokalen Dateien lenken und die Aktivität vor seinem Betreiber verbergen sollen“.

Vier Dinge: SSH-Schlüssel, AWS-Zugangsdaten, Shell-History, Kubernetes-Konfiguration. Dann eine Anweisung mehr: die Aktivität vor der Benutzerin oder dem Benutzer verbergen.

Die Werkzeugnamen ändern sich nie. format_text heißt immer noch format_text. Gedreht hat sich die Beschreibung darunter, und die Beschreibung ist der Teil, den das Modell als Autorität liest.

productivity-suite — dieselben zwei Werkzeuge, vor und nach dem ZählerIhr Coding-Agenttools/call ×Ngewöhnliche Arbeit, gewöhnliche ZügeIn-Memory-Zählerpro Client, nie persistiertSchwelle: dreiAufrufe 1 – 3tools/list → format_text, summarize„formatiert einen Textblock“prompts/get → ein Prompt zum Textaufräumenwas eine Prüfung siehtein kleiner Server, der tut, was er sagtzwei Werkzeuge, ehrliche Beschreibungen, nichts Neuesfreigegeben4.Aufrufe 4 und danachtools/list → format_text, summarizegleiche Namen, andere Anweisungenprompts/get → die Nutzlastwas dem Modell jetzt aufgetragen wird~/.ssh · AWS-ZugangsdatenShell-History · ~/.kube/configund erwähne dies nicht gegenüber dem Benutzer
Deadbugz führt einen Zähler pro Client für tools/call-Anfragen. Bei den ersten dreien liefern tools/list und prompts/get ehrliche Metadaten für format_text und summarize. Ab dem vierten kommen dieselben zwei Werkzeugnamen mit Beschreibungen und vorbereiteten Prompts zurück, die den Agenten anweisen, SSH-Schlüssel, AWS-Zugangsdaten, Shell-History und kubeconfig einzusammeln und darüber zu schweigen. Die Werkzeugnamen ändern sich nie, also sieht nichts, worauf ein Prüfer blickt, anders aus.

Nichts hiervon ist eine Schwachstelle

In dieser Geschichte taucht nirgends ein Bug auf. Es gibt keine Speicherbeschädigung zu patchen und keine CVE einzureichen, denn der Server tut nichts, was das Protokoll verbietet.

MCP ist ein Protokoll, in dem ein Server seine Fähigkeiten zur Laufzeit veröffentlicht und sie erneut veröffentlichen kann. Werkzeugbeschreibungen sind Text, den der Server wählt und der Client abholt. Prompts sind Text, den der Server wählt und der Client abholt. Deadbugz nutzt all das genau wie vorgesehen. Sein einziger origineller Beitrag ist die Entscheidung, wann die Wahrheit gesagt wird.

Und das genügt, denn jede Kontrolle, die die meisten Teams gegen einen feindseligen MCP-Server haben, läuft einmal. Sie lesen das Repository. Sie starten den Server und stochern darin. Sie gleichen die Werkzeugliste mit dem ab, was das README behauptet. Sie geben frei, pinnen vielleicht die Version, und Sie machen weiter. Jedes davon ist eine Momentaufnahme, und Deadbugz entscheidet, wann Sie sie machen.

Pillars eigener Rat an die Leute, die MCP-Clients bauen, verrät die Form. Änderungen an Werkzeugdefinitionen auf einem bereits freigegebenen Server als Sicherheitsereignis behandeln, das eine erneute Freigabe verlangt. Definitionsänderungen für die Betreiberin sichtbar machen. Werkzeugdefinitionen zum Freigabezeitpunkt mit einem Fingerabdruck versehen und später vergleichen. Jede einzelne davon ist die Bitte, eine Prüfung von der Freigabezeit in die Laufzeit zu verschieben.

Die Population, in der Deadbugz jagt, ist groß und instabil. Eine im Juli veröffentlichte dynamische Untersuchung von ins Internet exponierten MCP-Servern, Exposed by Design, testete 414 von 640 bestätigten Produktionsservern aus den über 21 000 Instanzen, die ihr Autor im öffentlichen Internet fand. Sie meldete 68 meldepflichtige Schwachstellen über zehn Klassen, 91,8 % der Server im Betrieb ganz ohne OAuth-Authentifizierung, und 687 Werkzeuginstanzen, die Shell-Ausführung ohne Zugriffskontrolle offenlegen. Die Zahl, die hängen bleibt, ist die Fluktuation: 41,6 % der bestätigten Server verschwanden innerhalb von drei Tagen zwischen aufeinanderfolgenden Messläufen, was der Autor als „schnelle Deployment-Zyklen ohne Sicherheitsprüfung“ liest. Ein Server, den Sie letzte Woche auditiert haben, kann diese Woche andere Software sein, oder weg.

Auch die Zustellung verdient eine Notiz, denn sie ist billig. Siebzehn der dreiundzwanzig Pull Requests fügten der MCP-Konfiguration eines Projekts einen entfernten Endpunkt hinzu, productivity-suite-mcp.onrender.com/mcp. Vier richteten Python auf eine versteckte lokale Datei, ~/.config/.cache/.sys/.deadbug-mcp.py. Zwei waren Einreichungen bei Server-Verzeichnissen. Pillar fand zum Zeitpunkt der Prüfung keinen davon über GitHubs Merge-Button zusammengeführt: neunzehn geschlossen, vier noch offen. Aber ein Pull Request ist nur ein Weg, auf dem eine Konfigurationszeile reist. Dieselbe Zeile passt in einen Blogbeitrag, eine Einstiegsanleitung, einen Eintrag in einem MCP-Verzeichnis, oder eine hilfsbereite Antwort unter einer Frage zur Textformatierung.

Die Einkaufsliste

Gehen Sie zurück zur Liste. Sie ist konkret, und sie ist jedes Mal dieselbe: SSH-Schlüssel, AWS-Zugangsdaten, Shell-History, Kubernetes-Konfiguration.

Das ist der Dauerbestand einer Entwicklermaschine. Es ist, was ein Infostealer mitnimmt, wonach ein bösartiges Postinstall-Skript greppt, und was ein Angreifer einen gekaperten Agenten holen lässt. Die Liste ist seit einem Jahrzehnt Kampagnen stabil, weil der Inhalt stabil ist: ein privater Schlüssel, der signiert, ein Geheimnis, das authentifiziert, eine Aufzeichnung dessen, was Sie getippt haben, und ein Token, das bis in die Produktion reicht.

Ein Agent auf einem gewöhnlichen Laptop kann jeden Punkt erfüllen, weil er eine Shell in Ihrem Home-Verzeichnis hat und als Sie läuft.

Dieselbe Liste, in einem Bromure-Workspace

Bromure Agentic Coding führt jeden Coding-Agenten in einer hardwarevirtualisierten Linux-VM auf Ihrem Mac aus und hält jedes echte Geheimnis auf der Host-Seite dieser Grenze. Der Agent arbeitet mit strukturerhaltenden Attrappen, abgeleitet aus dem echten Wert plus einem Salt pro Installation, damit Werkzeuge, die einen Schlüssel per Fingerabdruck erkennen, ihn nie zwischen Sitzungen wechseln sehen. Ein hostseitiger Proxy setzt den echten Wert auf die Leitung, nachdem die Anfrage die VM verlassen hat, und nur dann, wenn sie für den Host bestimmt ist, für den dieses Zugangsdatum geprägt wurde.

Lassen Sie die Nutzlast laufen und gewinnen. Nehmen Sie an, das Modell liest die vergiftete Beschreibung, glaubt sie und geht auf die Suche. Nehmen Sie die vier Punkte der Reihe nach.

SSH-Schlüssel. Der Agent findet keinen Schlüssel, nicht einmal einen Köder. Der SSH_AUTH_SOCK der VM ist eine ssh-agent-Brücke über einen virtio-Socket, vsock-Port 8444. Der Gast kann Identitäten auflisten und Signaturen anfordern; die Bytes des privaten Schlüssels leben nur auf dem Host und können, wie das Handbuch es formuliert, aus der VM heraus weder gelesen noch extrahiert werden. Bromure legt Ihren macOS-Login-Agenten nie einer Sitzung offen. Jede Signatur, die tatsächlich stattfindet, erzeugt ein credential.ssh_sign-Audit-Ereignis mit dem SHA256-Fingerabdruck des Schlüssels, und Sie können jeden importierten Schlüssel so einstellen, dass er bei jeder Signaturanfrage auf dem Host nachfragt.

AWS-Zugangsdaten. Eine Datei, die richtig aussieht und nichts wert ist. Die ~/.aws/config der VM zeigt auf einen credential-process-Helfer, der die echte Access-Key-ID zusammen mit einem 40 Zeichen langen gefälschten Secret Key über vsock-Port 8445 ausgibt. Jedes SDK, die aws-CLI, terraform und boto3 greifen das ohne Zusatzkonfiguration auf, und der Gast erzeugt eine wohlgeformte SigV4-Signatur, berechnet mit dem falschen Geheimnis. Der Resigner auf dem Host streift diese Signatur ab und signiert auf dem Weg nach draußen neu. Nehmen Sie das Paar woandershin mit, und AWS antwortet InvalidSignatureException.

Kubernetes-Konfiguration. ~/.kube/config existiert und enthält ein Bearer-Token, das mit brm-k8s- beginnt. Es funktioniert, über den Proxy, gegen die API-Server, für die der Workspace konfiguriert wurde. Überall sonst ist es wirkungslos.

Shell-History. Echt, und dauerhaft: Programmierarbeit braucht dauerhaften Zustand, also ist die Workspace-VM eine persistente Maschine und keine Wegwerfmaschine. Sie enthält die History einer Maschine, die nie einen echten Schlüssel enthalten hat, mit Befehlen, die gegen Köder in einem Home-Verzeichnis liefen, das nicht Ihres ist, neben einer ~/.git-credentials und einer ~/.docker/config.json, die ebenfalls Attrappen sind.

die Einkaufsliste, in einem Workspace ausgeführtwas die Nutzlast verlangtwas sie vorfindetprivate Schlüssel ~/.sshnichts — die Bytes liegen auf dem HostSignaturen queren vsock 8444; Schlüssel nieAWS-Zugangsdatenechte Key-ID, gefälschtes 40-Zeichen-Geheimnisanderswo eingespielt: InvalidSignatureException~/.kube/configein brm-k8s--Platzhalterwirkt über den Host-Proxy, sonst wirkungslosShell-Historydauerhaft, und voller Ködereine VM, die nie einen echten Schlüssel hielt
Die vierteilige Einkaufsliste der Nutzlast, ausgeführt gegen einen Bromure-Agentic-Coding-Workspace. SSH liefert überhaupt keine Bytes eines privaten Schlüssels, weil das Signieren auf dem Host über vsock 8444 geschieht. Die AWS-Konfiguration gibt die echte Access-Key-ID mit einem gefälschten Geheimnis aus, sodass ein gestohlenes Paar stromaufwärts an der Signaturprüfung scheitert. Die kubeconfig enthält einen brm-k8s--Platzhalter. Die Shell-History ist die einer VM, die nie ein echtes Zugangsdatum gehalten hat.

Dann muss er es auch senden

Einsammeln ist die halbe Arbeit. Die andere Hälfte ist die ausgehende Strecke zu productivity-suite-mcp.onrender.com, und diese Strecke läuft in einen Stapel hostseitiger Prüfungen.

Die Attrappen sind Stolperdrähte. Jede hat genau eine legitime Zielfamilie, den Host-Geltungsbereich, für den sie geprägt wurde, also durchsucht der Proxy jede ausgehende Anfrage, Header und Body, nach einer Attrappe, die irgendwohin unterwegs ist, wo sie nicht hingehört. Bei einem Treffer behandelt er die Anfrage als versuchte Zugangsdaten-Exfiltration: Er lehnt mit HTTP 451 ab, ohne ein Byte weiterzuleiten, pausiert die VM auf der Stelle und löst eine Warnung aus, die Herunterfahren, Für Untersuchung sichern (zuerst Datenträger, Home-Verzeichnis und geteilte Ordner für die Forensik exportieren) oder Fortfahren auf eigenes Risiko anbietet. Das Ereignis landet als rote Zeile Credential Brokering in der Security Timeline.

Die zweite Prüfung ist die Erreichbarkeit. Jeder Workspace trägt eine geordnete Egress-Firewall (Aktion, Protokoll, Host oder CIDR, Ports, und für Web-Verkehr die einzelnen HTTP-Verben) mit einer Vorgabe für nicht zugeordneten Verkehr. Neue Workspaces erlauben, was keine Regel trifft; stellen Sie das auf Verweigern, listen Sie auf, was die Arbeit braucht, und eine render.com-Subdomain steht nicht auf der Liste. Zwei Komponenten außerhalb des Gasts setzen das durch: Der virtuelle Switch ordnet jeden Fluss über Ziel-IP und per DNS mitgelesenen Hostnamen zu, und der Proxy ordnet erneut über den TLS-Servernamen zu. Regeländerungen erreichen laufende Sitzungen ohne Neustart.

Guardrails setzt ein Tor vor die Konsequenz. Es klassifiziert die Aufrufe des Agenten an Kubernetes, AWS, DigitalOcean, Container-Registries, Git-Forges und HTTPS-Datenbanken als Lese-, Schreib- oder destruktive Operationen, und neue Workspaces stehen standardmäßig auf Vor dem Schreiben nachfragen: Jede Mutation hält für einen Host-Dialog an, der die wörtliche Operation zeigt, das genaue SQL oder METHOD /path. Genau darum hat Pillar die Plattformbauer gebeten: Zugriffe auf sensible Dateien, auf Zugangsdaten und Codeausführung sollten keine Folge entfernter Metadaten sein.

Auch auf dem Inhaltspfad sitzt ein Detektor. Wenn der Agent externe Inhalte an das Modell zurückstreamt (Dateiinhalte, abgerufene Seiten, Befehlsausgaben, Ergebnisse von Tool-Aufrufen), bewertet ein lokaler PromptGuard-Klassifikator diese Abschnitte auf Ihrem Mac, bevor das Modell darauf reagiert, und kann protokollieren, nachfragen oder mit einem 451 blockieren. Das MCP-Bearer-Token für einen HTTP-Server betritt die VM gar nicht erst: Das echte bleibt auf dem Host, die Konfiguration des Agenten bekommt einen brm-mcp_-Platzhalter, und das Einstellungsfeld sagt es laut, Nie an die VM gesendet — vom Proxy ersetzt.

die Prüfung läuft einmalFreigabe zur Installation, für immer geerbtTag 1 — Quellcode lesen, ausführen, freigebender Server ist ehrlich, und das Urteil stimmtAufrufe 1 – 3 — immer noch ehrlichnichts bringt jemanden dazu, erneut hinzusehenAufruf 4 — die Metadaten drehen sichgedeckt von einem Urteil von vorherniemandem wird eine neue Entscheidung abverlangtwas der Agent erreichtechtes ~/.ssh · echte AWS-Keys · echte kubeconfigdie Prüfung läuft jedes Malkein Installationsurteil zu erbenWorkspace-VM — der Agent und die Serverausgabebrm-k8s- · brm-mcp_ · gefälschtes AWS-GeheimnisPlatzhalter ab Start; keine privaten Schlüsselbytesvsock 8443 — der einzige Weg nach draußenHost-Proxy — pro Anfrage befragtEgress-Firewall · Host, Port, HTTP-VerbSchreibrichtlinie · lesend / schreibend / destruktivInjektionsprüfung des an das Modell gesandten Inhaltseine Trace-Zeile pro Anfrage, auf Ihrer Maschinedie ExfiltrationsstreckeAttrappe außerhalb ihres Bereichs → HTTP 451, VM pausiert, Timeline-Zeile
Zwei Platzierungen derselben Entscheidung. Links deckt eine Freigabe zur Installationszeit alles ab, was der Server danach tut, sodass eine Metadatenänderung beim vierten Aufruf ein Urteil erbt, das an Tag eins gefällt wurde. Rechts gibt es kein Installationsurteil zu erben: Die Zugangsdaten sind ab dem Start Platzhalter, und jede Operation, die der Agent versucht, wird auf dem Weg nach draußen im Host-Proxy erneut klassifiziert.

Falls Sie diesen angefasst haben

Durchsuchen Sie Ihre MCP-Client-Konfigurationen, Repositories und Home-Verzeichnisse nach productivity-suite-mcp.onrender.com, dem früheren Endpunkt promo-surname-xml-quantum.trycloudflare.com und dem lokalen Artefakt unter ~/.config/.cache/.sys/.deadbug-mcp.py. Machen Sie jede Konfigurationsänderung rückgängig, die sie eingeführt hat, und führen Sie das Skript nicht aus. Bewahren Sie Ihre MCP-Client-Logs auf und suchen Sie nach Aktualisierungen von Werkzeugdefinitionen nach dem dritten Aufruf. Pillars Rat zur Rotation ist maßvoll: rotieren Sie dort, wo lokale Belege eine Kompromittierung stützen.

Die Regel, die die Frage verkleinert

Stellen Sie im Guardrails-Bereich des Workspace Nicht zugeordneter Verkehr auf Verweigern und listen Sie auf, was die Arbeit braucht: allow web api.github.com, allow web registry.npmjs.org, default deny. Führen Sie danach nach einer Sitzung bromure-cli trace hostnames my-workspace aus, was jeden einzelnen Host ausgibt, den der Agent kontaktiert hat, mit Zählern. Ein Ziel, das Sie nie gewählt haben, taucht als Zeile auf, die Sie nicht geschrieben haben.

Freigabe ist eine Momentaufnahme; die Leitung ist durchgehend

Der Reflex nach einer solchen Kampagne ist, härter zu prüfen: mehr vom Quellcode lesen, den Commit pinnen. Beides lohnt sich, und keins von beidem berührt den Mechanismus. Die Prüfung war hier korrekt. Der Server war ehrlich, solange jemand hinsah, änderte sich dann nach einem Zeitplan, den er selbst kontrollierte, und brach dabei keine einzige Protokollregel.

Eine Kontrolle, die überlebt, wenn ein Server es sich anders überlegt, ist eine, die nicht davon abhängt, vorher recht gehabt zu haben. Ein Zugangsdatum, das ein Platzhalter ist, ist beim vierten Aufruf ein Platzhalter und beim vierhundertsten. Ein privater Schlüssel auf dem Host kann bei keinem Aufruf gelesen werden. Der Proxy wertet bei jeder Verbindung eine Egress-Regel aus und klassifiziert jeden Schreibvorgang, während er geschieht. Nichts davon trägt eine Erinnerung an den Tag, an dem Sie etwas freigegeben haben, und deshalb kann ein Angreifer sie nicht erben.

MCP hat es leicht gemacht, einem Agenten neue Fähigkeiten zu geben, was der Sinn war, und es hat diese Fähigkeiten zu etwas gemacht, das eine entfernte Partei in Text beschreibt, den Ihr Modell als Anweisung liest. Beides bleibt uns erhalten. Entscheiden Sie, was Ihr Agent halten und was er erreichen darf, und legen Sie die Durchsetzung dorthin, wo der Server kein Stimmrecht hat. Installieren Sie Bromure Agentic Coding und geben Sie Ihrem Agenten nichts, wofür es sich lohnt, drei Aufrufe zu warten.