Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions .github/workflows/container-scan.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ jobs:
OSV_IGNOREFILE: ci/suppress_osv_scanner.toml
TRIVY_SCA_SARIF_OUTPUT: sca-trivy-container.sarif
OSV_SCA_SARIF_OUTPUT: sca-osv-container.sarif
# CVSS gate: fail at >= FAIL, warn in between. Override per project.
GATE_FAIL_THRESHOLD: "8.0"
GATE_WARN_THRESHOLD: "5.0"

# SAST /Linting
SEMGREP_CONFIG_RULESETS: >-
Expand Down Expand Up @@ -57,31 +60,31 @@ jobs:
- name: Upload Trivy SARIF to GitHub Security tab
id: upload_trivy
if: always()
uses: github/codeql-action/upload-sarif@c35d1b164463ee62a100735382aaaa525c5d3496 #v2.25.6
uses: github/codeql-action/upload-sarif@4e94bd11f71e507f7f87df81788dff88d1dacbfb # v4.31.2
with:
sarif_file: ${{ env.TRIVY_SCA_SARIF_OUTPUT }}
category: trivy-container-scanning

- name: Upload OSV Scanner SARIF to GitHub Security tab
id: upload_osv
if: always()
uses: github/codeql-action/upload-sarif@c35d1b164463ee62a100735382aaaa525c5d3496 #v2.25.6
uses: github/codeql-action/upload-sarif@4e94bd11f71e507f7f87df81788dff88d1dacbfb # v4.31.2
with:
sarif_file: ${{ env.OSV_SCA_SARIF_OUTPUT }}
category: osv-scanner-container-scanning

- name: Upload OpenGrep SARIF to GitHub Security tab
id: upload_opengrep
if: always()
uses: github/codeql-action/upload-sarif@c35d1b164463ee62a100735382aaaa525c5d3496 #v2.25.6
uses: github/codeql-action/upload-sarif@4e94bd11f71e507f7f87df81788dff88d1dacbfb # v4.31.2
with:
sarif_file: ${{ env.OPENGREP_SAST_SARIF_OUTPUT }}
category: opengrep-sast

- name: Upload Hadolint SARIF to GitHub Security tab
id: upload_hadolint
if: always()
uses: github/codeql-action/upload-sarif@c35d1b164463ee62a100735382aaaa525c5d3496 #v2.25.6
uses: github/codeql-action/upload-sarif@4e94bd11f71e507f7f87df81788dff88d1dacbfb # v4.31.2
with:
sarif_file: ${{ env.HADOLINT_SAST_SARIF_OUTPUT }}
category: hadolint-sast
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/sast.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ jobs:
- name: Upload Semgrep SARIF to GitHub Security tab
id: upload_semgrep
if: always()
uses: github/codeql-action/upload-sarif@c35d1b164463ee62a100735382aaaa525c5d3496 #v2.25.6
uses: github/codeql-action/upload-sarif@4e94bd11f71e507f7f87df81788dff88d1dacbfb # v4.31.2
with:
sarif_file: ${{ env.OPENGREP_SARIF_OUTPUT }}
category: semgrep-app
Expand Down
13 changes: 6 additions & 7 deletions .github/workflows/sca.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ jobs:
TRIVY_SARIF_OUTPUT: trivy-platform-ui.sarif
OSV_SARIF_OUTPUT: osv-scanner-platform-ui.sarif
SCA_MERGED_SARIF_OUTPUT: SCA-platform-ui-merged.sarif
# CVSS gate: fail at >= FAIL, warn in between. Override per project.
GATE_FAIL_THRESHOLD: "8.0"
GATE_WARN_THRESHOLD: "5.0"

steps:
- name: Check out repository
Expand All @@ -38,11 +41,7 @@ jobs:
key: ${{ runner.os }}-npm-v1-${{ hashFiles('**/package-lock.json') }}
restore-keys: ${{ runner.os }}-npm-v1-

- name: Install dependencies
run: |
npm ci

- name: Setup tools
- name: Setup tools and generate SBOM
run: bash ci/setup-tools.sh --install-tool trivy,osv-scanner --sbom-ecosystem npm

- name: Run SCA tools
Expand All @@ -51,15 +50,15 @@ jobs:
- name: Upload Trivy SARIF to GitHub Security tab
id: upload_trivy
if: always()
uses: github/codeql-action/upload-sarif@c35d1b164463ee62a100735382aaaa525c5d3496 #v2.25.6
uses: github/codeql-action/upload-sarif@4e94bd11f71e507f7f87df81788dff88d1dacbfb # v4.31.2
with:
sarif_file: ${{ env.TRIVY_SARIF_OUTPUT }}
category: trivy-app

