Comprehensive guide for securing GitHub Actions workflows, repositories, and CI/CD pipelines.
- Introduction
- Repository Security
- Workflow Security
- Secrets Management
- Access Control
- Supply Chain Security
- Runner Security
- Audit and Compliance
- Security Best Practices
- Incident Response
GitHub Actions provides multiple layers of security to protect your workflows and infrastructure:
- Repository-level security: Branch protection, code review requirements
- Workflow security: Secret management, access controls
- Supply chain security: Verified actions, dependency scanning
- Runner security: Isolated environments, self-hosted options
Common security threats in CI/CD:
- Secrets leakage in logs
- Supply chain attacks via malicious actions
- Unauthorized access to runners
- Credential theft
- Dependency vulnerabilities
- Navigate to repository Settings > Branches
- Click "Add rule" for main branch
- Configure settings:
# Required settings for main branch:
- Require pull request reviews before merging
- Required approvers: 2
- Dismiss stale reviews when new commits are pushed
- Require review from CODEOWNERS
- Require status checks to pass before merging
- Require branches to be up to date before merging
- Require branches to be up to date before merging
- Require conversation resolution before merging
- Limit who can push to matching branches
- Do not allow bypassing the above settings# Tag protection rules:
- Only allow users with admin access to push tags
- Require pattern matching for tags (e.g., v*)Create .github/CODEOWNERS:
# Default: Everyone
* @default-team
# Documentation
/docs/ @documentation-team
# Infrastructure
/terraform/ @infrastructure-team
/kubernetes/ @infrastructure-team
# Security
/security/ @security-team
# CI/CD
.github/workflows/ @devops-teamname: Dependency Review
on:
pull_request:
permissions:
contents: read
jobs:
dependency-review:
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Dependency Review
uses: actions/dependency-review-action@v4- Navigate to repository Settings > Actions > General
- Under "Workflow permissions", select:
- Read and write permissions: Required for deployments
- Read repository contents permission: For read-only workflows
# Use permissions at workflow level
permissions:
contents: read
issues: read
pull-requests: read
deployments: write# Bad: Using @main or @master
- uses: actions/checkout@main
# Good: Using specific version
- uses: actions/checkout@v4.1.1
# Better: Using commit SHA
- uses: actions/checkout@a81bbbf8298c0fa03ea29cdc473d45769f953675Create .github/dependabot.yml:
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
day: "monday"
labels:
- "dependencies"
- "github-actions"- Navigate to repository Settings > Environments
- Click "New environment"
- Configure:
- Name: production
- Environment secrets: Add production-specific secrets
- Deployment branches: Restrict to main branch
- Required reviewers: Add required reviewers
- Wait timer: Set wait time (e.g., 30 minutes)
jobs:
deploy:
runs-on: ubuntu-latest
environment:
name: production
url: https://example.com
steps:
- uses: actions/checkout@v4
- run: deploy.sh- Navigate to repository Settings > Secrets and variables > Actions
- Click "New repository secret"
- Add secret name and value
- Click "Add secret"
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Deploy
env:
API_KEY: ${{ secrets.API_KEY }}
DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
run: |
# Use secrets in your deployment script- Navigate to organization Settings > Secrets > Actions
- Click "New organization secret"
- Add secret name and value
- Select repositories with access
- Click "Add secret"
- Centralized management
- Consistent secrets across repositories
- Reduced duplication
- Easier rotation
- Navigate to repository Settings > Environments
- Select environment
- Add environment secrets
jobs:
deploy:
runs-on: ubuntu-latest
environment: production
steps:
- name: Deploy
env:
PRODUCTION_API_KEY: ${{ secrets.PRODUCTION_API_KEY }}
run: |
# Environment-specific secretsname: Rotate Secrets
on:
schedule:
- cron: '0 0 1 * *' # Monthly
jobs:
rotate:
runs-on: ubuntu-latest
steps:
- name: Generate New Secret
id: generate
run: |
NEW_SECRET=$(openssl rand -base64 32)
echo "secret=$NEW_SECRET" >> $GITHUB_OUTPUT
- name: Update Secret
uses: hmanzur/action-set-secret@v2
with:
name: API_KEY
value: ${{ steps.generate.outputs.secret }}
repository: ${{ github.repository }}
token: ${{ secrets.GITHUB_TOKEN }}| Permission Level | Capabilities |
|---|---|
| Admin | Full control, settings, billing |
| Maintain | Issues, pull requests, wikis |
| Write | Push to all branches |
| Triage | Manage issues and pull requests |
| Read | Read-only access |
- Grant least privilege required
- Regularly review and revoke access
- Use teams for group permissions
- Enable two-factor authentication
- Navigate to organization Settings > Teams
- Click "New team"
- Configure team permissions
- Add members to team
# Team-based access:
- @devops-team: Write access to all repositories
- @security-team: Read access to all repositories, Write to security repos
- @developers: Write access to application repositoriesjobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Setup SSH Key
run: |
mkdir -p ~/.ssh
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
ssh-keyscan -H github.com >> ~/.ssh/known_hosts
- name: Deploy via SSH
run: |
ssh user@server.com 'deploy.sh'# Check if action is verified
# Look for the "Verified creator" badge in the marketplace
# Example of verified actions:
- uses: actions/checkout@v4 # Verified
- uses: docker/setup-buildx-action@v3 # Verified
- uses: aws-actions/configure-aws-credentials@v4 # Verified# Use full reference including owner
- uses: actions/checkout@v4
# Avoid using unverified actions
# - uses: random-user/random-action@v1name: Audit Actions
on:
schedule:
- cron: '0 0 * * 0' # Weekly
jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Audit Workflow Actions
uses: trstringer/manual-approval@v1
with:
secret: ${{ secrets.GITHUB_TOKEN }}
approvers: security-team
minimum-approvals: 1Enable dependency graph:
- Navigate to repository Settings > Security & analysis
- Enable "Dependency graph"
Enable Dependabot alerts:
- Navigate to repository Settings > Security & analysis
- Enable "Dependabot alerts"
Create .github/dependabot.yml:
version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "daily"
versioning-strategy: increase
open-pull-requests-limit: 10- Ephemeral: Fresh runner for each job
- Isolated: Network and resource isolation
- Managed: Security updates by GitHub
- Audited: Regular security audits
- Use GitHub-hosted runners for public repositories
- Avoid storing sensitive data on runners
- Clean up temporary files after jobs
- Don't cache sensitive data
# Run as non-root user
sudo useradd -m -s /bin/bash runner
sudo usermod -aG docker runner
# Configure runner as service
sudo ./svc.sh install
sudo ./svc.sh start# Configure firewall rules
sudo ufw allow from 192.168.1.0/24 to any port 22
sudo ufw allow from 192.168.1.0/24 to any port 443
sudo ufw deny incoming
sudo ufw enable# Update system regularly
sudo apt update && sudo apt upgrade -y
# Install security tools
sudo apt install -y fail2ban ufw
# Configure automatic security updates
sudo apt install -y unattended-upgrades
sudo dpkg-reconfigure -plow unattended-upgrades- Navigate to organization Settings > Actions > Runner groups
- Click "New runner group"
- Configure:
- Name: production-runners
- Visibility: Private
- Repositories: Select repositories with access
jobs:
deploy:
runs-on:
group: production-runners
labels: [self-hosted, linux, x64]
steps:
- uses: actions/checkout@v4- Navigate to organization Settings > Audit log
- Filter by:
- User
- Repository
- Action
- Date range
# Use GitHub API to export logs
curl -H "Authorization: token $GITHUB_TOKEN" \
https://api.github.com/orgs/YOUR_ORG/audit-log \
> audit-log.json- Navigate to repository Security > Advisories
- Click "New draft security advisory"
- Fill in:
- Summary: Brief description
- Severity: Critical, High, Medium, Low
- Affected versions: Version range
- CVE ID: If applicable
name: Compliance Report
on:
schedule:
- cron: '0 0 1 * *' # Monthly
jobs:
compliance:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Generate Report
run: |
echo "# Compliance Report" > report.md
echo "## Repository: ${{ github.repository }}" >> report.md
echo "## Date: $(date)" >> report.md
echo "## Branch Protection: Enabled" >> report.md
- name: Upload Report
uses: actions/upload-artifact@v4
with:
name: compliance-report
path: report.md- Use least privilege permissions
- Pin action versions
- Use environment protection rules
- Implement approval workflows
- Regularly audit workflows
- Use environment-specific secrets
- Rotate secrets regularly
- Never log secrets
- Use secret scanning
- Implement secret rotation automation
- Use verified actions only
- Enable Dependabot alerts
- Implement dependency review
- Use signed commits
- Enable branch protection
- Prefer GitHub-hosted runners
- Isolate self-hosted runners
- Regularly update runners
- Monitor runner logs
- Implement network segmentation
- Monitor audit logs
- Enable security alerts
- Configure notifications
- Set up anomaly detection
- Revoke compromised credentials
- Disable affected workflows
- Isolate affected runners
- Suspend integrations
- Analyze audit logs
- Review workflow runs
- Check for unauthorized access
- Identify affected resources
- Rotate all secrets
- Update workflows
- Patch vulnerabilities
- Implement additional controls
# Revoke all tokens
# Navigate to: Developer settings > Personal access tokens
# Delete all tokens
# Revoke OAuth apps
# Navigate to: Settings > Applications > Authorized OAuth Apps
# Revoke access# Add temporary disable
name: Disabled Workflow
on:
push:
branches: [ main ]
jobs:
disabled:
runs-on: ubuntu-latest
steps:
- name: Workflow Disabled
run: echo "Workflow is disabled for security reasons"- GitHub Actions Security Documentation
- Securing Your Workflow
- Encrypted Secrets
- Dependabot Documentation
