DevTools Hub

Search tools

Search for a developer tool

CI/CD

GitHub Actions Secrets Checker

See where a workflow's secrets flow — unpinned third-party actions, log-echoed secrets, secrets: inherit, and pull_request_target exposure.

1 error 2 warnings
  • Passes "secrets.DEPLOY_TOKEN" to "some-org/deploy-action@v2", a third-party action where it's pinned to "v2", a mutable ref. If that action's code is ever compromised — a moved tag, a hijacked branch, a malicious update — it runs with these secrets. Pin it to a full commit SHA before trusting it with credentials.
    jobs.deploy.steps[1].with
  • Prints "secrets.DEPLOY_TOKEN" to the job log via echo. GitHub masks registered secret values in logs, but that masking is bypassed by any transformation of the value (base64, splitting characters, re-encoding) — avoid printing secrets even when they're expected to be masked.
    jobs.deploy.steps[2].run
  • This job has access to repository secrets while the workflow is triggered by "pull_request_target", which can run in the context of a fork's pull request. Make sure nothing this job checks out or executes is influenced by the PR — if it is, that's a path for the PR to exfiltrate these secrets.
    jobs.deploy
Secrets used
secrets.DEPLOY_TOKEN
  • deploy.steps[1] step with
  • deploy.steps[2] run script

What this checks

This traces every ${{ secrets.* }} reference in a workflow and checks how it's exposed, not whether it exists. The main check: a secret handed to a third-party action via with: that isn't pinned to a full commit SHA. That action's code runs with the secret in scope — if the tag it's pinned to ever moves or the action is compromised, the secret goes with it. A first-party action (actions/*, github/*), a local action, or an action already pinned to a SHA doesn't trigger this, since none of those carry the same supply-chain risk.

It also flags a secret printed to the job log via echo/print/console.log and similar (GitHub's log masking can be bypassed by transforming the value), secrets: inherit on a reusable workflow call (passes every secret the caller has, not just the ones actually needed), and any job that has secrets in scope while triggered by pull_request_target or workflow_run — triggers that can run in a fork's context. Below the findings, a Secrets used summary lists every secret referenced and exactly where, so you can see a workflow's full credential surface at a glance.

What it doesn't do

This doesn't flag a secret used normally — as a CLI argument, an environment variable consumed by your own code, or passed to a first-party/SHA-pinned action — since that's exactly what secrets are for. It also can't verify whether a job triggered by pull_request_target actually executes anything influenced by the PR; for that specific pattern (checking out and running a fork's code under an elevated-permissions trigger), see GitHub Actions Linter. For a hardcoded literal credential typed directly into the workflow (rather than a proper secrets.* reference), that's also the Linter's job.

FAQ

Why is passing a secret to actions/checkout fine, but not to a random action?

actions/checkout (and the rest of the actions/* and github/* orgs) are maintained by GitHub itself and used by nearly every workflow in existence — the trust bar is different than for a one-off third-party action with a handful of stars. The real signal this tool checks for either way is SHA pinning: pin any action that receives a secret and this check has nothing to flag, regardless of who publishes it.

My pull_request_target job legitimately needs secrets — now what?

That's a completely normal reason to use the trigger (e.g. a bot that labels PRs or posts a comment needs a token that plain pull_request doesn't get). The warning is a prompt to double-check, not a verdict — as long as the job never checks out or runs code influenced by the PR itself, the secrets stay safe.

Related tools