- name: Upload OSV Scanner SARIF to GitHub Security tab
id: upload_osv
if: always()
uses: github/codeql-action/upload-sarif@c35d1b164463ee62a100735382aaaa525c5d3496 #v2.25.6
uses: github/codeql-action/upload-sarif@4e94bd11f71e507f7f87df81788dff88d1dacbfb # v4.31.2
with:
sarif_file: ${{ env.OSV_SARIF_OUTPUT }}
category: osv-scanner-app
Expand Down
40 changes: 40 additions & 0 deletions .github/workflows/secrets-scan.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: Secret Scanning (gitleaks)

on:
pull_request:
push:
branches:
- master
workflow_dispatch:

permissions:
contents: read
security-events: write

jobs:
gitleaks:
runs-on: ubuntu-latest
env:
GITLEAKS_SARIF_OUTPUT: gitleaks.sarif

steps:
- name: Check out repository
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3

- name: Install gitleaks
run: bash ci/setup-tools.sh --install-tool gitleaks

- name: Install and run gitleaks
run: >
gitleaks dir .
--config ci/suppress_gitleaks.toml
--redact
--report-format sarif
--report-path "$GITLEAKS_SARIF_OUTPUT"

- name: Upload SARIF to code scanning
if: always()
uses: github/codeql-action/upload-sarif@c35d1b164463ee62a100735382aaaa525c5d3496 #v2.25.6
with:
sarif_file: ${{ env.GITLEAKS_SARIF_OUTPUT }}
category: gitleaks
24 changes: 24 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
exclude: .bumpversion.cfg
- id: check-merge-conflict
exclude: LICENSE
- id: check-added-large-files
- id: check-yaml
- id: check-json
- id: pretty-format-json
args: ["--autofix"]
exclude: slack.json
- id: check-xml
- id: detect-private-key
- id: check-case-conflict
- id: mixed-line-ending

- repo: https://github.com/gitleaks/gitleaks
rev: v8.30.0
hooks:
- id: gitleaks
6 changes: 3 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Step 1: Build the Angular app
FROM node:22-alpine AS build
FROM node:22-alpine@sha256:c610fcdfb1d5b4740dd70c284ed3cb16bb857e0f7166196e36a5501df7a3aa32 AS build
WORKDIR /app

# Install dependencies with cache mount for npm cache
Expand Down Expand Up @@ -28,7 +28,7 @@ RUN --mount=type=cache,target=/app/.angular/cache \
npm run build -- --configuration ${BUILD_CONFIGURATION}

# Step 2: Use Nginx to serve the Angular app
FROM nginx:alpine-slim
FROM nginx:alpine-slim@sha256:45b82ed5f285b90d63df07ba70430fdd8f25624b416617d9e6dc93412b2006dc
RUN apk upgrade --no-cache \
&& apk add --no-cache gettext-envsubst
ENV PLATFORM_BACKEND_SERVER=platform-backend-service:8080 \
Expand All @@ -50,4 +50,4 @@ RUN chown -R nginx:nginx /usr/share/nginx/html /etc/nginx/conf.d /var/cache/ngin

USER nginx
EXPOSE 80
CMD ["/docker-entrypoint.sh"]
CMD ["/docker-entrypoint.sh"]
27 changes: 15 additions & 12 deletions ci/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,21 @@ Following the OWASP DevSecOps model, scanning is split into three independent pi

