Three security pipelines, SAST, SCA and Container Scanning, that run the same way on your laptop and in GitHub Actions.
Each pipeline runs open source scanners, normalises their output to
SARIF, and applies one shared gate. Locally
they run in purpose-built Docker images through make. In CI they run natively on
the runner through the same ci/ scripts. Nothing differs between the two except
where the scripts execute, so a finding you reproduce locally is the finding CI
reports.
Built during Google Summer of Code 2026 with INCF and EBRAINS, following the OWASP DevSecOps Guideline, and running in production across the Medical Informatics Platform.
| Pipeline | Scanners | What it inspects | Fails on |
|---|---|---|---|
| SAST | OpenGrep with semgrep-rules | Your source code | Any ERROR severity rule match |
| SCA | Trivy, OSV-Scanner | A CycloneDX SBOM of your dependencies | CVSS at or above the threshold |
| Container Scanning | Hadolint, OpenGrep, Trivy, OSV-Scanner | The Dockerfile and the built image | Either of the above |
They are deliberately independent. No shared state, no ordering constraints. Run one, run all three, run them in parallel.
SAST matches pattern based rules against your own code. SCA generates a CycloneDX SBOM first and scans that, which decouples the scanners from the package manager: adding a language means teaching the SBOM step to produce a bill of materials, not modifying the scanners. Container Scanning does both against one image, linting the Dockerfile and scanning the built layers, then merges all four SARIF reports before applying the gate.
Why these particular tools, and what was rejected: docs/why-these-tools.md.
Requirements: Docker, make and Bash. The scanners install themselves inside the
images, so there is nothing else to set up.
git clone https://github.com/moghit-eou/reusable-ci-pipelines.git
cd reusable-ci-pipelines
make sast PROJECT=./test-golang
make sca PROJECT=./test-maven ECOSYSTEM=mavenContainer Scanning needs the image built first:
docker build -t test-maven:scan ./test-maven
make container-scan IMAGE=test-maven:scan SCAN_TYPE=scaThe first run builds the toolbox image, which takes a few minutes because it downloads the scanners and the Trivy vulnerability database. Later runs reuse the cached layers and take seconds.
SARIF reports are written inside PROJECT. make clean removes them along with
the toolbox images.
More on the local flow, including how to change settings without rebuilding: docs/local-runs.md.
Copy two folders. No submodule, no action to install, no dependency on this repository afterwards.
| Step | What to do |
|---|---|
| 1 | Copy ci/ to your repository root. Drop ci/docker/ unless you also want the local make flow. |
| 2 | Copy the workflows you need from .github/workflows/. |
| 3 | Point them at your code by editing the env: block. |
The minimum to edit:
env:
PROJECT: . # folder to scan, relative to the repository root
ECOSYSTEM: maven # SCA only: maven | npm | golang | generic | none
GATE_FAIL_THRESHOLD: "8.0"
GATE_WARN_THRESHOLD: "5.0"
SARIF_CATEGORY: sca-backend # must be unique per job
ARTIFACT_NAME: sca-report # must be unique per jobFor a repository with several projects in it, copy the job once per project and point
PROJECT at each folder.
Every job also needs:
permissions:
contents: read
security-events: write # to upload SARIF to the Security tabFull walkthrough of the three workflows, including dependency caching and the gotchas: docs/github-actions.md.
These pipelines run in production in the Medical Informatics Platform. Each repository is a different adoption shape, so pick the one closest to yours and copy from it.
| Repository | Shape | What to look at |
|---|---|---|
| platform-backend | One Maven project at the repository root | The simplest case: PROJECT: ., ECOSYSTEM: maven, one job per pipeline |
| platform-ui | One npm project at the repository root | The same shape with ECOSYSTEM: npm. Only the ecosystem and the rulesets change |
| datacatalog PR #18 | Three projects in one repository | A build matrix covering a Maven backend, an npm frontend and a Python project on generic, with per-project rulesets and SARIF categories |
The datacatalog case is the useful one if your repository holds more than one
project: it shows how far the env: block alone gets you, without touching any
pipeline code.
Those repositories vendor ci/ in a deliberately simplified form, with values written
directly into the scripts rather than read from the environment. That is a choice made
for their maintainers, who wanted a folder they could read without learning a
configuration layer first. This repository keeps the configurable version, which is
what you want when adopting into an unknown project.
SCA and Container Scanning read the security-severity property (the CVSS score) from
every SARIF result and take the maximum:
| Max CVSS | Status | Exit code |
|---|---|---|
at or above GATE_FAIL_THRESHOLD |
FAILED | 1, blocks the pipeline |
at or above GATE_WARN_THRESHOLD, below fail |
WARNING | 0, logged only |
| below warn | PASSED | 0 |
| tool crashed or wrote no SARIF | ERROR | 1, a broken scanner is never a pass |
Defaults are 8.0 and 5.0.
SAST gates differently. OpenGrep runs with --severity=ERROR --error, so any ERROR
level rule match fails the job. A pattern match has no CVE behind it and therefore no
CVSS score to threshold on, which is why the two models are kept apart rather than
merged. See docs/why-these-tools.md.
Reports upload even when the gate fails, because the upload steps are guarded on the SARIF file existing, not on the scan's exit code. A blocked build still publishes its findings.
SARIF is JSON, readable but unpleasant by hand. Four ways to look at it:
- GitHub Security tab, uploaded automatically by the workflows, with findings mapped to lines of code.
- VS Code, with the
SARIF Viewer
extension. The fastest way to triage a local
makerun. - SARIF Web Viewer, nothing to install, good for sharing a report with someone who has no checkout.
- The workflow artifact, useful when a run fails before the Security tab upload.
This repository is self-contained: everything you need to run, configure and adopt the
pipelines is here and in docs/.
If you want the reasoning in more depth, the EBRAINS DevSecOps Handbook is a companion write-up of one production rollout: the assessment that started it, the case studies behind each tool decision, and what went wrong on the way. It is written for the EBRAINS community and assumes that context, so it is extra reading rather than a prerequisite.
| Page | What it covers |
|---|---|
| docs/github-actions.md | Running in CI: the three workflows step by step, permissions, dependency caching |
| docs/local-runs.md | Running locally: the Makefile, the toolbox images, the env files |
| docs/configuration.md | Every environment variable, gate thresholds, Semgrep ruleset selection |
| docs/reproducibility.md | What is pinned, what follows the runner, what changes by design |
| docs/new-ecosystem.md | Adding a language the SBOM step does not know yet |
| docs/suppressions.md | Suppressing a false positive or an accepted risk |
| docs/why-these-tools.md | Tool choices, rejected alternatives, the two gate models |
| docs/troubleshooting.md | Empty scan targets, split verdicts between tools |
- Scanners: Trivy, OSV-Scanner, OpenGrep, Hadolint
- Rulesets: semgrep-rules, Semgrep Registry
- Standards: SARIF, CycloneDX
- Guideline: OWASP DevSecOps Guideline
- EBRAINS DevSecOps Handbook: moghit-eou/EBRAIN-DevSecOps-handbook
The code is licensed under the MIT License. The documentation (this README
and docs/) is licensed under CC BY-SA 4.0,
and code snippets in it may also be used under MIT.
You may use, modify and redistribute the code, including commercially. The only condition is that the copyright and license notice stays with copies or substantial portions of the code.