Listen to this Post

Introduction:
Software supply chain attacks have escalated from dependency confusion to compromising CI/CD automation tools. In the latest breach, threat actors hijacked the widely used `issues-helper` GitHub Action by retroactively redirecting every existing release tag to malicious commits not present in the default branch, enabling stealthy credential theft from workflow runners. This incident underscores a critical blind spot: even trusted, heavily-used Actions can be weaponized through Git tag manipulation, bypassing conventional branch protection and code review.
Learning Objectives:
- Detect and verify malicious tag redirection attacks in GitHub repositories using Git and GitHub CLI.
- Harden GitHub Actions workflows against credential theft and supply chain compromise.
- Implement immutable reference pinning and OIDC-based authentication to block tag-based poisoning.
You Should Know:
- How Attackers Exploit Git Tags to Redirect Workflow Execution
Attackers gained write access (via leaked tokens or compromised maintainer accounts) to the `actions-cool/issues-helper` repository. Instead of altering the default branch, they force-pushed new commits to every existing tag (e.g., v1.0.0, v1.0.1, etc.) – tags that workflows referenced using `@v1` or @v1.0.0. Because GitHub Actions resolves tags to mutable references, the runner executed the imposter commit, which dumped environment variables, secrets, and GITHUB_TOKEN to an attacker-controlled endpoint.
Step‑by‑step verification and analysis (Linux/macOS):
Clone the target repository (use a mirror to fetch all tags)
git clone --mirror https://github.com/actions-cool/issues-helper.git
cd issues-helper.git
List all tags and their current commit SHAs
git tag -l | xargs -I {} git show-ref --tags {} | tee tag_hashes.txt
Compare with known good SHAs from a trusted source (e.g., previous audit)
Check if a tag points to a commit that does NOT exist in any branch
git checkout tags/v1.0.0
git branch -r --contains HEAD Should show origin/main or default branch. If empty → suspicious
Windows (PowerShell):
git clone --mirror https://github.com/actions-cool/issues-helper.git
cd issues-helper.git
git tag | ForEach-Object { git rev-parse "$_"} > tag_hashes.txt
git checkout tags/v1.0.0
git branch -r --contains HEAD No output indicates the commit is tag-only (malicious)
What this does: The commands enumerate all tags, then check whether each tag’s commit exists within any branch’s history. A clean repository will have every tag commit reachable from a branch. If a commit is orphaned (only referenced by a tag), an attacker injected it.
2. Audit Your Workflows for Mutable Action References
Most workflows pin actions using `@v1` (major version tag) or `@v1.0.0` (exact tag). Both are mutable – an attacker can force-push a different commit to the same tag. To block this attack, you must switch to immutable references: full commit SHAs or SHA-pinned Docker actions.
Step‑by‑step remediation:
Find all GitHub Action references in your .yml workflow files grep -r "uses: .@v" .github/workflows/ For each found action, retrieve the current commit SHA from the action's repository Example: actions-cool/issues-helper gh api repos/actions-cool/issues-helper/git/refs/tags/v1.0.0 --jq '.object.sha' Output: a1b2c3d4... (but beware – this SHA could be malicious. Obtain SHA from a trusted source like a prior lockfile) Better: Use dependabot or renovate to pin SHAs automatically. Manual pin example: - uses: actions-cool/issues-helper@a1b2c3d4e5f67890abcdef1234567890abcdef12
Windows alternative (PowerShell + curl):
$url = "https://api.github.com/repos/actions-cool/issues-helper/git/refs/tags/v1.0.0" (Invoke-RestMethod -Uri $url).object.sha
Hardening rule: Never use @main, @master, or unqualified major tags in production workflows. Use a tool like `actions-pin` (npm) or a custom script to enforce SHA pins.
3. Detect Credential Exfiltration from Compromised Runners
The malicious action likely executed a script that read ${{ secrets.GITHUB_TOKEN }}, ${{ secrets.AWS_SECRET_ACCESS_KEY }}, and environment variables, then sent them to an external domain. You can detect similar behaviour by scanning runner logs and network egress.
Step‑by‑step log forensics (Linux):
Download workflow run logs using GitHub CLI
gh run list --limit 10 --json databaseId --jq '.[].databaseId' | while read id; do
gh run view $id --log > run_${id}.log
done
Search for suspicious outbound requests (curl, wget, nc, or base64 encoded strings)
grep -E "(curl|wget|http:|https:|base64|echo.|)" run_.log
Look for secret redaction warnings – GitHub redacts secrets in logs, but may leak partial
grep -i "secret" run_.log
Live detection during workflow execution (add to your workflow as a safety step):
- name: Block outbound internet (except allowed) run: | sudo iptables -A OUTPUT -p tcp --dport 80 -j DROP sudo iptables -A OUTPUT -p tcp --dport 443 -j DROP Allow only necessary endpoints (e.g., api.github.com, your registry) This will cause any exfiltration attempt to fail.
Note: This iptables approach works only on self-hosted runners or if you control the environment; GitHub-hosted runners do not allow modifying iptables. Instead, use a runtime security agent like Falco or Tetragon.
- Hardening GitHub Actions with OIDC and Minimal Secrets
The best defence is to avoid long-lived secrets entirely. Use OpenID Connect (OIDC) to exchange a short-lived JWT from GitHub for cloud credentials. Even if a malicious action steals the token, its lifetime is minutes and it is bound to the job.
Step‑by‑step OIDC configuration (AWS example):
In your workflow permissions: id-token: write Required to request OIDC JWT contents: read jobs: deploy: runs-on: ubuntu-latest steps: - name: Configure AWS credentials via OIDC uses: aws-actions/configure-aws-credentials@v3 with: role-to-assume: arn:aws:iam::123456789012:role/GitHubActionsRole aws-region: us-east-1 Now no static AWS keys are in secrets – attacker can't steal them
Verification on Linux:
After OIDC exchange, the environment variable AWS_WEB_IDENTITY_TOKEN_FILE contains a token cat $AWS_WEB_IDENTITY_TOKEN_FILE | jwt decode - decode token to see claims
Windows (PowerShell) with jwt-cli:
Get-Content $env:AWS_WEB_IDENTITY_TOKEN_FILE | jwt decode --json
- Prevent Tag Redirection with Repository Rulesets and Signed Tags
GitHub now supports rulesets that can block force-push to tags and require signed tags. Additionally, you can verify tag signatures before using an action.
Step‑by‑step enforcement:
On the repository owner side (actions-cool should have enabled this) Create a ruleset via GitHub CLI: gh api repos/:owner/:repo/rulesets -X POST -f name="Protect all tags" -f target="tag" -f enforcement="active" -f conditions[bash].ref_name.include="refs/tags/" -f rules[bash].type="deletion" -f rules[bash].parameters=false -f rules[bash].type="non_fast_forward" -f rules[bash].type="required_signatures" For workflow consumers: verify tag GPG signature before using git verify-tag v1.0.0 GPG error if unsigned or tampered
Automated verification in your workflow (before using any external action):
- name: Verify action tag signature run: | git clone --depth 1 https://github.com/actions-cool/issues-helper.git cd issues-helper git verify-tag v1.0.0 || exit 1 Fail if signature invalid
- Immediate Incident Response if You Used the Compromised Action
If your workflows referenced `actions-cool/issues-helper` between the compromise date and now, assume all secrets exposed – including GITHUB_TOKEN, which can write to your repository.
Step‑by‑step response (Linux / cross-platform):
1. Rotate all secrets stored in GitHub (Settings → Secrets → Actions)
gh secret list | awk '{print $1}' | while read secret; do
echo "Rotate $secret manually" No automated CLI to set secrets; do manually.
done
<ol>
<li>Revoke all personal access tokens (PATs) that have repo scope
gh api /user/personal-access-tokens --paginate | jq '.[].id' | while read id; do
gh api -X DELETE /user/personal-access-tokens/$id
done</p></li>
<li><p>Audit all workflow runs for unexpected steps (look for "Run malicious")
gh run list --limit 100 --json name,status,headSha,createdAt | jq '.[] | select(.name | contains("issues-helper"))'</p></li>
<li><p>Scan for backdoors – check if new collaborators were added via the stolen GITHUB_TOKEN
gh api repos/your-org/your-repo/collaborators
What Undercode Say:
- Key Takeaway 1: Git tags are not immutable – force-pushing to tags is permitted by default. Treat any action pin using a tag as potentially transient. Always pin to full commit SHAs after auditing the commit content.
- Key Takeaway 2: Workflow permissions must follow least privilege – never grant `contents: write` unless required, and replace static secrets with OIDC. The `GITHUB_TOKEN` default permissions are too broad; restrict them via `permissions:` block.
Analysis: The `issues-helper` attack reveals a gap between developer expectations (tags are immutable, especially version tags) and Git’s actual behaviour (tags are mutable references). Attackers exploited this by not altering the default branch, which keeps code review and branch protection silent. Supply chain defenders must now treat every third-party action as a potential risk until its SHA is verified against a trusted transparency log (e.g., Sigstore). The incident also highlights the need for runtime detection: many compromised workflows continue to run for weeks because logs show no obvious errors.
Expected Output:
Example of a safely pinned action after remediation: - name: My safe workflow uses: actions-cool/issues-helper@f47b8a2e9c1d5e7a3b9c0d4e6f8a2b1c3d5e7f9a immutable SHA with: helper-command: 'list-issues'
The above SHA is a placeholder – always verify the commit against the official repository’s signed tag.
Prediction:
Within the next 12 months, GitHub will introduce mandatory immutable release attestations for Actions marketplace listings, possibly integrating Sigstore’s cosign. Attackers will shift from tag hijacking to compromising GitHub App installation tokens or abusing self-hosted runner registration. Defenders will adopt runtime security policies using eBPF (e.g., Cilium Tetragon) on self-hosted runners, while OIDC will become the default authentication method for cloud deployments. We will also see a rise in automated dependency update tools that enforce SHA pins and block any mutable references in CI/CD pipelines.
▶️ Related Video (74% Match):
🎯Let’s Practice For Free:
IT/Security Reporter URL:
Reported By: Varshu25 Malicious – Hackers Feeds
Extra Hub: Undercode MoN
Basic Verification: Pass ✅


