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

Der Commit war ein Branch

Air hat Plugin4Shell am 17. September offengelegt: Die Plugin-Marketplaces von Claude Code, Codex, Gemini CLI und Copilot heften jedes Plugin an einen vierzig Zeichen langen Commit-Hash, und die Agenten checken diesen Hash aus, ohne je zu prüfen, ob sie ihn auch bekommen haben. Ein Angreifer, der einen Branch nach dem gepinnten Hash benennt, gewinnt den Checkout, und der Hintergrund-Updater erledigt den Rest, ohne Rückfrage und ohne Klick. Anthropic und OpenAI haben gepatcht. Google wird es nicht tun, Microsoft hat es nicht getan. In einem Bromure Agentic Coding Workspace landet der Code, der den Checkout gewinnt, in einer VM mit Platzhalter-Zugangsdaten, hinter einer Push-Policy und einem Egress-Regelwerk, die auf dem Host durchgesetzt werden.

Jeder Leitfaden zur Lieferkette sagt Ihnen, Abhängigkeiten an einen Hash zu heften, denn eine Versionsnummer ist ein Etikett, das jemand verschieben kann, und ein Hash ist der Inhalt selbst. Vier Coding-Agenten haben diesen Rat befolgt. Keiner von ihnen hat geprüft, ob der Hash das war, was er bekommen hat.

Ein Plugin besteht die Prüfung. Sie lesen es, oder Ihr Plattform-Team liest es, und es kommt auf die Freigabeliste, geheftet an einen Commit: vierzig hexadezimale Zeichen, die nur einen einzigen Baum beschreiben können, weil eine Änderung am Baum die Zeichen ändert. Genau darin liegt der Reiz. Wer die passenden Rechte hat, kann ein Tag verschieben oder eine Version neu veröffentlichen, aber ein Commit-Hash soll eine Tatsache sein.

Am 17. September haben drei Forscher von Air (Or Nevo, Dor Granat und Niv Hoffman) Plugin4Shell veröffentlicht. Ihr Befund: Der Pin ist nur so gut wie das, was ihn auflöst, und der Resolver in vier großen Coding-Agenten prüft seine eigene Arbeit nicht. The Register berichtete noch am selben Tag. Claude Code, OpenAIs Codex, Googles Gemini CLI und Microsofts Copilot sind alle betroffen.

Git bevorzugt die Ref

Ein Commit-Hash ist Inhalt, und er ist zugleich ein Name. Branches und Tags sind es auch, und git löst sie alle über dieselbe Suche auf. Geben Sie git eine Zeichenkette, die zugleich Branch-Name und Objekt-ID ist, muss es sich entscheiden; es entscheidet sich für den Branch. Die Forscher bringen es in einem Satz auf den Punkt: „Wenn ein Name zugleich eine gültige Ref und eine Objekt-ID ist, bevorzugt git die Ref.“

Git warnt durchaus. Es gibt für diese Referenz einen Mehrdeutigkeitshinweis aus, in ein Terminal, das Sie nicht lesen, weil ein Hintergrund-Updater den Checkout ausführt.

Git-Branch-Namen erlauben vierzig hexadezimale Zeichen. Ein Angreifer, der das Repository des Plugins kontrolliert, legt einen Branch mit genau dem Namen an, den der Marketplace gepinnt hat, und macht ihn zum Standard-Branch. Der Clone holt diesen Branch neben dem gleichnamigen Commit herunter. Dann führt der Agent seinen Checkout aus:

git clone <plugin repo> ./
git checkout aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

Der Checkout landet auf dem Branch. Gemini CLI nimmt einen anderen Weg zum selben Ziel: Es holt den richtigen Commit mit --ref und führt dann git checkout FETCH_HEAD aus, was zu einem Branch namens FETCH_HEAD auflöst, falls das Repository einen hat.

