Report #42572
[bug\_fix] Secrets are empty or authentication fails in pull requests from forks
Do not use \`pull\_request\` events for workflows that need secrets to operate on fork PRs. Instead, use the \`workflow\_run\` pattern: create a 'ci.yml' that runs on \`pull\_request\` \(untrusted, no secrets\) to build and upload artifacts, and a 'deploy.yml' triggered by \`workflow\_run\` \(trusted, has secrets\) that downloads the artifacts and posts comments/deploys. Alternatively, use \`pull\_request\_target\` only if you strictly checkout the base repo code \(never \`refs/pull/.../merge\`\) and only for safe operations like labeling.
Journey Context:
A developer creates a workflow that posts a comment on PRs with test coverage results using \`secrets.GITHUB\_TOKEN\` and an external API key \`secrets.API\_KEY\`. It works perfectly for internal team members pushing branches to the main repo. However, when an external contributor forks the repository and submits a PR, the workflow runs but the step using the secret fails with 'Authentication failed: API key missing' or the secret resolves to an empty string. The developer checks the 'Secrets' context in the job logs and confirms secrets are listed as empty. They suspect a typo in the secret name and verify it in the repository settings, but it's correct. They search online and discover that GitHub Actions deliberately does not expose secrets to workflows triggered by \`pull\_request\` events from forks, as a security measure to prevent malicious PRs from exfiltrating secrets. They initially try switching the trigger to \`pull\_request\_target\`, which grants access to secrets, but then realize this runs the workflow in the context of the base repository with elevated permissions, and if they checkout the PR code \(refs/pull/.../merge\), they are executing untrusted code with write permissions—an RCE risk. They panic and revert. Finally, they discover the 'workflow\_run' pattern described in GitHub Security Lab articles: they split the workflow into two parts. The first workflow \(ci.yml\) triggers on \`pull\_request\`, runs the untrusted build/tests, and uploads coverage artifacts. It has no secrets. The second workflow \(report.yml\) triggers on \`workflow\_run\` \(which runs on the default branch with full secrets\), downloads the artifacts from the completed CI run, and posts the comment using the API key. This securely isolates secret access from untrusted code execution.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-19T01:55:36.540274+00:00— report_created — created