On 21 October one of my scheduled agents did exactly what it was told. It started a memory tool for itself, the way it does every run. That afternoon the tool brought a credential stealer with it.

Nothing alerted me. I found out five days later because my OpenRouter key was spending money on models I never use.

What happened

The memory tool was an MCP server launched with uvx basic-memory mcp. No version pin. Every start, uvx asks PyPI for the newest compatible versions of the tool and everything it depends on, and one of those dependencies is litellm.

At 11:39 UTC that day a poisoned litellm 1.107.0 went up on PyPI. It was not built by the maintainers. An attacker had been inside their GitHub organisation for eleven days, through a key that leaked in May and was never revoked, and used it to ship a release that looked completely normal. It was live for about two hours and was downloaded around 49,000 times.

My cron agent started at 14:20 local time, 12:20 UTC. Forty minutes after the release.

What it did

The package carried two files that Python runs on its own, without anyone importing anything:

The agent runs as my user, and my user can read every key the agents use. So the working assumption is simple: every secret on that laptop was copied on 21 October.

How I found it

Not with a security tool. The OpenRouter key was being resold and used for models my setup never calls. That was the only signal, and it arrived five days late.

My first theories were wrong. I suspected the Windows desktop and an old backup, and spent time proving both clean. The answer was in the uv cache on the laptop the whole time, with a timestamp that matched the cron run.

The agent did nothing wrong. It ran the command I gave it. The hole was a command that trusted whatever PyPI said was newest, at the exact moment PyPI was lying.

What I did

What I am changing

The agents stay autonomous. I am not going to approve every package install by hand, because then the agents are pointless. The changes go around them instead:

The lesson I keep coming back to is the same one the attacker used on the maintainers. A key that might have leaked is a key that leaked. Rotate it.