Skip to main content
a promotional graphic telling you that PCI Compliance is no longer an annual exercise and that continuous monitory must be built in
Locking Down GitHub Actions After CompromiseResearch
5 min readFor Security Engineers

Locking Down GitHub Actions After Compromise

On September 16, two GitHub Actions repositories involved in the Mini Shai-Hulud supply-chain attack were reactivated with their malicious payloads intact. They remained compromised for nine days. If your workflows referenced these actions, you unknowingly ran compromised code in your CI/CD pipeline.

This incident wasn't due to a zero-day exploit or a sophisticated breach. It was a process failure: someone re-enabled disabled repositories without verifying their contents. For security engineers, this highlights a critical gap in managing third-party dependencies in automation pipelines.

Here's how you can address that gap in your environment.

The Problem: Your CI/CD Pipeline Trusts Too Much

When you reference a GitHub Action in your workflow, you're executing code from someone else's repository in your build environment. Most teams write something like this:

- uses: actions-cool/issues-helper@v1

That @v1 tag seems like version pinning, but it's not. Tags are mutable references that can point to different commits over time. If the repository owner updates the tag or if the account gets compromised, your next workflow run executes different code than you tested.

The Mini Shai-Hulud campaign affected 323 npm packages and 639 package versions. GitHub's dependency graph shows about 15,000 repositories depending on the issues-helper action alone. When those repositories came back online with malicious code, every workflow using tag-based references was vulnerable.

What You Need Before Starting

Before securing your GitHub Actions dependencies, gather:

  • Write access to your organization's repositories
  • Admin permissions in GitHub to modify organization-level settings
  • An inventory of all GitHub Actions your workflows use (we'll generate this)
  • 30 minutes for initial setup, plus 15 minutes per repository for conversion

You don't need additional tools or services for the core implementation. GitHub's native features handle commit pinning and Dependabot updates.

Step-by-Step Implementation

1. Audit Your Current Action References

Create a script to find all workflow files and extract action references:

#!/bin/bash
find . -path "*/.github/workflows/*.yml" -o -path "*/.github/workflows/*.yaml" | \
xargs grep -h "uses:" | \
sed 's/.*uses: //' | \
sort -u > actions-inventory.txt

Review the output. Look for any reference that uses a tag (@v1, @main) or branch name instead of a commit SHA.

2. Pin Actions to Commit SHAs

For each action in your inventory, find the current commit SHA that the tag points to:

# Example for actions/checkout@v4
git ls-remote https://github.com/actions/checkout v4

This returns the full commit SHA. Update your workflow file:

# Before
- uses: actions/checkout@v4

# After
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11  # v4.1.1

Add a comment with the version number so humans can read the workflow file without looking up SHAs.

3. Enable Dependabot for GitHub Actions

In each repository, create .github/dependabot.yml:

version: 2
updates:
  - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
      interval: "weekly"
    open-pull-requests-limit: 10
    reviewers:
      - "your-security-team"

Dependabot will open PRs when the upstream action releases new versions. You review the changes, verify the new commit, and merge. This gives you the security of pinning with the maintainability of automatic updates.

4. Set Organization-Level Policies

Navigate to your GitHub organization settings and configure:

Actions permissions:

  • Allow only verified and approved actions
  • Create an allowlist of actions your team has reviewed
  • Block any action not on the list

Workflow permissions:

  • Set default to read-only for GITHUB_TOKEN
  • Require approval for first-time contributors

These controls won't stop a compromised action that's already on your allowlist, but they prevent developers from adding new dependencies without review.

5. Implement Pre-Commit Validation

Add a pre-commit hook that rejects workflow files with non-SHA action references:

#!/bin/bash
# .git/hooks/pre-commit

WORKFLOWS=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.github/workflows/.*\.ya?ml$')

if [ -n "$WORKFLOWS" ]; then
  for file in $WORKFLOWS; do
    if grep -E 'uses:.*@[^a-f0-9]{40}' "$file"; then
      echo "Error: $file contains unpinned action references"
      echo "Use commit SHAs instead of tags or branches"
      exit 1
    fi
  done
fi

This catches mistakes before they reach your repository.

Validation: How to Verify It Works

After implementing these controls, verify your setup:

Test 1: Check SHA pinning

grep -r "uses:" .github/workflows/ | grep -v "@[a-f0-9]\{40\}"

This should return no results. If it does, you've got unpinned actions.

Test 2: Verify Dependabot PRs

Wait one week. You should see Dependabot PRs for any actions with available updates. If you don't, check your dependabot.yml configuration.

Test 3: Attempt to add an unapproved action

Create a test branch and add a workflow using an action not on your allowlist. The workflow should fail to run.

Test 4: Review audit logs

In your organization settings, check the audit log for workflows.approve_workflow_job events. You should see entries for any workflows requiring approval.

Maintenance: Ongoing Tasks

Weekly (15 minutes):

  • Review and merge Dependabot PRs for action updates
  • Check for new actions added to workflows in the past week
  • Verify they're pinned to commit SHAs

Monthly (30 minutes):

  • Audit your action allowlist against current usage
  • Remove approved actions you're no longer using
  • Review GitHub's security advisories for compromised actions

Quarterly (2 hours):

  • Re-run your action inventory script
  • Compare against your allowlist
  • Interview teams about any new automation needs

When an incident occurs:

If you discover a compromised action in your workflows:

  1. Immediately disable the affected workflows
  2. Review run logs for the past 90 days (GitHub's retention limit)
  3. Rotate any secrets the workflow could access
  4. Check for unauthorized changes to your repositories
  5. Update your allowlist to block the compromised action

The Mini Shai-Hulud repositories were accessible with malicious code for nine days. Your incident response window might be even shorter. Commit pinning limits your exposure to the specific SHA you've tested, not whatever code happens to be in the repository when your workflow runs.

This isn't about perfect security. It's about reducing your attack surface to something you can actually monitor and respond to. When you pin to commit SHAs and use Dependabot for updates, you turn supply-chain security from a trust problem into a review problem. And review problems you can solve.

Topics:Research
a promotional banner asking how ready are you for PCI DSS 4.0? With a call-to-action to get the checklist now.

You Might Also Like