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
268 changes: 268 additions & 0 deletions .github/workflows/dependency-track-syft.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,268 @@
name: Dependency-Track Syft Scan

on:
workflow_call:
inputs:
runner:
description: "Specify runner for the BOM workflow"
required: false
default: "dependency-track-runner"
type: string
secrets:
DTRACK_API_KEY:
required: true
workflow_dispatch:

concurrency:
group: dtrack-${{ github.workflow }}-syft-${{ github.ref }}
cancel-in-progress: true

jobs:
dependency-track-syft:
runs-on: ${{ inputs.runner }}
permissions:
contents: read
actions: read
security-events: write
steps:
- name: Resolve code scanning target (ref/sha)
id: code_scanning_target
run: |
set -euo pipefail
ref="${GITHUB_REF}"
sha="${GITHUB_SHA}"

pr_head_ref="$(jq -r '.pull_request.head.ref // empty' "$GITHUB_EVENT_PATH")"
pr_head_sha="$(jq -r '.pull_request.head.sha // empty' "$GITHUB_EVENT_PATH")"

if [ -n "$pr_head_ref" ] && [ -n "$pr_head_sha" ]; then
ref="refs/heads/$pr_head_ref"
sha="$pr_head_sha"
fi

echo "code_scanning_ref=$ref" >> "$GITHUB_OUTPUT"
echo "code_scanning_sha=$sha" >> "$GITHUB_OUTPUT"
echo "Resolved code scanning target: ref=$ref sha=$sha"

- name: Checkout caller repository
uses: actions/checkout@v4
with:
ref: ${{ steps.code_scanning_target.outputs.code_scanning_sha }}

- name: Checkout shared workflows repository
uses: actions/checkout@v4
with:
repository: EyeSeeTea/github-workflows
path: shared-workflows

- name: Read node version (fallback 20)
run: |
if [ -f .nvmrc ]; then
echo "NODE_VERSION=$(tr -d 'v' < .nvmrc)" >> "$GITHUB_ENV"
else
echo "NODE_VERSION=20" >> "$GITHUB_ENV"
fi

- name: Yarn install + SBOM (container)
run: |
podman run --rm \
-v "$GITHUB_WORKSPACE:/work" -w /work \
node:${NODE_VERSION}-bullseye \
bash -lc '
set -e
corepack enable
yarn install

# Install syft in the container
curl -sSfL https://raw.githubusercontent.com/anchore/syft/v1.42.2/install.sh \
| sh -s -- -b /usr/local/bin v1.42.2

# Generate CycloneDX JSON SBOM
syft . -o cyclonedx-json=bom_syft.json
'

- name: Upload BOM, analyze, and fetch metrics
id: dtrack_syft
env:
DTRACK_URL: ${{ vars.DTRACK_URL }}
DTRACK_API_KEY: ${{ secrets.DTRACK_API_KEY }}
PR_NUMBER: ${{ github.event.pull_request.number }}
run: |
set -euo pipefail
bash shared-workflows/scripts/upload_bom_and_fetch_metrics.sh \
--bom-file bom_syft.json \
--project-suffix syft

- name: Export SARIF findings (syft)
env:
DTRACK_URL: ${{ vars.DTRACK_URL }}
DTRACK_API_KEY: ${{ secrets.DTRACK_API_KEY }}
PROJECT_UUID: ${{ steps.dtrack_syft.outputs.project_uuid }}
run: |
set -euo pipefail

curl -sSf "$DTRACK_URL/api/v1/finding/project/$PROJECT_UUID?suppressed=false" \
-H "X-Api-Key: $DTRACK_API_KEY" \
-H "Accept: application/sarif+json" \
-o dtrack-syft.sarif

test -s dtrack-syft.sarif || { echo "::error::dtrack-syft.sarif is missing or empty"; exit 1; }
jq -e '.runs and (.runs | type == "array")' dtrack-syft.sarif >/dev/null

- name: Export VEX and VDR JSON for SARIF mapping (syft)
env:
DTRACK_URL: ${{ vars.DTRACK_URL }}
DTRACK_API_KEY: ${{ secrets.DTRACK_API_KEY }}
PROJECT_UUID: ${{ steps.dtrack_syft.outputs.project_uuid }}
run: |
set -euo pipefail

curl -sSf "$DTRACK_URL/api/v1/vex/cyclonedx/project/$PROJECT_UUID" \
-H "X-Api-Key: $DTRACK_API_KEY" \
-o vex_syft.json

