durabletask PyPI Supply Chain Compromise
Multi-Stage Credential Stealer, Worm, and Geotargeted Wiper in Microsoft's Azure Workflow SDK
Three malicious releases of Microsoft's durabletask Python SDK (1.4.1–1.4.3) were published to PyPI on May 19, 2026 by the TeamPCP threat group via a stolen PyPI API token. A silent import-time dropper fetched a second-stage payload (rope.pyz) that harvests credentials from AWS, Azure, GCP, Kubernetes, HashiCorp Vault, and 90+ developer tool configurations, worms to up to 10 adjacent hosts via AWS SSM and Kubernetes exec, and deploys a geotargeted disk wiper on Israeli and Iranian systems. PyPI yanked all three malicious versions within hours. Pin to durabletask==1.4.0 immediately.
Technical Breakdown
Microsoft durabletask PyPI Supply Chain Compromise (TeamPCP / Shai-Hulud Wave 4)
Three consecutive malicious versions of Microsoft's durabletask Python SDK were published to PyPI on May 19, 2026 via a compromised PyPI API token. Each version injects a 14-line dropper at import-time that silently fetches rope.pyz from attacker-controlled infrastructure and executes it, deploying a 17-module credential harvester, self-propagating worm, and geotargeted disk wiper. The attack bypassed all CI/CD controls — no GitHub tags, no workflow runs, and no commits matched the published versions.
durabletask==1.4.0
Root Cause
A PyPI API token for the durabletask package was compromised through a maintainer account previously linked to the @antv supply chain campaign. Because the repository used legacy token-based publishing rather than PyPI's OIDC Trusted Publishing, any holder of the token could publish packages directly via twine — no GitHub Actions workflow, no commit, no audit trail. All three versions were published within a 35-minute window with no corresponding git tags, releases, or CI/CD evidence.
Vulnerable Pattern
PyPI's default trust model is publication-token-based. Without OIDC Trusted Publishing, there is no binding between a published package version and a specific source commit or pipeline run. The attacker exploited this by publishing three progressively more invasive versions — each injecting the dropper into additional module entrypoints (__init__.py → task.py → five files) — to maximize the chance of execution across any import path the victim code followed.
Why AI Services Are High-Value
durabletask is the official Python SDK for Azure Durable Functions — workflow orchestration infrastructure that runs with elevated cloud credentials by design. CI/CD pipelines that import this package routinely have access to cloud IAM roles, Kubernetes service accounts, Vault tokens, and deployment secrets. The worm's use of AWS SSM SendCommand and Kubernetes exec means a single compromised import can laterally compromise an entire cloud tenant.
The Fix
Malicious versions 1.4.1–1.4.3 have been yanked from PyPI. Pin to durabletask==1.4.0 and verify with pip hash or a lockfile with hashes. If any of the malicious versions were imported on a Linux system, treat the host's entire credential set as fully compromised and rotate immediately — the payload executes silently on import with no user confirmation or visible side effects.
Attack Chain
PyPI Token Exfiltration
TeamPCP recovered a live PyPI API token from a maintainer account previously associated with the @antv supply chain campaign. Because durabletask used legacy long-lived token publishing rather than OIDC Trusted Publishing, the token alone was sufficient to twine upload new releases directly from any machine — no workflow, no commit, no review gate.
Enable PyPI OIDC Trusted Publishing on all packages you maintain. This binds publication authority to a specific GitHub Actions workflow and branch, making stolen tokens useless for publishing. Audit your PyPI account's API token list and revoke all non-OIDC tokens.
Progressively Injected Dropper
Three versions were published within 35 minutes (15:08–15:43 UTC), each expanding the infection surface: v1.4.1 injected __init__.py; v1.4.2 added task.py; v1.4.3 added entities/__init__.py, extensions/__init__.py, and payload/__init__.py. The 14-line dropper runs at import-time with all output redirected to suppress evidence.
Hash-pin all dependencies in requirements.txt using pip download --require-hashes. Monitor PyPI for unexpected version bumps on your critical packages. Any new version published outside a known release window should trigger an automated alert and hold before installation.
Second-Stage Payload Fetch
On first import, the dropper contacts check.git-service[.]com/rope.pyz (IP: 160.119.64.3, AS49870, Seychelles). If unreachable, it falls back to t.m-kosche[.]com (185.95.159.32, AS209101, Bulgaria). A third fallback uses GitHub dead-drops: commits containing the keyword FIRESCALE carry cryptographically signed, base64-encoded payload URLs. The stage-2 payload SHA256 is 069ac1dc7f7649b76bc72a11ac700f373804bfd81dab7e561157b703999f44ce.
Block outbound connections from CI/CD runners to non-approved external domains. Add check.git-service.com and t.m-kosche.com to your network blocklist now. Alert on any outbound connection from a Python process to domains not in your approved list during CI/CD runs.
Multi-Cloud Credential Harvesting
rope.pyz systematically queries AWS IMDS/Secrets Manager/SSM Parameter Store, Azure Key Vault/CLI token cache/certificate auth, GCP Secret Manager/service account JSON keys, Kubernetes secrets across all contexts and namespaces, HashiCorp Vault KV mounts, 1Password/Bitwarden/pass vaults, shell history files, SSH keys, Docker configs, Terraform state, and MCP server configurations — over 90 distinct credential paths. Harvested data is encrypted and exfiltrated to /api/public/version on the C2.
Check AWS CloudTrail for unusual GetSecretValue, GetParameter, and IMDS calls from CI/CD hosts. Review GCP Cloud Audit Logs and Kubernetes audit logs for bulk secret reads. Any system that imported a malicious version should be treated as fully compromised regardless of whether exfiltration is confirmed.
Worm Propagation & Persistence
The payload worms to up to 5 EC2 instances via AWS SSM SendCommand and up to 5 Kubernetes pods via kubectl exec, using ~/.cache/.sys-update-check and ~/.cache/.sys-update-check-k8s as infection markers to prevent re-infection. Persistence is established via a systemd service named pgsql-monitor.service with the binary at /usr/bin/pgmonitor.py or ~/.local/bin/pgmonitor.py.
Scan all Linux hosts reachable from CI/CD environments for pgsql-monitor.service in systemd and the infection markers. Check for pgmonitor.py at both paths. Restrict AWS SSM SendCommand via IAM policies limiting which instances CI/CD roles can target.
Geotargeted Disk Wiper
On systems with Israeli (he_IL locale, Jerusalem/Tel Aviv timezone) or Iranian (fa_IR, Tehran) markers, the payload has a 1-in-6 chance of triggering rm -rf /*, preceded by an audio file played via mpv fetched from /audio.mp3. This is irreversible — affected systems require full OS reinstallation.
Any Linux host with affected locale/timezone settings that imported a malicious version faces destructive risk. Do not attempt to remediate in place — isolate immediately, preserve forensic disk images if needed, then rebuild from a known-clean backup.
Impact
Supply chain attacks against Azure SDK components are uniquely damaging because they target infrastructure that runs with cloud-administrative credentials and operates across multi-cloud environments by design — a single CI/CD import can compromise an entire cloud tenant.
Cloud Credential Exposure
- AWS IAM keys, IMDS session tokens, and Secrets Manager secrets exfiltrated silently
- Azure service principal certificates and CLI credential caches harvested
- GCP service account JSON keys across all configured projects stolen
- HashiCorp Vault tokens from all KV mount paths accessible to the process
Lateral Movement & Infrastructure Takeover
- Worm propagates to up to 5 EC2 instances per infected host via AWS SSM SendCommand
- Kubernetes clusters accessible via the infected kubeconfig are traversed and infected
- Systemd persistence survives reboots and credential rotations on infected hosts
- 1.7M total downloads and ~103K weekly downloads — massive potential blast radius
Destructive & Data Loss Risk
- Geotargeted
rm -rf /*wiper activates on Israeli and Iranian systems with 1-in-6 probability - Shell history, SSH keys, and Docker configs expose infrastructure topology
- Terraform state exfiltration reveals full cloud resource inventory to attackers
- Wiper execution is irreversible — full OS reinstallation required
Defensive Tutorial
Pin to durabletask==1.4.0 and verify
Immediate
Run pip show durabletask on all affected environments. If version is 1.4.1, 1.4.2, or 1.4.3, treat the host as compromised regardless of whether you observe anomalous behavior — the payload executes silently. Downgrade with pip install "durabletask==1.4.0". Add the hash pin to your lockfile: the 1.4.0 wheel SHA256 is available via pip download durabletask==1.4.0 --no-deps && pip hash durabletask-1.4.0*.whl.
Rotate all credentials accessible from affected hosts
ImmediateFor any Linux host that imported a malicious version, rotate: AWS IAM access keys and session credentials, Azure service principal secrets and certificates, GCP service account keys (gcloud iam service-accounts keys list --iam-account=SA_EMAIL), all Kubernetes ServiceAccount tokens (kubectl get secrets -A | grep service-account), HashiCorp Vault tokens, and SSH keys. Assume all ~/.config, ~/.aws, ~/.kube, and shell history are exfiltrated.
Scan for persistence markers and systemd backdoor
ImmediateRun the following on all Linux hosts reachable from CI/CD environments:
systemctl status pgsql-monitor.service
ls -la ~/.cache/.sys-update-check ~/.cache/.sys-update-check-k8s 2>/dev/null
find /usr/bin /usr/local/bin ~/.local/bin -name "pgmonitor.py" 2>/dev/null
Any positive result confirms active infection. Isolate the host immediately, preserve a forensic image, then rebuild from a clean baseline. Do not attempt in-place remediation — assume the attacker has already exfiltrated credentials and established additional persistence mechanisms.
Block C2 domains at network perimeter
ImmediateBlock outbound traffic to check.git-service.com (160.119.64.3) and t.m-kosche.com (185.95.159.32) at your DNS and network layer. Review firewall logs for any prior connections to these domains or IPs — a successful connection confirms the payload was delivered. Also alert on the stage-2 hash: 069ac1dc7f7649b76bc72a11ac700f373804bfd81dab7e561157b703999f44ce in any file integrity monitoring system.
Enable OIDC Trusted Publishing on all PyPI packages
ImportantOIDC Trusted Publishing binds publication authority to a specific GitHub Actions workflow, repository, and branch — a stolen API token is useless for publishing. Go to your PyPI project settings → Publishing → Add a new publisher. Set the workflow name to your release workflow filename (e.g., release.yml) and the environment to pypi. Once enabled, revoke all legacy API tokens for that package.
Implement dependency hash pinning in CI/CD
ImportantGenerate a hash-pinned lockfile: pip-compile --generate-hashes requirements.in (using pip-tools). Use pip install --require-hashes -r requirements.txt in all CI/CD pipelines — pip will refuse to install any package whose hash doesn't match, blocking trojaned versions even if the same version number is reused. Commit the lockfile and gate merges on lockfile changes being explicitly approved.
Restrict AWS SSM and Kubernetes exec from CI/CD IAM roles
ImportantThe worm propagates via ssm:SendCommand and ssm:StartSession. Audit your CI/CD IAM roles and remove these permissions unless explicitly required. Use an IAM condition to limit SendCommand to specific instance tags: "aws:ResourceTag/AllowSSM": "true". Similarly, restrict kubectl exec access by reviewing RBAC bindings that grant pods/exec to service accounts used by CI/CD workflows.
Generate and monitor SBOMs for all deployed Python services
ImportantIntegrate SBOM generation into your release pipeline: pip install cyclonedx-bom && cyclonedx-py environment produces a CycloneDX SBOM from a virtual environment. Ingest SBOMs into a dependency tracking tool (Dependency-Track or GUAC) and configure alerts for new OSV/NVD advisories against your exact package versions. This gives you sub-day notification when a package in your fleet is flagged — not days after the attack when your CI logs are the only record.
Add egress filtering to all CI/CD runner environments
ImportantCI/CD runners should only reach PyPI (pypi.org, files.pythonhosted.org), your artifact registry, and explicitly approved endpoints. Block all other outbound DNS and TCP at the network level. This stops the dropper cold — rope.pyz can't be fetched if the runner can't reach arbitrary external domains. Use a private PyPI mirror (Artifactory, AWS CodeArtifact, or devpi) to further reduce the attack surface by controlling which packages and versions can be resolved.
References
- OSV Advisory MAL-2026-4174 — durabletask PyPI Compromise — Official OSV entry with affected versions and severity classification
- Technical Analysis Wiz — durabletask: TeamPCP's Latest PyPI Compromise — Detailed payload analysis, IOCs, C2 infrastructure, and attack chain breakdown
- Technical Analysis Snyk — The AntV Supply Chain Campaign Expands: Microsoft's durabletask PyPI Package Compromised — Campaign context and connection to prior @antv waves
- Researcher Disclosure SafeDep — Malicious durabletask on PyPI: Multi-Cloud Credential Stealer with Worm Capabilities — Worm propagation mechanics, wiper geotargeting, and persistence details
- Defensive Guide StepSecurity — Microsoft's durabletask PyPI Package Compromised in Supply Chain Attack — OIDC Trusted Publishing remediation and token compromise root cause
- GitHub Issue microsoft/durabletask-python — Issue #137: Potential malicious code execution in versions 1.4.1, 1.4.2, and 1.4.3 — Original disclosure issue with IOC hashes
- CWE Reference CWE-506 — Embedded Malicious Code: Software intentionally contains code that appears to serve a legitimate purpose but also contains malicious functionality.