qzc690385 on PyPI ran shell commands at install from Xinference model names and sent the output back, found on 7 November 2026.
On 7 November 2026, the PyPI package qzc690385 went up in twelve versions in under five hours. Every version runs shell commands when pip installs it. The package holds no real code. Its __init__.py is a comment.
The setup.py file talks to an Xinference server on the same machine. It reads commands from the names of registered models and writes the output back as new model names. Version 1.0.0 also starts a shell server that listens on five ports.
What happened
pip runs setup.py when it builds a source download. Every version of qzc690385 ships only a source download, so the code runs at install. The user does not need to import the package.
The code works with the Xinference API at http://127.0.0.1:9997/v1/model_registrations/flexible. Xinference is a server that runs AI models. The versions differ, but they share one channel:
- The code reads the list of registered models. It keeps the names that start with
xcmd0. - It decodes the rest of each name from base64 and runs the result with
/bin/sh. With no command, 1.0.4 runsid;hostname;uname -a. - It encodes the output in base64 and registers new models named
zqx9pplus a chunk of the output. Anyone who can read the model list can read the output.
Version 1.0.0 does more. It writes a small HTTP server to /tmp/.zqx and starts it in the background with nohup. The server listens on all interfaces on ports 9998, 9999, 8089, 8099 and 8777. It runs any shell command that a GET request sends with the token kq7731.
Version 1.1.1 writes /tmp/kiki_c2.py and starts it in the background. That script repeats the read, run and send-back steps every 6 seconds, so it keeps taking commands after the install ends.
Amazon Inspector reported 1.0.0 and 1.0.1 to the OpenSSF malicious packages database. OSV published the record MAL-2026-18751 at 10:21 UTC. The author kept uploading new versions after that.
Vigilance caught version 1.0.4 at 13:53 UTC on 7 November 2026, 1 minute 57 seconds after it went up on PyPI. The first bad version, 1.0.0, went up at 09:53 UTC. Counted from that upload, the time to catch was 4 hours. The OSV record MAL-2026-18751 came out at 10:21 UTC, before the catch. The scan showed one file, setup.py, that gained decode-and-run code and machine profiling. Sentinel saw version 1.0.0 at 09:55 UTC, but 1.0.0 had no earlier version to compare with, and the judge did not read it.
Vigilance reported versions 1.0.0 to 1.1.1 to PyPI at 14:35 UTC. PyPI quarantined the project by 14:50 UTC. No files are left to install.
Affected versions
- Every version from 1.0.0 to 1.1.1, all uploaded on 7 November 2026.
- The OSV record lists 1.0.0 and 1.0.1 only. The later versions use the same channel.
- No clean version exists. PyPI quarantined the project on 7 November 2026.
Indicators of compromise
- Package name
qzc690385, any version. - POST and GET requests to
http://127.0.0.1:9997/v1/model_registrations/flexible. - Xinference models named
xcmd0...,zqx9alive,zqx9p...,zqx9loop,zqx9err,zqx8...orzqxp.... - Files
/tmp/.zqx,/tmp/kiki_c2.py,/tmp/kiki_c2.log,/tmp/kiki_r.sh,/tmp/kiki_out.txtand/tmp/kiki_err.txt. - A process that listens on port 9998, 9999, 8089, 8099 or 8777.
- SHA-256 of qzc690385-1.0.0.tar.gz:
d94e0d5acdff4d40b504f49a9c258e070201bf4e077e5d674eda702216b2cc08
How to check
Check whether the package is installed.
pip show qzc690385
Look for the files and the background processes it leaves.
ls -la /tmp/.zqx /tmp/kiki_* 2>/dev/null; ps aux | grep -E "[.]zqx|kiki_c2"
If you run Xinference, list the registered models and look for the names above.
curl -s http://127.0.0.1:9997/v1/model_registrations/flexible | grep -oE "(xcmd0|zqx)[A-Za-z0-9_-]*"
What to do now
- Uninstall the package with
pip uninstall qzc690385. - Stop the background processes and delete the files in
/tmplisted above. - Delete every Xinference model whose name starts with
xcmd0orzqx. - Find out who can reach your Xinference API. Anyone who can register a model can send commands to a machine that installed this package.
- Treat the machine as compromised. Rotate every secret that the installing user can read.
What Vigilance would show (rebuilt from public reports)
Vigilance compares the version you trust with the new one and names the file that gained a new power. The block below is rebuilt from the public reports in the words Vigilance prints. It is not a captured scan, because the malicious release is not redistributed.
files scanned: 45 HEADS UP 1 file can now do things the old version could not. The rest changed and gained nothing. CHANGED setup.py It now runs a command on its own when it is installed and runs a hidden, encoded command. It did not before.
Frequently asked questions
Which qzc690385 versions are malicious?
Every version from 1.0.0 to 1.1.1. Each one runs shell commands from setup.py when pip installs it.
Does qzc690385 need Xinference to do harm?
The command channel uses an Xinference server on the same machine. Version 1.0.0 also starts its own shell server on five ports, and that server does not need Xinference.