critical Threat analysis

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.

#pypi#supply-chain#xinference#ai-ml#credential-theft
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 xinference versions 2.6.0, 2.6.1, and 2.6.2 as compromised and lists Xray ID XRAY-96896 [Source 1].
    • confirmed: JFrog identified xinference/__init__.py as the malicious file, SHA-256 e1e007ce4eab7774785617179d1c01a9381ae83abfd431aae8dba6f82d3ac127, and decoded stage SHA-256 values 077d49fa708f498969d7cdffe701eb64675baaa4968ded9bd97a4936dd56c21c and fe17e2ea4012d07d90ecb7793c1b0593a6138d25a9393192263e751660ec3cd0 [Source 1].
    • confirmed: JFrog observed exfiltration to https://whereisitat.lucyatemysuperbox.space/, archive name love.tar.gz, and HTTP header X-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

    ClassificationCriteriaEvidence to collectHandling decision
    Confirmed compromisexinference==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 exposedAn 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 exposedxinference 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 exposedEvidence 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.
    UnknownRequired 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 xinference versions 2.6.0, 2.6.1, and 2.6.2 as 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, archive love.tar.gz, and marker # hacked by teampcp [Source 1].
    • 2026-04-25: PyPI visible release history resumed at 2.7.0 after 2.5.0, leaving the compromised 2.6.x releases 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

    1. 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.
    2. PyPI: xinference release history - Role: REGISTRY_METADATA - Impact: Current visible project metadata and release-history gap around 2.6.x.