From workflow-skills
Pin GitHub Actions to commit SHAs to prevent supply chain attacks. Use when updating workflow files to pin third-party actions to full 40-character commit SHAs instead of mutable tags. Handles tag resolution, annotated tags, and validates against GitHub API. Also use when scanning workflow files to update all third-party actions to their latest versions.
npx claudepluginhub arosenkranz/claude-code-config --plugin workflow-skillsThis skill uses the workspace's default tool permissions.
Pin actions to immutable commit SHAs to prevent supply chain attacks.
Compares coding agents like Claude Code and Aider on custom YAML-defined codebase tasks using git worktrees, measuring pass rate, cost, time, and consistency.
Designs and optimizes AI agent action spaces, tool definitions, observation formats, error recovery, and context for higher task completion rates.
Designs, implements, and audits WCAG 2.2 AA accessible UIs for Web (ARIA/HTML5), iOS (SwiftUI traits), and Android (Compose semantics). Audits code for compliance gaps.
Pin actions to immutable commit SHAs to prevent supply chain attacks.
GitHub Actions tags are mutable - they can be deleted and recreated by attackers. In 2023, the tj-actions/changed-files action had all its tags tampered with malicious code. Pinning to commit SHAs provides:
When user requests updating GitHub Actions workflows (or when you detect unpinned actions):
uses: line with a third-party action:
uses: ./path)uses: docker://...)OWNER/REPO and current referenceFor each action needing updates:
Try releases first (most common):
# Use GITHUB_TOKEN if available for better rate limits
AUTH_HEADER=""
if [ -n "$GITHUB_TOKEN" ]; then
AUTH_HEADER="Authorization: Bearer $GITHUB_TOKEN"
fi
TAG=$(curl -s -H "$AUTH_HEADER" \
"https://api.github.com/repos/OWNER/REPO/releases/latest" \
| jq -r '.tag_name')
Fallback to tags if no releases exist:
TAG=$(curl -s -H "$AUTH_HEADER" \
"https://api.github.com/repos/OWNER/REPO/tags" \
| jq -r '.[0].name')
Get tag reference:
TAG_DATA=$(curl -s -H "$AUTH_HEADER" \
"https://api.github.com/repos/OWNER/REPO/git/ref/tags/$TAG" \
| jq -r '.object')
TAG_TYPE=$(echo "$TAG_DATA" | jq -r '.type')
TAG_SHA=$(echo "$TAG_DATA" | jq -r '.sha')
Handle annotated tags (dereference if needed):
if [ "$TAG_TYPE" = "tag" ]; then
# Annotated tag - need to dereference to get commit
SHA=$(curl -s -H "$AUTH_HEADER" \
"https://api.github.com/repos/OWNER/REPO/git/tags/$TAG_SHA" \
| jq -r '.object.sha')
else
# Lightweight tag - SHA points directly to commit
SHA="$TAG_SHA"
fi
Validate SHA is exactly 40 characters (security requirement)
Format as: owner/repo@<FULL_SHA> # <TAG>
owner/repo/path@<SHA> # <TAG>Strongly recommend using GITHUB_TOKEN environment variable:
Set token:
export GITHUB_TOKEN="ghp_your_token_here"
Or check for it in code:
if [ -z "$GITHUB_TOKEN" ]; then
echo "Warning: GITHUB_TOKEN not set. Rate limits: 60 req/hour"
echo "Set token for 5000 req/hour: export GITHUB_TOKEN='ghp_...'"
fi
releases/latest excludes prereleases - may not be the "latest" you expectowner/repo/path@sha^[a-f0-9]{40}$@<SHA> # <TAG>Input workflow:
name: CI
on: [push]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: docker/build-push-action@v5.0.0
Process:
Resolve actions/checkout@v4:
v4.1.1b4ffde65f46336ab88eb53be808477a3936bae11actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1Resolve docker/build-push-action@v5.0.0:
v5.0.00565240e2d4ab88bba5387d719585280857ece09docker/build-push-action@0565240e2d4ab88bba5387d719585280857ece09 # v5.0.0Output workflow:
name: CI
on: [push]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- uses: docker/build-push-action@0565240e2d4ab88bba5387d719585280857ece09 # v5.0.0
Input workflow:
steps:
# Already pinned - skip
- uses: actions/setup-node@1a4442cacd436585916779262731d5b162bc6ec7 # v3.8.2
# Local action - skip
- uses: ./.github/actions/custom-action
# Docker image - skip
- uses: docker://alpine:3.18
# Subdirectory action - preserve path
- uses: aws-actions/configure-aws-credentials/v4
# Needs updating
- uses: actions/cache@v3
Process:
aws-actions/configure-aws-credentials/v4:
actions/cache@v3:
Output workflow:
steps:
# Already pinned - unchanged
- uses: actions/setup-node@1a4442cacd436585916779262731d5b162bc6ec7 # v3.8.2
# Local action - unchanged
- uses: ./.github/actions/custom-action
# Docker image - unchanged
- uses: docker://alpine:3.18
# Subdirectory preserved with SHA
- uses: aws-actions/configure-aws-credentials/v4@e3dd6b9699d61f23e0e6b29e75fc7c3e56c6c7a2 # v4.0.1
# Updated to latest
- uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2 # v3.3.2
Scenario: Repo uses annotated tags (common in many actions)
API Response Sequence:
# Step 1: Get tag reference
GET /repos/actions/checkout/git/ref/tags/v4.1.1
{
"object": {
"type": "tag", # <-- Annotated tag!
"sha": "abc123..."
}
}
# Step 2: Dereference annotated tag
GET /repos/actions/checkout/git/tags/abc123...
{
"object": {
"type": "commit",
"sha": "b4ffde65f46336ab88eb53be808477a3936bae11" # <-- Real commit
}
}
Result: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
Official Documentation:
Security Guides:
Real-World Incidents: