From d7da6308b783cb03b34332523c32f6c5d5747370 Mon Sep 17 00:00:00 2001 From: moghit-eou Date: Fri, 3 Jul 2026 11:59:31 +0100 Subject: [PATCH 1/7] add: create files --- .github/scripts/readme.md | 1 + .github/scripts/run_sca.py | 12 ++++++++++++ .github/workflows/sca.yaml | 0 3 files changed, 13 insertions(+) create mode 100644 .github/scripts/readme.md create mode 100644 .github/scripts/run_sca.py create mode 100644 .github/workflows/sca.yaml diff --git a/.github/scripts/readme.md b/.github/scripts/readme.md new file mode 100644 index 0000000..85805b6 --- /dev/null +++ b/.github/scripts/readme.md @@ -0,0 +1 @@ +- [] \ No newline at end of file diff --git a/.github/scripts/run_sca.py b/.github/scripts/run_sca.py new file mode 100644 index 0000000..c5ce575 --- /dev/null +++ b/.github/scripts/run_sca.py @@ -0,0 +1,12 @@ +# + + + + + + +def main(): + pass + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/.github/workflows/sca.yaml b/.github/workflows/sca.yaml new file mode 100644 index 0000000..e69de29 From 6b7461f1efdc74410aa09a2238271b648e7fc67b Mon Sep 17 00:00:00 2001 From: moghit-eou Date: Fri, 3 Jul 2026 15:24:44 +0100 Subject: [PATCH 2/7] Introduce updated CI workflow setup --- .github/scripts/parse_sarif.py | 52 ++++++++++++ .github/scripts/readme.md | 1 - .github/scripts/run_sca.py | 101 ++++++++++++++++++++++- .github/scripts/setup-tools.sh | 12 +++ .github/scripts/supress_osv_scanner.toml | 4 + .github/scripts/supress_trivy.yaml | 3 + .github/workflows/sca.yaml | 64 ++++++++++++++ 7 files changed, 234 insertions(+), 3 deletions(-) create mode 100644 .github/scripts/parse_sarif.py create mode 100644 .github/scripts/setup-tools.sh create mode 100644 .github/scripts/supress_osv_scanner.toml create mode 100644 .github/scripts/supress_trivy.yaml diff --git a/.github/scripts/parse_sarif.py b/.github/scripts/parse_sarif.py new file mode 100644 index 0000000..638874e --- /dev/null +++ b/.github/scripts/parse_sarif.py @@ -0,0 +1,52 @@ +import json +import re +from dataclasses import dataclass + +TRIVY_MSG_RE = re.compile(r"Package:\s*(\S+)\nInstalled Version:\s*(\S+)") +OSV_MSG_RE = re.compile(r"Package\s+'([^@']+)@([^']+)'") +RUN_PATTERNS = [("trivy", TRIVY_MSG_RE), ("osv", OSV_MSG_RE)] + + +@dataclass +class EvaluationResult: + gate_failed: bool + gate_warn: bool + + +def evaluate(sarif_paths): + """Accepts a single SARIF path (merged, or one tool alone) or a list of paths.""" + if isinstance(sarif_paths, str): + sarif_paths = [sarif_paths] + + findings = {} # (cve_id, package) -> score + + for path in sarif_paths: + with open(path) as f: + sarif = json.load(f, strict=False) + + for run in sarif["runs"]: + driver_name = run["tool"]["driver"].get("name", "").lower() + msg_re = next((r for name, r in RUN_PATTERNS if name in driver_name), None) + if msg_re is None: + continue # unrecognized tool in this run, skip, don't crash + + rules = run["tool"]["driver"].get("rules", []) + scores = {r["id"]: r.get("properties", {}).get("security-severity") for r in rules} + + for res in run.get("results", []): + m = msg_re.search(res["message"]["text"]) + if not m: + continue + cve_id = res["ruleId"] + package, _version = m.groups() + score = scores.get(cve_id) + score = float(score) if score is not None else None + + key = (cve_id, package) + if key not in findings or (score or 0) > (findings[key] or 0): + findings[key] = score + + return EvaluationResult( + gate_failed=any(s is not None and s >= 8 for s in findings.values()), + gate_warn=any(s is not None and 5 <= s < 8 for s in findings.values()), + ) \ No newline at end of file diff --git a/.github/scripts/readme.md b/.github/scripts/readme.md index 85805b6..e69de29 100644 --- a/.github/scripts/readme.md +++ b/.github/scripts/readme.md @@ -1 +0,0 @@ -- [] \ No newline at end of file diff --git a/.github/scripts/run_sca.py b/.github/scripts/run_sca.py index c5ce575..b72dd38 100644 --- a/.github/scripts/run_sca.py +++ b/.github/scripts/run_sca.py @@ -1,12 +1,109 @@ -# +import subprocess +import os +import sys +import logging +import json +from parse_sarif import evaluate +GREEN = '\033[92m' +RED = '\033[91m' +RESET = '\033[0m' +BOLD = '\033[1m' +YELLOW = '\033[93m' +logging.basicConfig( + level=logging.INFO, + format='%(message)s' # Clean format to prevent double-timestamps in CI logs +) +logger = logging.getLogger("sca-orchestrator") +def run_trivy(): + cmd = [ + "trivy", "sbom", + "target/bom.json", + "--format", "sarif", + "--ignorefile", ".github/scripts/supress_trivy.yaml", + "--output", "trivy.sarif" + ] + + return subprocess.run(cmd).returncode + +def run_osv_scanner(): + cmd = [ + "osv-scanner", "scan", "source", + "--lockfile", "target/bom.json", + "--config", ".github/scripts/supress_osv_scanner.toml", + "--format", "sarif", + "--output-file", "osv-scanner.sarif" + ] + + return subprocess.run(cmd).returncode + +def merge_sarifs(): + merged = { + "$schema": "https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0.json", + "version": "2.1.0", + "runs": [], + } + + for path in ("trivy.sarif", "osv-scanner.sarif"): + if not os.path.exists(path): + logger.warning(f"{path} not found, skipping in merge") + continue + with open(path) as f: + sarif = json.load(f, strict=False) + merged["runs"].extend(sarif.get("runs", [])) + + with open("merged-SCA-platform-backend.sarif", "w") as f: + json.dump(merged, f) + + logger.info("SARIF files merged successfully.") def main(): - pass + tools = [run_trivy, run_osv_scanner] + + exit_codes = {} + for tool in tools: + exit_codes[tool.__name__] = tool() + logger.info("-" * 40) + + merge_sarifs() # combined artifact only, not used for the gate decision + + sarif_files = {"trivy": "trivy.sarif", "osv-scanner": "osv-scanner.sarif"} + tool_status = {} # "PASSED" | "WARNING" | "FAILED" + gate_failed = False + + for name, path in sarif_files.items(): + if not os.path.exists(path): + logger.error(f"{RED}[!] {name} SARIF file missing, skipping evaluation: {path}{RESET}") + tool_status[name] = "FAILED, Something is wrong with the tool execution, please check the logs." + 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 + elif eval_result.gate_warn: + tool_status[name] = "WARNING" # this tool found 5.0 <= CVSS < 8.0 + else: + tool_status[name] = "PASSED" # this tool found nothing >= 5.0 + + logger.info(f"\n{BOLD}========== SCA PIPELINE SUMMARY =========={RESET}") + for name, status in tool_status.items(): + if status == "PASSED": + logger.info(f"[{name}]: {GREEN}PASSED{RESET}") + elif status == "WARNING": + logger.warning(f"[{name}]: {YELLOW}WARNING (findings between 5.0 and 8.0){RESET}") + else: + logger.error(f"[{name}]: {RED}FAILED (CVSS >= 8.0 found){RESET}") + logger.info(f"{BOLD}=========================================={RESET}\n") + + if gate_failed: + sys.exit(1) if __name__ == "__main__": main() \ No newline at end of file diff --git a/.github/scripts/setup-tools.sh b/.github/scripts/setup-tools.sh new file mode 100644 index 0000000..c4a6917 --- /dev/null +++ b/.github/scripts/setup-tools.sh @@ -0,0 +1,12 @@ +#!/bin/bash +set -e + +# Install Trivy +curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin v0.71.1 + +# Install OSV Scanner +curl -L https://github.com/google/osv-scanner/releases/download/v2.4.0/osv-scanner_linux_amd64 -o /usr/local/bin/osv-scanner +chmod +x /usr/local/bin/osv-scanner + +# Generate the BOM using CycloneDX npm plugin +npx --yes @cyclonedx/cyclonedx-npm --output-format json --output-file target/bom.json \ No newline at end of file diff --git a/.github/scripts/supress_osv_scanner.toml b/.github/scripts/supress_osv_scanner.toml new file mode 100644 index 0000000..b5075b3 --- /dev/null +++ b/.github/scripts/supress_osv_scanner.toml @@ -0,0 +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 diff --git a/.github/scripts/supress_trivy.yaml b/.github/scripts/supress_trivy.yaml new file mode 100644 index 0000000..56f1b4f --- /dev/null +++ b/.github/scripts/supress_trivy.yaml @@ -0,0 +1,3 @@ +vulnerabilities: + - id: CVE-2026-54515 + statement: "False positive, not reachable code path" diff --git a/.github/workflows/sca.yaml b/.github/workflows/sca.yaml index e69de29..0c1b3ba 100644 --- a/.github/workflows/sca.yaml +++ b/.github/workflows/sca.yaml @@ -0,0 +1,64 @@ +name: SCA CI + +on: + push: + branches: + - ci/sca-pipeline-sbom + pull_request: + +jobs: + sca: + name: Software Composition Analysis + runs-on: ubuntu-latest # TODO: Migrate back to debian:trixie once pipeline MVP is fully stable + permissions: + contents: read + security-events: write # required for uploading SCA results to github security + + steps: + - name: Check out repository + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + + - name: Set up Python + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 #v6.2.0 + with: + python-version: '3.14.4' + + - name: Cache npm packages + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 #v6.1.0 + with: + path: ~/.npm + key: ${{ runner.os }}-npm-v1-${{ hashFiles('**/package-lock.json') }} + restore-keys: ${{ runner.os }}-npm-v1- + + - name: Install dependencies + run: npm ci + + - name: Setup tools + run: bash .github/scripts/setup-tools.sh + + - name: Setup tools + run: bash .github/scripts/setup-tools.sh + + - name: Run SCA tools + run: python .github/scripts/run_sca.py + + - name: Upload Trivy SARIF to GitHub Security tab + id: upload_trivy + if: always() + uses: github/codeql-action/upload-sarif@c35d1b164463ee62a100735382aaaa525c5d3496 #v2.25.6 + with: + sarif_file: trivy.sarif + + - name: Upload OSV Scanner SARIF to GitHub Security tab + id: upload_osv + if: always() + uses: github/codeql-action/upload-sarif@c35d1b164463ee62a100735382aaaa525c5d3496 #v2.25.6 + with: + sarif_file: osv-scanner.sarif + + - name: Upload merged SARIF to GitHub Security tab + if: ${{ always() && steps.upload_trivy.outcome == 'success' && steps.upload_osv.outcome == 'success' }} + uses: github/codeql-action/upload-sarif@c35d1b164463ee62a100735382aaaa525c5d3496 #v2.25.6 + with: + sarif_file: merged-SCA-platform-backend.sarif + category: merged-sca \ No newline at end of file From fb83788c1135bb8f4d7d3b35286324b6e1ba93f3 Mon Sep 17 00:00:00 2001 From: moghit-eou Date: Fri, 3 Jul 2026 15:25:30 +0100 Subject: [PATCH 3/7] Introduce updated CI workflow setup --- .github/workflows/sca.yaml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/sca.yaml b/.github/workflows/sca.yaml index 0c1b3ba..5105dc7 100644 --- a/.github/workflows/sca.yaml +++ b/.github/workflows/sca.yaml @@ -1,9 +1,6 @@ name: SCA CI on: - push: - branches: - - ci/sca-pipeline-sbom pull_request: jobs: From 55d244505ca653ee385d24f07904700d31887d3a Mon Sep 17 00:00:00 2001 From: moghit-eou Date: Fri, 3 Jul 2026 23:02:09 +0100 Subject: [PATCH 4/7] refactor: simplify SARIF gate evaluation, drop unused package parsing --- .github/scripts/parse_sarif.py | 50 ++++++++----------------- .gitignore | 68 ---------------------------------- 2 files changed, 15 insertions(+), 103 deletions(-) delete mode 100644 .gitignore diff --git a/.github/scripts/parse_sarif.py b/.github/scripts/parse_sarif.py index 638874e..85595fe 100644 --- a/.github/scripts/parse_sarif.py +++ b/.github/scripts/parse_sarif.py @@ -1,52 +1,32 @@ import json -import re from dataclasses import dataclass -TRIVY_MSG_RE = re.compile(r"Package:\s*(\S+)\nInstalled Version:\s*(\S+)") -OSV_MSG_RE = re.compile(r"Package\s+'([^@']+)@([^']+)'") -RUN_PATTERNS = [("trivy", TRIVY_MSG_RE), ("osv", OSV_MSG_RE)] - - @dataclass class EvaluationResult: gate_failed: bool gate_warn: bool - - + def evaluate(sarif_paths): - """Accepts a single SARIF path (merged, or one tool alone) or a list of paths.""" if isinstance(sarif_paths, str): sarif_paths = [sarif_paths] - findings = {} # (cve_id, package) -> score - + max_score = 0.0 for path in sarif_paths: with open(path) as f: sarif = json.load(f, strict=False) - for run in sarif["runs"]: - driver_name = run["tool"]["driver"].get("name", "").lower() - msg_re = next((r for name, r in RUN_PATTERNS if name in driver_name), None) - if msg_re is None: - continue # unrecognized tool in this run, skip, don't crash - + for run in sarif.get("runs", []): rules = run["tool"]["driver"].get("rules", []) - scores = {r["id"]: r.get("properties", {}).get("security-severity") for r in rules} - - for res in run.get("results", []): - m = msg_re.search(res["message"]["text"]) - if not m: - continue - cve_id = res["ruleId"] - package, _version = m.groups() - score = scores.get(cve_id) - score = float(score) if score is not None else None - - key = (cve_id, package) - if key not in findings or (score or 0) > (findings[key] or 0): - findings[key] = score - + severities = {r["id"]: r.get("properties", {}).get("security-severity") for r in rules} + + print(severities) + + for result in run.get("results", []): + score = severities.get(result["ruleId"]) + if score is not None: + max_score = max(max_score, float(score)) + print(max_score) return EvaluationResult( - gate_failed=any(s is not None and s >= 8 for s in findings.values()), - gate_warn=any(s is not None and 5 <= s < 8 for s in findings.values()), - ) \ No newline at end of file + gate_failed=max_score >= 8, + gate_warn=5 <= max_score < 8, + ) \ No newline at end of file diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 1abd2b0..0000000 --- a/.gitignore +++ /dev/null @@ -1,68 +0,0 @@ -# See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files. - -# Angular specific -.angular -.angular/cache - -# Compiled output -/dist -/tmp -/out-tsc -/bazel-out - -# Node -/node_modules -npm-debug.log -yarn-error.log - -# IDEs and editors -.idea/ -.project -.classpath -.c9/ -*.launch -.settings/ -*.sublime-workspace - -# Visual Studio Code -.vscode/* -!.vscode/settings.json -!.vscode/tasks.json -!.vscode/launch.json -!.vscode/extensions.json -.history/* - -# Miscellaneous -/.angular/cache -.codegraph/ -.sass-cache/ -/connect.lock -/coverage -/libpeerconnection.log -testem.log -/typings - -# System files -.DS_Store -Thumbs.db - -# Compiled source -*.js -*.js.map -!src/assets/env.js -!src/assets/env.template.js - -# TypeScript type definition files -*.d.ts - -# Environment variables (Make sure not to commit sensitive API keys) -src/environments/environment*.ts - -# Logs -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -# Temporary files -*.tmp -*.swp From 513fc7716a4ebe8764ec66dafc68c063a1dbdbc8 Mon Sep 17 00:00:00 2001 From: moghit-eou Date: Fri, 3 Jul 2026 23:42:13 +0100 Subject: [PATCH 5/7] refactor: simplify SARIF gate evaluation, drop unused package parsing --- .github/scripts/parse_sarif.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/scripts/parse_sarif.py b/.github/scripts/parse_sarif.py index 85595fe..16af3b4 100644 --- a/.github/scripts/parse_sarif.py +++ b/.github/scripts/parse_sarif.py @@ -18,14 +18,12 @@ def evaluate(sarif_paths): for run in sarif.get("runs", []): rules = run["tool"]["driver"].get("rules", []) severities = {r["id"]: r.get("properties", {}).get("security-severity") for r in rules} - - print(severities) - + for result in run.get("results", []): score = severities.get(result["ruleId"]) if score is not None: max_score = max(max_score, float(score)) - print(max_score) + return EvaluationResult( gate_failed=max_score >= 8, gate_warn=5 <= max_score < 8, From e8a403648458b00ee876259e41b533bf53fc851e Mon Sep 17 00:00:00 2001 From: moghit-eou Date: Fri, 3 Jul 2026 23:44:23 +0100 Subject: [PATCH 6/7] refactor: simplify SARIF gate evaluation, drop unused package parsing --- .gitignore | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1abd2b0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,68 @@ +# See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files. + +# Angular specific +.angular +.angular/cache + +# Compiled output +/dist +/tmp +/out-tsc +/bazel-out + +# Node +/node_modules +npm-debug.log +yarn-error.log + +# IDEs and editors +.idea/ +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# Visual Studio Code +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +.history/* + +# Miscellaneous +/.angular/cache +.codegraph/ +.sass-cache/ +/connect.lock +/coverage +/libpeerconnection.log +testem.log +/typings + +# System files +.DS_Store +Thumbs.db + +# Compiled source +*.js +*.js.map +!src/assets/env.js +!src/assets/env.template.js + +# TypeScript type definition files +*.d.ts + +# Environment variables (Make sure not to commit sensitive API keys) +src/environments/environment*.ts + +# Logs +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Temporary files +*.tmp +*.swp From e8b425d30b5c987942e75dd08d18905333c9a3fc Mon Sep 17 00:00:00 2001 From: moghit-eou Date: Sun, 5 Jul 2026 23:15:17 +0100 Subject: [PATCH 7/7] feat(sca): introduce container image scanning --- .../scripts/{run_sca.py => run_sca_app.py} | 15 ++- .github/scripts/run_sca_image.py | 109 ++++++++++++++++++ .github/scripts/setup-tools.sh | 23 +++- .github/workflows/{sca.yaml => sca_app.yml} | 17 ++- .github/workflows/sca_image.yml | 51 ++++++++ 5 files changed, 195 insertions(+), 20 deletions(-) rename .github/scripts/{run_sca.py => run_sca_app.py} (85%) create mode 100644 .github/scripts/run_sca_image.py rename .github/workflows/{sca.yaml => sca_app.yml} (79%) create mode 100644 .github/workflows/sca_image.yml diff --git a/.github/scripts/run_sca.py b/.github/scripts/run_sca_app.py similarity index 85% rename from .github/scripts/run_sca.py rename to .github/scripts/run_sca_app.py index b72dd38..60a86de 100644 --- a/.github/scripts/run_sca.py +++ b/.github/scripts/run_sca_app.py @@ -23,7 +23,7 @@ def run_trivy(): "target/bom.json", "--format", "sarif", "--ignorefile", ".github/scripts/supress_trivy.yaml", - "--output", "trivy.sarif" + "--output", "trivy-app.sarif" ] return subprocess.run(cmd).returncode @@ -34,19 +34,19 @@ def run_osv_scanner(): "--lockfile", "target/bom.json", "--config", ".github/scripts/supress_osv_scanner.toml", "--format", "sarif", - "--output-file", "osv-scanner.sarif" + "--output-file", "osv-scanner-app.sarif" ] return subprocess.run(cmd).returncode def merge_sarifs(): merged = { - "$schema": "https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0.json", + "$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/main/sarif-2.1/schema/sarif-schema-2.1.0.json", "version": "2.1.0", "runs": [], } - for path in ("trivy.sarif", "osv-scanner.sarif"): + for path in ("trivy-app.sarif", "osv-scanner-app.sarif"): if not os.path.exists(path): logger.warning(f"{path} not found, skipping in merge") continue @@ -54,13 +54,11 @@ def merge_sarifs(): sarif = json.load(f, strict=False) merged["runs"].extend(sarif.get("runs", [])) - with open("merged-SCA-platform-backend.sarif", "w") as f: + with open("merged-SCA-platform-ui-app.sarif", "w") as f: json.dump(merged, f) logger.info("SARIF files merged successfully.") - - def main(): tools = [run_trivy, run_osv_scanner] @@ -71,7 +69,7 @@ def main(): merge_sarifs() # combined artifact only, not used for the gate decision - sarif_files = {"trivy": "trivy.sarif", "osv-scanner": "osv-scanner.sarif"} + sarif_files = {"trivy": "trivy-app.sarif", "osv-scanner": "osv-scanner-app.sarif"} tool_status = {} # "PASSED" | "WARNING" | "FAILED" gate_failed = False @@ -103,6 +101,7 @@ def main(): logger.info(f"{BOLD}=========================================={RESET}\n") if gate_failed: + logger.error(f"{RED}One or more SCA tools failed the gate check.{RESET}") sys.exit(1) if __name__ == "__main__": diff --git a/.github/scripts/run_sca_image.py b/.github/scripts/run_sca_image.py new file mode 100644 index 0000000..08b8f28 --- /dev/null +++ b/.github/scripts/run_sca_image.py @@ -0,0 +1,109 @@ +import subprocess +import os +import sys +import logging +import json +from parse_sarif import evaluate + +GREEN = '\033[92m' +RED = '\033[91m' +RESET = '\033[0m' +BOLD = '\033[1m' +YELLOW = '\033[93m' + +logging.basicConfig( + level=logging.INFO, + format='%(message)s' # Clean format to prevent double-timestamps in CI logs +) +logger = logging.getLogger("sca-orchestrator") +def run_trivy(): + cmd = [ + "trivy", "image", + "platform-ui:testing", + "--format", "sarif", + "--ignorefile", ".github/scripts/supress_trivy.yaml", + "--output", "trivy-image.sarif" + ] + return subprocess.run(cmd).returncode + + +def run_osv_scanner(): + cmd = [ + "osv-scanner", "scan", "image", + "platform-ui:testing", + "--config", ".github/scripts/supress_osv_scanner.toml", + "--format", "sarif", + "--output-file", "osv-scanner-image.sarif" + ] + return subprocess.run(cmd).returncode + +def merge_sarifs(): + + merged = { + "$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/main/sarif-2.1/schema/sarif-schema-2.1.0.json", + "version": "2.1.0", + "runs": [], + } + + for path in ("trivy-image.sarif", "osv-scanner-image.sarif"): + if not os.path.exists(path): + logger.warning(f"{path} not found, skipping in merge") + continue + with open(path) as f: + sarif = json.load(f, strict=False) + merged["runs"].extend(sarif.get("runs", [])) + + with open("merged-SCA-platform-ui-image.sarif", "w") as f: + json.dump(merged, f) + + logger.info("SARIF files merged successfully.") + + + +def main(): + tools = [run_trivy, run_osv_scanner] + + exit_codes = {} + for tool in tools: + exit_codes[tool.__name__] = tool() + logger.info("-" * 40) + + merge_sarifs() # combined artifact only, not used for the gate decision + + sarif_files = {"trivy": "trivy-image.sarif", "osv-scanner": "osv-scanner-image.sarif"} + tool_status = {} # "PASSED" | "WARNING" | "FAILED" + gate_failed = False + + for name, path in sarif_files.items(): + if not os.path.exists(path): + logger.error(f"{RED}[!] {name} SARIF file missing, skipping evaluation: {path}{RESET}") + tool_status[name] = "FAILED, Something is wrong with the tool execution, please check the logs." + 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 # Fail the gate if any tool fails + elif eval_result.gate_warn: + tool_status[name] = "WARNING" # this tool found 5.0 <= CVSS < 8.0 + else: + tool_status[name] = "PASSED" # this tool found nothing >= 5.0 + + logger.info(f"\n{BOLD}========== SCA PIPELINE SUMMARY =========={RESET}") + for name, status in tool_status.items(): + if status == "PASSED": + logger.info(f"[{name}]: {GREEN}PASSED{RESET}") + elif status == "WARNING": + logger.warning(f"[{name}]: {YELLOW}WARNING (findings between 5.0 and 8.0){RESET}") + else: + logger.error(f"[{name}]: {RED}FAILED (CVSS >= 8.0 found){RESET}") + logger.info(f"{BOLD}=========================================={RESET}\n") + + 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 diff --git a/.github/scripts/setup-tools.sh b/.github/scripts/setup-tools.sh index c4a6917..a4ee827 100644 --- a/.github/scripts/setup-tools.sh +++ b/.github/scripts/setup-tools.sh @@ -8,5 +8,24 @@ curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/inst curl -L https://github.com/google/osv-scanner/releases/download/v2.4.0/osv-scanner_linux_amd64 -o /usr/local/bin/osv-scanner chmod +x /usr/local/bin/osv-scanner -# Generate the BOM using CycloneDX npm plugin -npx --yes @cyclonedx/cyclonedx-npm --output-format json --output-file target/bom.json \ No newline at end of file + +# Generate SBOM based on project type + +PROJECT_TYPE="${1:-none}" # maven | npm | none +case "$PROJECT_TYPE" in + maven) + echo "Generating SBOM for Maven project" + mvn org.cyclonedx:cyclonedx-maven-plugin:makeAggregateBom -q + ;; + npm) + echo "Generating SBOM for NPM project" + npx --yes @cyclonedx/cyclonedx-npm --output-file target/bom.json + ;; + none) + echo "No SBOM generation needed for image pipelines" + ;; + *) + echo "Unknown PROJECT_TYPE: $PROJECT_TYPE" >&2 # redirect error message to stderr + exit 1 + ;; +esac \ No newline at end of file diff --git a/.github/workflows/sca.yaml b/.github/workflows/sca_app.yml similarity index 79% rename from .github/workflows/sca.yaml rename to .github/workflows/sca_app.yml index 5105dc7..b7b9fe3 100644 --- a/.github/workflows/sca.yaml +++ b/.github/workflows/sca_app.yml @@ -1,4 +1,4 @@ -name: SCA CI +name: SCA CI - app on: pull_request: @@ -6,7 +6,7 @@ on: jobs: sca: name: Software Composition Analysis - runs-on: ubuntu-latest # TODO: Migrate back to debian:trixie once pipeline MVP is fully stable + runs-on: ubuntu-latest permissions: contents: read security-events: write # required for uploading SCA results to github security @@ -31,31 +31,28 @@ jobs: run: npm ci - name: Setup tools - run: bash .github/scripts/setup-tools.sh - - - name: Setup tools - run: bash .github/scripts/setup-tools.sh + run: bash .github/scripts/setup-tools.sh npm - name: Run SCA tools - run: python .github/scripts/run_sca.py + run: python .github/scripts/run_sca_app.py - name: Upload Trivy SARIF to GitHub Security tab id: upload_trivy if: always() uses: github/codeql-action/upload-sarif@c35d1b164463ee62a100735382aaaa525c5d3496 #v2.25.6 with: - sarif_file: trivy.sarif + sarif_file: trivy-app.sarif - name: Upload OSV Scanner SARIF to GitHub Security tab id: upload_osv if: always() uses: github/codeql-action/upload-sarif@c35d1b164463ee62a100735382aaaa525c5d3496 #v2.25.6 with: - sarif_file: osv-scanner.sarif + sarif_file: osv-scanner-app.sarif - name: Upload merged SARIF to GitHub Security tab if: ${{ always() && steps.upload_trivy.outcome == 'success' && steps.upload_osv.outcome == 'success' }} uses: github/codeql-action/upload-sarif@c35d1b164463ee62a100735382aaaa525c5d3496 #v2.25.6 with: - sarif_file: merged-SCA-platform-backend.sarif + sarif_file: merged-SCA-platform-ui-app.sarif category: merged-sca \ No newline at end of file diff --git a/.github/workflows/sca_image.yml b/.github/workflows/sca_image.yml new file mode 100644 index 0000000..7160906 --- /dev/null +++ b/.github/workflows/sca_image.yml @@ -0,0 +1,51 @@ +name: SCA CI - image + +on: + pull_request: + +jobs: + sca: + name: Software Composition Analysis + runs-on: ubuntu-latest + permissions: + contents: read + security-events: write # required for uploading SCA results to github security + + steps: + - name: Check out repository + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + + - name: Set up Python + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 #v6.2.0 + with: + python-version: '3.14.4' + + - name: Build Docker image + run: docker build -t platform-ui:testing . + + - name: Setup tools + run: bash .github/scripts/setup-tools.sh + + - name: Run SCA tools + run: python .github/scripts/run_sca_image.py + + - name: Upload Trivy SARIF to GitHub Security tab + id: upload_trivy + if: always() + uses: github/codeql-action/upload-sarif@c35d1b164463ee62a100735382aaaa525c5d3496 #v2.25.6 + with: + sarif_file: trivy-image.sarif + + - name: Upload OSV Scanner SARIF to GitHub Security tab + id: upload_osv + if: always() + uses: github/codeql-action/upload-sarif@c35d1b164463ee62a100735382aaaa525c5d3496 #v2.25.6 + with: + sarif_file: osv-scanner-image.sarif + + - name: Upload merged SARIF to GitHub Security tab + if: ${{ always() && steps.upload_trivy.outcome == 'success' && steps.upload_osv.outcome == 'success' }} + uses: github/codeql-action/upload-sarif@c35d1b164463ee62a100735382aaaa525c5d3496 #v2.25.6 + with: + sarif_file: merged-SCA-platform-ui-image.sarif + category: merged-sca \ No newline at end of file