GitHub Actions Secrets and Environments: Safe Deploy Setup
Practice4 minInnovialabs editorial team
In short
Put production credentials in environment secrets behind required reviewers and branch rules, replace long-lived cloud keys with OIDC, give GITHUB_TOKEN minimal permissions, and never run untrusted fork code with access to secrets.
The short answer
A safe deploy setup in GitHub Actions rests on four ideas:
- Scope secrets narrowly: production credentials live in a
productionenvironment, not at repository level. - Gate production: the environment requires a reviewer and accepts deploys only from protected branches.
- No long-lived cloud keys: the workflow gets short-lived credentials through OIDC.
- Assume logs and forks are hostile: masking is not a guarantee, and untrusted code must never run next to secrets.
Three levels of secrets
| Level | Who can use it | Use it for |
|---|---|---|
| Organization | Repositories you select in the access policy | Shared values: registry tokens, notification webhooks |
| Repository | Any workflow in the repository | Non-critical values used across jobs |
| Environment | Only jobs that reference the environment, and only after its protection rules pass | Deploy credentials for staging and production |
When names collide, the more specific level wins: environment over repository, repository over organization. Secrets are read with ${{ secrets.NAME }} and should be passed to steps through env, not pasted into inline scripts.
Protecting production
In Settings → Environments → production configure:
- Required reviewers — a person or team must approve each deploy job before it starts and before it gets the secrets.
- Deployment branches and tags — allow only
mainor release tags. A feature branch then cannot deploy even if someone edits the workflow. - Wait timer if you want a pause before deploys.
- Prevent self-review where available, so the author of a change cannot approve their own deploy.
Combine this with branch protection or rulesets on main: required pull request reviews, required status checks and no force pushes. Otherwise anyone with write access can push a modified workflow straight to the protected branch.
OIDC instead of cloud keys
Static access keys stored as secrets live for months and work from anywhere. With OIDC, GitHub issues a signed token for each run; the cloud provider exchanges it for temporary credentials, but only if the token's claims match the trust policy (repository, branch, environment).
name: deploy
on:
push:
branches: [main]
permissions:
contents: read
id-token: write
jobs:
deploy:
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v4
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/github-deploy
aws-region: eu-central-1
- run: ./scripts/deploy.sh
On the cloud side, restrict the trust policy to the exact subject, for example the production environment of a specific repository. A wildcard over the whole organization defeats the purpose. Major clouds and Vault support this flow.
How secrets leak
Through logs:
- GitHub masks exact secret values, but not transformed ones: base64-encoded, split, URL-encoded or embedded in JSON.
set -x, verbose CLI flags and debug logging can print headers and tokens.- Register derived values with
echo "::add-mask::$VALUE"before using them.
Through forks and untrusted code:
- Workflows triggered by
pull_requestfrom forks do not receive secrets — keep it that way. pull_request_targetandworkflow_runrun with the base repository's secrets. Checking out and running the PR's code in them hands your secrets to any outside contributor.- Inputs like PR titles and branch names inserted directly into
run:can lead to script injection. Pass them throughenvand quote them.
Through dependencies:
- Third-party actions run with your job's permissions. Pin them to a full commit SHA and review what they do.
Least-privilege checklist
- Set
permissions:at the workflow level; start fromcontents: readand add only what is needed. - Keep production secrets only in the
productionenvironment. - Rotate any secret that was ever printed, committed or shared.
- Remove unused secrets and environments.
- Review who has admin rights: admins can change protection rules.