Ein Name, zwei mögliche BedeutungenMarketplace-Pinaaaaaaaa…aaaageprüft, notiert, unverändertObjekt-ID im Repositoryaaaaaaaa…aaaader Code, der die Prüfung bestandBranch, dieselben vierzig Zeichenrefs/heads/aaaaaaaa…aaaawas der Angreifer gepusht hatgit checkout aaaa…aaaalöst zum Branch aufWarnung erscheint, niemand liestDer Agent fragt nie, was er tatsächlich ausgecheckt hat. Ein einziger Befehl täte es: git rev-parse HEAD.
Eine Zeichenkette, zwei Bedeutungen. Der Commit aaa…aaa und ein Branch namens aaa…aaa existieren beide im geklonten Repository. git checkout muss sich entscheiden, und es entscheidet sich für die Ref. Der Marketplace verzeichnet weiterhin denselben Pin, und der Baum auf der Platte gehört jemand anderem.

Fünf Schritte, und der letzte hat keinen Klick

Die Forscher legen den Angriff in fünf Zügen aus, und die ersten drei sind Dinge, die ein gut geführter Marketplace mit Absicht tut.

Pflanzen. Ein harmloses Plugin veröffentlichen. Es besteht die Prüfung beim Commit aaa…aaa, weil nichts daran falsch ist.

Verbreitung. Entwickler installieren es, geheftet an den geprüften Commit. Das ist der Schritt, in dem Sie tun, was die Leitfäden Ihnen sagen.

Versionssprung. Der Marketplace heftet neu an einen jüngeren Commit, bbb…bbb, ebenfalls harmlos und ebenfalls geprüft.

Rug-Pull. Der Angreifer legt einen Branch namens bbb…bbb an, macht ihn zum Standard und zeigt damit auf bösartigen Code. Er veröffentlicht nichts neu, und der Eintrag im Marketplace bewegt sich nicht. Die vierzig Zeichen, die Sie prüfen würden, sind die vierzig Zeichen, die Sie schon geprüft haben.

Vom Auto-Update zur Codeausführung. Claude Code und Codex aktualisieren installierte Plugins standardmäßig im Hintergrund. Das nächste geplante Update checkt den Branch aus. Air beschreibt diesen Schritt als „keine Rückfrage, kein Klick“.

Plugin4Shell, Schritt für Schrittwer handelt1ein harmloses Plugin erscheint und besteht die Prüfung bei aaa…aaaan diesem Punkt ist am Code nichts falschMarketplace2Entwickler installieren es, geheftet an den geprüften Commitdas ist die empfohlene Praxis, korrekt befolgtSie3der Marketplace heftet neu an einen jüngeren Commit, bbb…bbbebenfalls harmlos, ebenfalls geprüftMarketplace4ein Branch namens bbb…bbb entsteht und wird zum Standardnichts wird neu veröffentlicht; der verzeichnete Pin bleibtAngreifer5der Hintergrund-Updater checkt den Branch ausCodeausführung auf dem Rechner des Entwicklers — keine Rückfrage, kein Klickder Agent
Die Schritte eins bis drei sind der Marketplace, wie er entworfen wurde, und Schritt zwei ist der Entwickler, der dem Standardrat folgt. Der Angriff sind die Schritte vier und fünf, und keiner von beiden bittet den Entwickler, irgendetwas zu genehmigen.

Es gibt einen zweiten Weg hinein, der das Pflanzen überspringt: das Repository eines legitimen Plugin-Autors übernehmen und die bösartige Version an jeden Agenten ausliefern, der das Plugin bereits installiert hat. Der Branch-Trick ist derselbe, und es gibt keine Prüfung zu bestehen.

Air beschreibt das Ergebnis als „vollständige entfernte Codeausführung auf dem Rechner des Mitarbeiters“, laufend mit den Rechten dieses Mitarbeiters. Der Angreifer braucht keine Rechteausweitung. Der Code läuft als die Person, der der Laptop gehört, und erreicht ihre Dateien und ihren Produktionszugang.

