diff --git a/.github/scripts/README.md b/.github/scripts/README.md deleted file mode 100644 index afd9bac2..00000000 --- a/.github/scripts/README.md +++ /dev/null @@ -1,225 +0,0 @@ -# Software Composition Analysis (SCA) Pipeline - -Automated dependency and container vulnerability scanning for `platform-ui`, 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. Installing dependencies: `npm ci` vs `npm install`](#7-installing-dependencies-npm-ci-vs-npm-install) -- [8. Running it locally](#8-running-it-locally) -- [9. Environment variables](#9-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_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 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. Installing dependencies: `npm ci` vs `npm install` - -The SBOM generator (`@cyclonedx/cyclonedx-npm`) reads `package-lock.json` to determine exact dependency versions. That lockfile is only trustworthy if it's actually in sync with `package.json`, otherwise the SBOM describes a dependency tree that may not match what actually gets installed. - -Use `npm ci`, not `npm install`, before generating the SBOM: - -- **`npm ci`** installs strictly from `package-lock.json`, deletes `node_modules` first for a clean install, and **fails immediately** if `package.json` and `package-lock.json` are out of sync. It's built for CI: fast, deterministic, and it never rewrites the lockfile. -- **`npm install`** will update `package-lock.json` to resolve any mismatch with `package.json`. Fine on a dev machine, but in CI it means the lockfile that got committed and reviewed isn't necessarily the one that gets scanned. - -If `npm ci` fails, that's a signal `package-lock.json` is stale and needs to be regenerated locally (`npm install`, then commit the updated lockfile), not something to patch around in the pipeline. - ---- - -## 8. Running it locally - -**Image pipeline** -```bash -bash .github/scripts/setup-tools.sh # installs trivy + osv-scanner -docker build -t platform-ui:local . -python .github/scripts/run_sca_image.py -``` - -**App pipeline** - -> **Note:** `npm ci` installs strictly from `package-lock.json` and fails immediately if it's out of sync with `package.json`, that failure means the lockfile is stale and needs to be regenerated (`npm install`, to updated lockfile). - -```bash -npm ci -bash .github/scripts/setup-tools.sh npm # 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). - ---- -## 9. Environment variables -| Variable | `run_sca_image.py` Default | `run_sca_app.py` Default | Purpose | -|---|---|---|---| -| `IMAGE_NAME` | `platform-ui: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-ui-image.sarif` | `merged-SCA-platform-ui-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-ui:testing - TRIVY_IGNOREFILE: .github/scripts/suppress_trivy.yaml - ... -``` \ No newline at end of file diff --git a/.github/workflows/container-scan.yml b/.github/workflows/container-scan.yml index 93428261..ec9a72bc 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-ui-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 cc503cc5..c4902f01 100644 --- a/.github/workflows/sast.yml +++ b/.github/workflows/sast.yml @@ -19,7 +19,7 @@ jobs: semgrep-rules/javascript semgrep-rules/yaml semgrep-rules/package_managers p/default semgrep-rules/json OPENGREP_EXCLUDE: >- - *.sarif .github/scripts Dockerfile* dist/** build/** node_modules/** .angular/** + *.sarif ci/ Dockerfile* dist/** build/** node_modules/** .angular/** OPENGREP_SARIF_OUTPUT: sast-semgrep-app.sarif steps: @@ -33,10 +33,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 ceb22ebf..84fb0097 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-ui.sarif OSV_SARIF_OUTPUT: osv-scanner-platform-ui.sarif SCA_MERGED_SARIF_OUTPUT: SCA-platform-ui-merged.sarif @@ -43,10 +43,10 @@ jobs: npm ci - name: Setup tools - run: bash .github/scripts/setup-tools.sh --install-tool trivy,osv-scanner --sbom-ecosystem npm + run: bash ci/setup-tools.sh --install-tool trivy,osv-scanner --sbom-ecosystem npm - 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/ci/README.md b/ci/README.md new file mode 100644 index 00000000..15cf2515 --- /dev/null +++ b/ci/README.md @@ -0,0 +1,226 @@ +# 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 ci/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 npm` 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 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 + +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 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) + +`sca.yml` scans **application dependencies**, not the container. It installs dependencies, 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 +npm ci +bash ci/setup-tools.sh --install-tool trivy,osv-scanner --sbom-ecosystem npm # -> npx @cyclonedx/cyclonedx-npm -> target/bom.json +python ci/sca_scan.py +``` + +Use `npm ci`, not `npm install`, before generating the SBOM: `npm ci` installs strictly from `package-lock.json`, deletes `node_modules` first for a clean install, and **fails immediately** if `package.json` and `package-lock.json` are out of sync, and it never rewrites the lockfile. If it fails, that's a signal `package-lock.json` is stale and needs to be regenerated locally (`npm install`, then commit the updated lockfile), not something to patch around in the pipeline. + +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 ci/setup-tools.sh --install-tool opengrep,semgrep-rules +python ci/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: + # Example 1: non-reachable code path + - id: CVE-2026-54515 + statement: "Vulnerable code path is not reachable: affected function is dead code in our build." + 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." + 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" +ignoreUntil = 2026-09-30 +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. 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 77d3b5f6..2eb38c46 100644 --- a/.github/scripts/container_scan.py +++ b/ci/container_scan.py @@ -23,8 +23,8 @@ IMAGE_NAME = os.getenv("IMAGE_NAME", "platform-ui: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 100% rename from .github/scripts/parse_sarif.py rename to ci/parse_sarif.py 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 64e7992f..03538632 100644 --- a/.github/scripts/sast_scan.py +++ b/ci/sast_scan.py @@ -22,7 +22,7 @@ ).split() OPENGREP_EXCLUDE = os.getenv( "OPENGREP_EXCLUDE", - ".github/scripts *.sarif Dockerfile* dist/** build/** node_modules/** .angular/**" + "ci/ *.sarif Dockerfile* dist/** build/** node_modules/** .angular/**" ).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 96% rename from .github/scripts/sca_scan.py rename to ci/sca_scan.py index a4943d72..fc527b41 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-app.sarif") OSV_SARIF_OUTPUT = os.getenv("OSV_SARIF_OUTPUT", "osv-scanner-app.sarif") SCA_MERGED_SARIF_OUTPUT = os.getenv("SCA_MERGED_SARIF_OUTPUT", "SCA-platform-ui-merged.sarif") 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 100% rename from .github/scripts/suppress_trivy.yaml rename to ci/suppress_trivy.yaml diff --git a/package-lock.json b/package-lock.json index 723e1d89..807eb939 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9650,9 +9650,9 @@ } }, "node_modules/fast-uri": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.4.tgz", - "integrity": "sha512-8JnbkQ4juDyvYs4mgFGQqg4yCYtFDtUtmp2QIQq11ZZe5CFQ5wcqm1rqDgAh/QdMySuBnPzMUiJUNZG5N/AiQw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz", + "integrity": "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==", "dev": true, "funding": [ { @@ -9663,8 +9663,7 @@ "type": "opencollective", "url": "https://opencollective.com/fastify" } - ], - "license": "BSD-3-Clause" + ] }, "node_modules/fast-wrap-ansi": { "version": "0.2.2", @@ -10554,11 +10553,10 @@ "license": "MIT" }, "node_modules/ip-address": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", - "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==", + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.3.1.tgz", + "integrity": "sha512-1e9d3kb97NHJTIJDZW9rKqW2h6+dFa50Dy0fpPSMQp2ADje5gvKsXmdiK6dwY5t76TaTt5+P5N1Y/LoToIxP6g==", "dev": true, - "license": "MIT", "engines": { "node": ">= 12" } @@ -14491,11 +14489,10 @@ } }, "node_modules/socket.io-parser": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.6.tgz", - "integrity": "sha512-asJqbVBDsBCJx0pTqw3WfesSY0iRX+2xzWEWzrpcH7L6fLzrhyF8WPI8UaeM4YCuDfpwA/cgsdugMsmtz8EJeg==", + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.7.tgz", + "integrity": "sha512-IH/iSeO9T6gz1KkFleGDWkG9N3dl4jXVYUtMhIqH10Md0ttMer8nUNWiP1DKuNrybD2xBrixLJdCC9J6ECoYkg==", "dev": true, - "license": "MIT", "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.4.1" @@ -15195,11 +15192,10 @@ } }, "node_modules/undici": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.28.0.tgz", - "integrity": "sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.29.0.tgz", + "integrity": "sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==", "dev": true, - "license": "MIT", "engines": { "node": ">=20.18.1" } diff --git a/package.json b/package.json index 7ff8ed8b..dddc5861 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,7 @@ "express@4.22.2": { "body-parser": "1.20.6" }, - "fast-uri": "3.1.4", + "fast-uri": "3.1.5", "http-proxy-middleware": "3.0.7", "karma": { "body-parser": "1.20.6" @@ -80,11 +80,13 @@ "postcss": "8.5.18", "shell-quote": "1.9.0", "tmp": "0.2.7", - "undici": "7.28.0", + "undici": "7.29.0", "uuid": "11.1.1", "vite": "7.3.6", "webpack-dev-server": "5.2.6", "ws": "8.21.0", - "zrender": "6.1.0" + "zrender": "6.1.0", + "ip-address": "10.3.1", + "socket.io-parser": "4.2.7" } }