```
.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
└── 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)
├── publish_images.yml # publishes built images
└── ebrains.yml # EBRAINS-specific pipeline

ci/
├── 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.
Expand Down
14 changes: 6 additions & 8 deletions ci/container_scan.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@
import logging
import json
import argparse
from parse_sarif import evaluate

from parse_sarif import evaluate, GATE_FAIL_THRESHOLD, GATE_WARN_THRESHOLD

GREEN = '\033[92m'
RED = '\033[91m'
Expand Down Expand Up @@ -92,25 +91,24 @@ def handle_sca():
eval_result = evaluate(path)

if eval_result.gate_failed:
tool_status[name] = "FAILED" # this tool found CVSS >= 8.0
tool_status[name] = "FAILED"
gate_failed = True
elif eval_result.gate_warn:
tool_status[name] = "WARNING" # this tool found 5.0 <= CVSS < 8.0
tool_status[name] = "WARNING"
else:
tool_status[name] = "PASSED" # this tool found nothing >= 5.0
tool_status[name] = "PASSED"

# Print summary of results
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}")
logger.warning(f"[{name}]: {YELLOW}WARNING (findings between {GATE_WARN_THRESHOLD} and {GATE_FAIL_THRESHOLD}){RESET}")
elif status == "ERROR":
logger.error(f"[{name}]: {RED}ERROR (tool did not run correctly){RESET}")
else:
logger.error(f"[{name}]: {RED}FAILED (CVSS >= 8.0 found){RESET}")
logger.info(f"{BOLD}=========================================={RESET}\n")
logger.error(f"[{name}]: {RED}FAILED (CVSS >= {GATE_FAIL_THRESHOLD} found){RESET}")

# Exit with non-zero code if any tool failed the gate
if gate_failed:
Expand Down
8 changes: 6 additions & 2 deletions ci/parse_sarif.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import json
import os
from dataclasses import dataclass

GATE_FAIL_THRESHOLD = float(os.getenv("GATE_FAIL_THRESHOLD", "8.0"))
GATE_WARN_THRESHOLD = float(os.getenv("GATE_WARN_THRESHOLD", "5.0"))

@dataclass
class EvaluationResult:
gate_failed: bool
Expand Down Expand Up @@ -34,6 +38,6 @@ def evaluate(sarif_paths):
max_score = max(max_score, float(score))

return EvaluationResult(
gate_failed=max_score >= 8,
gate_warn=5 <= max_score < 8,
gate_failed=max_score >= GATE_FAIL_THRESHOLD,
gate_warn=GATE_WARN_THRESHOLD <= max_score < GATE_FAIL_THRESHOLD,
)
2 changes: 1 addition & 1 deletion ci/sast_scan.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
SEMGREP_CONFIG_RULESETS = os.getenv(
"SEMGREP_CONFIG_RULESETS",
" semgrep-rules/generic semgrep-rules/problem-based-packs semgrep-rules/bash "
" semgrep-rules/java auto semgrep-rules/yaml semgrep-rules/package_managers p/default "
" semgrep-rules/java auto semgrep-rules/yaml semgrep-rules/package_managers p/default"
).split()
OPENGREP_EXCLUDE = os.getenv(
"OPENGREP_EXCLUDE",
Expand Down
15 changes: 4 additions & 11 deletions ci/sca_scan.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import sys
import logging
import json
from parse_sarif import evaluate
from parse_sarif import evaluate, GATE_FAIL_THRESHOLD, GATE_WARN_THRESHOLD

GREEN = '\033[92m'
RED = '\033[91m'
Expand All @@ -27,15 +27,13 @@

def run_trivy():
cmd = [
"trivy", "sbom",
SBOM_PATH,
"trivy", "sbom", SBOM_PATH,
"--format", "sarif",
"--ignorefile", TRIVY_IGNOREFILE,
"--output", TRIVY_SARIF_OUTPUT
]
return subprocess.run(cmd).returncode


def run_osv_scanner():
cmd = [
"osv-scanner", "scan", "source",
Expand All @@ -49,7 +47,6 @@ def run_osv_scanner():
return 0 # OSV Scanner returns 1 if vulnerabilities are found, but we want to continue the pipeline
return exit_code


def merge_sarifs():
merged = {
"$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/main/sarif-2.1/schema/sarif-schema-2.1.0.json",
Expand All @@ -70,8 +67,6 @@ def merge_sarifs():

logger.info("SARIF files merged successfully.")



def main():

tools = {"trivy": run_trivy, "osv-scanner": run_osv_scanner}
Expand All @@ -90,7 +85,6 @@ def main():
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
Expand Down Expand Up @@ -120,12 +114,11 @@ def main():
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}")
logger.warning(f"[{name}]: {YELLOW}WARNING (findings between {GATE_WARN_THRESHOLD} and {GATE_FAIL_THRESHOLD}){RESET}")
elif status == "ERROR":
logger.error(f"[{name}]: {RED}ERROR (tool did not run correctly){RESET}")
else:
logger.error(f"[{name}]: {RED}FAILED (CVSS >= 8.0 found){RESET}")
logger.info(f"{BOLD}=========================================={RESET}\n")
logger.error(f"[{name}]: {RED}FAILED (CVSS >= {GATE_FAIL_THRESHOLD} found){RESET}")

# Exit with non-zero code if any tool failed the gate
if gate_failed:
Expand Down
Loading
Loading