TeamPCP's Kubernetes payload wipes Iranian nodes and backdoors the rest

TeamPCP's CanisterWorm payload deploys a privileged kube-system DaemonSet that wipes nodes set to Iran's timezone and backdoors all others. Detection steps.

TechLogHub Engineering Team
March 23, 2026
4 min read
0 views

Share Article

TeamPCP Deploys Iran‑Targeted Wiper in Kubernetes Attacks

On 22 March 2026, Aikido Security published analysis of a new payload from TeamPCP, the group behind the Trivy and CanisterWorm supply-chain attacks. Inside a Kubernetes cluster it deploys a privileged DaemonSet to every node. If the node's timezone or locale points to Iran, it wipes the host and reboots it. Elsewhere it installs a persistent backdoor. Any team whose CI runners or workloads ran the poisoned Trivy, npm or PyPI packages should check its clusters for this.

What happened

Aikido tied the payload to the CanisterWorm campaign through shared infrastructure. Both use the same Internet Computer (ICP) canister for command and control (tdtqy-oyaaa-aaaae-af2dq-cai.raw.icp0[.]io), the same backdoor code and the same /tmp/pglog drop path. A shell stager hosted on rotating trycloudflare.com tunnels downloads kubectl if it isn't installed, fetches a Python script and runs it.

The script makes two checks. It looks for Kubernetes through the service-account token path /var/run/secrets/kubernetes.io/serviceaccount or the KUBERNETES_SERVICE_HOST variable. It looks for Iran by reading /etc/timezone or timedatectl for Asia/Tehran or Iran, and checking LANG for fa_IR. Then:

  • Kubernetes + Iran: deploys DaemonSet host-provisioner-iran in kube-system, with a container named kamikaze running alpine:latest. It deletes the top-level directories of the host filesystem and forces a reboot.
  • Kubernetes, not Iran: deploys host-provisioner-std, which writes the CanisterWorm backdoor to each host and installs it as a systemd service.
  • No Kubernetes, Iran: runs rm -rf / --no-preserve-root, trying passwordless sudo if it isn't root.
  • No Kubernetes, not Iran: exits.

Both DaemonSets run privileged: true with hostPID and hostNetwork. They mount the host's / at /mnt/host and tolerate every taint, so they also land on control-plane nodes. A later variant no longer needs Kubernetes. It scans the local /24 for unauthenticated Docker APIs on TCP 2375 and starts privileged Alpine containers with the host root bind-mounted. It also spreads over SSH using keys from ~/.ssh and usernames parsed from /var/log/auth.log, with StrictHostKeyChecking=no.

Who is affected

  • Clusters where a TeamPCP payload ran with Kubernetes credentials. Likely routes include CI runners that used the compromised Trivy release or GitHub Actions on 19–20 March 2026, and machines that installed CanisterWorm-infected npm packages or the poisoned LiteLLM PyPI releases.
  • Any pod whose service account can create DaemonSets in kube-system. In practice that means cluster-admin or something close to it.
  • Hosts that expose the Docker Engine API without TLS on port 2375, or that hold SSH keys giving access to other hosts.
  • Machines configured with Iran's timezone or Persian locale face destruction. All others face persistent backdoors.

What it means for developers and engineering teams

None of this uses a Kubernetes vulnerability. It relies entirely on legitimate API calls made with credentials that were too broad. A DaemonSet is the most efficient way to run code on every node, and a privileged pod with hostPath: / is effectively root on the host. If a CI job, an operator or a developer kubeconfig can create one, then stealing that credential compromises the whole cluster.

Three common habits make this possible: CI pipelines that deploy with cluster-admin kubeconfigs, workloads that run with the default service account plus a generous RoleBinding, and no admission policy blocking privileged pods outside a small set of infrastructure namespaces. Kubernetes' built-in Pod Security Admission at the restricted or baseline level rejects privileged containers and hostPath mounts. The payload's kube-system target is a reminder to treat that namespace carefully too, for example with a policy engine rule that allows only known DaemonSets.

The locale-based targeting is also a warning about trusting geography signals. Changing a timezone is trivial, so it doesn't protect anyone. It shows the actor is willing to destroy systems, not just steal credentials.

What to do now

  1. Look for the DaemonSets: kubectl get ds -A | grep -E 'host-provisioner|kamikaze', and review every DaemonSet in kube-system that you don't recognize.
  2. Find privileged hostPath workloads: kubectl get pods -A -o json | jq -r '.items[] | select(any(.spec.volumes[]?; .hostPath.path=="/")) | .metadata.namespace+"/"+.metadata.name'
  3. Check nodes for /tmp/pglog, /tmp/.pg_state, /var/lib/pgmon/, /var/lib/svc_internal/runner.py, and systemd units pgmonitor or internal-monitor.
  4. Block egress to *.icp0.io and *.trycloudflare.com from nodes and runners unless you have a reason to allow it, and alert on any hits.
  5. Enforce Pod Security Admission: label application namespaces pod-security.kubernetes.io/enforce=restricted, and gate kube-system with an admission policy (Kyverno, Gatekeeper or ValidatingAdmissionPolicy).
  6. Scope CI credentials: deploy jobs get namespace-scoped Roles, never cluster-admin. Prefer short-lived OIDC-federated tokens.
  7. Close Docker port 2375 everywhere, or require TLS on 2376. Alert on outbound SSH using StrictHostKeyChecking=no.
  8. If you find the backdoor, rebuild the affected nodes and rotate every credential reachable from the cluster.

Related on TechLogHub

Sources

Stay Updated

Get the next deep dive in your inbox

Subscribe for product analysis, engineering explainers, and practical guides published on TechLogHub.

See what launched this week

One email a week: new and trending developer tools, fresh comparisons, and what shipped. Unsubscribe in one click.