GitHub Actions Secrets Not Available in Fork PRs ā Fix (2026)
Secrets are unavailable and workflows fail when PRs come from forks. Here's why GitHub blocks fork access to secrets by default and the right way to handle it safely.
Your workflow runs fine on your own branches but fails on PRs from forks because secrets show up as empty. This is intentional security behavior ā and there's a right way to handle it.
Why Fork PRs Can't Access Secrets
GitHub blocks secrets from workflows triggered by fork pull requests to prevent this attack:
- Attacker forks your repo
- Modifies the workflow to
echo $MY_SECRET - Opens a PR ā workflow runs with your secrets
- Attacker now has your AWS keys, Docker credentials, etc.
This is why pull_request events from forks run in a restricted environment with no secrets.
Diagnosing the Issue
# Your workflow shows this:
# Error: buildx failed with: error: denied: access forbidden
# Error: DOCKER_PASSWORD is empty
# Error: AWS credentials not found
# Check if the PR is from a fork
echo "github.event.pull_request.head.repo.fork = ${{ github.event.pull_request.head.repo.fork }}"If your PR comes from a fork, github.event.pull_request.head.repo.fork is true and secrets are empty.
Fix 1: Use pull_request_target (With Caution)
pull_request_target runs in the context of the base repo (not the fork), so it has access to secrets.
# ā ļø Only safe if you don't checkout fork code AND run it
on:
pull_request_target:
types: [opened, synchronize]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Deploy
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
run: echo "Secrets work here"WARNING: If you also actions/checkout and run code from the PR in a pull_request_target workflow, the fork code runs with your secrets ā exactly the attack described above.
Safe pattern with pull_request_target:
on:
pull_request_target:
jobs:
# Safe: only runs your own scripts, doesn't checkout fork code
label-pr:
runs-on: ubuntu-latest
steps:
- uses: actions/labeler@v4
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}Fix 2: Split Workflow ā Build in Fork Context, Deploy After Approval
This is the recommended pattern for CI/CD on fork PRs:
Workflow 1 ā build (runs on fork PR, no secrets needed):
# .github/workflows/ci.yml
name: CI
on:
pull_request:
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build
run: docker build -t myapp:pr-${{ github.event.pull_request.number }} .
- name: Test
run: docker run myapp:pr-${{ github.event.pull_request.number }} npm test
# Save the image as an artifact (no registry push yet)
- name: Save image
run: docker save myapp:pr-${{ github.event.pull_request.number }} | gzip > image.tar.gz
- uses: actions/upload-artifact@v4
with:
name: docker-image
path: image.tar.gz
retention-days: 1Workflow 2 ā deploy (triggered manually or after approval, has secrets):
# .github/workflows/deploy-pr.yml
name: Deploy PR
on:
workflow_run:
workflows: ["CI"]
types: [completed]
jobs:
deploy:
# Only run if CI passed
if: github.event.workflow_run.conclusion == 'success'
runs-on: ubuntu-latest
steps:
- name: Download image artifact
uses: actions/download-artifact@v4
with:
name: docker-image
run-id: ${{ github.event.workflow_run.id }}
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Load and push to registry
env:
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} # secrets available here
run: |
docker load < image.tar.gz
echo "$DOCKER_PASSWORD" | docker login -u myuser --password-stdin
docker push myapp:pr-${{ github.event.pull_request.number }}The key: workflow_run runs in the base repo context and has secrets. Build happens in fork context (no secrets), deploy happens in base repo context (has secrets).
Fix 3: Require Maintainer Approval for Fork PRs
GitHub has a built-in protection ā maintainers must approve first-time contributors before their workflows run:
Repo Settings ā Actions ā General ā Fork pull request workflows
ā "Require approval for first-time contributors"
This means a new contributor's PR won't run workflows until you approve. After approval, their PRs run automatically. This is a good default for open-source repos.
Fix 4: Use Environments with Protection Rules
jobs:
deploy:
environment: staging # requires approval
runs-on: ubuntu-latest
steps:
- run: echo "Deploying with ${{ secrets.API_KEY }}"
env:
API_KEY: ${{ secrets.API_KEY }}In GitHub repo Settings ā Environments ā staging ā Protection rules, require a reviewer before the environment job runs. Secrets are only available after approval.
Fix 5: Skip Deployment Steps for Fork PRs
If you just want CI to work but skip anything that needs secrets:
jobs:
ci:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build and test (always runs)
run: npm test
- name: Push to registry (skip for forks)
if: github.event.pull_request.head.repo.fork == false
env:
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
run: |
echo "$DOCKER_PASSWORD" | docker login -u myuser --password-stdin
docker push myapp:latestThis is the simplest approach if you just want tests to pass on fork PRs without any deployment.
Summary
| Approach | Use When |
|---|---|
pull_request_target (no checkout) | Safe labeling, PR comments, status checks |
Split CI + workflow_run deploy | Full CI/CD on fork PRs with secrets |
| Manual approval requirement | Open source repos, first-time contributors |
| Environment protection rules | Staging/prod deployments requiring review |
if: fork == false skip | Simple ā run tests, skip deploy for forks |
For most teams: use the if: fork == false skip for simple cases, and the split workflow_run pattern for repos where you need to deploy fork PRs.
Stay ahead of the curve
Get the latest DevOps, Kubernetes, AWS, and AI/ML guides delivered straight to your inbox. No spam ā just practical engineering content.
Related Articles
GitHub Actions Docker Push: Permission Denied / Unauthorized Fix (2026)
Getting 'permission denied' or 'unauthorized: authentication required' when pushing Docker images in GitHub Actions? Here are all the causes and fixes.
GitHub Actions Job Timeout ā Every Fix (2026)
Your GitHub Actions job times out after 6 hours or hits a custom timeout limit. Here's every cause ā hung Docker builds, hanging tests, stuck deployments, missing timeout config ā and the exact fix.
GitHub Actions 'No Space Left on Device': How to Fix Runner Disk Issues
GitHub Actions failing with 'no space left on device'? Here's how to free disk space on runners, optimize Docker builds, and handle large monorepos.