CI/CD Security
Check GitHub Actions workflows and GitLab CI files for unpinned actions, risky triggers and permissions, script injection, and curl-to-shell installs
CI/CD Security
CI/CD Security reads a repository's pipeline definitions and reports settings an attacker can use to run code in your builds or reach their secrets. It is separate from CI/CD Integration, which runs a Safeguard scan inside your pipeline: this checks the pipeline files themselves.
CI/CD Security is turned on per workspace. If Scan pipeline configs is not offered, ask your Safeguard contact to enable it.
What it reads
| File | Platform |
|---|---|
.github/workflows/*.yml, .github/workflows/*.yaml | GitHub Actions |
.gitlab-ci.yml, .gitlab-ci.yaml | GitLab CI/CD |
Bitbucket Pipelines, Jenkins and Azure Pipelines files are not checked yet.
Checks
| Rule | Severity | Platform | What it finds |
|---|---|---|---|
SG-CICD-001 | High | GitHub Actions | A third-party action pinned to a tag or branch instead of a full 40-character commit SHA, so whoever controls that ref controls your build. GitHub's own actions/* and github/* are not flagged. |
SG-CICD-002 | High | GitHub Actions | The pull_request_target trigger, which runs fork code with a read/write token and the repository's secrets. |
SG-CICD-003 | Critical | GitHub Actions | A run: step that puts untrusted event data (${{ github.event.* }}, github.head_ref) straight into a shell command: a pull request title or issue body can then run commands on the runner. |
SG-CICD-004 | Medium | GitHub Actions | permissions: write-all, which gives the job's token write access to every scope. |
SG-CICD-005 | High | GitHub Actions, GitLab CI/CD | A download piped straight into a shell (curl … | bash, wget … | sh), which runs the fetched script unverified. |
Run a scan
- In Safeguard, open Integrations → CI/CD Pipelines & Security.
- Choose Scan pipeline configs.
- Enter the repository URL, for example
https://github.com/org/repo.
Safeguard clones the repository, checks its pipeline files and lists what it finds with the file and line. Private repositories need a connected source control integration that covers them.
Fixing what it finds
- Pin actions to a commit SHA, and let Dependabot or Renovate keep the pins current.
- Prefer
pull_requestoverpull_request_target. If you need the latter, do not check out or run the pull request's code in that job. - Pass event data through
env:and quote it in the script, instead of interpolating${{ … }}intorun:. - Grant the least
permissions:each job needs, starting fromcontents: read. - Download, verify, then run installers instead of piping them into a shell.