curl -sSf "$DTRACK_URL/api/v1/bom/cyclonedx/project/$PROJECT_UUID?variant=vdr&format=JSON" \
-H "X-Api-Key: $DTRACK_API_KEY" \
-o vdr_syft.json

test -s vex_syft.json || { echo "::error::vex_syft.json is missing or empty"; exit 1; }
test -s vdr_syft.json || { echo "::error::vdr_syft.json is missing or empty"; exit 1; }

- name: Normalize SARIF for GitHub Code Scanning (syft)
run: |
set -euo pipefail

python3 shared-workflows/scripts/normalize_sarif.py \
--input-sarif dtrack-syft.sarif \
--output-sarif dtrack-syft.sarif \
--vdr vdr_syft.json \
--vex vex_syft.json \
--source syft \
--tool-name "OWASP Dependency-Track (syft)" \
--rule-id-namespace "syft::" \
--location-mode fallback \
--fallback-uri bom.json \
--fallback-line 1

jq -e '.runs[0].results[0].locations[0].physicalLocation.artifactLocation.uri' dtrack-syft.sarif >/dev/null

- name: Upload SARIF to GitHub Code Scanning (syft)
uses: github/codeql-action/upload-sarif@v4
with:
sarif_file: dtrack-syft.sarif
category: dependency-track-syft
ref: ${{ steps.code_scanning_target.outputs.code_scanning_ref }}
sha: ${{ steps.code_scanning_target.outputs.code_scanning_sha }}

- name: Compare with GitHub open alert instances (syft)
id: open_alerts_syft
env:
GH_TOKEN: ${{ github.token }}
TOOL_NAME: OWASP Dependency-Track (syft)
BASE_REF: ${{ github.base_ref }}
HEAD_REF_NAME: ${{ github.head_ref }}
run: |
set -euo pipefail
bash shared-workflows/scripts/report_open_code_scanning_alert_instances.sh \
--repo "$GITHUB_REPOSITORY" \
--tool-name "$TOOL_NAME" \
--head-ref "refs/heads/${HEAD_REF_NAME:-${GITHUB_REF_NAME}}" \
--base-ref "refs/heads/${BASE_REF:-${GITHUB_REF_NAME}}" \
--output-prefix "syft_open_alerts"

- name: Report code scanning summary (syft)
run: |
set -euo pipefail

