chore(deps): update dependency @playwright/mcp to v0.0.72 (#596) #938
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Build MCP Server Containers | |
| on: | |
| push: | |
| branches: [ main ] | |
| paths: | |
| - 'npx/**/*.yaml' | |
| - 'uvx/**/*.yaml' | |
| - 'go/**/*.yaml' | |
| - 'cmd/dockhand/**' | |
| - 'go.mod' | |
| - 'go.sum' | |
| pull_request: | |
| branches: [ main ] | |
| paths: | |
| - 'npx/**/*.yaml' | |
| - 'uvx/**/*.yaml' | |
| - 'go/**/*.yaml' | |
| - 'cmd/dockhand/**' | |
| - 'go.mod' | |
| - 'go.sum' | |
| workflow_dispatch: | |
| permissions: {} | |
| env: | |
| REGISTRY: ghcr.io | |
| IMAGE_NAME: ${{ github.repository }} | |
| jobs: | |
| discover-configs: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| outputs: | |
| configs: ${{ steps.find-configs.outputs.configs }} | |
| changed-configs: ${{ steps.find-configs.outputs.changed-configs }} | |
| scan-configs: ${{ steps.find-configs.outputs.scan-configs }} | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 | |
| with: | |
| fetch-depth: 0 # Need full history for change detection | |
| - name: Find configuration files to build | |
| id: find-configs | |
| env: | |
| EVENT_NAME: ${{ github.event_name }} | |
| BASE_REF: ${{ github.base_ref }} | |
| run: | | |
| # Find all spec.yaml files in protocol directories | |
| all_configs=$(find npx uvx go -name "spec.yaml" -type f 2>/dev/null | sort) | |
| if [ "$EVENT_NAME" == "workflow_dispatch" ]; then | |
| # For manual triggers, build all configs | |
| configs_to_build="$all_configs" | |
| configs_to_scan="$all_configs" | |
| echo "Manual trigger - building all configurations" | |
| elif [ "$EVENT_NAME" == "pull_request" ]; then | |
| # For PRs, build configs that changed compared to target branch | |
| changed_files=$(git diff --name-only origin/"$BASE_REF"...HEAD) | |
| configs_to_build="" | |
| configs_to_scan="" | |
| # Find configs whose spec.yaml files actually changed | |
| for config in $all_configs; do | |
| config_dir=$(dirname "$config") | |
| if echo "$changed_files" | grep -q "^$config$" || echo "$changed_files" | grep -q "^$config_dir/"; then | |
| configs_to_build="$configs_to_build$config"$'\n' | |
| configs_to_scan="$configs_to_scan$config"$'\n' | |
| fi | |
| done | |
| # If dockhand source, go.mod, or go.sum changed, rebuild all containers | |
| # but only scan the spec.yaml files that actually changed | |
| if echo "$changed_files" | grep -E "(cmd/dockhand/|go\.mod|go\.sum)"; then | |
| echo "Core files changed - rebuilding all containers (scanning only changed specs)" | |
| configs_to_build="$all_configs" | |
| fi | |
| else | |
| # For pushes to main, build configs that changed in this push | |
| changed_files=$(git diff --name-only HEAD~1..HEAD) | |
| configs_to_build="" | |
| configs_to_scan="" | |
| # Find configs whose spec.yaml files actually changed | |
| for config in $all_configs; do | |
| config_dir=$(dirname "$config") | |
| if echo "$changed_files" | grep -q "^$config$" || echo "$changed_files" | grep -q "^$config_dir/"; then | |
| configs_to_build="$configs_to_build$config"$'\n' | |
| configs_to_scan="$configs_to_scan$config"$'\n' | |
| fi | |
| done | |
| # If dockhand source, go.mod, or go.sum changed, rebuild all containers | |
| # but only scan the spec.yaml files that actually changed | |
| if echo "$changed_files" | grep -E "(cmd/dockhand/|go\.mod|go\.sum)"; then | |
| echo "Core files changed - rebuilding all containers (scanning only changed specs)" | |
| configs_to_build="$all_configs" | |
| fi | |
| fi | |
| # Convert to JSON array, filtering out empty lines | |
| configs_json=$(echo "$configs_to_build" | grep -v '^$' | jq -R -s -c 'split("\n")[:-1]') | |
| scan_configs_json=$(echo "$configs_to_scan" | grep -v '^$' | jq -R -s -c 'split("\n")[:-1]') | |
| all_configs_json=$(echo "$all_configs" | jq -R -s -c 'split("\n")[:-1]') | |
| echo "configs=$all_configs_json" >> $GITHUB_OUTPUT | |
| echo "changed-configs=$configs_json" >> $GITHUB_OUTPUT | |
| echo "scan-configs=$scan_configs_json" >> $GITHUB_OUTPUT | |
| echo "All configurations: $all_configs_json" | |
| echo "Configurations to build: $configs_json" | |
| verify-provenance: | |
| needs: discover-configs | |
| runs-on: ubuntu-latest | |
| # Verify package provenance when we have configs to build | |
| if: ${{ needs.discover-configs.outputs.changed-configs != '[]' }} | |
| strategy: | |
| matrix: | |
| config: ${{ fromJson(needs.discover-configs.outputs.changed-configs) }} | |
| fail-fast: false | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 | |
| - name: Set up Go | |
| uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6 | |
| with: | |
| go-version-file: 'go.mod' | |
| - name: Extract metadata from config | |
| id: meta | |
| env: | |
| CONFIG_FILE: ${{ matrix.config }} | |
| run: | | |
| config_file="$CONFIG_FILE" | |
| protocol=$(echo "$config_file" | cut -d'/' -f1) | |
| server_name=$(echo "$config_file" | cut -d'/' -f2) | |
| echo "config_file=$config_file" >> $GITHUB_OUTPUT | |
| echo "protocol=$protocol" >> $GITHUB_OUTPUT | |
| echo "server_name=$server_name" >> $GITHUB_OUTPUT | |
| - name: Build dockhand | |
| run: go build -o /tmp/dockhand ./cmd/dockhand | |
| - name: Verify package provenance | |
| id: provenance | |
| env: | |
| SERVER_NAME: ${{ steps.meta.outputs.server_name }} | |
| CONFIG_FILE: ${{ matrix.config }} | |
| run: | | |
| echo "🔍 Verifying package provenance for $SERVER_NAME" | |
| # Run provenance verification | |
| if /tmp/dockhand verify-provenance -c "$CONFIG_FILE" > "/tmp/provenance-${SERVER_NAME}.txt" 2>&1; then | |
| echo "provenance_passed=true" >> $GITHUB_OUTPUT | |
| echo "✅ Provenance verification passed" >> $GITHUB_STEP_SUMMARY | |
| else | |
| echo "provenance_passed=false" >> $GITHUB_OUTPUT | |
| echo "⚠️ Provenance verification failed or package has no provenance" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| # Display results | |
| cat "/tmp/provenance-${SERVER_NAME}.txt" | |
| # Save for artifact | |
| cp "/tmp/provenance-${SERVER_NAME}.txt" . | |
| - name: Upload provenance verification results | |
| if: always() | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 | |
| with: | |
| name: provenance-${{ steps.meta.outputs.server_name }} | |
| path: provenance-${{ steps.meta.outputs.server_name }}.txt | |
| retention-days: 30 | |
| mcp-security-scan: | |
| needs: [discover-configs, verify-provenance] | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| # Only scan configs whose spec.yaml files actually changed | |
| # (not all configs when only go.mod/dockhand changed, since scans don't use dockhand) | |
| if: ${{ needs.discover-configs.outputs.scan-configs != '[]' }} | |
| strategy: | |
| matrix: | |
| config: ${{ fromJson(needs.discover-configs.outputs.scan-configs) }} | |
| fail-fast: false | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 | |
| - name: Set up Python | |
| uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 | |
| with: | |
| python-version: '3.13' | |
| - name: Set up uv | |
| uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 | |
| with: | |
| enable-cache: true | |
| - name: Install dependencies | |
| id: install-deps | |
| run: | | |
| # Install pinned Python dependencies from requirements.txt | |
| uv pip install --system -r scripts/mcp-scan/requirements.txt | |
| # Verify installation | |
| mcp-scanner --help || true | |
| # Capture scanner version for attestation | |
| SCANNER_VERSION=$(uv pip show cisco-ai-mcp-scanner | grep "^Version:" | cut -d' ' -f2) | |
| echo "scanner_version=$SCANNER_VERSION" >> $GITHUB_OUTPUT | |
| echo "Installed mcp-scanner version: $SCANNER_VERSION" | |
| - name: Extract metadata from config | |
| id: meta | |
| env: | |
| CONFIG_FILE: ${{ matrix.config }} | |
| run: | | |
| config_file="$CONFIG_FILE" | |
| # Extract protocol and server name from path like "npx/context7/spec.yaml" | |
| protocol=$(echo "$config_file" | cut -d'/' -f1) | |
| server_name=$(echo "$config_file" | cut -d'/' -f2) | |
| echo "config_file=$config_file" >> $GITHUB_OUTPUT | |
| echo "protocol=$protocol" >> $GITHUB_OUTPUT | |
| echo "server_name=$server_name" >> $GITHUB_OUTPUT | |
| - name: Pre-install npm package | |
| if: steps.meta.outputs.protocol == 'npx' | |
| env: | |
| CONFIG_FILE: ${{ matrix.config }} | |
| run: | | |
| # mcp-scanner has a 60s timeout for server startup. npx downloading | |
| # a package can exceed this, so pre-install it globally first. | |
| package=$(python3 -c " | |
| import yaml | |
| with open('$CONFIG_FILE') as f: | |
| data = yaml.safe_load(f) | |
| spec = data['spec'] | |
| print(spec['package'] + '@' + spec.get('version', 'latest')) | |
| ") | |
| echo "Pre-installing $package..." | |
| npm install --global "$package" | |
| - name: Run MCP Security Scan | |
| id: scan | |
| env: | |
| MCP_SCANNER_ENABLE_LLM: ${{ vars.MCP_SCANNER_ENABLE_LLM || 'false' }} | |
| MCP_SCANNER_LLM_API_KEY: ${{ secrets.MCP_SCANNER_LLM_API_KEY }} | |
| MCP_SCANNER_LLM_MODEL: ${{ vars.MCP_SCANNER_LLM_MODEL || '' }} | |
| # Severity threshold for blocking. Findings below this severity are | |
| # surfaced as warnings but do not fail the job. One of: INFO, LOW, | |
| # MEDIUM, HIGH, CRITICAL. Tune via PR. | |
| MCP_SCANNER_BLOCK_SEVERITY: HIGH | |
| SERVER_NAME: ${{ steps.meta.outputs.server_name }} | |
| PROTOCOL: ${{ steps.meta.outputs.protocol }} | |
| CONFIG_FILE: ${{ matrix.config }} | |
| SCANNER_VERSION: ${{ steps.install-deps.outputs.scanner_version }} | |
| run: | | |
| echo "🔍 Scanning MCP server: $SERVER_NAME" | |
| # Generate config (outputs JSON with command/args/mock_env) | |
| config_json=$(python3 scripts/mcp-scan/generate_mcp_config.py \ | |
| "$CONFIG_FILE" \ | |
| "$PROTOCOL" \ | |
| "$SERVER_NAME") | |
| # Write config to file for run_scan.py | |
| scan_config="/tmp/scan-config-${SERVER_NAME}.json" | |
| echo "$config_json" > "$scan_config" | |
| # Run scan using Cisco AI Defense mcp-scanner | |
| # Note: stderr is redirected to a separate file to avoid corrupting JSON output | |
| scan_output="/tmp/mcp-scan-${SERVER_NAME}.json" | |
| scan_stderr="/tmp/mcp-scan-${SERVER_NAME}.stderr" | |
| if python3 scripts/mcp-scan/run_scan.py --config "$scan_config" \ | |
| > "$scan_output" 2> "$scan_stderr"; then | |
| echo "scan_passed=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "scan_passed=false" >> $GITHUB_OUTPUT | |
| # Show stderr in logs for debugging | |
| if [ -s "$scan_stderr" ]; then | |
| echo "Scanner stderr output:" | |
| cat "$scan_stderr" | |
| fi | |
| fi | |
| # Process results (pass the config file to check for allowed issues) | |
| python3 scripts/mcp-scan/process_scan_results.py \ | |
| "$scan_output" \ | |
| "$SERVER_NAME" \ | |
| "$CONFIG_FILE" \ | |
| > scan-summary.json | |
| # Copy scan output to current directory for artifact upload | |
| cp "$scan_output" "mcp-scan-${SERVER_NAME}.json" | |
| # Save scanner version for attestation (from install-deps step) | |
| echo "$SCANNER_VERSION" > scanner-version.txt | |
| - name: Upload scan results | |
| if: always() | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 | |
| with: | |
| name: mcp-scan-${{ steps.meta.outputs.server_name }} | |
| path: | | |
| mcp-scan-${{ steps.meta.outputs.server_name }}.json | |
| scan-summary.json | |
| scanner-version.txt | |
| retention-days: 30 | |
| build-containers: | |
| needs: [discover-configs, verify-provenance, mcp-security-scan] | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 60 | |
| # Only proceed if security scans passed or were skipped (provenance check is informational only) | |
| if: ${{ needs.discover-configs.outputs.changed-configs != '[]' && (needs.mcp-security-scan.result == 'success' || needs.mcp-security-scan.result == 'skipped') }} | |
| strategy: | |
| matrix: | |
| config: ${{ fromJson(needs.discover-configs.outputs.changed-configs) }} | |
| fail-fast: false | |
| permissions: | |
| contents: read | |
| packages: write | |
| id-token: write # Needed for OIDC token (sigstore) | |
| attestations: write # Needed for attestations | |
| security-events: write # Needed for Grype SARIF upload | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 | |
| - name: Set up Go | |
| uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6 | |
| with: | |
| go-version-file: 'go.mod' | |
| - name: Set up QEMU | |
| uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4 | |
| - name: Install Cosign | |
| uses: sigstore/cosign-installer@cad07c2e89fa2edd6e2d7bab4c1aa38e53f76003 # v4.1.1 | |
| - name: Install yq | |
| uses: mikefarah/yq@751d8ad57b84f1794661bc70c0afb92a22ad7b3c # v4.53.2 | |
| - name: Log in to Container Registry | |
| # Only login when we're going to push (main branch or manual trigger) | |
| if: github.ref == 'refs/heads/main' || github.event_name == 'workflow_dispatch' | |
| uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4 | |
| with: | |
| registry: ${{ env.REGISTRY }} | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Extract metadata from config | |
| id: meta | |
| env: | |
| CONFIG_FILE: ${{ matrix.config }} | |
| run: | | |
| config_file="$CONFIG_FILE" | |
| echo "config_file=$config_file" >> $GITHUB_OUTPUT | |
| # Extract protocol from directory name (first part of path) | |
| protocol=$(echo "$config_file" | cut -d'/' -f1) | |
| echo "protocol=$protocol" >> $GITHUB_OUTPUT | |
| # Extract server name from directory name (second part of path) | |
| server_name=$(echo "$config_file" | cut -d'/' -f2) | |
| echo "server_name=$server_name" >> $GITHUB_OUTPUT | |
| # Extract version from YAML file (spec.version) | |
| spec_version=$(yq '.spec.version' "$config_file" 2>/dev/null || echo "") | |
| # Use spec.version if available, otherwise "latest" | |
| if [ -n "$spec_version" ]; then | |
| version="$spec_version" | |
| else | |
| version="latest" | |
| fi | |
| echo "version=$version" >> $GITHUB_OUTPUT | |
| # Generate image name | |
| image_name="${REGISTRY}/${IMAGE_NAME}/${protocol}/${server_name}" | |
| echo "image_name=$image_name" >> $GITHUB_OUTPUT | |
| - name: Generate Dockerfile | |
| id: dockerfile | |
| env: | |
| CONFIG_FILE: ${{ steps.meta.outputs.config_file }} | |
| run: | | |
| echo "Generating Dockerfile for $CONFIG_FILE" | |
| # Create a temporary directory for the Dockerfile | |
| dockerfile_dir=$(mktemp -d) | |
| dockerfile_path="${dockerfile_dir}/Dockerfile" | |
| # Build and run dockhand to generate the Dockerfile | |
| go build -o /tmp/dockhand ./cmd/dockhand | |
| /tmp/dockhand build --config "$CONFIG_FILE" --output "${dockerfile_path}" | |
| echo "dockerfile_dir=$dockerfile_dir" >> $GITHUB_OUTPUT | |
| echo "dockerfile_path=$dockerfile_path" >> $GITHUB_OUTPUT | |
| # Display the generated Dockerfile for debugging | |
| echo "Generated Dockerfile:" | |
| cat "${dockerfile_path}" | |
| - name: Build and push Docker image | |
| id: build | |
| timeout-minutes: 30 | |
| uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7 | |
| with: | |
| context: ${{ steps.dockerfile.outputs.dockerfile_dir }} | |
| file: ${{ steps.dockerfile.outputs.dockerfile_path }} | |
| platforms: linux/amd64,linux/arm64 | |
| push: ${{ github.event_name != 'pull_request' }} | |
| tags: | | |
| ${{ steps.meta.outputs.image_name }}:${{ steps.meta.outputs.version }} | |
| ${{ steps.meta.outputs.image_name }}:latest | |
| labels: | | |
| org.opencontainers.image.title=${{ steps.meta.outputs.server_name }} | |
| org.opencontainers.image.description=MCP server for ${{ steps.meta.outputs.server_name }} | |
| org.opencontainers.image.vendor=Stacklok | |
| org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }} | |
| org.opencontainers.image.revision=${{ github.sha }} | |
| org.opencontainers.image.version=${{ steps.meta.outputs.version }} | |
| cache-from: type=gha | |
| cache-to: type=gha,mode=max | |
| sbom: true | |
| provenance: true | |
| - name: Build single-platform image for Grype scan | |
| id: build-for-scan | |
| uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7 | |
| with: | |
| context: ${{ steps.dockerfile.outputs.dockerfile_dir }} | |
| file: ${{ steps.dockerfile.outputs.dockerfile_path }} | |
| load: true | |
| tags: local-scan:${{ steps.meta.outputs.server_name }}-${{ steps.meta.outputs.version }} | |
| cache-from: type=gha | |
| - name: Sign container images with Cosign | |
| if: github.event_name != 'pull_request' | |
| env: | |
| DIGEST: ${{ steps.build.outputs.digest }} | |
| IMAGE_NAME: ${{ steps.meta.outputs.image_name }} | |
| VERSION: ${{ steps.meta.outputs.version }} | |
| run: | | |
| echo "Signing image ${IMAGE_NAME}@${DIGEST}" | |
| cosign sign --yes "${IMAGE_NAME}@${DIGEST}" | |
| # Also sign the tagged versions for better UX | |
| cosign sign --yes "${IMAGE_NAME}:${VERSION}" | |
| cosign sign --yes "${IMAGE_NAME}:latest" | |
| echo "✅ Images signed with Sigstore/Cosign" >> $GITHUB_STEP_SUMMARY | |
| - name: Download MCP security scan results | |
| id: download-scan | |
| if: github.event_name != 'pull_request' | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 | |
| with: | |
| name: mcp-scan-${{ steps.meta.outputs.server_name }} | |
| path: /tmp/scan-results | |
| continue-on-error: false # Fail if scan results are missing | |
| - name: Create security scan attestation (SCAI format) | |
| if: github.event_name != 'pull_request' | |
| env: | |
| DIGEST: ${{ steps.build.outputs.digest }} | |
| IMAGE_NAME: ${{ steps.meta.outputs.image_name }} | |
| CONFIG_FILE: ${{ matrix.config }} | |
| COMMIT_SHA: ${{ github.sha }} | |
| RUN_ID: ${{ github.run_id }} | |
| SERVER_URL: ${{ github.server_url }} | |
| REPO: ${{ github.repository }} | |
| run: | | |
| echo "Creating SCAI security scan attestation for ${IMAGE_NAME}@${DIGEST}" | |
| # Read scanner version from scan results | |
| SCANNER_VERSION="" | |
| if [ -f /tmp/scan-results/scanner-version.txt ]; then | |
| SCANNER_VERSION=$(cat /tmp/scan-results/scanner-version.txt | tr -d '\n') | |
| echo "Scanner version: $SCANNER_VERSION" | |
| fi | |
| # Generate SCAI attestation using Python script | |
| # Reference: https://github.com/in-toto/attestation/blob/main/spec/predicates/scai.md | |
| # Note: Uses github.server_url to support GitHub Enterprise Server | |
| # Note: Analyzers are read from scan-summary.json (extracted from scanner output) | |
| python3 scripts/mcp-scan/generate_scai_attestation.py \ | |
| /tmp/scan-results/scan-summary.json \ | |
| "$IMAGE_NAME" \ | |
| "${DIGEST}" \ | |
| --config-file "$CONFIG_FILE" \ | |
| --commit-sha "$COMMIT_SHA" \ | |
| --run-id "$RUN_ID" \ | |
| --run-url "${SERVER_URL}/${REPO}/actions/runs/${RUN_ID}" \ | |
| --producer-uri "${SERVER_URL}/${REPO}" \ | |
| --scanner-version "$SCANNER_VERSION" \ | |
| --scanner-uri "https://github.com/cisco-ai-defense/mcp-scanner" \ | |
| --validate \ | |
| --output /tmp/security-attestation.json | |
| # Show the generated attestation for debugging | |
| echo "Generated SCAI attestation:" | |
| cat /tmp/security-attestation.json | |
| # Attest the security scan results with SCAI predicate type | |
| cosign attest --yes \ | |
| --predicate /tmp/security-attestation.json \ | |
| --type https://in-toto.io/attestation/scai/v0.3 \ | |
| "${IMAGE_NAME}@${DIGEST}" | |
| # Extract attribute for summary | |
| ATTRIBUTE=$(jq -r '.predicate.attributes[0].attribute' /tmp/security-attestation.json) | |
| echo "✅ SCAI security scan attestation created (${ATTRIBUTE})" >> $GITHUB_STEP_SUMMARY | |
| # Clean up | |
| rm -f /tmp/security-attestation.json | |
| - name: Run Grype vulnerability scanner | |
| id: grype-scan | |
| uses: anchore/scan-action@e1165082ffb1fe366ebaf02d8526e7c4989ea9d2 # v7.4.0 | |
| with: | |
| image: "local-scan:${{ steps.meta.outputs.server_name }}-${{ steps.meta.outputs.version }}" | |
| severity-cutoff: "high" | |
| only-fixed: "true" | |
| output-format: "sarif" | |
| - name: Upload Grype results to GitHub Security | |
| uses: github/codeql-action/upload-sarif@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4 | |
| if: always() | |
| with: | |
| sarif_file: ${{ steps.grype-scan.outputs.sarif }} | |
| category: 'grype-${{ steps.meta.outputs.server_name }}' | |
| - name: Generate image summary | |
| env: | |
| CONFIG_FILE: ${{ steps.meta.outputs.config_file }} | |
| PROTOCOL: ${{ steps.meta.outputs.protocol }} | |
| SERVER_NAME: ${{ steps.meta.outputs.server_name }} | |
| IMAGE_NAME: ${{ steps.meta.outputs.image_name }} | |
| VERSION: ${{ steps.meta.outputs.version }} | |
| DIGEST: ${{ steps.build.outputs.digest }} | |
| EVENT_NAME: ${{ github.event_name }} | |
| run: | | |
| echo "## Container Build Summary" >> $GITHUB_STEP_SUMMARY | |
| echo "- **Config**: $CONFIG_FILE" >> $GITHUB_STEP_SUMMARY | |
| echo "- **Protocol**: $PROTOCOL" >> $GITHUB_STEP_SUMMARY | |
| echo "- **Server**: $SERVER_NAME" >> $GITHUB_STEP_SUMMARY | |
| echo "- **Image**: $IMAGE_NAME" >> $GITHUB_STEP_SUMMARY | |
| echo "- **Version**: $VERSION" >> $GITHUB_STEP_SUMMARY | |
| echo "- **Platforms**: linux/amd64, linux/arm64" >> $GITHUB_STEP_SUMMARY | |
| if [ "$EVENT_NAME" != "pull_request" ]; then | |
| echo "- **SBOM**: ✅ Attested" >> $GITHUB_STEP_SUMMARY | |
| echo "- **Build Provenance**: ✅ Attested" >> $GITHUB_STEP_SUMMARY | |
| echo "- **Security Scan**: ✅ Attested" >> $GITHUB_STEP_SUMMARY | |
| echo "- **Signatures**: ✅ Signed with Sigstore/Cosign" >> $GITHUB_STEP_SUMMARY | |
| echo "- **Grype Scan**: ✅ Completed (see Security tab)" >> $GITHUB_STEP_SUMMARY | |
| echo "- **Status**: ✅ Built, pushed, signed, and attested" >> $GITHUB_STEP_SUMMARY | |
| echo "- **Tags**:" >> $GITHUB_STEP_SUMMARY | |
| echo " - ${IMAGE_NAME}:${VERSION}" >> $GITHUB_STEP_SUMMARY | |
| echo " - ${IMAGE_NAME}:latest" >> $GITHUB_STEP_SUMMARY | |
| echo "- **Digest**: $DIGEST" >> $GITHUB_STEP_SUMMARY | |
| else | |
| echo "- **Grype Scan**: ✅ Completed (see Security tab)" >> $GITHUB_STEP_SUMMARY | |
| echo "- **Status**: ✅ Built (not pushed - PR)" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| # Save PR number as artifact for the mcp-scan-report workflow. | |
| # This is needed because workflow_run doesn't reliably provide PR info for fork PRs. | |
| save-pr-number: | |
| runs-on: ubuntu-latest | |
| permissions: {} | |
| if: github.event_name == 'pull_request' | |
| steps: | |
| - name: Save PR number | |
| env: | |
| PR_NUMBER: ${{ github.event.pull_request.number }} | |
| run: echo "$PR_NUMBER" > pr-number.txt | |
| - name: Upload PR number | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 | |
| with: | |
| name: pr-number | |
| path: pr-number.txt | |
| retention-days: 5 | |
| summary: | |
| needs: [discover-configs, verify-provenance, mcp-security-scan, build-containers] | |
| runs-on: ubuntu-latest | |
| permissions: {} | |
| if: always() | |
| steps: | |
| - name: Build Summary | |
| env: | |
| EVENT_NAME: ${{ github.event_name }} | |
| REF: ${{ github.ref }} | |
| ALL_CONFIGS: ${{ needs.discover-configs.outputs.configs }} | |
| CHANGED_CONFIGS: ${{ needs.discover-configs.outputs.changed-configs }} | |
| PROVENANCE_RESULT: ${{ needs.verify-provenance.result }} | |
| SCAN_RESULT: ${{ needs.mcp-security-scan.result }} | |
| BUILD_RESULT: ${{ needs.build-containers.result }} | |
| run: | | |
| echo "## Dockyard Build Summary" >> $GITHUB_STEP_SUMMARY | |
| echo "- **Trigger**: $EVENT_NAME" >> $GITHUB_STEP_SUMMARY | |
| echo "- **Ref**: $REF" >> $GITHUB_STEP_SUMMARY | |
| echo "- **Total Configs**: $ALL_CONFIGS" >> $GITHUB_STEP_SUMMARY | |
| echo "- **Changed Configs**: $CHANGED_CONFIGS" >> $GITHUB_STEP_SUMMARY | |
| if [ "$PROVENANCE_RESULT" == "success" ]; then | |
| echo "- **Provenance Verification**: ✅ All packages verified" >> $GITHUB_STEP_SUMMARY | |
| elif [ "$PROVENANCE_RESULT" == "failure" ]; then | |
| echo "- **Provenance Verification**: ⚠️ Some packages have provenance issues (non-blocking)" >> $GITHUB_STEP_SUMMARY | |
| elif [ "$PROVENANCE_RESULT" == "skipped" ]; then | |
| echo "- **Provenance Verification**: ⏭️ Skipped" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| if [ "$SCAN_RESULT" == "success" ]; then | |
| echo "- **Security Scan**: ✅ All MCP servers passed security scanning" >> $GITHUB_STEP_SUMMARY | |
| elif [ "$SCAN_RESULT" == "failure" ]; then | |
| echo "- **Security Scan**: ❌ Some MCP servers have security issues" >> $GITHUB_STEP_SUMMARY | |
| elif [ "$SCAN_RESULT" == "skipped" ]; then | |
| echo "- **Security Scan**: ⏭️ Skipped" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| if [ "$BUILD_RESULT" == "success" ]; then | |
| echo "- **Build Status**: ✅ All changed containers built successfully" >> $GITHUB_STEP_SUMMARY | |
| echo "- **Features**:" >> $GITHUB_STEP_SUMMARY | |
| echo " - 🏗️ Multi-architecture support (amd64, arm64)" >> $GITHUB_STEP_SUMMARY | |
| echo " - 📦 SBOM (Software Bill of Materials) included" >> $GITHUB_STEP_SUMMARY | |
| echo " - 🔐 Provenance attestation for supply chain security" >> $GITHUB_STEP_SUMMARY | |
| echo " - ✍️ Sigstore/Cosign signatures for image verification" >> $GITHUB_STEP_SUMMARY | |
| echo " - 🚀 GitHub Actions cache for faster builds" >> $GITHUB_STEP_SUMMARY | |
| elif [ "$BUILD_RESULT" == "failure" ]; then | |
| echo "- **Build Status**: ❌ Some containers failed to build" >> $GITHUB_STEP_SUMMARY | |
| elif [ "$BUILD_RESULT" == "skipped" ]; then | |
| echo "- **Build Status**: ⏭️ No configuration changes detected" >> $GITHUB_STEP_SUMMARY | |
| else | |
| echo "- **Build Status**: ⚠️ Build status unknown" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| # Add efficiency note | |
| changed_count=$(echo "$CHANGED_CONFIGS" | jq length) | |
| total_count=$(echo "$ALL_CONFIGS" | jq length) | |
| echo "- **Efficiency**: Built $changed_count out of $total_count configurations" >> $GITHUB_STEP_SUMMARY |