Die Vorschau konnte Ihren Home-Ordner lesen
Am 14. Oktober hat Noma Labs SneakPeek offengelegt, eine Lücke im Vorschaubereich von Claude Code Desktop. Eine HTML-Datei, auf über 256 KB aufgebläht und als Testabdeckungsbericht getarnt, wurde ohne Skript-Richtlinie von der Platte geladen, und ihr JavaScript las SSH-Schlüssel, AWS-Secrets, kubeconfigs und Claudes eigene OAuth-Tokens aus dem Home-Ordner des Entwicklers und schickte sie per Hintergrund-Beacon hinaus. Anthropic bewertete die Lücke mit CVSS 8.7 und behob sie im August. In Bromure Agentic Coding werden Seiten in einer separaten Wegwerf-VM gerendert, auf der keine Ihrer Dateien liegt, und im Home-Ordner des Agenten liegen Platzhalter statt Schlüsseln.
Das Repository sah normal aus. Die Datei war ein Coverage-Bericht. Der Entwickler bat den Agenten, ihn sich anzusehen, und der Vorschaubereich erledigte den Rest.
Ein Entwickler klont ein Repository und bittet Claude Code, sich den Testabdeckungsbericht darin anzusehen. Eine Routineanfrage. Der Bericht ist eine HTML-Datei. Wenn Claude in Claude Code Desktop eine HTML-Datei liest oder bearbeitet, rendert ein Vorschaubereich sie neben der Unterhaltung, ohne nachzufragen. Dieser Bericht enthält ein Skript. Innerhalb von Sekunden hat das Skript den privaten SSH-Schlüssel des Entwicklers gelesen, dazu AWS-Schlüssel, npm- und GitHub-Tokens, Kubernetes-Zugangsdaten und Claudes eigene OAuth-Tokens, und alles an einen Server des Angreifers geschickt.
Das ist der Proof of Concept, den Noma Labs am Mittwoch, dem 14. Oktober, unter dem Namen SneakPeek veröffentlicht hat. CSO Online berichtete am selben Tag. Anthropic bewertete die Lücke mit CVSS 8.7 (hoch) und stufte ihre Auswirkung als gleichwertig mit Codeausführung auf dem Host ein. Noma hatte sie am 4. Juli gemeldet. Der Fix kam am 17. August per Auto-Update in Claude Desktop 1.30096.1, nachdem ein erster Patch umgangen worden war. Ein GitHub-Advisory und eine CVE stehen noch aus.
Der Bug ist behoben. Die Konstellation, die ihn so lohnend machte, steht auf den meisten Entwickler-Laptops noch immer: etwas, das nicht vertrauenswürdige Inhalte rendert, unter Ihrem Konto läuft und neben allem liegt, was Ihnen gehört.
Drei Schutzmaßnahmen und eine Größengrenze
Der Vorschaubereich ist ein Chromium-Panel in der Electron-App. Noma zählte drei Schutzmaßnahmen um ihn herum und fand in jeder eine Lücke.
Eine Skript-Richtlinie für kleine Dateien. Dateien unter 256 KB wurden als
base64-data:-URLs mit einer Content Security Policy geladen, einer Regelliste,
die dem Browser sagt, welche Skripte laufen dürfen. Diese Richtlinie blockierte
Skripte. Dateien über 256 KB wurden in eine temporäre Datei geschrieben und als
file://-URL von der Platte geladen, und für diesen Weg galt die Richtlinie
nicht. Einen Bericht auf über 256 KB aufzublähen ist trivial. Echte
Coverage-Berichte sind oft so groß.
Ein Origin mit freier Hand auf der Platte. Der Origin einer Seite
entscheidet, was sie lesen darf. Eine data:-URL bekommt einen opaken Origin,
der nichts lesen kann. Eine von file:// geladene Seite bekam, in Nomas
Worten, „Same-Origin-Zugriff auf das gesamte lokale Dateisystem des Opfers“.
Das Skript leitete den Home-Ordner aus dem Pfad der temporären Datei ab und las
Dateien mit fetch() und XMLHttpRequest.
Ein Filter, der nicht eingeschaltet war. Der Bereich hatte einen
Anfragefilter, der file://-Zugriffe blockieren sollte. Er griff bei
Dev-Servern, die über den Proxy liefen, und blieb bei statischem HTML von der
Platte aus.
Um die Daten hinauszubringen, rief das Skript navigator.sendBeacon() auf, eine
Web-API, die im Hintergrund einen einseitigen HTTP-POST abschickt. Nichts in der
App schränkte ein, wohin der Bereich Anfragen senden durfte. Nomas Bericht nennt
zusätzlich die echte IP-Adresse des Entwicklers, ermittelt über WebRTC.
Anthropics erster Patch vom 24. Juli blockierte fetch() und XMLHttpRequest
auf file://-Pfade aus der Seite. Am selben Tag verlegte Noma die Lesezugriffe
in einen Web Worker, gestartet von einer blob:-URL, einem Kontext, den der
Patch nicht abdeckte, und alle drei Mängel funktionierten wieder. Der endgültige
Fix im August schloss die Lücke in der Richtlinie, das Laden über file:// und
den Worker-Weg zusammen. Nomas Forscher Eli Ainhorn fasste es für CSO so
zusammen: „Die Schutzmaßnahmen waren echt, sie deckten nur nicht jeden Weg ab.“
Der Agent war es nicht
SneakPeek kommt ohne Prompt Injection aus. Der Agent las eine Datei, die er lesen sollte, und die App um ihn herum renderte diese Datei. Das Skript lief im Rendering-Prozess der Vorschau, mit dem Zugriff der App auf Ihre Platte.
Crystal Morin von Sysdig sagte CSO, dass Logs auf Agentenebene diese Ausführung womöglich nicht erfassen, Verteidiger also Dateizugriffe und ausgehende Verbindungen auf der Laufzeitebene beobachten müssten. Ihr Sitzungsprotokoll würde eine harmlose Anfrage und eine harmlose Antwort zeigen.
Ein vorsichtiges Modell, strenge Berechtigungsregeln und ein Mensch, der jeden Befehl freigibt, hätten nichts geändert. Der Renderer saß auf dem Mac, lief unter Ihrem Konto, neben Ihrem Home-Ordner und allen Zugangsdaten, die sich dort angesammelt haben.
Ainhorns Rat an CSO zielt auf diesen zweiten Teil: langlebige Zugangsdaten von Entwicklerrechnern fernhalten und eng begrenzte, kurzlebige Tokens verwenden.
Derselbe Bericht in einer Bromure-Maschine
In Bromure Agentic Coding läuft jeder Agent in einer eigenen Linux-VM auf Ihrem Mac, Maschine genannt, und der eingebaute Browser, der Webseiten rendert, läuft in einer zweiten Wegwerf-VM daneben. Klonen Sie Nomas Repository in eine Maschine und bitten Sie Claude Code, sich den Coverage-Bericht anzusehen.
Den Bericht lesen heißt Text lesen
Der Agent liest coverage/index.html wie jede andere Datei: als Text in seinem
Kontext. Im Chat wird nichts gerendert. Klicken Sie die Datei im Bereich
Files an, zeigt die Detailansicht HTML als hervorgehobenen Quelltext, genau
wie eine .py- oder .json-Datei. Kein Skript der Seite läuft. Dateien über
512 KB bekommen gar keine Vorschau. Ein Bericht, der auf über 256 KB aufgebläht
wurde, um einer Skript-Richtlinie auszuweichen, erscheint als 300 KB Markup.
Gerendert wird in einer anderen Maschine
Wenn Sie den Bericht tatsächlich gerendert sehen wollen, stellt der Agent den Ordner aus seiner Maschine bereit und öffnet ihn im Browserbereich, unter der IP-Adresse der Maschine. Der Browser ist ein vollständiges Chromium und läuft in einer eigenen Sidecar-VM. Im Handbuch heißt es: „Eine Datei, die der Agent in sein Home-Verzeichnis schreibt, liegt nicht auf der Platte des Browsers.“
Das Skript läuft und findet nichts zu lesen. Der Origin der Seite ist
http://192.168.64.x:8000, ein gewöhnlicher Web-Origin. Chromium lässt ihn
keine file://-Pfade lesen. Ein Skript, das daran vorbeikäme, über einen Worker
oder einen Browser-Bug, landete auf einem flüchtigen Copy-on-Write-Klon des
Browser-Images: kein ~/.ssh, kein ~/.aws, keine kubeconfig, kein OAuth-Token,
keines Ihrer Projekte. Der Home-Ordner, den es aus einem temporären Pfad
ableitet, gehört einer leeren Chromium-VM. Der Beacon geht leer hinaus.
Standardmäßig behält der Browser kein Profil. Stay signed in to websites ist aus, Cookies und Anmeldungen verschwinden also beim Schließen des Browsers, und ein ausgeblendeter Browser wird nach fünf Minuten abgebaut. WebRTC bleibt aus, solange Sie Kamera oder Mikrofon nicht in den Browser-Einstellungen der Maschine erlauben, der IP-Adressen-Trick hat also nichts, was er aufrufen könnte.
Im Home-Ordner des Agenten liegen Platzhalter
Richten Sie dasselbe Skript nun auf die VM des Agenten. Angenommen, der Testbefehl des Repositorys schreibt und öffnet den Bericht selbst, sodass der Code dort läuft, wo der Agent arbeitet. Gehen Sie Nomas Liste in diesem Home-Ordner durch:
- Privater SSH-Schlüssel. Es gibt keinen.
SSH_AUTH_SOCKhängt an einer Agent-Brücke zum Mac. Die VM kann Identitäten auflisten und Signaturen anfordern, und die Bytes des privaten Schlüssels verlassen nie den Host.ls ~/.ssh/id_*findet nichts. - AWS.
~/.aws/configverweist auf einen Helper, der die echte Access Key ID mit einem falschen, 40 Zeichen langen Secret ausgibt. Der Mac signiert jede Anfrage auf dem Weg zu AWS mit dem echten Secret neu. - Kubernetes.
~/.kube/configist synthetisch.kubectlspricht mit dem Proxy auf dem Mac, der die echten Cluster-Zugangsdaten hält. - Docker-, git- und npm-Tokens.
~/.docker/config.jsonund~/.git-credentialsenthalten Platzhalter, die wie die echten aussehen. Tokens, die Sie importieren, aus einer.envoder von Hand, werden zu Platzhaltern, die an ihre Hosts gebunden sind. - Claudes OAuth-Tokens. Sie bleiben auf dem Mac. Der Agent läuft mit einem Platzhalter-Schlüssel, und der Proxy setzt bei Anfragen an Anthropic das echte Access-Token auf die Leitung.
Die Platzhalter sind zugleich Stolperdrähte. Der Proxy auf dem Mac durchsucht
jede Anfrage, die die Maschine verlässt, Header und Body, nach einem
Platzhalter, der zu einem Host unterwegs ist, für den er nicht ausgestellt
wurde. Ein Beacon, der das brm-docker-…-Passwort oder das falsche
GitHub-Token zum Server des Angreifers trägt, bekommt HTTP 451. Nichts wird
weitergeleitet, die VM pausiert, und auf dem Mac öffnet sich eine Warnung mit
dem Titel This environment may have been compromised, mit einer Vorschau des
Tokens, dem Namen der Zugangsdaten, dem Host, zu dem sie gehören, und dem Host,
an den sie gingen. Die Erkennung landet als rote Zeile in der Security
Timeline. Stellen Sie Unmatched traffic in der Firewall Outbound
connections der Maschine auf Deny, dann wird eine Verbindung zu einem
Host, den das Projekt nicht nutzt, schon vorher abgewiesen.
Damit ist Morins Punkt zur Laufzeitebene abgedeckt. Die Prüfungen sitzen auf dem Mac, auf der Leitung, außerhalb des Agenten und außerhalb von allem, was in der VM rendert.
Der Fix und die nächste Vorschau
Anthropic hat SneakPeek im August behoben, und Noma hat erneut getestet. Zwischen Meldung und Fix lagen sechs Wochen, und der erste Patch fiel noch am Tag seiner Auslieferung einem Web Worker zum Opfer. Renderer-Fixes laufen oft so. Eine Browser-Engine hat viele Wege, ein Skript zu starten, und viele Wege, eine Anfrage zu stellen, und jede Schutzmaßnahme muss alle abdecken.
Coding-Tools fügen laufend Bereiche hinzu, die etwas rendern: HTML-Vorschauen, Markdown mit eingebetteten Inhalten, Notebook-Ausgaben, Dev-Server, Screenshots der App, die der Agent gerade gebaut hat. Jeder davon ist ein Renderer, der auf Inhalte zeigt, die der Entwickler nicht geschrieben hat, aus Repositorys, die er vor einer Minute geklont hat. Auf einem Laptop läuft jeder davon neben dem Home-Ordner.
In Bromure läuft der Bereich, der Seiten rendert, in einer VM, auf der keine Ihrer Dateien liegt, und die Maschine, in der der Agent arbeitet, enthält Platzhalter. Ein Renderer-Bug legt dort ein Wegwerf-Chromium offen und ein paar Zeichenketten, die kein Server akzeptiert.
Quelltext, keine Seite
Der Agent liest HTML als Text, und der Files-Bereich zeigt es als hervorgehobenen Quelltext. Einen Bericht zur Durchsicht zu öffnen, führt keines seiner Skripte aus.
Ein Browser mit leerer Platte
Seiten werden in einer separaten Wegwerf-Chromium-VM gerendert. Die Dateien des Agenten und die Ihres Macs liegen nicht auf ihrer Platte, und beim Schließen vergisst sie alles.
Ein Home-Ordner voller Platzhalter
Kein privater SSH-Schlüssel, ein falsches AWS-Secret, eine synthetische kubeconfig, Platzhalter für Registry- und git-Tokens. Die echten Zugangsdaten bleiben verschlüsselt auf dem Mac.
Ein Alarm auf der Leitung
Ein Platzhalter, der an einen Host geht, für den er nicht ausgestellt wurde, bekommt ein 451. Die VM pausiert, eine Warnung nennt beide Hosts, und die Security Timeline hält es fest.
Sehen Sie sich den Bericht an
„Sieh dir den Coverage-Bericht an“ ist so ziemlich die gewöhnlichste Bitte, die man an einen Coding-Agenten richten kann. Entwickler werden weiter nach Dateien in Repositorys fragen, die sie nicht geschrieben haben, und Tools werden diese Dateien weiter rendern, um hilfreich zu sein.
Fragen Sie in einer Bromure-Maschine, wo die Seite in einer VM rendert, in der es nichts zu lesen gibt, und die Schlüssel des Agenten Platzhalter sind. Mehr über Platzhalter und den Alarm, den sie auslösen, lesen Sie in Der Agent leakte seinen eigenen Schlüssel und Der Schlüssel war Ihre .env-Datei.