← Actions secrets: inherit Check
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.
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.
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.
secrets: { TOKEN: ${{ secrets.TOKEN }} }.pull_request_target/workflow_run) on the smallest secret set it actually needs, or move the part that needs no secrets to the unprivileged pull_request trigger instead.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.