Hunting Bugs in CI/CD Pipelines for Bug Bounty
CI/CD is where source code, cloud credentials, package registries, and production deployment rights meet. That makes a pipeline bug powerful—and easy to test recklessly. Before touching a build system, read scope twice. Many programs include their public repositories but exclude social engineering, destructive builds, and attacks on third-party CI providers.
The safest findings come from reading trust boundaries, then proving them with a fork, a draft pull request, or a canary secret you own.
Start with the workflow files
Public repositories expose their pipeline design in .github/workflows, .gitlab-ci.yml, Jenkinsfiles, and build scripts. Mark every event trigger and every place untrusted data crosses into a privileged job.
In GitHub Actions, pull_request_target deserves immediate attention. It runs in the base repository context and may receive secrets or a writable token. That can be safe if it only labels a pull request. It becomes dangerous when the job checks out the contributor's branch and executes its code.
Follow the data:
- Does a fork-controlled script run after checkout?
- Are issue titles, branch names, or commit messages interpolated into a shell command?
- Can a pull request alter a build config consumed by a privileged later job?
- Does an artifact from an untrusted job get downloaded and executed by a deployment job?
Pinned action references matter too. A third-party action referenced by a mutable tag can change underneath the workflow. A full commit SHA narrows that supply-chain risk.
Artifacts and caches cross boundaries
Build artifacts feel inert, yet privileged jobs unzip, source, publish, or deploy them. If an untrusted workflow can upload an artifact under a predictable name and a release workflow fetches "latest," you may be able to replace what gets shipped.
Caches have similar confusion bugs. A fork job that writes to a cache later restored by a trusted branch can poison compiler output, dependencies, or scripts. Check cache keys, branch isolation, restore prefixes, and whether restored files are executed.
Do not publish a poisoned package or deploy to production. A text file with a random marker, carried from the untrusted stage into a controlled privileged test step, is enough to show the boundary failure.
Logs, previews, and forgotten control planes
Recon still pays. Look for Jenkins, Argo CD, Tekton, registry, and artifact-host subdomains. Public dashboards may leak repository names, environment variables, build logs, internal hostnames, and one-click rebuild actions. Test authentication and object authorization with owned projects where possible.
Pull-request preview environments expand the app's attack surface. They may expose debug routes, default credentials, broad cloud roles, or wildcard DNS pointing at abandoned infrastructure. Treat a preview as production-adjacent: prove access with a canary, not customer data.
Secrets in logs need context. A masked token fragment is not a finding. A live credential committed to history or printed by a failed build can be critical, but validation should use an identity endpoint or the least-privileged read possible. Never launch deployments just to prove a key works.
Write the attack path, not a pile of YAML
A strong report reads: an external contributor changes file X; privileged workflow Y executes it under token Z; that token can write releases or access environment Q. Include exact line references, trigger conditions, permission blocks, and a non-destructive proof.
Recommend least-privilege job permissions, separate untrusted build and trusted release workflows, never executing fork code under pull_request_target, environment approvals, immutable action pins, artifact attestations, isolated caches, short-lived identity federation, and secret rotation after exposure.
Pipeline security rewards patience. One clear path from contributor-controlled bytes to a privileged effect beats twenty screenshots of interesting CI settings.