Safeguard
Developer Tools•

Why can't you use secrets in an if condition in GitHub Actions?

GitHub Actions does not allow the secrets context in if conditions. Here is why, the supported workaround, and the mistakes to avoid when gating steps.

Safeguard Security Team
4 min read

GitHub Actions does not allow the secrets context inside if conditionals. The docs say secrets cannot be directly referenced there. The usual workaround is to map the secret to a job level environment variable, then test that variable in the condition. Never print the value to check it.

What the docs say

The secrets documentation states that secrets cannot be directly referenced in if conditionals. The contexts reference backs this up with an availability table. For a job level if, the allowed contexts are github, needs, vars, and inputs. For a step level if, they are github, needs, strategy, matrix, job, runner, env, vars, steps, and inputs. The secrets context appears in neither list.

The practical meaning: an expression such as "if secrets.DEPLOY_KEY is set" fails workflow validation or never behaves as you hoped.

Why it makes sense

The secrets context is meant to be handed to steps as input, not used to steer the workflow graph. Limiting where secrets can appear reduces accidental exposure, for example a condition that is echoed into a log or a summary. The same reference notes that the secrets context is not available for composite actions for security reasons.

The workaround

Expose the secret as an environment variable at the job level, then test the variable. The availability table allows env in step conditions, which is why this pattern works.

jobs:
  publish:
    runs-on: ubuntu-latest
    env:
      HAS_TOKEN: ${{ secrets.PUBLISH_TOKEN != '' }}
    steps:
      - name: Publish only when a token exists
        if: env.HAS_TOKEN == 'true'
        run: ./publish.sh
        env:
          PUBLISH_TOKEN: ${{ secrets.PUBLISH_TOKEN }}

Here the flag holds only true or false, not the secret itself. The secret is passed again only to the one step that needs it.

One more behavior to know: if a secret has not been set, an expression that references it returns an empty string. That is why the comparison above works, and also why a typo in a secret name fails quietly instead of loudly.

Common mistakes

  • Putting the secret itself in a job level env variable and then testing it. This widens exposure to every step in the job. Store a boolean instead.
  • Printing the variable to debug the condition. Redaction depends on exact matches, so derived or transformed values may not be masked.
  • Treating a missing secret as a success. Forked pull requests do not receive secrets (apart from GITHUB_TOKEN), and Dependabot triggered runs do not either. A step guarded this way will silently skip. Make sure skipping is what you want.
  • Relying on the skip as a security boundary. Whether a step runs is not the same as who may trigger the workflow. Use environments with required reviewers for that.

A better design for optional steps

If a step needs a credential only on the main branch, do not test for the secret at all. Put the step in a job that references an environment limited to the main branch, and let the platform withhold the secrets elsewhere. The condition then reads the branch name, which is a context that is allowed in conditions, and the secret simply is not present on other refs.

Quick check

Is there an official flag for "is this secret set"?

The docs do not offer one. The pattern above, a boolean in an environment variable, is an inference from the context availability table rather than documented guidance, so test it in a scratch repository first.

Do forked pull requests get secrets?

No. Secrets are not passed to the runner when a workflow is triggered from a forked repository, except GITHUB_TOKEN.

Safeguard policies and gates can check workflow files in pull requests, so a change that exposes a secret to more steps can be reviewed before merge. A free scan is at gold.safeguard.sh/scan.

Sources

Last reviewed: 19 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.