sarif_uploaded_total="$(
jq -r '
[
.runs[]?.results[]?
| [
(.ruleId // ""),
(.properties.name // "unknown-package"),
(.properties.version // "")
]
| join("|")
]
| unique
| length
' dtrack-syft.sarif
)"
ui_open_total="${{ steps.open_alerts_syft.outputs.syft_open_alerts_head_instances_count }}"
ui_open_critical="${{ steps.open_alerts_syft.outputs.syft_open_alerts_head_critical_count }}"
ui_open_high="${{ steps.open_alerts_syft.outputs.syft_open_alerts_head_high_count }}"
ui_open_medium="${{ steps.open_alerts_syft.outputs.syft_open_alerts_head_medium_count }}"
ui_open_low="${{ steps.open_alerts_syft.outputs.syft_open_alerts_head_low_count }}"
ui_open_unknown="${{ steps.open_alerts_syft.outputs.syft_open_alerts_head_unknown_count }}"
ui_new_total="${{ steps.open_alerts_syft.outputs.syft_open_alerts_introduced_instances_count }}"
ui_new_critical="${{ steps.open_alerts_syft.outputs.syft_open_alerts_introduced_instances_critical_count }}"
ui_new_high="${{ steps.open_alerts_syft.outputs.syft_open_alerts_introduced_instances_high_count }}"
ui_new_medium="${{ steps.open_alerts_syft.outputs.syft_open_alerts_introduced_instances_medium_count }}"
ui_new_low="${{ steps.open_alerts_syft.outputs.syft_open_alerts_introduced_instances_low_count }}"
ui_new_unknown="${{ steps.open_alerts_syft.outputs.syft_open_alerts_introduced_instances_unknown_count }}"
ui_baseline_missing="${{ steps.open_alerts_syft.outputs.syft_open_alerts_baseline_missing }}"
branch_name="${GITHUB_HEAD_REF:-$GITHUB_REF_NAME}"
query="is:open branch:${branch_name} tool:\"OWASP Dependency-Track (syft)\""
branch_url="https://github.com/${GITHUB_REPOSITORY}/security/code-scanning?query=$(jq -rn --arg value "$query" '$value|@uri')"

sarif_uploaded_total="${sarif_uploaded_total:-0}"
ui_open_total="${ui_open_total:-0}"
ui_open_critical="${ui_open_critical:-0}"
ui_open_high="${ui_open_high:-0}"
ui_open_medium="${ui_open_medium:-0}"
ui_open_low="${ui_open_low:-0}"
ui_open_unknown="${ui_open_unknown:-0}"
ui_new_total="${ui_new_total:-0}"
ui_new_critical="${ui_new_critical:-0}"
ui_new_high="${ui_new_high:-0}"
ui_new_medium="${ui_new_medium:-0}"
ui_new_low="${ui_new_low:-0}"
ui_new_unknown="${ui_new_unknown:-0}"
ui_baseline_missing="${ui_baseline_missing:-false}"

echo "::notice::Dependency-Track Syft (GitHub alert instances) new vs base: critical=$ui_new_critical high=$ui_new_high total=$ui_new_total baseline_missing=$ui_baseline_missing"
echo "### Dependency-Track Syft code scanning" >> "$GITHUB_STEP_SUMMARY"
echo "- SARIF findings uploaded: $sarif_uploaded_total" >> "$GITHUB_STEP_SUMMARY"
echo "- Open alert instances in branch (GitHub UI):" >> "$GITHUB_STEP_SUMMARY"
echo " - critical: $ui_open_critical" >> "$GITHUB_STEP_SUMMARY"
echo " - high: $ui_open_high" >> "$GITHUB_STEP_SUMMARY"
echo " - medium: $ui_open_medium" >> "$GITHUB_STEP_SUMMARY"
echo " - low: $ui_open_low" >> "$GITHUB_STEP_SUMMARY"
echo " - unknown: $ui_open_unknown" >> "$GITHUB_STEP_SUMMARY"
echo " - total: $ui_open_total" >> "$GITHUB_STEP_SUMMARY"
echo "- New alert instances vs base (head - base):" >> "$GITHUB_STEP_SUMMARY"
echo " - critical: $ui_new_critical" >> "$GITHUB_STEP_SUMMARY"
echo " - high: $ui_new_high" >> "$GITHUB_STEP_SUMMARY"
echo " - medium: $ui_new_medium" >> "$GITHUB_STEP_SUMMARY"
echo " - low: $ui_new_low" >> "$GITHUB_STEP_SUMMARY"
echo " - unknown: $ui_new_unknown" >> "$GITHUB_STEP_SUMMARY"
echo " - total: $ui_new_total" >> "$GITHUB_STEP_SUMMARY"
echo "- Baseline missing for instance comparison (base has no open instances): $ui_baseline_missing" >> "$GITHUB_STEP_SUMMARY"
echo "- Branch alerts URL: $branch_url" >> "$GITHUB_STEP_SUMMARY"

- name: Enforce newly introduced vulnerability gate (critical/high)
run: |
set -euo pipefail

crit="${{ steps.open_alerts_syft.outputs.syft_open_alerts_introduced_instances_critical_count }}"
high="${{ steps.open_alerts_syft.outputs.syft_open_alerts_introduced_instances_high_count }}"
med="${{ steps.open_alerts_syft.outputs.syft_open_alerts_introduced_instances_medium_count }}"
low="${{ steps.open_alerts_syft.outputs.syft_open_alerts_introduced_instances_low_count }}"
branch_name="${GITHUB_HEAD_REF:-$GITHUB_REF_NAME}"
query="is:open branch:${branch_name} tool:\"OWASP Dependency-Track (syft)\""
branch_url="https://github.com/${GITHUB_REPOSITORY}/security/code-scanning?query=$(jq -rn --arg value "$query" '$value|@uri')"

crit="${crit:-0}"
high="${high:-0}"
med="${med:-0}"
low="${low:-0}"

echo "New alert instances detected vs base (head - base): critical=$crit high=$high medium=$med low=$low"

if [ "$crit" -gt 0 ] || [ "$high" -gt 0 ]; then
echo "::error::New critical/high alert instances detected vs base (critical=$crit, high=$high, medium=$med, low=$low). Review: $branch_url"
exit 1
fi

echo "No new critical/high alert instances detected vs base"
Loading