Store deployment credentials as environment secrets, not repository secrets, and attach required reviewers and deployment branch rules to the environment. A contractor with write access can then run tests, but a job cannot read production secrets until someone you trust approves it.
Why repository secrets are the problem
GitHub's Actions security guidance says that any user with write access to a repository has read access to all secrets configured in that repository. If a contractor can push a workflow change on a branch, they can in principle make a job print or send a repository secret. Review rules reduce that risk but should not be your only line.
The fix is to reduce what repository secrets contain. Keep only low value, test scoped credentials there. Put anything that touches staging data or production in an environment.
How environments protect secrets
GitHub states that environment secrets are only available to workflow jobs that use that environment, and that jobs can only access them after any configured protection rules, such as required reviewers, pass. Three settings matter most:
- Required reviewers. You can name up to six people or teams. Only one needs to approve for the job to proceed.
- Prevent self-review. An option that stops a user approving a deployment run that they themselves triggered.
- Deployment branches. Limit which branches can deploy to the environment, so a contractor's feature branch cannot target production.
A wait timer is also available if you want a delay window before a job proceeds.
Setup steps
- Create environments named for their purpose, such as staging and production.
- Move deploy keys, cloud credentials and API tokens out of repository secrets and into the matching environment.
- Add required reviewers who are staff, never the contractor team, and turn on self-review prevention.
- Restrict deployment branches to your protected default branch and release branches.
- Reference the environment in the workflow job that deploys. Jobs without it get no access.
- Put the workflow directory under code owner review so the reference to an environment cannot be quietly edited. The post on CODEOWNERS for workflow files covers it.
A sensible split of duties
| Task | Who can do it | Control |
|---|---|---|
| Push a feature branch | Contractor | Branch rules |
| Run unit tests | Contractor, via CI | No secrets in the job |
| Merge to the default branch | Staff approver | Required review |
| Deploy to staging | Automatic after merge | Environment branch rule |
| Deploy to production | Staff approver | Required reviewers on environment |
Related token hygiene
GitHub also recommends setting the default GITHUB_TOKEN permission to read access for repository contents, then raising permissions per job where needed. Do that in the same pass. A contractor workflow that only needs to read code should not carry a token that can write to it.
If a contractor needs a credential for their own local testing, issue a scoped, test only credential that you can revoke, and do not share a copy of production values. The related post on what to give a contractor instead of a production dump covers data, and the same logic applies to keys.
After the engagement
Rotate anything the contractor could have touched through a repository secret. Environments narrow the list, which makes this step shorter. Follow the contractor offboarding checklist for the rest.
Safeguard helps with the surrounding pull request checks through policies and gates, and with evidence for auditors through compliance and evidence automation. It does not replace sound secret handling in your pipeline.
Quick check
Can a required reviewer be the contractor?
Technically any person or team you name can be a reviewer, but you should choose staff. Self-review prevention also stops a user approving runs they triggered.
Do environments replace branch protection?
No. They guard secrets and deployments. Branch protection guards what gets merged. Use both.
Sources
Last reviewed: 19 October 2026.