šŸŽ‰ DevOps Interview Prep Bundle is live — 1000+ Q&A across 20 topicsGet it →
All Articles

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.

DevOpsBoysMay 2, 20264 min read
Share:Tweet

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:

  1. Attacker forks your repo
  2. Modifies the workflow to echo $MY_SECRET
  3. Opens a PR — workflow runs with your secrets
  4. 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

bash
# 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.

yaml
# āš ļø 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:

yaml
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):

yaml
# .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: 1

Workflow 2 — deploy (triggered manually or after approval, has secrets):

yaml
# .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

yaml
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:

yaml
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:latest

This is the simplest approach if you just want tests to pass on fork PRs without any deployment.


Summary

ApproachUse When
pull_request_target (no checkout)Safe labeling, PR comments, status checks
Split CI + workflow_run deployFull CI/CD on fork PRs with secrets
Manual approval requirementOpen source repos, first-time contributors
Environment protection rulesStaging/prod deployments requiring review
if: fork == false skipSimple — 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.

Newsletter

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

Comments