From 1f860378c6687657aa6b27f9604f2d0247d482bf Mon Sep 17 00:00:00 2001 From: moghit-eou Date: Tue, 4 Aug 2026 14:03:17 +0100 Subject: [PATCH 1/3] docs: restructure README for container scan, SCA, and SAST pipelines --- .github/README.md | 200 +++++++++++++++++++++++++++++++++++ .github/scripts/README.md | 212 -------------------------------------- 2 files changed, 200 insertions(+), 212 deletions(-) create mode 100644 .github/README.md delete mode 100644 .github/scripts/README.md diff --git a/.github/README.md b/.github/README.md new file mode 100644 index 00000000..14343aab --- /dev/null +++ b/.github/README.md @@ -0,0 +1,200 @@ +# Application Security Pipelines + +Automated security scanning for this service, enforced during Continuous Integration (CI) to block known vulnerabilities and insecure code before merge, automatically on Pull Requests. + +Following the OWASP DevSecOps model, scanning is split into three independent pipelines, each with its own workflow, orchestrator script, and gate: + +| Pipeline | Workflow | Scans | Tools | +|---|---|---|---| +| **Container Scanning** | `container-scan.yml` | The built Docker image + the Dockerfile | Trivy, OSV-Scanner (image CVEs) . Hadolint, OpenGrep (Dockerfile SAST) | +| **SCA** (Software Composition Analysis) | `sca.yml` | Application dependencies, via SBOM | Trivy, OSV-Scanner | +| **SAST** (Static Application Security Testing) | `sast.yml` | Application source code | OpenGrep | + +## Table of Contents + +- [1. Repository layout](#1-repository-layout) +- [2. Architecture](#2-architecture) +- [3. Tool installation (`setup-tools.sh`)](#3-tool-installation-setup-toolssh) +- [4. Pipeline: Container Scanning](#4-pipeline-container-scanning) +- [5. Pipeline: Software Composition Analysis (SCA)](#5-pipeline-software-composition-analysis-sca) +- [6. Pipeline: Static Application Security Testing (SAST)](#6-pipeline-static-application-security-testing-sast) +- [7. Gate status reference](#7-gate-status-reference) +- [8. Suppressing a false positive](#8-suppressing-a-false-positive) + +## 1. Repository layout + +``` +.github/ +├── workflows/ +│ ├── container-scan.yml # builds the image, scans the Dockerfile (SAST) and image (SCA) +│ ├── sca.yml # resolves deps, generates SBOM, scans it (SCA) +│ └── sast.yml # scans source code (SAST) +└── scripts/ + ├── setup-tools.sh # installs trivy, osv-scanner, opengrep, hadolint, semgrep-rules + ├── container_scan.py # orchestrator for container-scan.yml + ├── sca_scan.py # orchestrator for sca.yml + ├── sast_scan.py # orchestrator for sast.yml + ├── parse_sarif.py # shared: reads SARIF security-severity scores + ├── suppress_trivy.yaml # shared Trivy ignore file + └── suppress_osv_scanner.toml # shared OSV-Scanner ignore file +``` + +> **Note:** all three workflows trigger on `pull_request`, `workflow_dispatch`, and a weekly Monday 02:00 UTC schedule, and run independently in parallel. Each has its own gate and its own category in the GitHub Security tab. + +## 2. Architecture + +```mermaid +flowchart LR + PR["Pull Request"] --> CS["container-scan.yml"] --> SEC[("GitHub Security Tab")] + PR --> SCA["sca.yml"] --> SEC + PR --> SAST["sast.yml"] --> SEC +``` + +All three trigger independently and run in parallel; each uploads its own SARIF category to the Security tab. + +--- + +## 3. Tool installation (`setup-tools.sh`) + +```bash +bash .github/scripts/setup-tools.sh --install-tool [--sbom-ecosystem maven|npm|none] +``` + +`--install-tool` accepts a comma-separated list (or `all`): + +| Tool | Installed from | Used by | +|---|---|---| +| `trivy` | official release tarball, SHA256-pinned | Container Scanning (sca), SCA | +| `osv-scanner` | GitHub release binary, SHA256-pinned | Container Scanning (sca), SCA | +| `opengrep` | GitHub release binary, SHA256-pinned | Container Scanning (sast), SAST | +| `hadolint` | GitHub release binary, SHA256-pinned | Container Scanning (sast) | +| `semgrep-rules` | cloned from `semgrep/semgrep-rules` at a pinned commit | Container Scanning (sast), SAST | + +`--sbom-ecosystem maven` generates `target/bom.json` afterward. `container-scan.yml`scans the built image directly and needs no SBOM. + +All tool versions and SHA256 checksums are pinned at the top of the script (with `# renovate:` markers so Renovate bumps version + checksum together). The script stops and prints the failing line/command on any error rather than continuing silently. + +--- + +## 4. Pipeline: Container Scanning + +`container-scan.yml` builds the Docker image once, then runs `container_scan.py` twice against it, once per `--scan-type`: + +- **`--scan-type sast`** → runs **Hadolint** and **OpenGrep** against the `Dockerfile` itself (bad practices, missing pinning, insecure instructions). +- **`--scan-type sca`** → runs **Trivy** and **OSV-Scanner** against the *built image* (OS packages, layers). + +Both steps run regardless of each other (`if: always()`), all four SARIF files are uploaded individually to the Security tab, then merged into one artifact via `--merge-sarif` for retention. + +`container_scan.py` is a single CLI shared by both scan types: + +``` +$ python3 .github/scripts/container_scan.py --help +usage: sec-orchestrator [-h] [-s {sast,sca}] [-i IMAGE] [--merge-sarif SARIF_FILE [SARIF_FILE ...]] [--merge-output MERGE_OUTPUT] + +Agnostic DevSecOps Container scanning Pipeline Orchestrator + +options: + -h, --help show this help message and exit + -s, --scan-type {sast,sca} + Specify the security methodology to execute (e.g., sast, sca) + -i, --image IMAGE Target Docker image reference + --merge-sarif SARIF_FILE [SARIF_FILE ...] + List of SARIF files to merge into one report + --merge-output MERGE_OUTPUT + Output path for the merged SARIF file +``` + +**Running it locally:** +```bash +docker build -t app:local . +bash .github/scripts/setup-tools.sh --install-tool trivy,osv-scanner,opengrep,hadolint,semgrep-rules +python .github/scripts/container_scan.py --scan-type sast +python .github/scripts/container_scan.py --scan-type sca --image app:local +``` + +## 5. Pipeline: Software Composition Analysis (SCA) + +`sca.yml` scans **application dependencies**, not the container. It resolves the Maven dependency tree, generates an SBOM (CycloneDX), and scans that SBOM with **Trivy** and **OSV-Scanner** via `sca_scan.py`. + +Both tools need to be installed first, same as Container Scanning, via `setup-tools.sh --install-tool trivy,osv-scanner`. + +**Running it locally:** +```bash +mvn dependency:resolve -q +bash .github/scripts/setup-tools.sh --install-tool trivy,osv-scanner --sbom-ecosystem maven # -> mvn org.cyclonedx:cyclonedx-maven-plugin:makeAggregateBom -> target/bom.json +python .github/scripts/sca_scan.py +``` + +`mvn dependency:resolve` pulls the full dependency tree into `.m2` first, so the CycloneDX plugin has something resolved to build the SBOM from. + +Trivy and OSV-Scanner both run against the SBOM, findings are evaluated by `parse_sarif.evaluate()`, and the two SARIF files are merged into one artifact. This uses the same CVSS-score gate model as the SCA half of Container Scanning. + +## 6. Pipeline: Static Application Security Testing (SAST) + +`sast.yml` scans **source code** (not the Dockerfile, not dependencies) with **OpenGrep**. + +`run_opengrep()` runs twice: once to write the full SARIF report, once as the actual gate, using the same command both times with different flags. + +**Running it locally:** +```bash +bash .github/scripts/setup-tools.sh --install-tool opengrep,semgrep-rules +python .github/scripts/sast_scan.py +``` + +--- + +## 7. Gate status reference + +Two different gate models are in play, depending on whether a tool reports **CVE severity** or **rule severity**: + +### CVSS-score gate (SCA tools: Trivy, OSV-Scanner; both container-image and SBOM scans) + +`parse_sarif.evaluate()` reads the `security-severity` property of each SARIF result and takes the **highest score across all results**. That single number decides the status: + +| Status | Meaning | Blocks the pipeline? | +|---|---|---| +| `PASSED` | Highest score < 5.0 | No | +| `WARNING` | Highest score 5.0 to 7.9 | No (logged only) | +| `FAILED` | Highest score ≥ 8.0 | **Yes** | +| `ERROR` | Tool crashed / SARIF missing | **Yes** | + +### Rule-severity gate (SAST tools: OpenGrep, Hadolint) + +These tools don't report CVSS. Each tool's own severity threshold (`--severity=ERROR --error` for OpenGrep, `--failure-threshold error` for Hadolint) decides the status directly: + +| Status | Meaning | Blocks the pipeline? | +|---|---|---| +| `PASSED` | No error-severity findings | No | +| `FAILED` | Error-severity findings present | **Yes** | +| `ERROR` | Tool did not run correctly | **Yes** | + +Both `container_scan.py --scan-type sast` and `sast_scan.py` use this model. `container_scan.py --scan-type sca` and `sca_scan.py` use the CVSS-score model above. + +All three pipelines write their findings as SARIF files, which are uploaded to the GitHub Security tab, but they're also plain JSON you can inspect directly. To browse a SARIF file locally without the Security tab (e.g. one downloaded from the workflow artifacts), drop it into a SARIF viewer such as [Microsoft's SARIF Web Component](https://microsoft.github.io/sarif-web-component/). + +--- + +## 8. Suppressing a false positive + +Suppression applies to the **CVSS-score tools** (Trivy, OSV-Scanner) and is shared across Container Scanning and SCA, since both point at the same two ignore files. + +**Trivy** (`suppress_trivy.yaml`): +```yaml +vulnerabilities: + - id: CVE-2026-54515 + statement: "The proposed fix version 2.21.5 not yet released" +``` + +**OSV-Scanner** (`suppress_osv_scanner.toml`): +```toml +[[IgnoredVulns]] +id = "GHSA-5jmj-h7xm-6q6v" # or CVE-2026-54515, GO-2022-0968 ... +ignoreUntil = 2026-09-30 +reason = "The proposed fix version 2.21.5 not yet released" +``` + +Refer to the official docs for complete suppression options: +- **Trivy**: [Filtering and ignore files](https://trivy.dev/docs/latest/configuration/filtering/#trivyignoreyaml) +- **OSV-Scanner**: [Ignore vulnerabilities by ID](https://google.github.io/osv-scanner/configuration/#ignore-vulnerabilities-by-id) + +OpenGrep/Hadolint findings (SAST) aren't suppressed through a shared ignore file in this setup; handle those at the rule/finding level instead. diff --git a/.github/scripts/README.md b/.github/scripts/README.md deleted file mode 100644 index 5d931c13..00000000 --- a/.github/scripts/README.md +++ /dev/null @@ -1,212 +0,0 @@ -# Software Composition Analysis (SCA) Pipeline - -Automated dependency and container vulnerability scanning for `platform-backend`, enforced during the Continuous Integration (CI) to block known vulnerabilities (CVEs) before merge automatically on Pull Requests. - -The pipeline executes a dual-layer scanning strategy using **Trivy** and **OSV-Scanner**: - -- **Application Scanning**: Analyzes source code dependencies and lockfiles via generated Software Bill of Materials (SBOMs). - -- **Infrastructure Scanning**: Analyzes OS-level packages and layers within the built Docker containers. - -## Table of Contents - -- [1. Repository layout](#1-repository-layout) -- [2. Architecture](#2-architecture) -- [3. How one pipeline run works](#3-how-one-pipeline-run-works) -- [4. Tool installation & SBOM generation (`setup-tools.sh`)](#4-tool-installation--sbom-generation-setup-toolssh) -- [5. Suppressing a false positive](#5-suppressing-a-false-positive) -- [6. Exit codes: how "vulnerabilities found" is told apart from "tool broke"](#6-exit-codes-how-vulnerabilities-found-is-told-apart-from-tool-broke) -- [7. Running it locally](#7-running-it-locally) -- [8. Environment variables](#8-environment-variables) - -## 1. Repository layout - -``` -.github/ -├── workflows/ -│ ├── sca_image.yml # builds the image, runs the image-scan pipeline -│ └── sca_app.yml # generates an SBOM, runs the SBOM-scan pipeline -└── scripts/ - ├── setup-tools.sh # installs trivy + osv-scanner,generates SBOM - ├── run_sca_image.py # orchestrator for the image pipeline - ├── run_sca_app.py # orchestrator for the app/SBOM pipeline - ├── parse_sarif.py # Reads SARIF security-severity scores of vulnerabilities. - ├── suppress_trivy.yaml # Trivy ignore file - └── suppress_osv_scanner.toml # OSV-Scanner ignore file -``` - -## 2. Architecture - -```mermaid -flowchart TD - Trig["Pull Request"] --> WF1["sca_image.yml"] - Trig --> WF2["sca_app.yml"] - - subgraph "Image Pipeline" - WF1 --> DB["Build Target Image"] - DB --> ST1["setup-tools.sh"] - ST1 --> RSI["run_sca_image.py"] - RSI -.-> |Generates .sarif| UP1["upload-sarif"] - end - - subgraph "App Pipeline" - WF2 --> MVN["Resolve Dependencies"] - MVN --> ST2["setup-tools.sh maven"] - ST2 --> RSA["run_sca_app.py"] - RSA -.-> |Generates .sarif| UP2["upload-sarif"] - end - - UP1 --> SEC[("GitHub Security Tab")] - UP2 --> SEC -``` - -> **Note:** both workflows trigger on `pull_request` only and run independently in parallel. Within each workflow, Trivy and OSV-Scanner findings are aggregated into that workflow's own pass/warn/fail gate. - ---- - - - -## 3. How one pipeline run works - -`run_sca_image.py` and `run_sca_app.py` are structurally identical , only the Trivy/OSV-Scanner subcommands. The logic below applies to both. - -```mermaid -graph TD - Start(["run_sca_*.py"]) --> Run["Run Trivy + OSV-Scanner"] - Run --> Eval["Evaluate each tool's SARIF"] - Eval --> Status{"Tool status"} - - Status -- "crashed / SARIF missing" --> Error["ERROR"] - Status -- "score >= 8.0" --> Failed["FAILED"] - Status -- "score 5.0-7.9" --> Warn["WARNING"] - Status -- "score < 5.0" --> Passed["PASSED"] - - Error --> Gate{"Any FAILED or ERROR?"} - Failed --> Gate - Warn --> Gate - Passed --> Gate - - Gate -- "yes" --> Exit1["exit 1 -> job fails"] - Gate -- "no" --> Exit0["exit 0 -> job passes"] -``` - -### Gate status reference - -| Status | Meaning | Blocks the pipeline? | -|---|---|---| -| `PASSED` | Highest `security-severity` score finding is below 5.0 | No | -| `WARNING` | Highest finding is 5.0–7.9 | No (logged only) | -| `FAILED` | Highest finding is ≥ 8.0 | **Yes** | -| `ERROR` | Unexpected failure occurred during execution| **Yes** | - -`parse_sarif.evaluate()` reads the CVSS score of each individual vulnerability from the SARIF's `security-severity` property, then takes the highest one across all results in that file. That single number decides `PASSED`, `WARNING`, or `FAILED` for the tool. - ---- -## 4. Tool installation & SBOM generation (`setup-tools.sh`) - -```bash -bash .github/scripts/setup-tools.sh [maven|npm|none] -``` - -1. Installs Trivy (`TRIVY_VERSION`, default `v0.71.1`) via the official install script. -2. Installs OSV-Scanner (`OSV_SCANNER_VERSION`, default `v2.4.0`) as a standalone binary from GitHub Releases. -3. Based on the positional argument, optionally generates an SBOM: - - `maven` → `mvn org.cyclonedx:cyclonedx-maven-plugin:makeAggregateBom -q` (writes `target/bom.json`) - - `npm` → `npx --yes @cyclonedx/cyclonedx-npm --output-file target/bom.json` - - `none` → skipped (used by `sca_image.yml`, which scans the image directly and doesn't need an SBOM) - -The script runs with `set -euo pipefail` plus an `ERR` trap, so it stops and prints the failing line/command on any error rather than continuing silently. - -## 5. Suppressing a false positive - -If it's a false positive or an accepted-risk finding, add it to the relevant ignore file below so it stops blocking the gate. -For example, to ignore a specific vulnerability: - -**Trivy** (`suppress_triviyaml`): -```yaml -vulnerabilities: - - id: CVE-2026-54515 - statement: "The proposed fix version 2.21.5 not yet released" -``` - -**OSV-Scanner** (`suppress_osv_scanner.toml`): -```toml -[[IgnoredVulns]] -id = "GHSA-5jmj-h7xm-6q6v" # or CVE-2026-54515 ,GO-2022-0968 ... -ignoreUntil = 2026-09-30 -reason = "The proposed fix version 2.21.5 not yet released" -``` - -Refer to the official documentation for complete suppression options: - -- **Trivy**: [Filtering and ignore files](https://trivy.dev/docs/latest/configuration/filtering/#trivyignoreyaml) -- **OSV-Scanner**: [Ignore vulnerabilities by ID](https://google.github.io/osv-scanner/configuration/#ignore-vulnerabilities-by-id) - - ---- - -## 6. Exit codes: how "vulnerabilities found" is told apart from "tool broke" - -**Trivy** exits `0` by default regardless of findings. Since these scripts don't change this, any non-zero exit code means the scan itself failed (e.g., bad image reference, Docker problems, or malformed SBOM). - -**OSV-Scanner** uses its exit code to report scan results, per its own docs: - -| Exit code | Meaning | -|---|---| -| `0` | Scan completed, no known vulnerabilities | -| `1` | Scan completed, vulnerabilities **were** found | -| `1–126` | Reserved for other vulnerability-result-related outcomes | -| `127` | General error | -| `128` | No packages found (scan format didn't pick up any files) | -| `129–255` | Reserved for non-result errors | - -`run_osv_scanner()` in both orchestrators normalizes exit code `1` to `0`, since finding vulnerabilities isn't a tool failure, the real pass/warn/fail decision comes later from the SARIF scores. Any other non-zero code (127, 128, etc.) is flagged `ERROR`. - ---- - -## 7. Running it locally - - -**Image pipeline** -```bash -bash .github/scripts/setup-tools.sh # installs trivy + osv-scanner -docker build -t platform-backend:local . -python .github/scripts/run_sca_image.py -``` -**App pipeline** - -> **Note:** `mvn dependency:resolve` pulls all dependencies into `.m2` first, so the CycloneDX plugin has a resolved tree to build the SBOM from. - -```bash -mvn dependency:resolve -bash .github/scripts/setup-tools.sh maven # installs tools + generates target/bom.json -python .github/scripts/run_sca_app.py -``` - -All output paths and ignore-file locations are overridable via environment variables (see next section). - ---- - -## 8. Environment variables -| Variable | `run_sca_image.py` Default | `run_sca_app.py` Default | Purpose | -|---|---|---|---| -| `IMAGE_NAME` | `platform-backend:local` | — | Image reference to scan | -| `SBOM_PATH` | — | `target/bom.json` | SBOM to scan | -| `TRIVY_IGNOREFILE` | `suppress_trivy.yaml` | `suppress_trivy.yaml` | Trivy suppression file | -| `OSV_IGNOREFILE` | `suppress_osv_scanner.toml` | `suppress_osv_scanner.toml` | OSV-Scanner suppression file | -| `TRIVY_SARIF_OUTPUT` | `trivy-image.sarif` | `trivy-app.sarif` | Trivy output path | -| `OSV_SARIF_OUTPUT` | `osv-scanner-image.sarif` | `osv-scanner-app.sarif` | OSV-Scanner output path | -| `MERGED_SARIF_OUTPUT` | `merged-SCA-platform-backend-image.sarif` | `merged-SCA-platform-backend-app.sarif` | Combined artifact path | - - -Each script hardcodes a default value for every variable via `os.getenv("VAR", "default")`. -The workflow's `env:` block sets the actual env var, which overrides that default at runtime. -the Python default only applies if no env var is set at all (e.g. running the script locally without one). - -For example `sca_image.yml`: -```yaml -env: - IMAGE_NAME: platform-backend:testing - TRIVY_IGNOREFILE: .github/scripts/suppress_trivy.yaml - ... -``` \ No newline at end of file From b2be0f78eb8a7e2db1b6f178824ee73002310e40 Mon Sep 17 00:00:00 2001 From: moghit-eou Date: Wed, 5 Aug 2026 20:47:28 +0100 Subject: [PATCH 2/3] ci: migrate scripts and update paths to vendor-neutral ci directory --- .github/workflows/container-scan.yml | 12 ++++----- .github/workflows/sast.yml | 6 ++--- .github/workflows/sca.yml | 8 +++--- {.github => ci}/README.md | 18 ++++++------- {.github/scripts => ci}/container_scan.py | 4 +-- {.github/scripts => ci}/parse_sarif.py | 2 +- {.github/scripts => ci}/sast_scan.py | 2 +- {.github/scripts => ci}/sca_scan.py | 26 +++++++++---------- {.github/scripts => ci}/setup-tools.sh | 0 .../scripts => ci}/suppress_osv_scanner.toml | 2 +- {.github/scripts => ci}/suppress_trivy.yaml | 2 +- 11 files changed, 41 insertions(+), 41 deletions(-) rename {.github => ci}/README.md (92%) rename {.github/scripts => ci}/container_scan.py (97%) rename {.github/scripts => ci}/parse_sarif.py (99%) rename {.github/scripts => ci}/sast_scan.py (96%) rename {.github/scripts => ci}/sca_scan.py (94%) rename {.github/scripts => ci}/setup-tools.sh (100%) rename {.github/scripts => ci}/suppress_osv_scanner.toml (53%) rename {.github/scripts => ci}/suppress_trivy.yaml (98%) diff --git a/.github/workflows/container-scan.yml b/.github/workflows/container-scan.yml index 12275735..c1341c18 100644 --- a/.github/workflows/container-scan.yml +++ b/.github/workflows/container-scan.yml @@ -18,8 +18,8 @@ jobs: CONTAINER_SCAN_MERGED_SARIF_OUTPUT: container-scan-platform-backend-merged.sarif # SCA / CVE - TRIVY_IGNOREFILE: .github/scripts/suppress_trivy.yaml - OSV_IGNOREFILE: .github/scripts/suppress_osv_scanner.toml + TRIVY_IGNOREFILE: ci/suppress_trivy.yaml + OSV_IGNOREFILE: ci/suppress_osv_scanner.toml TRIVY_SCA_SARIF_OUTPUT: sca-trivy-container.sarif OSV_SCA_SARIF_OUTPUT: sca-osv-container.sarif @@ -44,15 +44,15 @@ jobs: - name: Setup tools run: | - bash .github/scripts/setup-tools.sh \ + bash ci/setup-tools.sh \ --install-tool trivy,osv-scanner,opengrep,hadolint,semgrep-rules - name: Run SAST scanning - run: python .github/scripts/container_scan.py --scan-type sast + run: python ci/container_scan.py --scan-type sast - name: Run SCA scanning if: always() - run: python .github/scripts/container_scan.py --scan-type sca --image ${{ env.IMAGE_NAME }} + run: python ci/container_scan.py --scan-type sca --image ${{ env.IMAGE_NAME }} - name: Upload Trivy SARIF to GitHub Security tab id: upload_trivy @@ -89,7 +89,7 @@ jobs: - name: Merge all SARIF reports if: always() run: | - python .github/scripts/container_scan.py \ + python ci/container_scan.py \ --merge-sarif "${{ env.TRIVY_SCA_SARIF_OUTPUT }}" "${{ env.OSV_SCA_SARIF_OUTPUT }}" "${{ env.OPENGREP_SAST_SARIF_OUTPUT }}" "${{ env.HADOLINT_SAST_SARIF_OUTPUT }}" \ --merge-output "${{ env.CONTAINER_SCAN_MERGED_SARIF_OUTPUT }}" diff --git a/.github/workflows/sast.yml b/.github/workflows/sast.yml index 1e65d042..786dbc81 100644 --- a/.github/workflows/sast.yml +++ b/.github/workflows/sast.yml @@ -18,7 +18,7 @@ jobs: semgrep-rules/generic semgrep-rules/problem-based-packs semgrep-rules/bash semgrep-rules/java auto semgrep-rules/yaml semgrep-rules/package_managers p/default OPENGREP_EXCLUDE: >- - *.sarif .github/scripts Dockerfile* .pre-commit-config.yaml docs/** README.md AGENTS.md + *.sarif ci/ Dockerfile* .pre-commit-config.yaml docs/** README.md AGENTS.md OPENGREP_SARIF_OUTPUT: sast-semgrep-app.sarif steps: @@ -32,10 +32,10 @@ jobs: python-version: '3.14.4' - name: Setup tools - run: bash .github/scripts/setup-tools.sh --install-tool opengrep,semgrep-rules + run: bash ci/setup-tools.sh --install-tool opengrep,semgrep-rules - name: Run SAST scanning - run: python .github/scripts/sast_scan.py + run: python ci/sast_scan.py - name: Upload Semgrep SARIF to GitHub Security tab id: upload_semgrep diff --git a/.github/workflows/sca.yml b/.github/workflows/sca.yml index 08b72a2e..c57bf84a 100644 --- a/.github/workflows/sca.yml +++ b/.github/workflows/sca.yml @@ -15,8 +15,8 @@ jobs: security-events: write # required for uploading SCA results to github security env: SBOM_PATH: target/bom.json - TRIVY_IGNOREFILE: .github/scripts/suppress_trivy.yaml - OSV_IGNOREFILE: .github/scripts/suppress_osv_scanner.toml + TRIVY_IGNOREFILE: ci/suppress_trivy.yaml + OSV_IGNOREFILE: ci/suppress_osv_scanner.toml TRIVY_SARIF_OUTPUT: trivy-platform-backend.sarif OSV_SARIF_OUTPUT: osv-scanner-platform-backend.sarif SCA_MERGED_SARIF_OUTPUT: SCA-platform-backend-merged.sarif @@ -42,10 +42,10 @@ jobs: run: mvn dependency:resolve -q - name: Setup tools - run: bash .github/scripts/setup-tools.sh --install-tool trivy,osv-scanner --sbom-ecosystem maven + run: bash ci/setup-tools.sh --install-tool trivy,osv-scanner --sbom-ecosystem maven - name: Run SCA tools - run: python .github/scripts/sca_scan.py + run: python ci/sca_scan.py - name: Upload Trivy SARIF to GitHub Security tab id: upload_trivy diff --git a/.github/README.md b/ci/README.md similarity index 92% rename from .github/README.md rename to ci/README.md index 14343aab..b3e80e4f 100644 --- a/.github/README.md +++ b/ci/README.md @@ -57,7 +57,7 @@ All three trigger independently and run in parallel; each uploads its own SARIF ## 3. Tool installation (`setup-tools.sh`) ```bash -bash .github/scripts/setup-tools.sh --install-tool [--sbom-ecosystem maven|npm|none] +bash ci/setup-tools.sh --install-tool [--sbom-ecosystem maven|npm|none] ``` `--install-tool` accepts a comma-separated list (or `all`): @@ -88,7 +88,7 @@ Both steps run regardless of each other (`if: always()`), all four SARIF files a `container_scan.py` is a single CLI shared by both scan types: ``` -$ python3 .github/scripts/container_scan.py --help +$ python3 ci/container_scan.py --help usage: sec-orchestrator [-h] [-s {sast,sca}] [-i IMAGE] [--merge-sarif SARIF_FILE [SARIF_FILE ...]] [--merge-output MERGE_OUTPUT] Agnostic DevSecOps Container scanning Pipeline Orchestrator @@ -107,9 +107,9 @@ options: **Running it locally:** ```bash docker build -t app:local . -bash .github/scripts/setup-tools.sh --install-tool trivy,osv-scanner,opengrep,hadolint,semgrep-rules -python .github/scripts/container_scan.py --scan-type sast -python .github/scripts/container_scan.py --scan-type sca --image app:local +bash ci/setup-tools.sh --install-tool trivy,osv-scanner,opengrep,hadolint,semgrep-rules +python ci/container_scan.py --scan-type sast +python ci/container_scan.py --scan-type sca --image app:local ``` ## 5. Pipeline: Software Composition Analysis (SCA) @@ -121,8 +121,8 @@ Both tools need to be installed first, same as Container Scanning, via `setup-to **Running it locally:** ```bash mvn dependency:resolve -q -bash .github/scripts/setup-tools.sh --install-tool trivy,osv-scanner --sbom-ecosystem maven # -> mvn org.cyclonedx:cyclonedx-maven-plugin:makeAggregateBom -> target/bom.json -python .github/scripts/sca_scan.py +bash ci/setup-tools.sh --install-tool trivy,osv-scanner --sbom-ecosystem maven # -> mvn org.cyclonedx:cyclonedx-maven-plugin:makeAggregateBom -> target/bom.json +python ci/sca_scan.py ``` `mvn dependency:resolve` pulls the full dependency tree into `.m2` first, so the CycloneDX plugin has something resolved to build the SBOM from. @@ -137,8 +137,8 @@ Trivy and OSV-Scanner both run against the SBOM, findings are evaluated by `pars **Running it locally:** ```bash -bash .github/scripts/setup-tools.sh --install-tool opengrep,semgrep-rules -python .github/scripts/sast_scan.py +bash ci/setup-tools.sh --install-tool opengrep,semgrep-rules +python ci/sast_scan.py ``` --- diff --git a/.github/scripts/container_scan.py b/ci/container_scan.py similarity index 97% rename from .github/scripts/container_scan.py rename to ci/container_scan.py index 018c1d35..e909dc01 100644 --- a/.github/scripts/container_scan.py +++ b/ci/container_scan.py @@ -23,8 +23,8 @@ IMAGE_NAME = os.getenv("IMAGE_NAME", "platform-backend:local") # --- SCA / CVE (Trivy + OSV) --- -TRIVY_IGNOREFILE = os.getenv("TRIVY_IGNOREFILE", ".github/scripts/suppress_trivy.yaml") -OSV_IGNOREFILE = os.getenv("OSV_IGNOREFILE", ".github/scripts/suppress_osv_scanner.toml") +TRIVY_IGNOREFILE = os.getenv("TRIVY_IGNOREFILE", "ci/suppress_trivy.yaml") +OSV_IGNOREFILE = os.getenv("OSV_IGNOREFILE", "ci/suppress_osv_scanner.toml") TRIVY_SCA_SARIF_OUTPUT = os.getenv("TRIVY_SCA_SARIF_OUTPUT", "sca-trivy-container.sarif") OSV_SCA_SARIF_OUTPUT = os.getenv("OSV_SCA_SARIF_OUTPUT", "sca-osv-container.sarif") diff --git a/.github/scripts/parse_sarif.py b/ci/parse_sarif.py similarity index 99% rename from .github/scripts/parse_sarif.py rename to ci/parse_sarif.py index f07d0249..5d35c970 100644 --- a/.github/scripts/parse_sarif.py +++ b/ci/parse_sarif.py @@ -36,4 +36,4 @@ def evaluate(sarif_paths): return EvaluationResult( gate_failed=max_score >= 8, gate_warn=5 <= max_score < 8, - ) \ No newline at end of file + ) diff --git a/.github/scripts/sast_scan.py b/ci/sast_scan.py similarity index 96% rename from .github/scripts/sast_scan.py rename to ci/sast_scan.py index 8213defb..5c40496d 100644 --- a/.github/scripts/sast_scan.py +++ b/ci/sast_scan.py @@ -23,7 +23,7 @@ ).split() OPENGREP_EXCLUDE = os.getenv( "OPENGREP_EXCLUDE", - "*.sarif .github/scripts Dockerfile* .pre-commit-config.yaml docs/** README.md AGENTS.md" + "*.sarif ci/ Dockerfile* .pre-commit-config.yaml docs/** README.md AGENTS.md" ).split() OPENGREP_SARIF_OUTPUT = os.getenv("OPENGREP_SARIF_OUTPUT", "sast-opengrep-app.sarif") diff --git a/.github/scripts/sca_scan.py b/ci/sca_scan.py similarity index 94% rename from .github/scripts/sca_scan.py rename to ci/sca_scan.py index 62441785..b3297d94 100644 --- a/.github/scripts/sca_scan.py +++ b/ci/sca_scan.py @@ -19,8 +19,8 @@ # Configurable values SBOM_PATH = os.getenv("SBOM_PATH", "target/bom.json") -TRIVY_IGNOREFILE = os.getenv("TRIVY_IGNOREFILE", ".github/scripts/suppress_trivy.yaml") -OSV_IGNOREFILE = os.getenv("OSV_IGNOREFILE", ".github/scripts/suppress_osv_scanner.toml") +TRIVY_IGNOREFILE = os.getenv("TRIVY_IGNOREFILE", "ci/suppress_trivy.yaml") +OSV_IGNOREFILE = os.getenv("OSV_IGNOREFILE", "ci/suppress_osv_scanner.toml") TRIVY_SARIF_OUTPUT = os.getenv("TRIVY_SARIF_OUTPUT", "trivy-platform-backend.sarif") OSV_SARIF_OUTPUT = os.getenv("OSV_SARIF_OUTPUT", "osv-scanner-platform-backend.sarif") SCA_MERGED_SARIF_OUTPUT = os.getenv("SCA_MERGED_SARIF_OUTPUT", "SCA-platform-backend-merged.sarif") @@ -36,7 +36,7 @@ def run_trivy(): def run_osv_scanner(): cmd = [ - "osv-scanner", "scan", "source", + "osv-scanner", "scan", "source", "--lockfile", SBOM_PATH, "--config", OSV_IGNOREFILE, "--format", "sarif", @@ -73,33 +73,33 @@ def main(): sarif_files = {"trivy": TRIVY_SARIF_OUTPUT, "osv-scanner": OSV_SARIF_OUTPUT} tool_status = {} # "PASSED" | "WARNING" | "FAILED" | "ERROR" gate_failed = False - + # Run each SCA tool and collect their exit codes for name, tool_fn in tools.items(): exit_code = tool_fn() logger.info("-" * 40) - + path = sarif_files[name] if exit_code != 0 and os.path.exists(path): logger.error(f"{RED}[!] {name} exit code {exit_code} but wrote {path}{RESET}") tool_status[name] = "ERROR" gate_failed = True - + merge_sarifs() # combined artifact only, not used for the gate decision # Evaluate each SARIF file for gate decision for name, path in sarif_files.items(): if name in tool_status: continue # already flagged ERROR above, don't overwrite it - + if not os.path.exists(path): logger.error(f"{RED}[!] {name} SARIF missing: {path},tool failed to run (not a vulnerability){RESET}") tool_status[name] = "ERROR" gate_failed = True continue - + eval_result = evaluate(path) - + if eval_result.gate_failed: tool_status[name] = "FAILED" # this tool found CVSS >= 8.0 gate_failed = True @@ -107,7 +107,7 @@ def main(): tool_status[name] = "WARNING" # this tool found 5.0 <= CVSS < 8.0 else: tool_status[name] = "PASSED" # this tool found nothing >= 5.0 - + # Print summary of results logger.info(f"\n{BOLD}========== SCA PIPELINE SUMMARY =========={RESET}") for name, status in tool_status.items(): @@ -120,11 +120,11 @@ def main(): else: logger.error(f"[{name}]: {RED}FAILED (CVSS >= 8.0 found){RESET}") logger.info(f"{BOLD}=========================================={RESET}\n") - + # Exit with non-zero code if any tool failed the gate if gate_failed: logger.error(f"{RED}One or more SCA tools failed the gate check.{RESET}") sys.exit(1) - + if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/.github/scripts/setup-tools.sh b/ci/setup-tools.sh similarity index 100% rename from .github/scripts/setup-tools.sh rename to ci/setup-tools.sh diff --git a/.github/scripts/suppress_osv_scanner.toml b/ci/suppress_osv_scanner.toml similarity index 53% rename from .github/scripts/suppress_osv_scanner.toml rename to ci/suppress_osv_scanner.toml index b5075b31..0dc9630f 100644 --- a/.github/scripts/suppress_osv_scanner.toml +++ b/ci/suppress_osv_scanner.toml @@ -1,4 +1,4 @@ [[IgnoredVulns]] id = "GHSA-5jmj-h7xm-6q6v" ignoreUntil = 2026-09-30 -reason = "The proposed fix version 2.21.5 not yet released" \ No newline at end of file +reason = "The proposed fix version 2.21.5 not yet released" diff --git a/.github/scripts/suppress_trivy.yaml b/ci/suppress_trivy.yaml similarity index 98% rename from .github/scripts/suppress_trivy.yaml rename to ci/suppress_trivy.yaml index 5c8cae89..f1ee9450 100644 --- a/.github/scripts/suppress_trivy.yaml +++ b/ci/suppress_trivy.yaml @@ -1,3 +1,3 @@ vulnerabilities: - id: CVE-2026-54515 - statement: "The proposed fix version 2.21.5 not yet released" \ No newline at end of file + statement: "The proposed fix version 2.21.5 not yet released" From a1c06b3e8ed39ab9840944b97826f04141a80ee1 Mon Sep 17 00:00:00 2001 From: moghit-eou Date: Thu, 6 Aug 2026 11:59:27 +0100 Subject: [PATCH 3/3] docs: improve vuln suppression examples --- ci/README.md | 34 ++++++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/ci/README.md b/ci/README.md index b3e80e4f..ca91b851 100644 --- a/ci/README.md +++ b/ci/README.md @@ -181,20 +181,46 @@ Suppression applies to the **CVSS-score tools** (Trivy, OSV-Scanner) and is shar **Trivy** (`suppress_trivy.yaml`): ```yaml vulnerabilities: + # Example 1: non-reachable code path - id: CVE-2026-54515 - statement: "The proposed fix version 2.21.5 not yet released" + statement: "Vulnerable code path is not reachable: affected function is dead code in our build (compiled out via CGO_ENABLED=0), confirmed by static analysis." + expires: 2026-09-30 # The expiration date of the ignore finding + + # Example 2: low severity, accepted risk with an owner and a ticket + - id: CVE-2025-11111 + statement: "Low severity; affects an optional dev-only dependency not shipped in production images. Risk accepted, see SEC-5678." + expires: 2026-10-15 + + # Example 3: scope the ignore instead of ignoring everywhere. paths limits it + # to specific files, purls limits it to specific packages (by PURL). Without + # either, the ignore applies to every file/package where this id shows up. + - id: CVE-2024-33333 + paths: + - "test/fixtures/legacy-bundle.jar" + purls: + - "pkg:maven/org.example/legacy-lib" + statement: "Only present in test fixtures; not part of the shipped artifact." + expires: 2026-11-01 ``` **OSV-Scanner** (`suppress_osv_scanner.toml`): ```toml +# Example 1: vulnerable code path is not reachable in how we use the library. [[IgnoredVulns]] -id = "GHSA-5jmj-h7xm-6q6v" # or CVE-2026-54515, GO-2022-0968 ... +id = "GHSA-5jmj-h7xm-6q6v" ignoreUntil = 2026-09-30 -reason = "The proposed fix version 2.21.5 not yet released" +reason = "Vulnerable function is never called." + +# Example 2: low-severity, accepted as risk. +# Only use this pattern for LOW/MEDIUM severity findings with limited impact, +[[IgnoredVulns]] +id = "GHSA-9jx5-6pgf-crrp" +ignoreUntil = 2026-10-15 +reason = "Low severity DoS in a dev-only tool, not present in production build. Risk accepted by security team." ``` Refer to the official docs for complete suppression options: - **Trivy**: [Filtering and ignore files](https://trivy.dev/docs/latest/configuration/filtering/#trivyignoreyaml) - **OSV-Scanner**: [Ignore vulnerabilities by ID](https://google.github.io/osv-scanner/configuration/#ignore-vulnerabilities-by-id) -OpenGrep/Hadolint findings (SAST) aren't suppressed through a shared ignore file in this setup; handle those at the rule/finding level instead. +OpenGrep/Hadolint findings (SAST) aren't suppressed through a shared ignore file in this setup, handle those at the rule/finding level instead.