diff --git a/.github/workflows/compare-vulnerabilities-PR.yml b/.github/workflows/compare-vulnerabilities-PR.yml
new file mode 100644
index 00000000..c9e27deb
--- /dev/null
+++ b/.github/workflows/compare-vulnerabilities-PR.yml
@@ -0,0 +1,55 @@
+name: PR – Vulnerability guard
+run-name: 'Compare vulnerabilities between (Base) ${{ github.event.pull_request.base.ref }} and (Head) ${{ github.event.pull_request.head.ref }} (${{ github.event.pull_request.head.sha }}) by @${{ github.actor }}'
+on:
+ pull_request:
+ types: [ opened, synchronize, reopened ]
+
+permissions:
+ contents: read
+ pull-requests: write # needed to create/update PR comments
+
+jobs:
+ compare-branches:
+ runs-on: ${{ vars.UBUNTU_VERSION }}
+
+ steps:
+ # 1) Checkout head branch only
+ - name: Checkout PR head
+ uses: actions/checkout@v4
+ with:
+ ref: ${{ github.event.pull_request.head.sha }}
+ fetch-depth: 0
+ fetch-tags: true
+
+ - name: Set up JDK 8
+ uses: actions/setup-java@v4
+ with:
+ distribution: 'temurin'
+ java-version: '8'
+ cache: 'maven'
+
+ #
+ # - name: Ensure base is available
+ # run: |
+ # git fetch origin ${{ github.event.pull_request.base.ref }}:refs/remotes/origin/${{ github.event.pull_request.base.ref }} --tags --prune
+
+ # 3) Run the action
+ - name: Vulnerability Diff (Syft+Grype)
+ uses: sec-open/vuln-diff-action@v2.0.0-alpha.1
+ with:
+ base_ref: ${{ github.event.pull_request.base.ref }}
+ head_ref: ${{ github.event.pull_request.head.sha }}
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+ slack_webhook_url: ${{ secrets.SLACK_SECURITY_WEBHOOK_URL }}
+ html_logo_url: "https://zettagenomics.com/wp-content/uploads/2022/10/Zetta-reversed-out-full-logo-dark-background.png"
+
+# write_summary: "true"
+# upload_artifact: "true"
+# min_severity: "LOW"
+# report_html: "true"
+# report_pdf: "true"
+# pr_comment: "true"
+# artifact_name: "vulnerability-diff-${{ github.event.inputs.branch_a }}-vs-${{ github.event.inputs.branch_b }}"
+# pr_comment_marker: ""
+
+# title_logo_url: "https://zettagenomics.com/wp-content/uploads/2022/10/Zetta-reversed-out-full-logo-dark-background.png"
\ No newline at end of file
diff --git a/.github/workflows/compare-vulnerabilities.yml b/.github/workflows/compare-vulnerabilities.yml
index 0c7a98cd..70650b0b 100644
--- a/.github/workflows/compare-vulnerabilities.yml
+++ b/.github/workflows/compare-vulnerabilities.yml
@@ -1,273 +1,75 @@
-name: Compare vulnerabilities (Syft SBOM -> Grype) between two branches (robust)
-
+name: Compare vulnerabilities (Syft SBOM -> Grype) between two branches
+run-name: 'Compare vulnerabilities between ${{ inputs.branch_a }} (base) and ${{ inputs.branch_b }} (head) by @${{ github.actor }}'
on:
+ workflow_call:
+ inputs:
+ branch_a:
+ type: string
+ description: 'Base branch (e.g. develop)'
+ required: true
+ branch_b:
+ type: string
+ description: 'Head branch (e.g. TASK-1234)'
+ required: true
+ secrets:
+ SLACK_SECURITY_WEBHOOK_URL:
+ required: false
+
workflow_dispatch:
inputs:
branch_a:
- description: 'First branch to compare (e.g. main)'
+ description: 'Base branch (e.g. develop)'
required: true
default: 'develop'
branch_b:
- description: 'Second branch to compare (e.g. feature/fix-branch)'
+ description: 'Head branch (e.g. TASK-1234)'
required: true
- default: ''
jobs:
- compare-sbom-grype:
- runs-on: ubuntu-latest
- env:
- REPORT_DIR: reports
- steps:
- - name: Prepare workspace
- run: |
- set -euo pipefail
- mkdir -p "${REPORT_DIR}"
-
- - name: Checkout branch A
- uses: actions/checkout@v4
- with:
- ref: ${{ github.event.inputs.branch_a }}
- path: branchA
- fetch-depth: 0
+ compare-branches:
+ runs-on: ${{ vars.UBUNTU_VERSION }}
- - name: Checkout branch B
+ steps:
+ - name: Checkout head branch
uses: actions/checkout@v4
with:
ref: ${{ github.event.inputs.branch_b }}
- path: branchB
fetch-depth: 0
+ fetch-tags: true
+ - name: Restore Puppeteer cache
+ uses: actions/cache@v4
+ with:
+ path: ~/.cache/puppeteer
+ key: puppeteer-${{ runner.os }}
- - name: Install dependencies (jq, unzip)
- run: |
- sudo apt-get update
- sudo apt-get install -y jq unzip
-
- - name: Install Syft
- run: |
- set -euo pipefail
- curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sudo sh -s -- -b /usr/local/bin
- syft version
-
- - name: Install Grype
- run: |
- set -euo pipefail
- curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sudo sh -s -- -b /usr/local/bin
- grype version
-
- - name: Generate SBOM for branch A (CycloneDX JSON)
- run: |
- set -euo pipefail
- syft dir:./branchA -o cyclonedx-json="${REPORT_DIR}/branchA-sbom.cdx.json"
-
- - name: Generate SBOM for branch B (CycloneDX JSON)
- run: |
- set -euo pipefail
- syft dir:./branchB -o cyclonedx-json="${REPORT_DIR}/branchB-sbom.cdx.json"
-
- - name: Scan SBOM with Grype (branch A)
- run: |
- set -euo pipefail
- grype sbom:"${REPORT_DIR}/branchA-sbom.cdx.json" -o json > "${REPORT_DIR}/branchA-grype.json"
-
- - name: Scan SBOM with Grype (branch B)
- run: |
- set -euo pipefail
- grype sbom:"${REPORT_DIR}/branchB-sbom.cdx.json" -o json > "${REPORT_DIR}/branchB-grype.json"
-
- - name: "debug - show match counts and sample (for troubleshooting)"
- id: debug
- run: |
- set -euo pipefail
- echo "---- branchA summary ----"
- if [ -f "${REPORT_DIR}/branchA-grype.json" ]; then
- jq '.matches | length' "${REPORT_DIR}/branchA-grype.json" || true
- jq '.matches[0:5]' "${REPORT_DIR}/branchA-grype.json" || true
- else
- echo "branchA-grype.json missing"
- fi
- echo "---- branchB summary ----"
- if [ -f "${REPORT_DIR}/branchB-grype.json" ]; then
- jq '.matches | length' "${REPORT_DIR}/branchB-grype.json" || true
- jq '.matches[0:5]' "${REPORT_DIR}/branchB-grype.json" || true
- else
- echo "branchB-grype.json missing"
- fi
-
- - name: Generate comparison report (table + full MD)
- id: gen_report
- run: |
- set -euo pipefail
- A_BRANCH="${{ github.event.inputs.branch_a }}"
- B_BRANCH="${{ github.event.inputs.branch_b }}"
- A_GRYPE="${REPORT_DIR}/branchA-grype.json"
- B_GRYPE="${REPORT_DIR}/branchB-grype.json"
- OUT="${REPORT_DIR}/comparison-report.md"
- mkdir -p "$(dirname "$OUT")"
-
- # comprueba que los JSON existen
- if [ ! -s "$A_GRYPE" ]; then
- echo "ERROR: ${A_GRYPE} not found or empty" >&2
- exit 1
- fi
- if [ ! -s "$B_GRYPE" ]; then
- echo "ERROR: ${B_GRYPE} not found or empty" >&2
- exit 1
- fi
-
- # --- extraer entradas formateadas: ID|pkg:version|SEVERITY ---
- jq -r '[ .matches[]? as $m |
- ($m.vulnerability.id // "-") as $id |
- ( if ($m.artifact | type) == "object"
- then (($m.artifact.name // $m.artifact.id // "-") + ":" + ($m.artifact.version // "-"))
- else (($m.artifact // "-") + ":" + "-")
- end) as $pv |
- (($id + "|" + $pv + "|" + (($m.vulnerability.severity // "") | ascii_upcase)))
- ] | .[]' "$A_GRYPE" | sort -u > /tmp/a_entries.txt || true
-
- jq -r '[ .matches[]? as $m |
- ($m.vulnerability.id // "-") as $id |
- ( if ($m.artifact | type) == "object"
- then (($m.artifact.name // $m.artifact.id // "-") + ":" + ($m.artifact.version // "-"))
- else (($m.artifact // "-") + ":" + "-")
- end) as $pv |
- (($id + "|" + $pv + "|" + (($m.vulnerability.severity // "") | ascii_upcase)))
- ] | .[]' "$B_GRYPE" | sort -u > /tmp/b_entries.txt || true
-
- # union de entradas (unique)
- cat /tmp/a_entries.txt /tmp/b_entries.txt | sort -u > /tmp/all_entries.txt || true
-
- # --- crear archivo ordenado por severidad desc (rank) y luego por ID ---
- awk -F'|' '
- BEGIN {
- map["CRITICAL"]=5; map["HIGH"]=4; map["MEDIUM"]=3; map["LOW"]=2; map["UNKNOWN"]=1;
- }
- {
- id=$1; pv=$2; sev=toupper($3);
- rank = (sev in map ? map[sev] : 0);
- # output: rank|sev|id|pv
- printf("%d|%s|%s|%s\n", rank, sev, id, pv);
- }
- ' /tmp/all_entries.txt | sort -t'|' -k1,1nr -k3,3 | cut -d'|' -f2- > /tmp/all_sorted.txt || true
-
- # --- START MD FILE ---
- echo "# Vulnerability comparison: ${A_BRANCH} **vs** ${B_BRANCH}" > "${OUT}"
- echo "" >> "${OUT}"
-
- # --- TABLE requested: Severity | VulnerabilityID | package:version | branches ---
- echo "| Severity | VulnerabilityID | package:version | branches |" >> "${OUT}"
- echo "|---|---|---|---|" >> "${OUT}"
-
- if [ -s /tmp/all_sorted.txt ]; then
- while IFS= read -r line; do
- # line format: SEV|ID|PKG:VER
- sev=$(echo "$line" | awk -F'|' '{print $1}')
- id=$(echo "$line" | awk -F'|' '{print $2}')
- pv=$(echo "$line" | awk -F'|' '{print $3}')
-
- inA=0; inB=0
- # membership checks use original a_entries/b_entries (ID|pkg|sev)
- entry="${id}|${pv}|${sev}"
- if grep -Fxq "$entry" /tmp/a_entries.txt 2>/dev/null; then inA=1; fi
- if grep -Fxq "$entry" /tmp/b_entries.txt 2>/dev/null; then inB=1; fi
-
- if [ "$inA" -eq 1 ] && [ "$inB" -eq 1 ]; then
- branches="**BOTH**"
- elif [ "$inA" -eq 1 ]; then
- branches="${A_BRANCH}"
- else
- branches="${B_BRANCH}"
- fi
-
- echo "| ${sev} | ${id} | ${pv} | ${branches} |" >> "${OUT}"
- done < /tmp/all_sorted.txt
- else
- echo "| - | - | - | - |" >> "${OUT}"
- echo "" >> "${OUT}"
- fi
-
- echo "" >> "${OUT}"
- # --- Totals y resto del MD (se mantienen para contexto) ---
- totalA=$(jq -r '[ .matches[]?.vulnerability?.id ] | unique | length' "${A_GRYPE}" 2>/dev/null || echo 0)
- totalB=$(jq -r '[ .matches[]?.vulnerability?.id ] | unique | length' "${B_GRYPE}" 2>/dev/null || echo 0)
- echo "- **Total unique vulnerability IDs**: ${totalA} (${A_BRANCH}) | ${totalB} (${B_BRANCH})" >> "${OUT}"
- echo "" >> "${OUT}"
-
- # tabla de severidad (como antes)
- echo "| Severity | ${A_BRANCH} | ${B_BRANCH} |" >> "${OUT}"
- echo "|---:|---:|---:|" >> "${OUT}"
- for sev in CRITICAL HIGH MEDIUM LOW UNKNOWN; do
- ca=$(jq --arg s "$sev" '[ .matches[]?.vulnerability? | select((.severity // "") | ascii_upcase == $s) | .id ] | unique | length' "${A_GRYPE}" 2>/dev/null || echo 0)
- cb=$(jq --arg s "$sev" '[ .matches[]?.vulnerability? | select((.severity // "") | ascii_upcase == $s) | .id ] | unique | length' "${B_GRYPE}" 2>/dev/null || echo 0)
- echo "| $sev | $ca | $cb |" >> "${OUT}"
- done
-
- echo "" >> "${OUT}"
- echo "----" >> "${OUT}"
- echo "Artifacts included:" >> "${OUT}"
- echo "- ${REPORT_DIR}/branchA-sbom.cdx.json" >> "${OUT}"
- echo "- ${REPORT_DIR}/branchB-sbom.cdx.json" >> "${OUT}"
- echo "- ${REPORT_DIR}/branchA-grype.json" >> "${OUT}"
- echo "- ${REPORT_DIR}/branchB-grype.json" >> "${OUT}"
- echo "- ${REPORT_DIR}/comparison-report.md (this file)" >> "${OUT}"
-
-
- - name: Create ZIP of reports
- run: |
- set -euo pipefail
- cd "${REPORT_DIR}"
- zip -r comparison-artifacts.zip . || true
+ - name: Set up JDK 8
+ uses: actions/setup-java@v4
+ with:
+ distribution: 'temurin'
+ java-version: '8'
+ cache: 'maven'
+ # 3) Run the action
+ - name: Vulnerability Diff (Syft+Grype)
+ uses: sec-open/vuln-diff-action@v2.0.0-alpha.3
+ with:
+ base_ref: ${{ github.event.inputs.branch_a }} # pass 'develop'
+ head_ref: ${{ github.event.inputs.branch_b }} # pass 'TASK-7908'
+ html_logo_url: "https://zettagenomics.com/wp-content/uploads/2022/10/Zetta-reversed-out-full-logo-dark-background.png"
- - name: "Publish table to GitHub Actions summary (only the table, sorted)"
+ - name: Save Puppeteer cache
if: always()
- run: |
- set -euo pipefail
- SUMMARY="$GITHUB_STEP_SUMMARY"
- A_BRANCH="${{ github.event.inputs.branch_a }}"
- B_BRANCH="${{ github.event.inputs.branch_b }}"
-
- # Si no hay sorted file, intenta generarlo a partir de /tmp/all_entries.txt
- if [ ! -s /tmp/all_sorted.txt ] && [ -s /tmp/all_entries.txt ]; then
- awk -F'|' '
- BEGIN { map["CRITICAL"]=5; map["HIGH"]=4; map["MEDIUM"]=3; map["LOW"]=2; map["UNKNOWN"]=1; }
- {
- id=$1; pv=$2; sev=toupper($3);
- rank = (sev in map ? map[sev] : 0);
- printf("%d|%s|%s|%s\n", rank, sev, id, pv);
- }
- ' /tmp/all_entries.txt | sort -t'|' -k1,1nr -k3,3 | cut -d'|' -f2- > /tmp/all_sorted.txt || true
- fi
-
- # Header table for summary
- echo "| Severity | VulnerabilityID | package:version | branches |" >> "$SUMMARY"
- echo "|---|---|---|---|" >> "$SUMMARY"
-
- if [ -s /tmp/all_sorted.txt ]; then
- while IFS= read -r line; do
- sev=$(echo "$line" | awk -F'|' '{print $1}')
- id=$(echo "$line" | awk -F'|' '{print $2}')
- pv=$(echo "$line" | awk -F'|' '{print $3}')
-
- inA=0; inB=0
- entry="${id}|${pv}|${sev}"
- if grep -Fxq "$entry" /tmp/a_entries.txt 2>/dev/null; then inA=1; fi
- if grep -Fxq "$entry" /tmp/b_entries.txt 2>/dev/null; then inB=1; fi
+ uses: actions/cache@v4
+ with:
+ path: ~/.cache/puppeteer
+ key: puppeteer-${{ runner.os }}
- if [ "$inA" -eq 1 ] && [ "$inB" -eq 1 ]; then
- branches="**BOTH**"
- elif [ "$inA" -eq 1 ]; then
- branches="${A_BRANCH}"
- else
- branches="${B_BRANCH}"
- fi
- echo "| ${sev} | ${id} | ${pv} | ${branches} |" >> "$SUMMARY"
- done < /tmp/all_sorted.txt
- else
- echo "| - | - | - | - |" >> "$SUMMARY"
- fi
+# build_command: ""
+# write_summary: "true"
+# upload_artifact: "true"
+# artifact_name: "vulnerability-diff-${{ github.event.inputs.branch_a }}-vs-${{ github.event.inputs.branch_b }}"
+# report_html: "true"
+# report_pdf: "true"
+# min_severity: "LOW"
+# title_logo_url: "https://zettagenomics.com/wp-content/uploads/2022/10/Zetta-reversed-out-full-logo-dark-background.png"
- - name: Upload artifacts (reports)
- uses: actions/upload-artifact@v4
- with:
- name: vuln-comparison-${{ github.run_id }}-${{ github.event.inputs.branch_a }}-vs-${{ github.event.inputs.branch_b }}-$(date +%s)
- path: ${{ env.REPORT_DIR }}
diff --git a/pom.xml b/pom.xml
index eb71ba27..79eb1f45 100644
--- a/pom.xml
+++ b/pom.xml
@@ -18,11 +18,11 @@
commons-datastore
-
+
2.14.3
3.14.0
1.7.36
- 1.7.7
+ 1.11.4
4.11.5
8.8.2
1.69