Skip to content

Security scan

Security scan #70

Workflow file for this run

# Security scan workflow
#
# This workflow performs security scanning using multiple tools to identify vulnerabilities,
# secrets, configuration issues, and code quality problems across the entire codebase.
# It also scans GitHub Actions workflows for potential security issues.
#
# Security Tools:
# - Zizmor: GitHub Actions workflow security scanner
# - Bandit: Python security linter
# - Trivy: Vulnerability and misconfiguration scanner
# - Semgrep: Static analysis tool for finding bugs and security issues
# - ZAP: Dynamic application security testing (DAST) tool
# - Schemathesis: API fuzz testing tool
name: "Security scan"
on:
schedule:
# Run security checks every day at 2 AM UTC
- cron: "0 2 * * *"
workflow_dispatch:
push:
branches:
- main
- release/**
tags:
- v[0-9]+\.[0-9]+\.[0-9]+-RC[0-9]+
pull_request:
branches:
- main
- release/**
permissions: {} # No permissions by default
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
zizmor-scan:
runs-on: ${{ github.repository_owner == 'open-edge-platform' && 'overflow' || 'ubuntu-latest' }}
permissions:
contents: read
security-events: write # Needed to upload the results to code-scanning dashboard
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Run Zizmor scan
uses: open-edge-platform/geti-ci/actions/zizmor@e80098b3d180db37914f11ff6021f9fa34d0bb9f
with:
scan-scope: ${{ github.event_name == 'pull_request' && 'changed' || 'all' }}
severity-level: ${{ github.event_name == 'pull_request' && 'LOW' || 'LOW' }}
confidence-level: ${{ github.event_name == 'pull_request' && 'LOW' || 'LOW' }}
fail-on-findings: ${{ github.event_name == 'pull_request' && 'true' || 'false' }}
bandit-scan:
runs-on: ${{ github.repository_owner == 'open-edge-platform' && 'overflow' || 'ubuntu-latest' }}
permissions:
contents: read
security-events: write # Needed to upload the results to code-scanning dashboard
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Run Bandit scan
uses: open-edge-platform/geti-ci/actions/bandit@e80098b3d180db37914f11ff6021f9fa34d0bb9f
with:
scan-scope: ${{ github.event_name == 'pull_request' && 'changed' || 'all' }}
severity-level: ${{ github.event_name == 'pull_request' && 'HIGH' || 'LOW' }}
confidence-level: ${{ github.event_name == 'pull_request' && 'LOW' || 'LOW' }}
config_file: ".github/bandit_config.yml"
fail-on-findings: ${{ github.event_name == 'pull_request' && 'true' || 'false' }}
semgrep-scan:
runs-on: ${{ github.repository_owner == 'open-edge-platform' && 'overflow' || 'ubuntu-latest' }}
permissions:
contents: read
security-events: write # Needed to upload the results to code-scanning dashboard
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
fetch-depth: 0
- name: Run Semgrep scan
uses: open-edge-platform/geti-ci/actions/semgrep@e80098b3d180db37914f11ff6021f9fa34d0bb9f
with:
scan-scope: ${{ github.event_name == 'pull_request' && 'changed' || 'all' }}
severity: ${{ github.event_name == 'pull_request' && 'HIGH' || 'LOW' }}
fail-on-findings: ${{ github.event_name == 'pull_request' && 'true' || 'false' }}
trivy-scan:
if: github.event_name != 'pull_request'
runs-on: ${{ github.repository_owner == 'open-edge-platform' && 'overflow' || 'ubuntu-latest' }}
permissions:
contents: read
security-events: write # Needed to upload the results to code-scanning dashboard
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Run Trivy scan
id: trivy
uses: open-edge-platform/geti-ci/actions/trivy@e80098b3d180db37914f11ff6021f9fa34d0bb9f
with:
scan_type: "fs"
scan-scope: all
severity: "LOW"
scanners: "vuln,secret,config"
format: "sarif"
timeout: "15m"
ignore_unfixed: "true"
trivy-docker-image-scan:
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
runs-on: ${{ github.repository_owner == 'open-edge-platform' && 'overflow' || 'ubuntu-latest' }}
permissions:
contents: read
strategy:
matrix:
ai-device: [cpu, cuda, xpu]
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Install dependencies
uses: ./.github/actions/install-dependencies
with:
cleanup-runner: "true"
- name: Build docker image
working-directory: application
id: image-name
env:
DEVICE: ${{ matrix.ai-device }}
run: |
just build-target="${DEVICE}" docker-build-context="--no-cache" build-image
IMAGE_NAME=$(just --evaluate build-target="${DEVICE}" image-name)
echo "image=${IMAGE_NAME}" >> $GITHUB_OUTPUT
echo "Image: ${IMAGE_NAME}"
- name: Cleanup docker cache
run: docker builder prune -f
- name: Run Trivy scan
id: trivy
uses: open-edge-platform/geti-ci/actions/trivy@e80098b3d180db37914f11ff6021f9fa34d0bb9f
with:
artifact-name: "trivy-results-docker-${{ matrix.ai-device }}"
scan_type: "image"
scan_target: "${{ steps.image-name.outputs.image }}"
scan-scope: all
severity: "LOW"
scanners: "vuln,secret,config"
format: "table"
timeout: "15m"
ignore_unfixed: "true"
dast-scan:
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
runs-on: ${{ github.repository_owner == 'open-edge-platform' && 'overflow' || 'ubuntu-latest' }}
permissions:
contents: read
strategy:
matrix:
ai-device: [xpu]
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Install dependencies
uses: ./.github/actions/install-dependencies
with:
cleanup-runner: "true"
- name: Build docker image
working-directory: application
id: image-name
env:
DEVICE: ${{ matrix.ai-device }}
run: |
just build-target="${DEVICE}" docker-build-context="--no-cache" build-image
IMAGE_NAME=$(just --evaluate build-target="${DEVICE}" image-name)
echo "image=${IMAGE_NAME}" >> $GITHUB_OUTPUT
echo "Image: ${IMAGE_NAME}"
- name: Start container
env:
IMAGE_NAME: ${{ steps.image-name.outputs.image }}
run: |
CONTAINER_ID=$(docker run -d --rm --name test-container --publish 9100:9100 --env PORT=9100 --env HOST=0.0.0.0 ${IMAGE_NAME})
echo "Started container: $CONTAINER_ID"
echo "CONTAINER_ID=$CONTAINER_ID" >> $GITHUB_ENV
TEST_URL="localhost:9100"
# Wait for container to be ready (max 60 seconds)
echo "Waiting for container to be ready..."
for i in {1..12}; do
if curl -sf $TEST_URL/health > /dev/null 2>&1; then
echo "✅ Health check passed!"
break
fi
if [ $i -eq 12 ]; then
echo "❌ Health check failed after 60 seconds"
docker logs $CONTAINER_ID
exit 1
fi
echo "Attempt $i/12 - waiting 5 seconds..."
sleep 5
done
EXPECTED_TITLE="Geti Instant Learn"
echo "Checking HTML title..."
HTML_CONTENT=$(curl -sf $TEST_URL)
if echo "$HTML_CONTENT" | grep -q "<title>.*$EXPECTED_TITLE.*</title>"; then
echo "✅ HTML title check passed - found: $EXPECTED_TITLE"
else
echo "❌ HTML title check failed - expected title '$EXPECTED_TITLE' not found"
echo "Actual HTML content:"
echo "$HTML_CONTENT"
exit 1
fi
echo "All checks passed successfully!"
- name: Run ZAP API Scan
uses: zaproxy/action-api-scan@5158fe4d9d8fcc75ea204db81317cce7f9e5453d # v0.10.0
with:
target: "http://localhost:9100/api/openapi.json"
format: "openapi"
cmd_options: "-a"
allow_issue_writing: false
fail_action: false
artifact_name: zapapi
- name: Run ZAP Full Scan
uses: zaproxy/action-full-scan@3c58388149901b9a03b7718852c5ba889646c27c # v0.13.0
with:
target: "http://localhost:9100"
cmd_options: "-a -j"
allow_issue_writing: false
fail_action: false
artifact_name: zapfull
- name: Run Schemathesis Scan
uses: schemathesis/action@806cace2053cbbac93188e1281ff7da415643160 # v3
continue-on-error: true
with:
schema: "http://localhost:9100/api/openapi.json"
args: >-
--continue-on-failure
--report-dir schemathesis-report
--report junit
--report-junit-path schemathesis-report/junit.xml
- name: Upload Schemathesis report
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
if: always()
with:
name: schemathesis-report-${{ matrix.ai-device }}
path: schemathesis-report/junit.xml
- name: Re-check container health after testing
if: always()
run: |
TEST_URL="localhost:9100"
echo "Running post-scan health check..."
if curl -sf $TEST_URL/health > /dev/null 2>&1; then
echo "✅ Post-scan health check passed!"
else
echo "❌ Post-scan health check failed"
echo "=== Container logs ==="
docker logs $CONTAINER_ID || true
exit 1
fi
- name: Save container logs to file
if: always()
run: |
mkdir -p dast-scan-logs
if [ -n "$CONTAINER_ID" ]; then
echo "Saving logs for container $CONTAINER_ID"
docker logs "$CONTAINER_ID" > dast-scan-logs/container-$CONTAINER_ID.log 2>&1 || true
else
echo "CONTAINER_ID not set; attempting to capture any matching container logs" > dast-scan-logs/container-lookup.log
docker ps --filter "ancestor=geti-tune-${{ matrix.device }}" --format "{{.ID}} {{.Image}}" >> dast-scan-logs/container-lookup.log || true
fi
- name: Upload container logs
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
if: always()
with:
name: container-logs
path: dast-scan-logs/
- name: Stop and Cleanup Container
if: always()
run: |
if [ -n "${CONTAINER_ID}" ]; then
echo "Stopping container ${CONTAINER_ID}..."
docker stop ${CONTAINER_ID}
fi