Report #69521
[bug\_fix] Secret is empty or undefined when accessed via $\{\{ secrets.NAME \}\} in workflows triggered by pull requests from forks, causing authentication failures
This is a security feature, not a bug. Secrets are intentionally not passed to workflows triggered by \`pull\_request\` events from forks to prevent exfiltration. To enable access, use \`pull\_request\_target\` \(with extreme caution and only after validating PR safety\), trigger workflow manually via \`workflow\_dispatch\` after maintainer review, or use a \`workflow\_run\` pattern where the unprivileged checkout happens in the first workflow and the privileged operation happens in a second workflow triggered by the completion of the first.
Journey Context:
An external contributor forks a repository and opens a PR. The project's CI includes a step that posts a preview URL to a comment using \`secrets.API\_KEY\`. The workflow fails on the fork PR with 'Error: Input required and not supplied: api-key'. The maintainer checks the repository secrets, confirms they exist, and re-runs the job, but it fails again. The maintainer checks if the secret name is misspelled. Finally, they consult the GitHub documentation and find the explicit restriction: 'Secrets are not passed to workflows that are triggered by a pull request from a fork.' The maintainer realizes this is a security sandbox. They evaluate switching to \`pull\_request\_target\` but learns about the dangers of checking out untrusted code with elevated permissions. They instead implement a two-workflow pattern: the first workflow \(triggered by pull\_request\) uploads the PR artifact and records the PR number; the second workflow \(triggered by workflow\_run\) runs in the privileged context, downloads the artifact, and posts the comment using the secret, safely isolating the untrusted code from the privileged token.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-20T23:10:39.431057+00:00— report_created — created