← Actions secrets: inherit Check

What is GitHub Actions secrets: inherit, and when is it risky?

A reusable workflow is a job that calls another workflow file instead of running its own steps directly:

jobs:
  call-workflow:
    uses: octo-org/example-repo/.github/workflows/reusable.yml@main
    secrets: inherit

secrets: inherit is a shorthand on a reusable-workflow call that passes every secret the calling workflow can see to the called workflow, instead of naming specific ones. It is risky in two independent ways: when the called workflow is hosted in a different repository you do not review on the same schedule, and when the job that inherits the secrets is reachable from a pull_request_target or workflow_run trigger a stranger's fork can raise.

By default, a reusable-workflow call receives no secrets at all — the caller has to name each one explicitly (secrets: { TOKEN: ${{ secrets.TOKEN }} }). That is convenient to skip, and GitHub's own documentation is upfront that skipping it should be used with caution.

Risk one: the called workflow is external

A reusable workflow can live in the same repository (uses: ./.github/workflows/reusable.yml) or in a different one entirely (uses: OWNER/REPO/.github/workflows/reusable.yml@ref). A local call is reviewed under the same process as everything else in your repository. An external call is code someone else controls — a compromised maintainer account, a force-pushed branch, or simply a decision made at that other repository becomes a way to walk off with every secret you inherited to it, not just the ones the workflow was written to use.

Risk two: the job is reachable from a fork

pull_request_target and workflow_run jobs run in the context of the base repository — with its secrets — even though the event that triggered them was raised by a stranger's fork. A job like "post a comment on the PR" might genuinely need one secret. secrets: inherit hands it the entire set, so a fork-reachable run has access to far more than the task in front of it requires.

Both risks are independent: a call can be external, fork-reachable, both, or neither.

The fix

Check your own workflows

Paste your workflow YAML into Actions secrets: inherit Check to find every external-workflow and fork-reachable secrets: inherit call across every workflow file at once, or run the CLI in your own repository:

python3 check.py --fail-on-risk

That exits with a nonzero status if any workflow has either pattern — wire it into your own CI to block a newly-added one before it merges.