← Alex Reed

I Scanned 15 GitHub Workflows and Found 96 Security Issues

April 25, 2026 · 8 min read

I built a security scanner for GitHub Actions workflows. Then I pointed it at keephq/keep — a YC W23 startup with 5,000+ GitHub stars — to see what would happen.

The results were not subtle.

96
findings
15
workflows scanned
14/15
had checkout issues

This isn't a hit piece. Keep is a well-maintained project with active contributors. The problems I found are industry-normal. That's the point. If a funded startup with a real engineering team has 96 workflow security issues, yours probably does too.

The scanner

actions-audit is a Python CLI that parses your .github/workflows/ directory and checks for five categories of misconfigurations:

  1. Unpinned action versions — using actions/checkout@v4 instead of a SHA
  2. checkout persist-credentials — the default leaves auth tokens in the local git config
  3. pull_request_target misuse — grants PR code access to repo secrets
  4. Secrets in PR context — using secrets in steps that run on untrusted code
  5. Context injection — unsanitized github.* values in run: blocks

What I found in KeepHQ

1. Unpinned actions everywhere (60+ instances)

Nearly every uses: line referenced a tag, not a SHA:

# What they had:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
- uses: aws-actions/configure-aws-credentials@v4

# What it should be:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
- uses: actions/setup-python@8d9ed9ac5c534f38775b415c983e8fb3e9e7b0a1
Why it matters: Tag aliases can be moved by the action maintainer. If v4 gets repointed to malicious code, your CI runs it. Pinning to a SHA means the content is immutable. GitHub's own security hardening guide recommends SHA pinning.

2. Checkout credential leakage (14 of 15 workflows)

The default behavior of actions/checkout persists the GitHub token in .git/config after the step completes. Any later step — including third-party actions — can read it and push to your repo.

# Fix: one line per workflow
- uses: actions/checkout@v4
  with:
    persist-credentials: false
14 out of 15 workflows in KeepHQ were missing this flag. That's 93% of their CI pipelines leaving auth tokens accessible to every subsequent step.

3. pull_request_target with secrets access

Two workflows used pull_request_target, which runs in the context of the base repository — meaning it has access to repo secrets. Combined with an explicit checkout of the PR head, this gives forked PR code access to your secrets.

# Dangerous pattern:
on:
  pull_request_target:
steps:
  - uses: actions/checkout@v4
    with:
      ref: ${{ github.event.pull_request.head.sha }}
  - name: Deploy
    env:
      AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
    run: ./deploy.sh

A malicious PR can exfiltrate AWS_SECRET_ACCESS_KEY by modifying deploy.sh to echo it to an external endpoint.

4. Context injection in run: blocks

Several workflows interpolated GitHub context directly into shell commands:

# Dangerous:
run: echo "Building ${{ github.head_ref }}"

# Safe:
env:
  HEAD_REF: ${{ github.head_ref }}
run: echo "Building ${{ env.HEAD_REF }}"

The first version is vulnerable to injection if an attacker crafts a branch name containing shell commands. The second uses GitHub's environment variable mediation, which sanitizes values.

The full breakdown

Category Count Severity
Unpinned actions 60+ Medium
persist-credentials: true (default) 14 Medium
pull_request_target + secrets 2 High
Context injection 12+ Low-Medium
Secrets in PR context 3 High

How to fix your workflows (checklist)

  1. Pin every action to a SHA. Yes, it's verbose. Do it anyway. You can automate this with actions/checkout's pin-action script or just look up SHAs manually.
  2. Add persist-credentials: false to every actions/checkout step. There is almost no reason to leave this on.
  3. Avoid pull_request_target unless you absolutely need it, and never combine it with secrets.* access.
  4. Use environment variables for GitHub context values in run: blocks instead of direct interpolation.
  5. Run a scanner. Mine or someone else's. The point is to check systematically, not manually.

Try it yourself

The scanner is open-source and runs locally — no API keys, no network access needed:

pip install actions-audit
actions-audit scan /path/to/repo/.github/workflows/

It outputs JSON for CI integration and color-coded terminal output for manual review. Exit code is non-zero if it finds issues, so you can gate PRs on it.

Source code: git.sr.ht/~alexreed/portfolio

The uncomfortable truth

KeepHQ is not special here. I picked it because it's a real project with real users and real engineers. I could have scanned almost any repo with 15+ workflows and found similar numbers.

The CI/CD supply chain is the soft underbelly of most engineering organizations. We spend enormous effort securing production infrastructure and almost none securing the pipelines that deploy to it. A compromised GitHub Action doesn't need to hack your servers — it already runs with your credentials.

Scan your workflows. Fix the easy stuff. Pin your actions. Turn off credential persistence. It's 30 minutes of work that eliminates most of the attack surface.