Set secrets at step level, on the one step that needs them. An environment variable set at workflow or job level is visible to every step after it, including third-party actions that never needed the value. Narrow scope means a compromised action in the same job has less to steal.
The scoping options
In a workflow file, the env key can sit at three levels: the whole workflow, a single job, or a single step. A value at workflow level is present in every job. At job level it is present in every step of that job. At step level it exists only for that step.
Secrets given as action inputs behave differently from environment variables, but the principle is the same. GitHub's metadata reference notes that input parameters are stored as environment variables, each named with an INPUT_ prefix. A step that passes a secret as an input hands it to that action, and only that action.
Why wide scope is risky
Every action in a job runs with the job's environment. If you export a deploy token at job level because one step needs it, then a linter action, a cache action and a notification action in the same job all run with that token in their environment. If any one of them is compromised, the token is available to it without any extra step.
GitHub's secure use guidance frames the same risk from the other direction: a compromise of a single action within a workflow can be very significant, because the action can reach secrets and the repository token. Reducing what is in scope reduces what a single compromise can reach.
Compare the patterns
| Pattern | Who sees the secret |
|---|---|
| env at workflow level | Every step of every job |
| env at job level | Every step of that job |
| env or input on one step | That step only |
| Separate job with its own environment | That job only, after any approval rules |
How to apply it
- Search your workflows for
env:blocks at the top and underjobs. List every secret referenced there. - For each, find the steps that actually read it. Often it is one command.
- Move the secret down to that step's
env, or pass it as an input to the single action that needs it. - Order the steps so that third-party actions with no need for the secret run before it is introduced, and in some cases in a different job altogether.
- For deploy credentials, use a separate job tied to a protected environment. GitHub documents that a job cannot access environment secrets until required reviewers approve, and that environment secrets are only available to jobs that use the environment. See environment required reviewers and deployment approval.
- Prefer short-lived credentials from OIDC over stored secrets where your cloud supports it.
A before and after
Before: a job sets a registry token in its job level env block. Five steps run, three of them third-party actions.
After: the job has no token in env. Only the publish step sets it, in that step's own env block. The three third-party actions run with no registry token available to them.
Nothing about the workflow's function changed. The exposure did.
Watch the other leaks
Scoping does not fix everything. Secrets can still reach logs through careless commands, and masking has limits, as covered in what secret masking cannot hide. Pass secrets through environment variables rather than command line arguments, so they do not show up in a process list on shared runners.
Quick check
Does step level scope work with reusable workflows?
Inside the called workflow, yes. The secret reaches the called workflow only if the caller passes it, and then the called workflow's own steps decide the scope.
Is job level ever fine?
If every step in the job is code you wrote and reviewed, the difference is small. Once third-party actions share the job, step level is the safer habit.
Sources
- GitHub Docs: Metadata syntax for GitHub Actions
- GitHub Docs: Secure use reference
- GitHub Docs: Managing environments for deployment
Last reviewed: 19 October 2026.