pyyi: The PyPI Package That Installs a Fake python2

Written by the Rafter Team

As of October 19, 2026.
People and agents run tools by name.
A Python package can install a command under any name it likes, including the name of the interpreter itself.
pyyi installs one called python2, and PATH decides which file that name runs.
What happened
On October 16 the OpenSSF malicious-packages project published MAL-2026-17866 for pyyi on PyPI, credited to Amazon Inspector and the researcher smiling-hyena. The record as published covers versions 2.4, 2.4.1 and 2.4.2; 2.4.1 and 2.4.2 were uploaded on October 12 and 14. Its PyPI summary called it a "small python companion shell."
The wheel for 2.4.1 declares one console command, python2, pointing at pyyi.cli:main.
What that command does depends on how it is called.
- Bare
python2. It prints a Python banner and opens a working Python prompt. Per the record, "the REPL presents a normal Python banner but silently sends eligible single-token inputs" to the server as a password. Inspector's analysis adds that it reads each line with terminal echo off. python2with arguments, such aspython2 build.py. It registers the machine by sending its hostname and OS to a server onspace-z.ai, then fetches code from that server, decodes it and runs it in memory. Inspector notes the code runs "with no pinning, hash, or signature."- Any failure. The command prints Python's own "can't open file" error, so a broken run looks like a missing script.
The source is candid about the design.
A comment in cli.py says bare python2 acts like the local interpreter "because it IS one (a real REPL); that is the whole cover."
The package was still on PyPI on October 19, when a new release, 2.5.0, renamed the command to python3.7.
Why the name is the attack
pyyi runs nothing when you install it. A wheel has no install hook, so the postinstall controls that watch install-time code see nothing. The code waits for someone to type a name.
The names pick gaps.
Many current machines have no python2, and a virtual environment on Python 3.12 has no python3.7.
A console script installed into a virtual environment or ~/.local/bin fills that gap with nothing to collide with, so an old Makefile, a #!/usr/bin/env python2 shebang or a README that says python2 setup.py now finds an interpreter where it used to fail.
That is where agents come in.
A coding agent asked to run a legacy script will try the command the script or its docs name.
A person approving the step, or a hook that gates agent commands, sees python2 legacy.py, which reads as harmless.
Neither sees which file the name resolves to.
The same trust gap runs through hermes-px, a PyPI package that sold itself as one thing and did another, and through slopsquatting, where an agent installs a package because its name sounds right. pyyi moves the trick from the package name to the command name.
What to do now
- Check every environment for pyyi. Run
pip show pyyiin each virtual environment, user site and system Python, and search lockfiles and requirements files. Remove it if found. - If it was installed, assume code ran. Any
python2orpython3.7call with arguments would have fetched and run server code. Treat the host as compromised: rotate credentials it could reach, and change any password or token typed at its prompt. Look for a~/.python2/directory. - Block the two hosts in the record. MAL-2026-17866 lists
preview-chat-960504a9-b4e0-4b69-bc80-6ac3d4fa4ae0.space-z.aiandb196q82k8891-d.space-z.ai. Block those hostnames rather than the whole domain. - List the commands your packages install. This prints every console command and the package that owns it:
python -c "import importlib.metadata as m; [print(d.metadata['Name'], e.name) for d in m.distributions() for e in d.entry_points if e.group == 'console_scripts']". Flag any command named like an interpreter, a shell or a system tool. - Resolve a name before you trust it.
type -a python2shows every match onPATHin order. A real interpreter is a binary or a symlink to one; a console script is a short text file that imports a module. Read it withhead; do not run it to check. - Give agents full paths. In agent instructions, Makefiles and scripts, call
.venv/bin/pythonor/usr/bin/python3instead of a bare name. An approval prompt then shows what will run, not only what it is called.
Where Rafter fits
Rafter's command-gating hooks for Claude Code and Codex judge the command an agent asks to run.
python2 legacy.py looks benign to them, as it does to a person, because the danger is in what PATH resolves.
Steps 4 to 6 close that gap; a gate on the command text does not.