Es ist das dritte Mal, dass diese Forscher dieselbe Form zählen. Ihre früheren Arbeiten zu bösartigen Agent-Skills beschreiben eine, die mehr als 26.000 Agenten erreichte, und eine Übernahmekampagne, die 925 bereits genutzte Skills auf 134.000 Agenten kaperte. Jene Kampagnen brauchten Sie, um etwas Schlechtes zu installieren; diese brauchte Sie, um etwas Gutes zu installieren und zu warten.

Zwei der vier haben keinen Patch

Air hat im Juni an alle vier Hersteller gemeldet.

Anthropic hat Claude Code am 17. Juni gepatcht, in Version 2.1.179. OpenAIs Korrektur kam in Codex 0.146.0 und wurde am 12. August verifiziert. Google antwortete am 4. August, Gemini CLI sei abgekündigt und werde nicht gepatcht. Google verweist Nutzer stattdessen auf Antigravity, sodass eine Gemini-CLI- Installation, die auf der Platte bleibt, verwundbar bleibt. Microsoft hat für Copilot nichts ausgeliefert. GitHub sagt, die eigene Plattform blockiere Branch- und Tag-Namen, die wie Commit-SHAs aussehen, was Plugins auf GitHub abdeckt und für ein anderswo gehostetes Plugin-Repository nichts tut, Bitbucket eingeschlossen. The Register merkt mit Microsofts eigener Zahl an, dass fast 90 % der Fortune 500 Copilot einsetzen.

Die Korrektur selbst ist eine Zeile, und sie muss im Agenten laufen:

test "$(git rev-parse HEAD)" = "<pinned-sha>" || abort

Den Commit auflösen, der im Arbeitsbaum gelandet ist, und die Weiterarbeit verweigern, wenn er nicht passt. Kein Marketplace kann das für Sie tun, denn Ihr Agent löst den Pin auf Ihrem Rechner auf, nachdem der Marketplace zu Ende gesprochen hat.

Für eine große Zahl von Entwicklern, die das heute lesen, ist „aktualisieren Sie Ihren Agenten“ keine verfügbare Antwort. Bleibt die andere Frage: Was findet der Code vor, wenn er den Checkout gewinnt und zu laufen beginnt?

In einem Bromure Agentic Coding Workspace

Airs Formulierung, laufend mit den Rechten des Mitarbeiters, ist genau. Ein Workspace existiert, um sie falsch zu machen.

In Bromure Agentic Coding läuft der Agent in einer hardwarevirtualisierten Linux-VM auf Ihrem Mac, und die VM hält nie echte Zugangsdaten. Beim Start der Sitzung schreibt die App strukturerhaltende Platzhalter in die Umgebung, in der der Agent läuft: ANTHROPIC_API_KEY, GH_TOKEN, LINEAR_API_KEY und die übrigen als Umgebungsvariablen, und passende Fälschungen in ~/.git-credentials, ~/.docker/config.json, ~/.kube/config, ~/.aws/config und die MCP-Konfigurationsdateien des Agenten. Jeder Platzhalter behält die Form, die seine Werkzeuge erwarten, also nehmen sie ihn an: ein GitHub-Platzhalter ist ghp_ plus 36 Zeichen, ein Anthropic- Platzhalter beginnt mit sk-ant-api03-brm-, ein Kubernetes-Bearer-Token beginnt mit brm-k8s-. Bromure leitet jeden davon per HKDF-SHA256 mit einem installationsspezifischen Salt aus dem echten Wert ab, was ihn über Sitzungen hinweg stabil hält und für jeden, der ihn einsammelt, wertlos macht.

Die echten Werte liegen verschlüsselt auf dem Host. Ein Proxy auf der Host-Seite setzt sie auf der Leitung ein, nachdem die Anfrage die VM verlassen hat, und nur dann, wenn das Ziel zu dem Host passt, für den diese Zugangsdaten geprägt wurden. Ein Plugin, das das Home-Verzeichnis nach Konfigurationsdateien durchkämmt, das Erste, was diese Art von Payload tut, sammelt einen Satz wohlgeformter Platzhalter ein.

