Scan your own code and containers in CI: Gitleaks, Semgrep and Trivy
Set up a GitHub Actions workflow that runs Gitleaks, Semgrep and Trivy on every push of your own project, learn what each step catches, and read the first report without being overwhelmed.
Drafted with AI from official documentation. Scripts were run in a test environment and repository links verified. Awaiting a human check. Did it work for you? Last edited Outdated?
A good first layer of security checks for a project costs little: three open source scanners, each answering a different question, run on every push. Gitleaks asks whether a secret was committed. Semgrep asks whether the source code contains known bug and vulnerability patterns. Trivy asks whether the dependencies and the container image carry published vulnerabilities or obvious misconfigurations. This how-to wires all three into one GitHub Actions workflow for a repository you control and explains how to read the output. The same commands work in GitLab CI or any other runner.
Before you start
- A repository of your own on GitHub with Actions enabled.
- Optionally a
Dockerfileat the root; the Trivy image step is skipped if there is none. - Scan only projects you own or are permitted to test. These tools run against your own code and images and never touch anyone else’s systems.
1. Add the workflow
Create .github/workflows/security.yml with three jobs that run independently, so one failure does not hide the others:
name: security
on:
push:
pull_request:
permissions:
contents: read
jobs:
secrets:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
static-analysis:
runs-on: ubuntu-latest
container:
image: semgrep/semgrep
steps:
- uses: actions/checkout@v4
- run: semgrep scan --config auto --error
dependencies-and-image:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: aquasecurity/[email protected]
with:
scan-type: fs
scan-ref: .
scanners: vuln,misconfig,secret
severity: HIGH,CRITICAL
exit-code: '1'
- if: hashFiles('Dockerfile') != ''
run: docker build -t local/app:ci .
- if: hashFiles('Dockerfile') != ''
uses: aquasecurity/[email protected]
with:
scan-type: image
image-ref: local/app:ci
severity: HIGH,CRITICAL
exit-code: '1'
Commit and push. The Actions tab shows three jobs; each turns red when its scanner finds something at or above the threshold you set.
2. What each job catches
secrets checks out the full history (fetch-depth: 0) so Gitleaks can walk every commit, not just the latest tree. It reports strings that look like API keys, tokens, passwords and private keys, with the commit and author. A hit here means the credential must be rotated, because removing the file in a later commit does not remove it from history.
static-analysis runs Semgrep inside its official container with --config auto, which selects community rulesets for the languages it detects. Findings are code patterns: a SQL query built with string concatenation, a subprocess call with shell=True, a disabled TLS verification, a weak hash used for passwords. --error makes the job fail when there are findings of the default severity. Each result names the rule, and the rule page explains the risk and the usual fix.
dependencies-and-image runs Trivy twice. The file system scan reads lock files such as package-lock.json, requirements.txt or go.sum and reports dependencies with published vulnerabilities of HIGH or CRITICAL severity, plus misconfigurations in any Dockerfile, Kubernetes manifest or Terraform file and any secrets it spots. If a Dockerfile exists, the image is built and scanned, which adds the operating system packages of the base image to the report.
3. Read the first report
The first run on an existing project usually produces more output than expected. Work through it in this order:
- Gitleaks findings first. Confirm each one; if it is a real credential, rotate it immediately, then add the path or fingerprint to
.gitleaksignoreonly for confirmed false positives such as example keys in documentation. - Trivy CRITICAL entries next. The report lists the installed version and the fixed version; most are solved by bumping a dependency or the base image tag and rebuilding.
- Semgrep findings last, starting with those tagged as security rather than best practice. Fix the obvious ones; for rules that do not apply to your project, create a
.semgrep.ymlthat excludes them rather than lowering the whole threshold. - Once the run is green, keep it green. New findings now correspond to the change that introduced them, which is far easier to triage than a backlog.
To see the full detail of a Trivy or Semgrep result locally, run the same command on your machine; the entries on each tool in this topic show the one-line installs.
Adjusting the thresholds
The example fails the build on HIGH and CRITICAL vulnerabilities and on default-severity Semgrep findings. Teams that prefer to see results without blocking merges can set exit-code: '0' for Trivy and drop --error for Semgrep for a few weeks, then tighten. Both tools can also emit SARIF, which GitHub displays in the Security tab of the repository when uploaded with the github/codeql-action/upload-sarif action, though that requires the code scanning feature to be available on your plan.
Official documentation
Did this work for you?
No votes yet. Be the first.
If it did not work, the report form below tells the editors what went wrong.
See also
- 0313 What is CI/CD? CI/CD is the habit of automatically testing every code change and, once it passes, deploying it without manual steps. Beginner · Explains an idea · Any system Article Pipelines, runners & hooks 15 Oct 2026
- 0378 Continuous integration explained What continuous integration means, why small frequent merges plus an automated build on every change prevent integration pain, what a good pipeline… Beginner · Explains an idea · Any system Article Pipelines, runners & hooks 15 Oct 2026
- 0379 Your first GitHub Actions workflow: run tests on every push Build a working GitHub Actions pipeline from nothing: a small Python project with a test, a workflow file that runs pytest on… Beginner · Guided project · Any system Article GitHub Actions · Pipelines, runners & hooks 15 Oct 2026
- 0381 pre-commit hooks: catch mistakes before they are committed Install the pre-commit framework, configure hooks for whitespace, YAML and JSON syntax, large files, leaked private keys, Python linting and shellcheck, run… Beginner · Step by step · Linux, macOS, Windows Article Pipelines, runners & hooks · Setup 15 Oct 2026
Repos in the wild
- pre-commit: a framework for managing multi-language Git hooks pre-commit/pre-commit The pre-commit repository: the tool that installs, isolates and runs Git hooks declared in a YAML file, with hooks written in Python, Node, Go, Rust, Ruby, Docker and more, pinned to exact versions. GitHub
- Woodpecker CI: a lightweight, self-hosted CI engine with container-based pipelines woodpecker-ci/woodpecker The Woodpecker CI repository: a community fork of Drone that runs every pipeline step in its own container, connects to GitHub, Gitea, Forgejo, GitLab and Bitbucket, and runs on a single small server. GitHub
- SecLists: wordlists for authorised security testing danielmiessler/SecLists A large, maintained collection of wordlists (usernames, passwords, URLs, fuzzing strings, patterns) used in penetration tests with permission and in defensive checks such as auditing your own password policy. GitHub
- PayloadsAllTheThings: a knowledge base organised by web vulnerability class swisskyrepo/PayloadsAllTheThings A community-maintained reference that gives each class of web and application weakness its own section with background, detection notes and remediation references, read by AppSec teams, defenders and authorised testers. GitHub
History
Every saved edit is kept. The plot on the sleeve draws one line per edit. Open any edit to see exactly what changed.
- Dominik +5,389 chars