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: local repository and exported telemetry scope

    #!/usr/bin/env python3
    import os
    import sys
    import json
    import subprocess
    from pathlib import Path
    
    ROOT = sys.argv[1] if len(sys.argv) > 1 else "."
    LOG_ROOT = os.environ.get("LOG_ROOT", "")
    OUT = Path(os.environ.get("OUT", "hp-xinference-pypi-credential-hijack-scope"))
    SINCE = "2026-04-22T00:00:00Z"
    UNTIL = "2026-04-23T23:59:59Z"
    
    PACKAGES = [
    ]
    VERSIONS = [
      "xinference==2.6.0",
      "xinference==2.6.1",
      "xinference==2.6.2",
    ]
    FILES = [
      "xinference/__init__.py",
      "love.tar.gz",
      "f",
    ]
    DOMAINS = [
      "whereisitat.lucyatemysuperbox.space",
      "love.tar.gz",
    ]
    URLS = [
      "https://whereisitat.lucyatemysuperbox.space/",
    ]
    IPS = [
    ]
    HASHES = [
      "e1e007ce4eab7774785617179d1c01a9381ae83abfd431aae8dba6f82d3ac127",
      "077d49fa708f498969d7cdffe701eb64675baaa4968ded9bd97a4936dd56c21c",
      "fe17e2ea4012d07d90ecb7793c1b0593a6138d25a9393192263e751660ec3cd0",
    ]
    PROCESS_PATTERNS = [
      "curl --data-binary",
      "subprocess.Popen",
    ]
    NETWORK_PATTERNS = [
    ]
    
    # Positive signal: repository, lockfile, artifact, process, or network telemetry contains one of the exact incident selectors above.
    # Escalation: any match tied to a production build, CI run, deployed asset, or secret-bearing host moves the asset to presumed exposed.
    
    OUT.mkdir(parents=True, exist_ok=True)
    indicators_file = OUT / "indicators.txt"
    
    # Collect unique indicators
    indicators = set()
    for group in [PACKAGES, VERSIONS, FILES, DOMAINS, URLS, IPS, HASHES, PROCESS_PATTERNS, NETWORK_PATTERNS]:
        for val in group:
            if val:
                indicators.add(val)
    
    with open(indicators_file, "w") as f:
        for ind in sorted(indicators):
            f.write(ind + "\n")
    
    print(f"[+] Written unique selectors to {indicators_file}")
    
    # Walk local directory
    print(f"[+] Scanning directory: {ROOT} for selectors...")
    matches = []
    exclude_dirs = {"node_modules", "vendor", "dist", ".git"}
    for root, dirs, filenames in os.walk(ROOT):
        dirs[:] = [d for d in dirs if d not in exclude_dirs]
        for filename in filenames:
            filepath = Path(root) / filename
            try:
                content = filepath.read_text(errors="ignore")
                for ind in indicators:
                    if ind in content:
                        matches.append(f"{filepath}: found '{ind}'")
            except Exception:
                pass
    
    if matches:
        (OUT / "repository-indicator-matches.txt").write_text("\n".join(matches) + "\n")
        print(f"[!] Found {len(matches)} matches in codebase!")
    
    # Optional Log Scanning
    if LOG_ROOT and os.path.exists(LOG_ROOT):
        print(f"[+] Scanning telemetry log directory: {LOG_ROOT}...")
        log_matches = []
        for root, _, filenames in os.walk(LOG_ROOT):
            for filename in filenames:
                filepath = Path(root) / filename
                try:
                    content = filepath.read_text(errors="ignore")
                    for ind in indicators:
                        if ind in content:
                            log_matches.append(f"{filepath}: found '{ind}'")
                except Exception:
                    pass
        if log_matches:
            (OUT / "exported-telemetry-indicator-matches.txt").write_text("\n".join(log_matches) + "\n")
            print(f"[!] Found {len(log_matches)} matches in logs!")
    
        if PACKAGES:
            registry_dir = OUT / "registry"
            registry_dir.mkdir(exist_ok=True)
            for package in PACKAGES:
                if not package: continue
                safe_name = package.replace("/", "__")
                print(f"[+] Querying pip index for {package}...")
                res = subprocess.run(["python3", "-m", "pip", "index", "versions", package], capture_output=True, text=True)
                if res.returncode == 0:
                    (registry_dir / f"pypi-{safe_name}-versions.txt").write_text(res.stdout)
                subprocess.run(["python3", "-m", "pip", "download", "--no-deps", package, "-d", str(registry_dir)], capture_output=True)
    
    print(f"[+] Wrote scope artifacts under {OUT}")

    Downstream Abuse Audits

    Script: GitHub organization run, release, secret, and workflow audit

    #!/usr/bin/env python3
    import os
    import sys
    import json
    import subprocess
    from pathlib import Path
    
    if "ORG" not in os.environ:
        print("ERROR: Set ORG environment variable to the GitHub organization to audit", file=sys.stderr)
        sys.exit(1)
    
    ORG = os.environ["ORG"]
    SINCE = "2026-04-22T00:00:00Z"
    UNTIL = "2026-04-23T23:59:59Z"
    OUT = Path(os.environ.get("OUT", "hp-xinference-pypi-credential-hijack-github-audit"))
    
    SELECTORS = [
      "xinference==2.6.0",
      "xinference==2.6.1",
      "xinference==2.6.2",
      "xinference/__init__.py",
      "love.tar.gz",
      "f",
      "whereisitat.lucyatemysuperbox.space",
      "https://whereisitat.lucyatemysuperbox.space/",
      "e1e007ce4eab7774785617179d1c01a9381ae83abfd431aae8dba6f82d3ac127",
      "077d49fa708f498969d7cdffe701eb64675baaa4968ded9bd97a4936dd56c21c",
      "fe17e2ea4012d07d90ecb7793c1b0593a6138d25a9393192263e751660ec3cd0",
    ]
    
    # Positive signal: a workflow run, release, secret, key, package, or workflow change overlaps the exposure window and references an incident selector.
    # Remediation trigger: unauthorized post-exposure write activity or a secret-bearing run matching an incident selector requires token revocation and downstream cloud/registry review.
    
    OUT.mkdir(parents=True, exist_ok=True)
    (OUT / "runs").mkdir(exist_ok=True)
    (OUT / "logs").mkdir(exist_ok=True)
    (OUT / "repos").mkdir(exist_ok=True)
    
    # 1. Write incident-selectors file
    selectors_file = OUT / "incident-selectors.txt"
    with open(selectors_file, "w") as sf:
        for s in SELECTORS:
            if s:
                sf.write(s + "\n")
    
    # 2. Get list of repos
    print(f"[+] Fetching repositories for organization: {ORG}")
    repo_res = subprocess.run(["gh", "repo", "list", ORG, "--limit", "1000", "--json", "nameWithOwner"], capture_output=True, text=True)
    if repo_res.returncode != 0:
        print(f"[-] Failed to fetch repos: {repo_res.stderr}", file=sys.stderr)
        sys.exit(1)
    
    repos = [r["nameWithOwner"] for r in json.loads(repo_res.stdout)]
    
    for repo in repos:
        safe_repo = repo.replace("/", "__")
        print(f"[+] Auditing repository: {repo}")
    
        # Check runs in the window
        runs_res = subprocess.run([
            "gh", "api", f"/repos/{repo}/actions/runs",
            "-f", "per_page=100",
            "-f", f"created=>={SINCE}",
            "--paginate"
        ], capture_output=True, text=True)
    
        if runs_res.returncode == 0:
            try:
                all_runs = json.loads(runs_res.stdout).get("workflow_runs", [])
                filtered_runs = [r for r in all_runs if r["created_at"] <= UNTIL]
    
                if filtered_runs:
                    with open(OUT / "runs" / f"{safe_repo}-runs.jsonl", "w") as rf:
                        for run in filtered_runs:
                            rf.write(json.dumps(run) + "\n")
    
                            # Fetch log dynamically
                            run_id = str(run["id"])
                            log_res = subprocess.run(["gh", "run", "view", run_id, "--repo", repo, "--log"], capture_output=True, text=True)
                            if log_res.returncode == 0:
                                (OUT / "logs" / f"{safe_repo}-{run_id}.log").write_text(log_res.stdout)
    
                            # Fetch details
                            view_res = subprocess.run(["gh", "run", "view", run_id, "--repo", repo, "--json", "databaseId,workflowName,headSha,event,createdAt,jobs"], capture_output=True, text=True)
                            if view_res.returncode == 0:
                                (OUT / "runs" / f"{safe_repo}-{run_id}.json").write_text(view_res.stdout)
            except Exception as e:
                print(f"[-] Error parsing runs for {repo}: {e}")
    
        # Check releases in window
        subprocess.run(["gh", "api", f"/repos/{repo}/releases", "-f", "per_page=100", "--paginate"], capture_output=True)
        # Check repo secrets updated in window
        subprocess.run(["gh", "api", f"/repos/{repo}/actions/secrets", "-f", "per_page=100", "--paginate"], capture_output=True)
        # Check deploy keys
        subprocess.run(["gh", "api", f"/repos/{repo}/keys", "-f", "per_page=100", "--paginate"], capture_output=True)
    
    # Scan output directory for any indicator selector matches
    print("[+] Scanning gathered telemetry for indicator matches...")
    subprocess.run(["rg", "-n", "--hidden", "--fixed-strings", "-f", str(selectors_file), str(OUT)], capture_output=False)
    
    print(f"[+] Wrote GitHub audit artifacts under {OUT}")

    Script: cloud OIDC and deployment credential follow-on audit

    #!/usr/bin/env python3
    import os
    import json
    import subprocess
    from pathlib import Path
    
    SINCE = "2026-04-22T00:00:00Z"
    UNTIL = "2026-04-23T23:59:59Z"
    OUT = Path(os.environ.get("OUT", "hp-xinference-pypi-credential-hijack-cloud-audit"))
    AWS_REGIONS = os.environ.get("AWS_REGIONS", "us-east-1").split(",")
    
    # Positive signal: token exchange or privileged write activity occurs in the exposure window from GitHub, CI/CD, package registry, or deployment automation identity.
    # Remediation trigger: unexpected write, deploy, IAM, secret, or registry activity tied to an exposed CI/CD path requires trust-policy disablement and credential rotation.
    
    OUT.mkdir(parents=True, exist_ok=True)
    
    # 1. AWS CloudTrail Audit
    print("[+] Querying AWS CloudTrail for Web Identity token exchanges...")
    aws_events = []
    for region in AWS_REGIONS:
        res = subprocess.run([
            "aws", "cloudtrail", "lookup-events",
            "--region", region,
            "--start-time", SINCE,
            "--end-time", UNTIL,
            "--lookup-attributes", "AttributeKey=EventName,AttributeValue=AssumeRoleWithWebIdentity",
            "--output", "json"
        ], capture_output=True, text=True)
    
        if res.returncode == 0:
            try:
                events = json.loads(res.stdout).get("Events", [])
                for event in events:
                    ct = json.loads(event.get("CloudTrailEvent", "{}"))
                    ct["region"] = region
                    aws_events.append(ct)
            except Exception as e:
                print(f"[-] Error parsing AWS CloudTrail events: {e}")
    
    if aws_events:
        with open(OUT / "aws-assume-role-with-web-identity.jsonl", "w") as f:
            for ev in aws_events:
                f.write(json.dumps(ev) + "\n")
    
        # Audit follow-on events for returned access keys
        for ev in aws_events:
            access_key = ev.get("responseElements", {}).get("credentials", {}).get("accessKeyId")
            region = ev.get("region", "us-east-1")
            if access_key:
                print(f"[+] Enumerating AWS events for AccessKey: {access_key}")
                f_res = subprocess.run([
                    "aws", "cloudtrail", "lookup-events",
                    "--region", region,
                    "--start-time", SINCE,
                    "--end-time", UNTIL,
                    "--lookup-attributes", f"AttributeKey=AccessKeyId,AttributeValue={access_key}",
                    "--output", "json"
                ], capture_output=True, text=True)
                if f_res.returncode == 0:
                    try:
                        f_events = json.loads(f_res.stdout).get("Events", [])
                        with open(OUT / "aws-follow-on-api-calls.jsonl", "a") as ff:
                            for fe in f_events:
                                ff.write(fe.get("CloudTrailEvent", "{}") + "\n")
                    except Exception as e:
                        print(f"[-] Error writing follow-on events: {e}")
    
    # 2. Azure Activity Log Audit
    print("[+] Querying Azure activity logs...")
    az_res = subprocess.run([
        "az", "monitor", "activity-log", "list",
        "--start-time", SINCE,
        "--end-time", UNTIL,
        "--query", "[?contains(operationName.value, 'write') || contains(operationName.value, 'delete') || contains(operationName.value, 'Microsoft.ManagedIdentity')]",
        "-o", "json"
    ], capture_output=True, text=True)
    
    if az_res.returncode == 0:
        (OUT / "azure-write-delete-activity.json").write_text(az_res.stdout)
    
    # 3. GCP Logging Audit
    print("[+] Querying GCP Cloud Logging...")
    gcp_filter = f'timestamp>="{SINCE}" AND timestamp<="{UNTIL}" AND (protoPayload.methodName="google.sts.v1.SecurityTokenService.ExchangeToken" OR protoPayload.methodName:"GenerateAccessToken" OR protoPayload.methodName:"CreateServiceAccountKey" OR protoPayload.methodName:"SetIamPolicy")'
    gcp_res = subprocess.run([
        "gcloud", "logging", "read", gcp_filter,
        "--format", "json"
    ], capture_output=True, text=True)
    
    if gcp_res.returncode == 0:
        (OUT / "gcp-token-and-iam-activity.json").write_text(gcp_res.stdout)
    
    print(f"[+] Wrote cloud audit artifacts under {OUT}")

    Script: registry metadata and artifact audit

    #!/usr/bin/env python3
    import os
    import json
    import subprocess
    from pathlib import Path
    
    SINCE = "2026-04-22T00:00:00Z"
    OUT = Path(os.environ.get("OUT", "hp-xinference-pypi-credential-hijack-registry-audit"))
    PACKAGES = [
    ]
    VERSIONS = [
      "xinference==2.6.0",
      "xinference==2.6.1",
      "xinference==2.6.2",
    ]
    
    # Positive signal: registry metadata, package tarballs, or cached artifacts contain the exact affected package/version values.
    # Remediation trigger: any internal package cache, build artifact, or deployment using these package/version values requires exposure scoping.
    
    OUT.mkdir(parents=True, exist_ok=True)
    
    with open(OUT / "affected-versions.txt", "w") as av:
        for version in VERSIONS:
            if version:
                av.write(version + "\n")
    
        # 1. Audit PyPI dependencies in project files
        print("[+] Scanning PyPI dependency files...")
        for file in ["requirements.txt", "poetry.lock", "Pipfile.lock", "pyproject.toml", "setup.py"]:
            if Path(file).exists():
                subprocess.run(["rg", "-n", "--hidden", "--fixed-strings", "-f", str(OUT / "affected-versions.txt"), file])
    
        # 2. Query registry metadata and download packages for local analysis
        packages_dir = OUT / "packages"
        metadata_dir = OUT / "metadata"
        packages_dir.mkdir(exist_ok=True)
        metadata_dir.mkdir(exist_ok=True)
        for package in PACKAGES:
            if not package: continue
            print(f"[+] Querying pip index for {package}...")
            res = subprocess.run(["python3", "-m", "pip", "index", "versions", package], capture_output=True, text=True)
            if res.returncode == 0:
                (metadata_dir / f"{package}-versions.txt").write_text(res.stdout)
            subprocess.run(["python3", "-m", "pip", "download", "--no-deps", package, "-d", str(packages_dir)], capture_output=True)
    
        # 3. HOW TO REVOKE AND ROTATE EXPOSED PYPI PUBLISHING TOKENS:
        # PyPI does not support token revocation via CLI. Follow these exact steps:
        # 1. Log in to https://pypi.org/manage/account/
        # 2. Scroll to the "API tokens" section and click "Remove" on any compromised tokens.
        # 3. Generate a new API token limited to the specific project scope.
        # 4. Update your CI/CD secrets using the GitHub CLI:
        #    subprocess.run(["gh", "secret", "set", "PYPI_API_TOKEN", "--body", "pypi-AgEIcHlwaS5vcm...", "--repo", "my-org/my-repo"])
    
    print(f"[+] Wrote registry audit artifacts 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.

    IOC Clipboard

    9 IOCs
    Defang IOCs
    domain whereisitat.lucyatemysuperbox.space whereisitat[.]lucyatemysuperbox[.]space
    domain love.tar.gz love[.]tar[.]gz
    url https://whereisitat.lucyatemysuperbox.space/ hxxps://whereisitat[.]lucyatemysuperbox[.]space/
    hash e1e007ce4eab7774785617179d1c01a9381ae83abfd431aae8dba6f82d3ac127 e1e007ce4eab7774785617179d1c01a9381ae83abfd431aae8dba6f82d3ac127
    hash 077d49fa708f498969d7cdffe701eb64675baaa4968ded9bd97a4936dd56c21c 077d49fa708f498969d7cdffe701eb64675baaa4968ded9bd97a4936dd56c21c
    hash fe17e2ea4012d07d90ecb7793c1b0593a6138d25a9393192263e751660ec3cd0 fe17e2ea4012d07d90ecb7793c1b0593a6138d25a9393192263e751660ec3cd0
    file xinference/__init__.py xinference/__init__.py
    file love.tar.gz love.tar.gz
    file f f