Nach dem Checkout: dieselben drei Züge, zweimalein gewöhnlicher Entwicklerrechnerdas Home-Verzeichnis lesensk-ant-api03-… ghp_… AKIA…echte Schlüssel, echte Tokens, echte kubeconfigklapptin erreichbare Repositories pushengit push → git-receive-packdie eigenen git-Zugangsdaten des Entwicklersklapptdie Sammlung irgendwohin sendenPOST https://attacker.examplegewöhnliches HTTPS nach außen, nichts im Wegklapptein Bromure Agentic Coding Workspacedas Home-Verzeichnis lesensk-ant-api03-brm-… ghp_… brm-k8s-…Platzhalter; die echten liegen auf dem HostKöderin erreichbare Repositories pushengit-receive-pack = Schreibenblockiert, oder ein Host-Dialog für SiePolicydie Sammlung irgendwohin senden451 — kein Byte weitergeleitetEgress-Regel, dann wird die VM pausiertgestoppt
Derselbe Checkout, dasselbe bösartige Plugin, zwei Rechner. Auf einem gewöhnlichen Laptop erbt der Code die echten Zugangsdaten des Entwicklers und erreicht alles, was dieser erreicht. In einem Workspace sind die gelesenen Zugangsdaten Platzhalter, der versuchte Push trifft auf eine Schreib-Policy auf dem Host, und der Ruf nach Hause trifft auf eine Egress-Regel, die der Gast nicht bearbeiten kann.

Der Push muss auf dem Host genehmigt werden

Das Wertvollste, was ein kompromittiertes Plugin auf einem Entwicklerrechner tun kann, ist pushen. So wird aus einem Laptop hundert Repositories.

Guardrails klassifiziert jeden Aufruf, den der Agent an GitHub richtet. Ein git push über HTTPS kommt am Proxy als git-receive-pack an, was als Schreibzugriff zählt. Der Modus nur lesen weist ihn ab. Der Modus vor dem Schreiben fragen öffnet einen Dialog auf Ihrem Mac mit dem Titel Allow write on "<scope>" from workspace "<name>"?, der die Operation wörtlich zeigt statt einer Zusammenfassung. Ein git fetch zählt als Lesezugriff und geht durch, ohne Sie zu unterbrechen.

Die Klassifizierung läuft im Host-Proxy, außerhalb der VM. Code im Gast kann sie weder abschalten noch umgehen, weil er nicht auf dem Rechner läuft, der die Entscheidung trifft.

Der Ruf nach Hause trifft auf eine Regel, die der Gast nicht bearbeiten kann

Jeder Workspace hat eine Egress-Firewall: eine geordnete Liste im pf-Stil aus Erlaubnis- und Verbotsregeln über Host, IP-Bereich, Protokoll, Port und, für Web-Verkehr, das einzelne HTTP-Verb. allow web api.example.com GET,POST gibt dem Agenten Abfragen ohne Änderungen, auf der Leitung, egal welches Werkzeug in der VM die Anfrage stellt.

Zwei Schichten setzen das durch. Der virtuelle Netzwerk-Switch wendet die Regeln auf jeden Fluss an, nach Ziel-IP und per DNS mitgelesenem Hostnamen, und deckt damit einfaches TCP und UDP ebenso ab wie HTTPS. Der Proxy wendet sie erneut an, nach TLS-Servernamen und HTTP-Methode. Bromure leitet den Verkehr der Ports 80 und 443 der VM ohne Mitwirkung des Gasts in den Proxy um, sodass nichts darin sich der Inspektion entziehen kann. Ihre Änderungen erreichen laufende Sitzungen sofort, was an dem Tag zählt, an dem Sie eine Offenlegung wie diese lesen.

