GitHub Actions Tutorial — Security Considerations
Security Considerations
Self-hosted runners execute code from your repository — including pull requests from forks. Without proper security, a malicious PR can access your network, secrets, and infrastructure. This page covers the threat model and mitigations.
The Threat Model
Attacker submits PR with malicious workflow
│
▼
Self-hosted runner executes the code
│
├── Access to private network
├── Access to runner's file system
├── Access to environment variables
├── Potential credential theft
└── Lateral movement to other systems
GitHub-hosted runners are disposable VMs — they’re destroyed after each job. Self-hosted runners persist, making them a higher-value target.
Critical Rules
1. Never Use Self-Hosted Runners on Public Repos
Anyone can fork a public repository and submit a PR. If your self-hosted runner processes that PR, the attacker’s code runs on your machine.
| Repository | Self-Hosted Runners | Recommendation |
|---|---|---|
| Public | ❌ | Use GitHub-hosted runners |
| Private (trusted contributors) | ✅ | Acceptable with controls |
| Private (external contributors) | ⚠️ | Use with fork restrictions |
2. Use Ephemeral Runners
Ephemeral runners eliminate persistent state:
./config.sh --ephemeral --url ... --token ...
No credentials or data persist between jobs.
3. Restrict Fork PR Workflows
For private repos with external contributors:
Repository Settings → Actions → General:
- Fork pull request workflows: Require approval for all outside collaborators
- Fork pull request workflows from outside collaborators: Require approval for first-time contributors
4. Use Runner Groups for Access Control
Limit which repositories can use sensitive runners:
Group: production-runners
Access: my-org/deploy-service ONLY
Group: general-runners
Access: All repositories
Network Security
Isolate Runner Networks
┌─────────────────────────┐
│ Runner Network (DMZ) │
│ ┌─────────────────┐ │
│ │ Self-hosted │ │
│ │ runner │ │
│ └─────────────────┘ │
│ │ restricted │
│ ▼ │
│ Allowed outbound: │
│ - github.com │
│ - npm/pip registries │
│ - Deploy targets │
│ │
│ Blocked: │
│ - Internal databases │
│ - Admin panels │
│ - Other services │
└─────────────────────────┘
- Place runners in an isolated network segment
- Use firewall rules to limit what they can access
- Only allow necessary outbound connections
Secret Management
| Practice | Detail |
|---|---|
| Use GitHub Secrets | Don’t store credentials on the runner |
| Use OIDC | Short-lived tokens instead of long-lived keys |
| Rotate tokens | Regularly rotate runner registration tokens |
| Ephemeral runners | Secrets don’t persist between jobs |
Workflow Security Practices
Pin Action Versions by SHA
# ❌ Tag can be moved by the action author
- uses: actions/checkout@v4
# ✅ SHA is immutable
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
Limit GITHUB_TOKEN Permissions
permissions:
contents: read # minimum needed
pull-requests: write # only if needed
Audit Third-Party Actions
Before using any action, review:
- Source code in the action’s repository
- Whether it’s from a verified creator
- Recent activity and maintenance status
Common Mistakes
| Mistake | Fix |
|---|---|
| Self-hosted runners on public repos | Move to GitHub-hosted or restrict fork PRs |
| Persistent runners with secrets | Use ephemeral mode |
| No network isolation | Place runners in DMZ with firewall rules |
Using @v1 tags on untrusted actions |
Pin to full SHA |
Quick Reference
| Practice | Priority |
|---|---|
| No self-hosted on public repos | 🔴 Critical |
| Ephemeral runners | 🔴 Critical |
| Restrict fork PR workflows | 🟡 High |
| Runner groups for access control | 🟡 High |
| Network isolation | 🟡 High |
| Pin actions by SHA | 🟢 Recommended |
| Minimal GITHUB_TOKEN permissions | 🟢 Recommended |
← Previous: Ephemeral Runners
Next: Practice Exercises →