Xinference PyPI 2.6.x Import-Time Credential Exfiltration
JFrog reported that the legitimate PyPI package xinference shipped malicious versions 2.6.0, 2.6.1, and 2.6.2 with import-time code in xinference/__init__.py. The payload collected host and secret material into love.tar.gz and posted it to whereisitat.lucyatemysuperbox.space with header X-QT-SR: 14.
On this page 0% read
Executive Summary
JFrog reported a PyPI supply-chain compromise of the legitimate xinference package affecting versions 2.6.0, 2.6.1, and 2.6.2. The malicious code lived in xinference/__init__.py, so exposure requires installation plus Python import, CLI startup, service startup, or another path that loads the package [Source 1].
The decoded payload created a temporary directory, ran a second-stage collector, captured output to a temporary file named f, compressed it into love.tar.gz, and uploaded it to https://whereisitat.lucyatemysuperbox.space/ using curl --data-binary and header X-QT-SR: 14 [Source 1]. JFrog lists the package file hash, stage hashes, archive name, actor marker string, affected versions, and Xray ID XRAY-96896 [Source 1].
Current PyPI release history shows 2.5.0 before 2.7.0; the malicious 2.6.x releases are no longer in the visible history. Use version selectors and package-cache evidence as the primary exposure proof, then use C2/domain/header/hash selectors to separate confirmed execution from presumed exposure [Source 2].
Key Facts
event_type: "legitimate PyPI package compromise"
ecosystem: "PyPI"
package:
name: "xinference"
malicious_versions:
- "2.6.0"
- "2.6.1"
- "2.6.2"
visible_current_pypi_gap: "2.5.0 to 2.7.0"
collection_window_utc:
start: "2026-04-22T00:00:00Z"
end: "2026-04-23T23:59:59Z"
execution_trigger: "Python import or service/CLI path loading xinference/__init__.py"
malicious_file:
path: "xinference/__init__.py"
sha256: "e1e007ce4eab7774785617179d1c01a9381ae83abfd431aae8dba6f82d3ac127"
decoded_stage_hashes_sha256:
stage_1: "077d49fa708f498969d7cdffe701eb64675baaa4968ded9bd97a4936dd56c21c"
stage_2: "fe17e2ea4012d07d90ecb7793c1b0593a6138d25a9393192263e751660ec3cd0"
network_iocs:
- "whereisitat.lucyatemysuperbox.space"
- "https://whereisitat.lucyatemysuperbox.space/"
protocol_artifacts:
- "curl --data-binary"
- "X-QT-SR: 14"
file_artifacts:
- "love.tar.gz"
- "f"
strings:
- "# hacked by teampcp"
credentials_at_risk:
- "environment variables"
- "SSH keys"
- "cloud credentials"
- "Kubernetes tokens and configs"
- "Docker registry credentials"
- "PyPI/npm/Cargo publishing tokens"
- ".env secrets"
- "TLS private keys and certificates"
- "database credentials"
- "wallet keys and seed material"
Source Confidence & Evidence Mapping
- confirmed: JFrog identified
xinferenceversions2.6.0,2.6.1, and2.6.2as compromised and lists Xray IDXRAY-96896[Source 1]. - confirmed: JFrog identified
xinference/__init__.pyas the malicious file, SHA-256e1e007ce4eab7774785617179d1c01a9381ae83abfd431aae8dba6f82d3ac127, and decoded stage SHA-256 values077d49fa708f498969d7cdffe701eb64675baaa4968ded9bd97a4936dd56c21candfe17e2ea4012d07d90ecb7793c1b0593a6138d25a9393192263e751660ec3cd0[Source 1]. - confirmed: JFrog observed exfiltration to
https://whereisitat.lucyatemysuperbox.space/, archive namelove.tar.gz, and HTTP headerX-QT-SR: 14[Source 1]. - unclear: Public evidence does not prove the exact PyPI publish credential theft path. JFrog describes a likely path based on prior campaign patterns, but the specific initial access vector remains unconfirmed [Source 1].
- not_observed: JFrog states the payload does not include persistence, a reverse shell, destructive wiping, ransomware, or privilege escalation [Source 1].
Impact Determination
| Classification | Criteria | Evidence to collect | Handling decision |
|---|---|---|---|
| Confirmed compromise | xinference==2.6.0, xinference==2.6.1, or xinference==2.6.2 was installed and import/runtime/C2 evidence exists. | Installed package metadata, lockfile, package cache, Python import traces, xinference/__init__.py hash, love.tar.gz, C2 DNS/HTTP, X-QT-SR: 14, # hacked by teampcp. | Preserve the host/container/runner evidence, isolate the asset, revoke credentials reachable from the Python process, and run the downstream audits below. |
| Presumed exposed | An affected version was installed in a host, image, notebook, virtualenv, or CI job, but import/network telemetry is missing. | pip freeze, requirements*.txt, poetry.lock, uv.lock, image layer output, CI install output, package cache. | Treat secret material readable by that environment as exposed unless import non-execution can be proven. |
| Potentially exposed | xinference was unpinned or upgraded during the April 22-23 collection window, but resolved version evidence is incomplete. | Dependency manifests, package proxy records, CI output, image build history, notebook environment exports. | Collect resolver/cache evidence before reducing scope. |
| Not exposed | Evidence shows no malicious 2.6.x version in source, lockfiles, package caches, images, hosts, or runtime telemetry. | Negative dependency inventory, virtualenv/container search, package cache query, and network selector search. | Keep negative evidence with the case record and close this event for that asset. |
| Unknown | Required package inventory, CI output, endpoint telemetry, image inventory, or network telemetry is unavailable. | Named telemetry gap with system, owner, and retention status. | Keep high-value AI/ML, GPU, CI, and cloud-adjacent assets in scope until evidence is recovered or risk is explicitly accepted. |
Minimum Evidence To Collect
package_evidence:
- "xinference==2.6.0"
- "xinference==2.6.1"
- "xinference==2.6.2"
- "xinference/__init__.py SHA-256 e1e007ce4eab7774785617179d1c01a9381ae83abfd431aae8dba6f82d3ac127"
runtime_evidence:
- "Python import of xinference"
- "subprocess.Popen child Python execution"
- "curl --data-binary"
network_evidence:
- "whereisitat.lucyatemysuperbox.space"
- "https://whereisitat.lucyatemysuperbox.space/"
- "X-QT-SR: 14"
file_evidence:
- "love.tar.gz"
- "temporary collector output file named f"
string_evidence:
- "# hacked by teampcp"
Timeline
- 2026-04-22: JFrog published research identifying
xinferenceversions2.6.0,2.6.1, and2.6.2as compromised and yanked [Source 1]. - 2026-04-22: JFrog described the malicious payload in
xinference/__init__.py, including import-time execution and staged collection/exfiltration [Source 1]. - 2026-04-22: JFrog listed the IOCs: affected package versions, C2 domain/URL, header
X-QT-SR: 14, SHA-256 hashes, archivelove.tar.gz, and marker# hacked by teampcp[Source 1]. - 2026-04-25: PyPI visible release history resumed at
2.7.0after2.5.0, leaving the compromised2.6.xreleases absent from the current visible release list [Source 2].
What Happened
Attackers published malicious xinference versions directly to PyPI under the legitimate package name. JFrog reports that this was not a lookalike package; the affected identity was the real xinference package [Source 1].
The malicious code was placed in xinference/__init__.py, making import and service startup the relevant execution boundary. The first stage decoded a second-stage collector, ran it in a child Python interpreter, wrote collected output to a temporary file named f, compressed that file into love.tar.gz, and uploaded the archive with curl to the C2 URL [Source 1].
JFrog’s decoded collector logic targeted host inventory and secret locations. The article specifically calls out SSH material, cloud credentials, Kubernetes service account tokens/configs, Docker registry credentials, package publishing tokens, .env secrets, TLS material, database passwords, and wallet key material as credential classes requiring downstream scoping [Source 1].
Technical Analysis
Package Manipulation
package_identity:
registry: "PyPI"
package: "xinference"
malicious_versions:
- "2.6.0"
- "2.6.1"
- "2.6.2"
malicious_file:
path: "xinference/__init__.py"
sha256: "e1e007ce4eab7774785617179d1c01a9381ae83abfd431aae8dba6f82d3ac127"
execution_trigger: "module import or startup path that imports xinference"
Payload Behavior
The stage-one payload created temporary working storage, decoded and piped a second-stage collector into a child Python interpreter, captured stdout into f, compressed the output into love.tar.gz, and sent the archive using curl --data-binary [Source 1].
Exfiltration
exfiltration:
domain: "whereisitat.lucyatemysuperbox.space"
url: "https://whereisitat.lucyatemysuperbox.space/"
method: "HTTP POST"
tool: "curl"
body_mode: "--data-binary"
custom_header: "X-QT-SR: 14"
archive: "love.tar.gz"
Affected Assets and Blast Radius
affected_assets:
ecosystems:
- "PyPI"
packages:
- "xinference==2.6.0"
- "xinference==2.6.1"
- "xinference==2.6.2"
environments:
- "developer virtualenvs"
- "Jupyter notebook kernels"
- "GPU inference servers"
- "CI runners"
- "container images built from affected requirements"
- "Kubernetes pods importing xinference"
downstream_systems:
- "AWS"
- "GCP"
- "Azure"
- "Kubernetes"
- "Docker registries"
- "PyPI/npm/Cargo registries"
- "Git hosting"
not_currently_known_to_affect:
- "visible PyPI releases 2.5.0 and 2.7.0 when installed from official current metadata"
Indicators of Compromise
package_versions:
- "xinference==2.6.0"
- "xinference==2.6.1"
- "xinference==2.6.2"
files:
- "xinference/__init__.py"
- "love.tar.gz"
- "f"
hashes_sha256:
- "e1e007ce4eab7774785617179d1c01a9381ae83abfd431aae8dba6f82d3ac127"
- "077d49fa708f498969d7cdffe701eb64675baaa4968ded9bd97a4936dd56c21c"
- "fe17e2ea4012d07d90ecb7793c1b0593a6138d25a9393192263e751660ec3cd0"
domains:
- "whereisitat.lucyatemysuperbox.space"
urls:
- "https://whereisitat.lucyatemysuperbox.space/"
process_patterns:
- "curl --data-binary"
- "subprocess.Popen"
headers:
- "X-QT-SR: 14"
strings:
- "# hacked by teampcp"
Detection and Hunting
Script: Python environment, source, image-export, and telemetry selector sweep
#!/usr/bin/env python3
import json
import os
import subprocess
import sys
from pathlib import Path
ROOT = Path(sys.argv[1] if len(sys.argv) > 1 else ".").resolve()
LOG_ROOT = Path(os.environ.get("LOG_ROOT", "")).resolve() if os.environ.get("LOG_ROOT") else None
OUT = Path(os.environ.get("OUT", "hp-xinference-2-6-x-scope")).resolve()
COLLECTION_START = "2026-04-22T00:00:00Z"
COLLECTION_END = "2026-04-23T23:59:59Z"
SELECTORS = [
"xinference",
"xinference==2.6.0",
"xinference==2.6.1",
"xinference==2.6.2",
"2.6.0",
"2.6.1",
"2.6.2",
"xinference/__init__.py",
"whereisitat.lucyatemysuperbox.space",
"https://whereisitat.lucyatemysuperbox.space/",
"X-QT-SR: 14",
"love.tar.gz",
"# hacked by teampcp",
"e1e007ce4eab7774785617179d1c01a9381ae83abfd431aae8dba6f82d3ac127",
"077d49fa708f498969d7cdffe701eb64675baaa4968ded9bd97a4936dd56c21c",
"fe17e2ea4012d07d90ecb7793c1b0593a6138d25a9393192263e751660ec3cd0",
]
DEPENDENCY_FILES = {
"requirements.txt",
"requirements.lock",
"pyproject.toml",
"poetry.lock",
"uv.lock",
"Pipfile.lock",
"environment.yml",
"conda-lock.yml",
"sbom.json",
"cyclonedx.json",
}
# Positive signal: affected xinference 2.6.x selectors appear in dependency, virtualenv, image/SBOM, package cache, import traces, or network telemetry.
# Escalation: affected version plus import evidence, C2 selector, X-QT-SR: 14, or malicious hash moves the asset to confirmed compromise.
OUT.mkdir(parents=True, exist_ok=True)
(OUT / "matches").mkdir(exist_ok=True)
(OUT / "python-env").mkdir(exist_ok=True)
selectors_file = OUT / "xinference-selectors.txt"
selectors_file.write_text("\n".join(SELECTORS) + "\n")
def scan_tree(base: Path, label: str) -> list[dict]:
findings = []
if not base.exists():
return findings
skip = {".git", "node_modules", ".venv", "venv", "__pycache__", "dist", "build"}
for root, dirs, files in os.walk(base):
dirs[:] = [d for d in dirs if d not in skip]
for name in files:
p = Path(root) / name
if p.stat().st_size > 50_000_000:
continue
try:
data = p.read_text(errors="ignore")
except Exception:
continue
hits = [s for s in SELECTORS if s in data]
if hits:
findings.append({"scope": label, "path": str(p), "hits": sorted(set(hits))})
return findings
findings = scan_tree(ROOT, "root")
if LOG_ROOT:
findings.extend(scan_tree(LOG_ROOT, "log_root"))
for item in findings:
print(f"[MATCH] {item['scope']} {item['path']} :: {', '.join(item['hits'])}")
(OUT / "matches" / "selector-matches.jsonl").write_text(
"".join(json.dumps(x, sort_keys=True) + "\n" for x in findings)
)
(OUT / "matches" / "dependency-file-hits.jsonl").write_text(
"".join(json.dumps(x, sort_keys=True) + "\n" for x in findings if Path(x["path"]).name in DEPENDENCY_FILES)
)
commands = {
"pip-freeze.txt": ["python3", "-m", "pip", "freeze"],
"pip-show-xinference.txt": ["python3", "-m", "pip", "show", "xinference"],
"pip-index-xinference.txt": ["python3", "-m", "pip", "index", "versions", "xinference"],
}
for name, cmd in commands.items():
res = subprocess.run(cmd, text=True, capture_output=True)
(OUT / "python-env" / name).write_text(res.stdout + res.stderr)
if findings:
print(f"[!] {len(findings)} selector-bearing files written under {OUT}")
else:
print(f"[+] No xinference incident selectors found under {ROOT}")
Downstream Abuse Audits
Script: cloud, Kubernetes, GitHub, and registry activity collector
#!/usr/bin/env python3
import os
import subprocess
from pathlib import Path
OUT = Path(os.environ.get("OUT", "hp-xinference-2-6-x-downstream")).resolve()
ORG = os.environ.get("ORG", "")
K8S_AUDIT_LOG = os.environ.get("K8S_AUDIT_LOG", "")
AWS_REGIONS = [r for r in os.environ.get("AWS_REGIONS", "us-east-1").split(",") if r]
SINCE = "2026-04-22T00:00:00Z"
UNTIL = "2026-04-30T23:59:59Z"
SELECTORS = [
"xinference==2.6.0",
"xinference==2.6.1",
"xinference==2.6.2",
"whereisitat.lucyatemysuperbox.space",
"X-QT-SR: 14",
"love.tar.gz",
"# hacked by teampcp",
"e1e007ce4eab7774785617179d1c01a9381ae83abfd431aae8dba6f82d3ac127",
]
# Positive signal: post-exposure IAM, secret-manager, registry, Kubernetes, or source-control activity occurs from an identity reachable by an environment that installed or imported xinference 2.6.x.
# Remediation trigger: CreateAccessKey, GetSecretValue, PutRolePolicy, CreateServiceAccountKey, SetIamPolicy, kubernetes secret read, or registry publish activity after exposure requires revocation of that identity and rebuild of the affected runtime.
OUT.mkdir(parents=True, exist_ok=True)
(OUT / "aws").mkdir(exist_ok=True)
(OUT / "gcp").mkdir(exist_ok=True)
(OUT / "azure").mkdir(exist_ok=True)
(OUT / "github").mkdir(exist_ok=True)
(OUT / "kubernetes").mkdir(exist_ok=True)
selectors_file = OUT / "selectors.txt"
selectors_file.write_text("\n".join(SELECTORS) + "\n")
def run(cmd: list[str], outfile: Path) -> None:
res = subprocess.run(cmd, text=True, capture_output=True)
outfile.write_text(res.stdout + res.stderr)
for region in AWS_REGIONS:
for event_name in [
"AssumeRole",
"AssumeRoleWithWebIdentity",
"GetSecretValue",
"CreateAccessKey",
"PutRolePolicy",
"UpdateAssumeRolePolicy",
"GetParameter",
"GetParameters",
"PutImage",
]:
run(
[
"aws", "cloudtrail", "lookup-events",
"--region", region,
"--start-time", SINCE,
"--end-time", UNTIL,
"--lookup-attributes", f"AttributeKey=EventName,AttributeValue={event_name}",
"--output", "json",
],
OUT / "aws" / f"{region}-{event_name}.json",
)
run(["gcloud", "logging", "read", f'timestamp>="{SINCE}" AND timestamp<="{UNTIL}" AND (protoPayload.methodName:"GenerateAccessToken" OR protoPayload.methodName:"CreateServiceAccountKey" OR protoPayload.methodName:"SetIamPolicy" OR protoPayload.methodName:"AccessSecretVersion")', "--format=json"], OUT / "gcp" / "iam-and-secret-activity.json")
run(["az", "monitor", "activity-log", "list", "--start-time", SINCE, "--end-time", UNTIL, "-o", "json"], OUT / "azure" / "activity-log.json")
if ORG:
run(["gh", "repo", "list", ORG, "--limit", "1000", "--json", "nameWithOwner,pushedAt,visibility"], OUT / "github" / "repos.json")
run(["gh", "api", f"/orgs/{ORG}/packages?package_type=container", "-f", "per_page=100", "--paginate"], OUT / "github" / "container-packages.json")
if K8S_AUDIT_LOG:
run(["rg", "-n", "secrets|get|list|create|patch|delete|token|serviceaccount", K8S_AUDIT_LOG], OUT / "kubernetes" / "secret-and-token-audit-hits.txt")
subprocess.run(["rg", "-n", "--hidden", "--fixed-strings", "-f", str(selectors_file), str(OUT)])
print(f"[+] Downstream artifacts written under {OUT}")
Sources
- JFrog Security Research: TeamPCP strikes again: Xinference PyPI package compromised - Role: PRIMARY_RESEARCH - Impact: Affected versions, malicious file, payload behavior, IOCs, hashes, C2, header, archive name, and non-persistence notes.
- PyPI: xinference release history - Role: REGISTRY_METADATA - Impact: Current visible project metadata and release-history gap around
2.6.x.