Darunter sitzt der Kompromittierungsdetektor, standardmäßig an und ohne etwas zu konfigurieren. Der Proxy durchsucht jede ausgehende Anfrage in einem einzigen Durchgang nach den für diesen Workspace geprägten Platzhalter-Tokens. Ein Platzhalter, adressiert an einen Host, für den er nie geprägt wurde, ist die Signatur einer Exfiltration, und der Proxy antwortet darauf mit HTTP 451 und leitet kein Byte weiter. Er pausiert die VM auf der Stelle und löst einen Alarm aus, der anbietet, den Workspace herunterzufahren, Platte und Home-Ordner zur Untersuchung zu exportieren oder auf eigenes Risiko weiterzumachen. Bromure markiert den Workspace als kompromittiert, und der nächste Start löscht die VM-Platte und den Home-Ordner, behält aber Ihre Tokens, SSH-Schlüssel und Workspace-Einstellungen. Nur der Platzhalter ist abgeflossen, es gibt also nichts zu rotieren.

Zero-Click heißt, es gibt nichts zu bemerken

Die schärfste Eigenschaft von Schritt fünf ist seine Stille. Ein Hintergrund-Update eines Plugins erzeugt weder Rückfrage noch Tool-Aufruf, also gibt die Oberfläche des Agenten dem Entwickler nichts, woran er es festmachen könnte. Airs Punkt ist, dass der Exploit in dem Teil des Systems läuft, den zu beobachten Sie keinen Anlass haben.

Ein Workspace verlegt das Beobachten woandershin. Die Sicherheits-Engines schreiben jede Entscheidung als Zeile in das Fenster Security Timeline auf Ihrem Mac: Firewall-Urteile (evil.example:443 tcp — blocked), Guardrails-Durchsetzungen (DELETE api.github.com/repos/… — blocked), Credential Brokering einschließlich der roten Zeile blocked — exfiltration attempt, VM paused, dazu Lieferketten-Urteile und Prompt-Injection-Treffer, farbcodiert und filterbar. Eine Sitzung kann außerdem eine verschlüsselte Spur der ausgeführten Befehle und der erreichten Hosts aufzeichnen.

Ein Plugin, das sich selbst aktualisiert hat, ohne es Ihnen zu sagen, muss trotzdem Anfragen stellen, und jede davon überquert eine Grenze, die ihre eigenen Notizen auf der anderen Seite des Hypervisors führt.

Prüfen Sie zuerst die Version Ihres Agenten

Claude Code 2.1.179 und Codex 0.146.0 enthalten die Korrektur. Wenn Sie Gemini CLI einsetzen, wird es keine geben. Wenn Sie Copilot einsetzen, gibt es noch keine.

Schalten Sie Plugin-Auto-Updates ab, wo Sie können

Die Zero-Click-Eigenschaft kommt vom Hintergrund-Updater. Manuelle Updates holen für Schritt fünf einen Menschen zurück in die Schleife.

Prüfen Sie den Checkout selbst in der CI

git rev-parse HEAD nach einem gepinnten Checkout, verglichen mit dem Pin, ist dieselbe einzeilige Prüfung, die die Hersteller nachrüsten mussten. Sie funktioniert überall, wo Sie per SHA klonen.

Nehmen Sie an, dass jemand den Checkout gewinnt

Pinning, Prüfung und die Kuratierung des Marketplace haben hier alle gehalten, und der Angriff kam trotzdem durch. Der Ausgang hängt dann davon ab, was der siegreiche Code erreichen kann.

Plugin4Shell erwischt den sorgfältigen Entwickler zu denselben Bedingungen wie den sorglosen. Wer installiert hat, was gerade angesagt war, kam nicht schlechter davon als der, der den Quelltext las, den Hash pinnte und den Pin notierte. Inhaltsadressierung sollte diesen zweiten Entwickler schützen, und sie tat es nicht, weil git die Adresse über eine Suche auflöst, die dieselben Zeichen auch für etwas anderes verwendet.

Pinnen Sie weiter. Und nehmen Sie dann an, dass eine Kontrolle, die auf einer Suche oder einem Namensabgleich ruht, eines Tages die falsche Antwort bekommt, während Sie schlafen. Entwerfen Sie für diesen Morgen: Lassen Sie den Agenten dort laufen, wo kein Schlüssel liegt, der das Mitnehmen wert wäre, und halten Sie die Aufzeichnung dessen, was er getan hat, auf der anderen Seite einer Grenze, die der Code nicht erreichen kann.