Secrets Management in GitHub Actions
GitHub Actions workflows need secrets for deployments, API calls, and package publishing. This guide covers how to manage those secrets without leaking them.
Use GitHub Repository Secrets
Store secrets in repository or organization settings, never in workflow files:
# .github/workflows/deploy.yml
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Deploy
env:
API_KEY: ${{ secrets.DEPLOY_API_KEY }}
run: ./deploy.sh
Reference: GitHub — Using Secrets in GitHub Actions
Use Environments for Sensitive Secrets
GitHub Environments let you add protection rules before a workflow can access secrets:
- Required reviewers: a human must approve before the workflow runs.
- Wait timer: enforced delay before deployment.
- Deployment branches: only specific branches can access the environment.
jobs:
deploy-production:
runs-on: ubuntu-latest
environment: production # requires approval
steps:
- name: Deploy
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
run: ./deploy.sh
Reference: GitHub — Using Environments for Deployment
Never Print Secrets in Logs
GitHub automatically masks secrets in logs, but this is not foolproof:
# BAD — can leak through encoding, substrings, or indirection
- run: echo "Key is ${{ secrets.API_KEY }}"
# BAD — base64 encoding bypasses masking
- run: echo "${{ secrets.API_KEY }}" | base64
# GOOD — mask additional values manually
- run: |
echo "::add-mask::$DERIVED_VALUE"
./deploy.sh
Common leak vectors:
- Error messages that include the secret value.
- Debug mode (
ACTIONS_STEP_DEBUG) showing environment. - Encoding transformations (base64, URL encoding).
- Multi-line secrets where only the first line is masked.
Reference: GitHub — Masking a Value in a Log
Restrict pull_request_target Workflows
The pull_request_target event runs with access to repository secrets, even for PRs from forks. This is a major attack vector.
# DANGEROUS — runs untrusted code with secrets access
on: pull_request_target
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }} # checks out fork code
- run: npm install # runs fork's package.json scripts with secrets in env
Rules:
- Never checkout PR code in
pull_request_targetworkflows. - If you must, run untrusted code in a separate job without secrets access.
- Prefer
pull_request(no secrets access) for CI on external PRs.
Reference: GitHub — Events That Trigger Workflows
Use OIDC Instead of Long-Lived Credentials
GitHub Actions supports OpenID Connect (OIDC) for cloud provider authentication. This eliminates long-lived secrets entirely.
AWS
permissions:
id-token: write
contents: read
steps:
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/github-deploy
aws-region: us-east-1
Reference: GitHub — Configuring OIDC in AWS
GCP
steps:
- uses: google-github-actions/auth@v2
with:
workload_identity_provider: projects/123/locations/global/workloadIdentityPools/github/providers/my-repo
service_account: [email protected]
Reference: GitHub — Configuring OIDC in GCP
Azure
steps:
- uses: azure/login@v2
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
Reference: GitHub — Configuring OIDC in Azure
Pin Actions to Full SHA
Third-party actions can be compromised. Pin to a commit SHA, not a tag:
# BAD — tag can be moved to a malicious commit
- uses: actions/checkout@v4
# GOOD — immutable reference
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
Reference: GitHub — Security Hardening for GitHub Actions
Set Minimum Permissions
Use the permissions key to restrict what the GITHUB_TOKEN can do:
permissions:
contents: read # only what's needed
packages: write # if publishing packages
Start with no permissions and add only what the workflow needs:
permissions: {} # no permissions by default
Reference: GitHub — Automatic Token Authentication
Rotate Secrets
- Rotate secrets on a regular schedule (90 days or less).
- Rotate immediately if a secret may have been exposed.
- Use GitHub's secret scanning to detect leaked tokens: GitHub — Secret Scanning
Checklist
- All secrets stored in GitHub Settings (repo or org level), not in code
- Environments configured with required reviewers for production
- No
echoor logging of secret values -
pull_request_targetworkflows do not checkout fork code with secrets - OIDC used for cloud provider auth (no long-lived keys)
- Third-party actions pinned to full commit SHA
-
GITHUB_TOKENpermissions set to minimum required - Secret rotation schedule in place
- Secret scanning enabled
Related Guides
Content is AI-assisted and reviewed by our team, but issues may be missed and best practices evolve rapidly, send corrections to [email protected]. Always consult official documentation and validate key implementation decisions before making design or security choices.
