From ee48c2e7166368e79fbd3da37548ea1b9ae3bcd7 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Sun, 8 Feb 2026 10:18:40 +0000 Subject: [PATCH] fix: use double quotes for environment variable assignments in workflows - Updated environment variable assignments in multiple workflow files to use double quotes for consistency and to prevent potential issues with variable expansion. - Refactored echo commands to group multiple lines into a single block for improved readability in the following workflows: - release-goreleaser.yml - renovate_prune.yml - security-pr.yml - security-weekly-rebuild.yml - supply-chain-pr.yml - supply-chain-verify.yml - update-geolite2.yml - waf-integration.yml - weekly-nightly-promotion.yml --- .github/workflows/auto-add-to-project.yml | 8 +- .github/workflows/auto-versioning.yml | 8 +- .github/workflows/benchmark.yml | 7 +- .github/workflows/cerberus-integration.yml | 116 +++++------ .github/workflows/ci-pipeline.yml | 46 +++-- .github/workflows/codecov-upload.yml | 4 +- .github/workflows/codeql.yml | 59 +++--- .github/workflows/crowdsec-integration.yml | 141 ++++++++------ .github/workflows/docker-build.yml | 132 +++++++------ .github/workflows/docs-to-issues.yml | 46 ++--- .github/workflows/docs.yml | 26 +-- .github/workflows/e2e-tests-split.yml | 182 +++++++++--------- .github/workflows/gh_cache_cleanup.yml | 9 +- .github/workflows/nightly-build.yml | 24 +-- .github/workflows/quality-checks.yml | 119 ++++++------ .github/workflows/rate-limit-integration.yml | 131 +++++++------ .github/workflows/release-goreleaser.yml | 2 +- .github/workflows/renovate_prune.yml | 4 +- .github/workflows/security-pr.yml | 36 ++-- .github/workflows/security-weekly-rebuild.yml | 44 +++-- .github/workflows/supply-chain-pr.yml | 20 +- .github/workflows/supply-chain-verify.yml | 117 ++++++----- .github/workflows/update-geolite2.yml | 16 +- .github/workflows/waf-integration.yml | 106 +++++----- .../workflows/weekly-nightly-promotion.yml | 98 +++++----- 25 files changed, 812 insertions(+), 689 deletions(-) diff --git a/.github/workflows/auto-add-to-project.yml b/.github/workflows/auto-add-to-project.yml index 1c0f497f..7ae5b43c 100644 --- a/.github/workflows/auto-add-to-project.yml +++ b/.github/workflows/auto-add-to-project.yml @@ -18,9 +18,9 @@ jobs: id: project_check run: | if [ -n "${{ secrets.PROJECT_URL }}" ]; then - echo "has_project=true" >> $GITHUB_OUTPUT + echo "has_project=true" >> "$GITHUB_OUTPUT" else - echo "has_project=false" >> $GITHUB_OUTPUT + echo "has_project=false" >> "$GITHUB_OUTPUT" fi - name: Add issue or PR to project @@ -29,8 +29,8 @@ jobs: continue-on-error: true with: project-url: ${{ secrets.PROJECT_URL }} - github-token: ${{ secrets.ADD_TO_PROJECT_PAT }} + github-token: ${{ secrets.ADD_TO_PROJECT_PAT || secrets.GITHUB_TOKEN }} - name: Skip summary if: steps.project_check.outputs.has_project == 'false' - run: echo "PROJECT_URL secret missing; skipping project assignment." >> $GITHUB_STEP_SUMMARY + run: echo "PROJECT_URL secret missing; skipping project assignment." >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/auto-versioning.yml b/.github/workflows/auto-versioning.yml index 48a44ee2..ba0753a0 100644 --- a/.github/workflows/auto-versioning.yml +++ b/.github/workflows/auto-versioning.yml @@ -66,22 +66,22 @@ jobs: VERSION_NO_V="${RAW#v}" TAG="v${VERSION_NO_V}" echo "Determined tag: $TAG" - echo "tag=$TAG" >> $GITHUB_OUTPUT + echo "tag=$TAG" >> "$GITHUB_OUTPUT" - name: Check for existing GitHub Release id: check_release run: | - TAG=${{ steps.determine_tag.outputs.tag }} + TAG="${{ steps.determine_tag.outputs.tag }}" echo "Checking for release for tag: ${TAG}" STATUS=$(curl -s -o /dev/null -w "%{http_code}" \ -H "Authorization: token ${GITHUB_TOKEN}" \ -H "Accept: application/vnd.github+json" \ "https://api.github.com/repos/${GITHUB_REPOSITORY}/releases/tags/${TAG}") || true if [ "${STATUS}" = "200" ]; then - echo "exists=true" >> $GITHUB_OUTPUT + echo "exists=true" >> "$GITHUB_OUTPUT" echo "โ„น๏ธ Release already exists for tag: ${TAG}" else - echo "exists=false" >> $GITHUB_OUTPUT + echo "exists=false" >> "$GITHUB_OUTPUT" echo "โœ… No existing release found for tag: ${TAG}" fi env: diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 5228bd98..891ed904 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -1,6 +1,9 @@ name: Go Benchmark on: + workflow_run: + workflows: ["Docker Build, Publish & Test"] + types: [completed] workflow_dispatch: concurrency: @@ -67,6 +70,6 @@ jobs: PERF_MAX_MS_GETSTATUS_P95_PARALLEL: 1500ms PERF_MAX_MS_LISTDECISIONS_P95: 2000ms run: | - echo "## ๐Ÿ” Running performance assertions (TestPerf)" >> $GITHUB_STEP_SUMMARY + echo "## ๐Ÿ” Running performance assertions (TestPerf)" >> "$GITHUB_STEP_SUMMARY" go test -run TestPerf -v ./internal/api/handlers -count=1 | tee perf-output.txt - exit ${PIPESTATUS[0]} + exit "${PIPESTATUS[0]}" diff --git a/.github/workflows/cerberus-integration.yml b/.github/workflows/cerberus-integration.yml index d22e021a..3b7ee23e 100644 --- a/.github/workflows/cerberus-integration.yml +++ b/.github/workflows/cerberus-integration.yml @@ -3,6 +3,11 @@ name: Cerberus Integration # Phase 2-3: Build Once, Test Many - Use registry image instead of building # This workflow now waits for docker-build.yml to complete and pulls the built image on: + workflow_run: + workflows: + - Docker Build, Publish & Test + types: + - completed workflow_dispatch: inputs: image_tag: @@ -40,12 +45,12 @@ jobs: # Manual trigger uses provided tag if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then if [[ -n "$MANUAL_TAG" ]]; then - echo "tag=${MANUAL_TAG}" >> $GITHUB_OUTPUT + echo "tag=${MANUAL_TAG}" >> "$GITHUB_OUTPUT" else # Default to latest if no tag provided - echo "tag=latest" >> $GITHUB_OUTPUT + echo "tag=latest" >> "$GITHUB_OUTPUT" fi - echo "source_type=manual" >> $GITHUB_OUTPUT + echo "source_type=manual" >> "$GITHUB_OUTPUT" exit 0 fi @@ -72,16 +77,16 @@ jobs: fi # Immutable tag with SHA suffix prevents race conditions - echo "tag=pr-${PR_NUM}-${SHORT_SHA}" >> $GITHUB_OUTPUT - echo "source_type=pr" >> $GITHUB_OUTPUT + echo "tag=pr-${PR_NUM}-${SHORT_SHA}" >> "$GITHUB_OUTPUT" + echo "source_type=pr" >> "$GITHUB_OUTPUT" else # Non-PR workflow_run uses short SHA tag (matches docker-build.yml) - echo "tag=sha-${SHORT_SHA}" >> $GITHUB_OUTPUT - echo "source_type=sha" >> $GITHUB_OUTPUT + echo "tag=sha-${SHORT_SHA}" >> "$GITHUB_OUTPUT" + echo "source_type=sha" >> "$GITHUB_OUTPUT" fi - echo "sha=${SHORT_SHA}" >> $GITHUB_OUTPUT - echo "Determined image tag: $(cat $GITHUB_OUTPUT | grep tag=)" + echo "sha=${SHORT_SHA}" >> "$GITHUB_OUTPUT" + echo "Determined image tag: $(grep tag= "$GITHUB_OUTPUT")" # Pull image from Docker Hub with retry logic - name: Pull Docker image from registry @@ -119,63 +124,66 @@ jobs: run: | chmod +x scripts/cerberus_integration.sh scripts/cerberus_integration.sh 2>&1 | tee cerberus-test-output.txt - exit ${PIPESTATUS[0]} + exit "${PIPESTATUS[0]}" - name: Dump Debug Info on Failure if: failure() run: | - echo "## ๐Ÿ” Debug Information" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY + { + echo "## ๐Ÿ” Debug Information" + echo "" - echo "### Container Status" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - docker ps -a --filter "name=charon" --filter "name=cerberus" --filter "name=backend" >> $GITHUB_STEP_SUMMARY 2>&1 || true - echo '```' >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY + echo "### Container Status" + echo '```' + docker ps -a --filter "name=charon" --filter "name=cerberus" --filter "name=backend" 2>&1 || true + echo '```' + echo "" - echo "### Security Status API" >> $GITHUB_STEP_SUMMARY - echo '```json' >> $GITHUB_STEP_SUMMARY - curl -s http://localhost:8480/api/v1/security/status 2>/dev/null | head -100 >> $GITHUB_STEP_SUMMARY || echo "Could not retrieve security status" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY + echo "### Security Status API" + echo '```json' + curl -s http://localhost:8480/api/v1/security/status 2>/dev/null | head -100 || echo "Could not retrieve security status" + echo '```' + echo "" - echo "### Caddy Admin Config" >> $GITHUB_STEP_SUMMARY - echo '```json' >> $GITHUB_STEP_SUMMARY - curl -s http://localhost:2319/config 2>/dev/null | head -200 >> $GITHUB_STEP_SUMMARY || echo "Could not retrieve Caddy config" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY + echo "### Caddy Admin Config" + echo '```json' + curl -s http://localhost:2319/config 2>/dev/null | head -200 || echo "Could not retrieve Caddy config" + echo '```' + echo "" - echo "### Charon Container Logs (last 100 lines)" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - docker logs charon-cerberus-test 2>&1 | tail -100 >> $GITHUB_STEP_SUMMARY || echo "No container logs available" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY + echo "### Charon Container Logs (last 100 lines)" + echo '```' + docker logs charon-cerberus-test 2>&1 | tail -100 || echo "No container logs available" + echo '```' + } >> "$GITHUB_STEP_SUMMARY" - name: Cerberus Integration Summary if: always() run: | - echo "## ๐Ÿ”ฑ Cerberus Integration Test Results" >> $GITHUB_STEP_SUMMARY - if [ "${{ steps.cerberus-test.outcome }}" == "success" ]; then - echo "โœ… **All Cerberus tests passed**" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "### Test Results:" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - grep -E "โœ“|PASS|TC-[0-9]|=== ALL" cerberus-test-output.txt || echo "See logs for details" - grep -E "โœ“|PASS|TC-[0-9]|=== ALL" cerberus-test-output.txt >> $GITHUB_STEP_SUMMARY || echo "See logs for details" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "### Features Tested:" >> $GITHUB_STEP_SUMMARY - echo "- WAF (Coraza) payload inspection" >> $GITHUB_STEP_SUMMARY - echo "- Rate limiting enforcement" >> $GITHUB_STEP_SUMMARY - echo "- Security handler ordering" >> $GITHUB_STEP_SUMMARY - echo "- Legitimate traffic flow" >> $GITHUB_STEP_SUMMARY - else - echo "โŒ **Cerberus tests failed**" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "### Failure Details:" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - grep -E "โœ—|FAIL|Error|failed" cerberus-test-output.txt | head -30 >> $GITHUB_STEP_SUMMARY || echo "See logs for details" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - fi + { + echo "## ๐Ÿ”ฑ Cerberus Integration Test Results" + if [ "${{ steps.cerberus-test.outcome }}" == "success" ]; then + echo "โœ… **All Cerberus tests passed**" + echo "" + echo "### Test Results:" + echo '```' + grep -E "โœ“|PASS|TC-[0-9]|=== ALL" cerberus-test-output.txt || echo "See logs for details" + echo '```' + echo "" + echo "### Features Tested:" + echo "- WAF (Coraza) payload inspection" + echo "- Rate limiting enforcement" + echo "- Security handler ordering" + echo "- Legitimate traffic flow" + else + echo "โŒ **Cerberus tests failed**" + echo "" + echo "### Failure Details:" + echo '```' + grep -E "โœ—|FAIL|Error|failed" cerberus-test-output.txt | head -30 || echo "See logs for details" + echo '```' + fi + } >> "$GITHUB_STEP_SUMMARY" - name: Cleanup if: always() diff --git a/.github/workflows/ci-pipeline.yml b/.github/workflows/ci-pipeline.yml index 8342e4eb..0a374e06 100644 --- a/.github/workflows/ci-pipeline.yml +++ b/.github/workflows/ci-pipeline.yml @@ -134,7 +134,7 @@ jobs: - name: Normalize image name run: | IMAGE_NAME=$(echo "${{ env.IMAGE_NAME }}" | tr '[:upper:]' '[:lower:]') - echo "IMAGE_NAME=${IMAGE_NAME}" >> $GITHUB_ENV + echo "IMAGE_NAME=${IMAGE_NAME}" >> "$GITHUB_ENV" - name: Determine image push policy id: image-policy @@ -168,8 +168,12 @@ jobs: local sanitized sanitized=$(echo "$raw" | tr '[:upper:]' '[:lower:]') - sanitized=$(echo "$sanitized" | sed 's/[^a-z0-9-]/-/g' | sed 's/--*/-/g') - sanitized=$(echo "$sanitized" | sed 's/^[^a-z0-9]*//' | sed 's/[^a-z0-9-]*$//') + sanitized=${sanitized//[^a-z0-9-]/-} + while [[ "$sanitized" == *"--"* ]]; do + sanitized=${sanitized//--/-} + done + sanitized=${sanitized##[^a-z0-9]*} + sanitized=${sanitized%%[^a-z0-9-]*} if [ -z "$sanitized" ]; then sanitized="branch" @@ -177,7 +181,7 @@ jobs: sanitized=$(echo "$sanitized" | cut -c1-"$max_len") - sanitized=$(echo "$sanitized" | sed 's/^[^a-z0-9]*//') + sanitized=${sanitized##[^a-z0-9]*} if [ -z "$sanitized" ]; then sanitized="branch" fi @@ -201,10 +205,12 @@ jobs: TAGS+=("${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:${BRANCH_SHA_TAG}") TAGS+=("${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${BRANCH_SHA_TAG}") - if [[ "${{ github.ref_name }}" == feature/* ]]; then - TAGS+=("${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:${BRANCH_TAG}") - TAGS+=("${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${BRANCH_TAG}") - fi + case "${{ github.ref_name }}" in + feature/*) + TAGS+=("${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:${BRANCH_TAG}") + TAGS+=("${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${BRANCH_TAG}") + ;; + esac fi if [ "${{ github.event_name }}" != "pull_request" ] && \ @@ -274,15 +280,15 @@ jobs: run: | DIGEST="${{ steps.build.outputs.digest }}" if [ -z "${DIGEST}" ]; then - echo "image_ref_dockerhub=" >> $GITHUB_OUTPUT - echo "image_ref_ghcr=" >> $GITHUB_OUTPUT + echo "image_ref_dockerhub=" >> "$GITHUB_OUTPUT" + echo "image_ref_ghcr=" >> "$GITHUB_OUTPUT" else IMAGE_REF_DOCKERHUB="${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}@${DIGEST}" IMAGE_REF_GHCR="${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}@${DIGEST}" - echo "image_ref_dockerhub=${IMAGE_REF_DOCKERHUB}" >> $GITHUB_OUTPUT - echo "image_ref_ghcr=${IMAGE_REF_GHCR}" >> $GITHUB_OUTPUT + echo "image_ref_dockerhub=${IMAGE_REF_DOCKERHUB}" >> "$GITHUB_OUTPUT" + echo "image_ref_ghcr=${IMAGE_REF_GHCR}" >> "$GITHUB_OUTPUT" fi - echo "image_tag=${{ steps.tags.outputs.image_tag }}" >> $GITHUB_OUTPUT + echo "image_tag=${{ steps.tags.outputs.image_tag }}" >> "$GITHUB_OUTPUT" integration-cerberus: name: Integration - Cerberus @@ -461,10 +467,10 @@ jobs: CGO_ENABLED: 1 run: | bash scripts/go-test-coverage.sh 2>&1 | tee backend/test-output.txt - exit ${PIPESTATUS[0]} + exit "${PIPESTATUS[0]}" - name: Upload coverage artifact - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1 with: name: backend-coverage path: backend/coverage.txt @@ -497,10 +503,10 @@ jobs: - name: Run frontend tests and coverage run: | bash scripts/frontend-test-coverage.sh 2>&1 | tee frontend/test-output.txt - exit ${PIPESTATUS[0]} + exit "${PIPESTATUS[0]}" - name: Upload coverage artifact - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1 with: name: frontend-coverage path: frontend/coverage @@ -564,7 +570,7 @@ jobs: path: coverage/e2e - name: Upload coverage to Codecov - uses: codecov/codecov-action@7f9fc5e3cf521e84e0c9a667b0f6c6ad08c94b82 # v5.1.3 + uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5 with: token: ${{ secrets.CODECOV_TOKEN }} flags: backend @@ -572,7 +578,7 @@ jobs: fail_ci_if_error: false - name: Upload frontend coverage to Codecov - uses: codecov/codecov-action@7f9fc5e3cf521e84e0c9a667b0f6c6ad08c94b82 # v5.1.3 + uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5 with: token: ${{ secrets.CODECOV_TOKEN }} flags: frontend @@ -580,7 +586,7 @@ jobs: fail_ci_if_error: false - name: Upload E2E coverage to Codecov - uses: codecov/codecov-action@7f9fc5e3cf521e84e0c9a667b0f6c6ad08c94b82 # v5.1.3 + uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5 with: token: ${{ secrets.CODECOV_TOKEN }} flags: e2e diff --git a/.github/workflows/codecov-upload.yml b/.github/workflows/codecov-upload.yml index b39739ef..2cb795e2 100644 --- a/.github/workflows/codecov-upload.yml +++ b/.github/workflows/codecov-upload.yml @@ -51,7 +51,7 @@ jobs: CGO_ENABLED: 1 run: | bash scripts/go-test-coverage.sh 2>&1 | tee backend/test-output.txt - exit ${PIPESTATUS[0]} + exit "${PIPESTATUS[0]}" - name: Upload backend coverage to Codecov uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5 @@ -88,7 +88,7 @@ jobs: working-directory: ${{ github.workspace }} run: | bash scripts/frontend-test-coverage.sh 2>&1 | tee frontend/test-output.txt - exit ${PIPESTATUS[0]} + exit "${PIPESTATUS[0]}" - name: Upload frontend coverage to Codecov uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5 diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index d2149228..7384f365 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -68,49 +68,54 @@ jobs: - name: Check CodeQL Results if: always() run: | - echo "## ๐Ÿ”’ CodeQL Security Analysis Results" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "**Language:** ${{ matrix.language }}" >> $GITHUB_STEP_SUMMARY - echo "**Query Suite:** security-and-quality" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - # Find SARIF file (CodeQL action creates it in various locations) - SARIF_FILE=$(find ${{ runner.temp }} -name "*${{ matrix.language }}*.sarif" -type f 2>/dev/null | head -1) + SARIF_FILE=$(find "${{ runner.temp }}" -name "*${{ matrix.language }}*.sarif" -type f 2>/dev/null | head -1) + + { + echo "## ๐Ÿ”’ CodeQL Security Analysis Results" + echo "" + echo "**Language:** ${{ matrix.language }}" + echo "**Query Suite:** security-and-quality" + echo "" + } >> "$GITHUB_STEP_SUMMARY" if [ -f "$SARIF_FILE" ]; then echo "Found SARIF file: $SARIF_FILE" - RESULT_COUNT=$(jq '.runs[].results | length' "$SARIF_FILE" 2>/dev/null || echo 0) ERROR_COUNT=$(jq '[.runs[].results[] | select(.level == "error")] | length' "$SARIF_FILE" 2>/dev/null || echo 0) WARNING_COUNT=$(jq '[.runs[].results[] | select(.level == "warning")] | length' "$SARIF_FILE" 2>/dev/null || echo 0) NOTE_COUNT=$(jq '[.runs[].results[] | select(.level == "note")] | length' "$SARIF_FILE" 2>/dev/null || echo 0) - echo "**Findings:**" >> $GITHUB_STEP_SUMMARY - echo "- ๐Ÿ”ด Errors: $ERROR_COUNT" >> $GITHUB_STEP_SUMMARY - echo "- ๐ŸŸก Warnings: $WARNING_COUNT" >> $GITHUB_STEP_SUMMARY - echo "- ๐Ÿ”ต Notes: $NOTE_COUNT" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY + { + echo "**Findings:**" + echo "- ๐Ÿ”ด Errors: $ERROR_COUNT" + echo "- ๐ŸŸก Warnings: $WARNING_COUNT" + echo "- ๐Ÿ”ต Notes: $NOTE_COUNT" + echo "" - if [ "$ERROR_COUNT" -gt 0 ]; then - echo "โŒ **CRITICAL:** High-severity security issues found!" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "### Top Issues:" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - jq -r '.runs[].results[] | select(.level == "error") | "\(.ruleId): \(.message.text)"' "$SARIF_FILE" 2>/dev/null | head -5 >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - else - echo "โœ… No high-severity issues found" >> $GITHUB_STEP_SUMMARY - fi + if [ "$ERROR_COUNT" -gt 0 ]; then + echo "โŒ **CRITICAL:** High-severity security issues found!" + echo "" + echo "### Top Issues:" + echo '```' + jq -r '.runs[].results[] | select(.level == "error") | "\(.ruleId): \(.message.text)"' "$SARIF_FILE" 2>/dev/null | head -5 + echo '```' + else + echo "โœ… No high-severity issues found" + fi + } >> "$GITHUB_STEP_SUMMARY" else - echo "โš ๏ธ SARIF file not found - check analysis logs" >> $GITHUB_STEP_SUMMARY + echo "โš ๏ธ SARIF file not found - check analysis logs" >> "$GITHUB_STEP_SUMMARY" fi - echo "" >> $GITHUB_STEP_SUMMARY - echo "View full results in the [Security tab](https://github.com/${{ github.repository }}/security/code-scanning)" >> $GITHUB_STEP_SUMMARY + { + echo "" + echo "View full results in the [Security tab](https://github.com/${{ github.repository }}/security/code-scanning)" + } >> "$GITHUB_STEP_SUMMARY" - name: Fail on High-Severity Findings if: always() run: | - SARIF_FILE=$(find ${{ runner.temp }} -name "*${{ matrix.language }}*.sarif" -type f 2>/dev/null | head -1) + SARIF_FILE=$(find "${{ runner.temp }}" -name "*${{ matrix.language }}*.sarif" -type f 2>/dev/null | head -1) if [ -f "$SARIF_FILE" ]; then ERROR_COUNT=$(jq '[.runs[].results[] | select(.level == "error")] | length' "$SARIF_FILE" 2>/dev/null || echo 0) diff --git a/.github/workflows/crowdsec-integration.yml b/.github/workflows/crowdsec-integration.yml index 95e4b708..b8d0edbc 100644 --- a/.github/workflows/crowdsec-integration.yml +++ b/.github/workflows/crowdsec-integration.yml @@ -3,6 +3,9 @@ name: CrowdSec Integration # Phase 2-3: Build Once, Test Many - Use registry image instead of building # This workflow now waits for docker-build.yml to complete and pulls the built image on: + workflow_run: + workflows: ["Docker Build, Publish & Test"] + types: [completed] workflow_dispatch: inputs: image_tag: @@ -40,12 +43,15 @@ jobs: # Manual trigger uses provided tag if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then if [[ -n "$MANUAL_TAG" ]]; then - echo "tag=${MANUAL_TAG}" >> $GITHUB_OUTPUT + TAG_VALUE="$MANUAL_TAG" else # Default to latest if no tag provided - echo "tag=latest" >> $GITHUB_OUTPUT + TAG_VALUE="latest" fi - echo "source_type=manual" >> $GITHUB_OUTPUT + { + echo "tag=${TAG_VALUE}" + echo "source_type=manual" + } >> "$GITHUB_OUTPUT" exit 0 fi @@ -72,16 +78,20 @@ jobs: fi # Immutable tag with SHA suffix prevents race conditions - echo "tag=pr-${PR_NUM}-${SHORT_SHA}" >> $GITHUB_OUTPUT - echo "source_type=pr" >> $GITHUB_OUTPUT + { + echo "tag=pr-${PR_NUM}-${SHORT_SHA}" + echo "source_type=pr" + } >> "$GITHUB_OUTPUT" else # Non-PR workflow_run uses short SHA tag (matches docker-build.yml) - echo "tag=sha-${SHORT_SHA}" >> $GITHUB_OUTPUT - echo "source_type=sha" >> $GITHUB_OUTPUT + { + echo "tag=sha-${SHORT_SHA}" + echo "source_type=sha" + } >> "$GITHUB_OUTPUT" fi - echo "sha=${SHORT_SHA}" >> $GITHUB_OUTPUT - echo "Determined image tag: $(cat $GITHUB_OUTPUT | grep tag=)" + echo "sha=${SHORT_SHA}" >> "$GITHUB_OUTPUT" + echo "Determined image tag: $(grep tag= "$GITHUB_OUTPUT")" # Pull image from Docker Hub with retry logic - name: Pull Docker image from registry @@ -119,90 +129,93 @@ jobs: run: | chmod +x .github/skills/scripts/skill-runner.sh .github/skills/scripts/skill-runner.sh integration-test-crowdsec 2>&1 | tee crowdsec-test-output.txt - exit ${PIPESTATUS[0]} + exit "${PIPESTATUS[0]}" - name: Run CrowdSec Startup and LAPI Tests id: lapi-test run: | chmod +x .github/skills/scripts/skill-runner.sh .github/skills/scripts/skill-runner.sh integration-test-crowdsec-startup 2>&1 | tee lapi-test-output.txt - exit ${PIPESTATUS[0]} + exit "${PIPESTATUS[0]}" - name: Dump Debug Info on Failure if: failure() run: | - echo "## ๐Ÿ” Debug Information" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY + { + echo "## ๐Ÿ” Debug Information" + echo "" - echo "### Container Status" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - docker ps -a --filter "name=charon" --filter "name=crowdsec" >> $GITHUB_STEP_SUMMARY 2>&1 || true - echo '```' >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY + echo "### Container Status" + echo '```' + docker ps -a --filter "name=charon" --filter "name=crowdsec" 2>&1 || true + echo '```' + echo "" - # Check which test container exists and dump its logs - if docker ps -a --filter "name=charon-crowdsec-startup-test" --format "{{.Names}}" | grep -q "charon-crowdsec-startup-test"; then - echo "### Charon Startup Test Container Logs (last 100 lines)" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - docker logs charon-crowdsec-startup-test 2>&1 | tail -100 >> $GITHUB_STEP_SUMMARY || echo "No container logs available" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - elif docker ps -a --filter "name=charon-debug" --format "{{.Names}}" | grep -q "charon-debug"; then - echo "### Charon Container Logs (last 100 lines)" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - docker logs charon-debug 2>&1 | tail -100 >> $GITHUB_STEP_SUMMARY || echo "No container logs available" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - fi - echo "" >> $GITHUB_STEP_SUMMARY + # Check which test container exists and dump its logs + if docker ps -a --filter "name=charon-crowdsec-startup-test" --format "{{.Names}}" | grep -q "charon-crowdsec-startup-test"; then + echo "### Charon Startup Test Container Logs (last 100 lines)" + echo '```' + docker logs charon-crowdsec-startup-test 2>&1 | tail -100 || echo "No container logs available" + echo '```' + elif docker ps -a --filter "name=charon-debug" --format "{{.Names}}" | grep -q "charon-debug"; then + echo "### Charon Container Logs (last 100 lines)" + echo '```' + docker logs charon-debug 2>&1 | tail -100 || echo "No container logs available" + echo '```' + fi + echo "" - # Check for CrowdSec specific logs if LAPI test ran - if [ -f "lapi-test-output.txt" ]; then - echo "### CrowdSec LAPI Test Failures" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - grep -E "โœ— FAIL|โœ— CRITICAL|CROWDSEC.*BROKEN" lapi-test-output.txt >> $GITHUB_STEP_SUMMARY 2>&1 || echo "No critical failures found in LAPI test" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - fi + # Check for CrowdSec specific logs if LAPI test ran + if [ -f "lapi-test-output.txt" ]; then + echo "### CrowdSec LAPI Test Failures" + echo '```' + grep -E "โœ— FAIL|โœ— CRITICAL|CROWDSEC.*BROKEN" lapi-test-output.txt 2>&1 || echo "No critical failures found in LAPI test" + echo '```' + fi + } >> "$GITHUB_STEP_SUMMARY" - name: CrowdSec Integration Summary if: always() run: | - echo "## ๐Ÿ›ก๏ธ CrowdSec Integration Test Results" >> $GITHUB_STEP_SUMMARY + { + echo "## ๐Ÿ›ก๏ธ CrowdSec Integration Test Results" # CrowdSec Preset Integration Tests if [ "${{ steps.crowdsec-test.outcome }}" == "success" ]; then - echo "โœ… **CrowdSec Hub Presets: Passed**" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "### Preset Test Results:" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY + echo "โœ… **CrowdSec Hub Presets: Passed**" + echo "" + echo "### Preset Test Results:" + echo '```' grep -E "^โœ“|^===|^Pull|^Apply" crowdsec-test-output.txt || echo "See logs for details" - grep -E "^โœ“|^===|^Pull|^Apply" crowdsec-test-output.txt >> $GITHUB_STEP_SUMMARY || echo "See logs for details" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY + echo '```' else - echo "โŒ **CrowdSec Hub Presets: Failed**" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "### Preset Failure Details:" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - grep -E "^โœ—|Unexpected|Error|failed|FAIL" crowdsec-test-output.txt | head -20 >> $GITHUB_STEP_SUMMARY || echo "See logs for details" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY + echo "โŒ **CrowdSec Hub Presets: Failed**" + echo "" + echo "### Preset Failure Details:" + echo '```' + grep -E "^โœ—|Unexpected|Error|failed|FAIL" crowdsec-test-output.txt | head -20 || echo "See logs for details" + echo '```' fi - echo "" >> $GITHUB_STEP_SUMMARY + echo "" # CrowdSec Startup and LAPI Tests if [ "${{ steps.lapi-test.outcome }}" == "success" ]; then - echo "โœ… **CrowdSec Startup & LAPI: Passed**" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "### LAPI Test Results:" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - grep -E "^\[TEST\]|โœ“ PASS|Check [0-9]|CrowdSec LAPI" lapi-test-output.txt >> $GITHUB_STEP_SUMMARY || echo "See logs for details" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY + echo "โœ… **CrowdSec Startup & LAPI: Passed**" + echo "" + echo "### LAPI Test Results:" + echo '```' + grep -E "^\[TEST\]|โœ“ PASS|Check [0-9]|CrowdSec LAPI" lapi-test-output.txt || echo "See logs for details" + echo '```' else - echo "โŒ **CrowdSec Startup & LAPI: Failed**" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "### LAPI Failure Details:" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - grep -E "โœ— FAIL|โœ— CRITICAL|Error|failed" lapi-test-output.txt | head -20 >> $GITHUB_STEP_SUMMARY || echo "See logs for details" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY + echo "โŒ **CrowdSec Startup & LAPI: Failed**" + echo "" + echo "### LAPI Failure Details:" + echo '```' + grep -E "โœ— FAIL|โœ— CRITICAL|Error|failed" lapi-test-output.txt | head -20 || echo "See logs for details" + echo '```' fi + } >> "$GITHUB_STEP_SUMMARY" - name: Cleanup if: always() diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index 216ec8d5..e6d522a6 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -65,7 +65,7 @@ jobs: - name: Normalize image name run: | IMAGE_NAME=$(echo "${{ env.IMAGE_NAME }}" | tr '[:upper:]' '[:lower:]') - echo "IMAGE_NAME=${IMAGE_NAME}" >> $GITHUB_ENV + echo "IMAGE_NAME=${IMAGE_NAME}" >> "$GITHUB_ENV" - name: Determine skip condition id: skip env: @@ -104,8 +104,8 @@ jobs: echo "Force building on feature branch (PR)" fi - echo "skip_build=$should_skip" >> $GITHUB_OUTPUT - echo "is_feature_push=$is_feature_push" >> $GITHUB_OUTPUT + echo "skip_build=$should_skip" >> "$GITHUB_OUTPUT" + echo "is_feature_push=$is_feature_push" >> "$GITHUB_OUTPUT" - name: Set up QEMU if: steps.skip.outputs.skip_build != 'true' @@ -119,7 +119,7 @@ jobs: run: | docker pull alpine:3.23.3 DIGEST=$(docker inspect --format='{{index .RepoDigests 0}}' alpine:3.23.3) - echo "image=$DIGEST" >> $GITHUB_OUTPUT + echo "image=$DIGEST" >> "$GITHUB_OUTPUT" - name: Log in to GitHub Container Registry if: steps.skip.outputs.skip_build != 'true' @@ -142,7 +142,7 @@ jobs: id: branch-tags run: | BRANCH_NAME="${TRIGGER_REF#refs/heads/}" - SHORT_SHA="$(echo ${{ env.TRIGGER_HEAD_SHA }} | cut -c1-7)" + SHORT_SHA="$(echo "${{ env.TRIGGER_HEAD_SHA }}" | cut -c1-7)" sanitize_tag() { local raw="$1" @@ -150,15 +150,19 @@ jobs: local sanitized sanitized=$(echo "$raw" | tr '[:upper:]' '[:lower:]') - sanitized=$(echo "$sanitized" | sed 's/[^a-z0-9-]/-/g' | sed 's/--*/-/g') - sanitized=$(echo "$sanitized" | sed 's/^[^a-z0-9]*//' | sed 's/[^a-z0-9-]*$//') + sanitized=${sanitized//[^a-z0-9-]/-} + while [[ "$sanitized" == *"--"* ]]; do + sanitized=${sanitized//--/-} + done + sanitized=${sanitized##[^a-z0-9]*} + sanitized=${sanitized%%[^a-z0-9-]*} if [ -z "$sanitized" ]; then sanitized="branch" fi sanitized=$(echo "$sanitized" | cut -c1-"$max_len") - sanitized=$(echo "$sanitized" | sed 's/^[^a-z0-9]*//') + sanitized=${sanitized##[^a-z0-9]*} if [ -z "$sanitized" ]; then sanitized="branch" fi @@ -170,11 +174,11 @@ jobs: BASE_BRANCH=$(sanitize_tag "${BRANCH_NAME}" 120) BRANCH_SHA_TAG="${BASE_BRANCH}-${SHORT_SHA}" - echo "branch_sha_tag=${BRANCH_SHA_TAG}" >> $GITHUB_OUTPUT + echo "branch_sha_tag=${BRANCH_SHA_TAG}" >> "$GITHUB_OUTPUT" if [[ "$TRIGGER_REF" == refs/heads/feature/* ]]; then - echo "feature_branch_tag=${SANITIZED_BRANCH}" >> $GITHUB_OUTPUT - echo "feature_branch_sha_tag=${BRANCH_SHA_TAG}" >> $GITHUB_OUTPUT + echo "feature_branch_tag=${SANITIZED_BRANCH}" >> "$GITHUB_OUTPUT" + echo "feature_branch_sha_tag=${BRANCH_SHA_TAG}" >> "$GITHUB_OUTPUT" fi - name: Generate Docker metadata @@ -260,7 +264,7 @@ jobs: # Extract digest for downstream jobs (format: sha256:xxxxx) DIGEST=$(cat /tmp/image-digest.txt) - echo "digest=${DIGEST}" >> $GITHUB_OUTPUT + echo "digest=${DIGEST}" >> "$GITHUB_OUTPUT" echo "โœ… Build complete. Digest: ${DIGEST}" # For PRs only, pull the image back locally for artifact creation @@ -355,13 +359,13 @@ jobs: echo "" echo "==> Caddy version:" - timeout 30s docker run --rm --pull=never $IMAGE_REF caddy version || echo "โš ๏ธ Caddy version check timed out or failed" + timeout 30s docker run --rm --pull=never "$IMAGE_REF" caddy version || echo "โš ๏ธ Caddy version check timed out or failed" echo "" echo "==> Extracting Caddy binary for inspection..." - CONTAINER_ID=$(docker create --pull=never $IMAGE_REF) - docker cp ${CONTAINER_ID}:/usr/bin/caddy ./caddy_binary - docker rm ${CONTAINER_ID} + CONTAINER_ID=$(docker create --pull=never "$IMAGE_REF") + docker cp "${CONTAINER_ID}:/usr/bin/caddy" ./caddy_binary + docker rm "$CONTAINER_ID" # Determine the image reference based on event type if [ "${{ env.TRIGGER_EVENT }}" = "pull_request" ]; then @@ -451,17 +455,17 @@ jobs: echo "" echo "==> CrowdSec cscli version:" - timeout 30s docker run --rm --pull=never $IMAGE_REF cscli version || echo "โš ๏ธ CrowdSec version check timed out or failed (may not be installed for this architecture)" + timeout 30s docker run --rm --pull=never "$IMAGE_REF" cscli version || echo "โš ๏ธ CrowdSec version check timed out or failed (may not be installed for this architecture)" echo "" echo "==> Extracting cscli binary for inspection..." - CONTAINER_ID=$(docker create --pull=never $IMAGE_REF) - docker cp ${CONTAINER_ID}:/usr/local/bin/cscli ./cscli_binary 2>/dev/null || { + CONTAINER_ID=$(docker create --pull=never "$IMAGE_REF") + docker cp "${CONTAINER_ID}:/usr/local/bin/cscli" ./cscli_binary 2>/dev/null || { echo "โš ๏ธ cscli binary not found - CrowdSec may not be available for this architecture" - docker rm ${CONTAINER_ID} + docker rm "$CONTAINER_ID" exit 0 } - docker rm ${CONTAINER_ID} + docker rm "$CONTAINER_ID" echo "" echo "==> Checking if Go toolchain is available locally..." @@ -533,9 +537,9 @@ jobs: id: trivy-check run: | if [ -f trivy-results.sarif ]; then - echo "exists=true" >> $GITHUB_OUTPUT + echo "exists=true" >> "$GITHUB_OUTPUT" else - echo "exists=false" >> $GITHUB_OUTPUT + echo "exists=false" >> "$GITHUB_OUTPUT" fi - name: Upload Trivy results @@ -597,15 +601,17 @@ jobs: - name: Create summary if: steps.skip.outputs.skip_build != 'true' run: | - echo "## ๐ŸŽ‰ Docker Image Built Successfully!" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "### ๐Ÿ“ฆ Image Details" >> $GITHUB_STEP_SUMMARY - echo "- **GHCR**: ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}" >> $GITHUB_STEP_SUMMARY - echo "- **Docker Hub**: ${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}" >> $GITHUB_STEP_SUMMARY - echo "- **Tags**: " >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - echo "${{ steps.meta.outputs.tags }}" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY + { + echo "## ๐ŸŽ‰ Docker Image Built Successfully!" + echo "" + echo "### ๐Ÿ“ฆ Image Details" + echo "- **GHCR**: ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}" + echo "- **Docker Hub**: ${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}" + echo "- **Tags**: " + echo '```' + echo "${{ steps.meta.outputs.tags }}" + echo '```' + } >> "$GITHUB_STEP_SUMMARY" scan-pr-image: name: Security Scan PR Image @@ -621,15 +627,15 @@ jobs: - name: Normalize image name run: | IMAGE_NAME=$(echo "${{ env.IMAGE_NAME }}" | tr '[:upper:]' '[:lower:]') - echo "IMAGE_NAME=${IMAGE_NAME}" >> $GITHUB_ENV + echo "IMAGE_NAME=${IMAGE_NAME}" >> "$GITHUB_ENV" - name: Determine PR image tag id: pr-image run: | - SHORT_SHA=$(echo "${{ env.TRIGGER_HEAD_SHA }}" | cut -c1-7) + SHORT_SHA="$(echo "${{ env.TRIGGER_HEAD_SHA }}" | cut -c1-7)" PR_TAG="pr-${{ env.TRIGGER_PR_NUMBER }}-${SHORT_SHA}" - echo "tag=${PR_TAG}" >> $GITHUB_OUTPUT - echo "image_ref=${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:${PR_TAG}" >> $GITHUB_OUTPUT + echo "tag=${PR_TAG}" >> "$GITHUB_OUTPUT" + echo "image_ref=${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:${PR_TAG}" >> "$GITHUB_OUTPUT" - name: Log in to GitHub Container Registry uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 @@ -692,12 +698,14 @@ jobs: - name: Create scan summary if: always() run: | - echo "## ๐Ÿ”’ PR Image Security Scan" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "- **Image**: ${{ steps.pr-image.outputs.image_ref }}" >> $GITHUB_STEP_SUMMARY - echo "- **PR**: #${{ env.TRIGGER_PR_NUMBER }}" >> $GITHUB_STEP_SUMMARY - echo "- **Commit**: ${{ env.TRIGGER_HEAD_SHA }}" >> $GITHUB_STEP_SUMMARY - echo "- **Scan Status**: ${{ steps.trivy-scan.outcome == 'success' && 'โœ… No critical vulnerabilities' || 'โŒ Vulnerabilities detected' }}" >> $GITHUB_STEP_SUMMARY + { + echo "## ๐Ÿ”’ PR Image Security Scan" + echo "" + echo "- **Image**: ${{ steps.pr-image.outputs.image_ref }}" + echo "- **PR**: #${{ env.TRIGGER_PR_NUMBER }}" + echo "- **Commit**: ${{ env.TRIGGER_HEAD_SHA }}" + echo "- **Scan Status**: ${{ steps.trivy-scan.outcome == 'success' && 'โœ… No critical vulnerabilities' || 'โŒ Vulnerabilities detected' }}" + } >> "$GITHUB_STEP_SUMMARY" test-image: name: Test Docker Image @@ -715,19 +723,25 @@ jobs: run: | raw="${{ github.repository_owner }}/${{ github.event.repository.name }}" IMAGE_NAME=$(echo "$raw" | tr '[:upper:]' '[:lower:]') - echo "IMAGE_NAME=${IMAGE_NAME}" >> $GITHUB_ENV + echo "IMAGE_NAME=${IMAGE_NAME}" >> "$GITHUB_ENV" - name: Determine image tag id: tag run: | - if [[ "${{ env.TRIGGER_REF }}" == "refs/heads/main" ]]; then - echo "tag=latest" >> $GITHUB_OUTPUT - elif [[ "${{ env.TRIGGER_REF }}" == "refs/heads/development" ]]; then - echo "tag=dev" >> $GITHUB_OUTPUT - elif [[ "${{ env.TRIGGER_REF }}" == refs/tags/v* ]]; then - echo "tag=${TRIGGER_REF#refs/tags/v}" >> $GITHUB_OUTPUT - else - echo "tag=sha-$(echo ${{ env.TRIGGER_HEAD_SHA }} | cut -c1-7)" >> $GITHUB_OUTPUT - fi + TRIGGER_REF="${{ env.TRIGGER_REF }}" + case "$TRIGGER_REF" in + refs/heads/main) + echo "tag=latest" >> "$GITHUB_OUTPUT" + ;; + refs/heads/development) + echo "tag=dev" >> "$GITHUB_OUTPUT" + ;; + refs/tags/v*) + echo "tag=${TRIGGER_REF#refs/tags/v}" >> "$GITHUB_OUTPUT" + ;; + *) + echo "tag=sha-$(echo "${{ env.TRIGGER_HEAD_SHA }}" | cut -c1-7)" >> "$GITHUB_OUTPUT" + ;; + esac - name: Log in to GitHub Container Registry uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 @@ -737,7 +751,7 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} - name: Pull Docker image - run: docker pull ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.tag.outputs.tag }} + run: docker pull "${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.tag.outputs.tag }}" - name: Create Docker Network run: docker network create charon-test-net @@ -756,7 +770,7 @@ jobs: --network charon-test-net \ -p 8080:8080 \ -p 80:80 \ - ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.tag.outputs.tag }} + "${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.tag.outputs.tag }}" # Wait for container to be healthy (max 3 minutes) echo "Waiting for container to start..." @@ -784,7 +798,9 @@ jobs: - name: Create test summary if: always() run: | - echo "## ๐Ÿงช Docker Image Test Results" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "- **Image**: ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.tag.outputs.tag }}" >> $GITHUB_STEP_SUMMARY - echo "- **Integration Test**: ${{ job.status == 'success' && 'โœ… Passed' || 'โŒ Failed' }}" >> $GITHUB_STEP_SUMMARY + { + echo "## ๐Ÿงช Docker Image Test Results" + echo "" + echo "- **Image**: ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.tag.outputs.tag }}" + echo "- **Integration Test**: ${{ job.status == 'success' && 'โœ… Passed' || 'โŒ Failed' }}" + } >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/docs-to-issues.yml b/.github/workflows/docs-to-issues.yml index 5356c609..5d7e1fb7 100644 --- a/.github/workflows/docs-to-issues.yml +++ b/.github/workflows/docs-to-issues.yml @@ -325,8 +325,8 @@ jobs: run: | mkdir -p docs/issues/created CREATED_ISSUES='${{ steps.process.outputs.created_issues }}' - echo "$CREATED_ISSUES" | jq -r '.[].file' | while read file; do - if [ -f "$file" ] && [ ! -z "$file" ]; then + echo "$CREATED_ISSUES" | jq -r '.[].file' | while IFS= read -r file; do + if [ -f "$file" ] && [ -n "$file" ]; then filename=$(basename "$file") timestamp=$(date +%Y%m%d) mv "$file" "docs/issues/created/${timestamp}-${filename}" @@ -348,29 +348,31 @@ jobs: - name: Summary if: always() run: | - echo "## Docs to Issues Summary" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - CREATED='${{ steps.process.outputs.created_issues }}' ERRORS='${{ steps.process.outputs.errors }}' DRY_RUN='${{ github.event.inputs.dry_run }}' - if [ "$DRY_RUN" = "true" ]; then - echo "๐Ÿ” **Dry Run Mode** - No issues were actually created" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - fi + { + echo "## Docs to Issues Summary" + echo "" - echo "### Created Issues" >> $GITHUB_STEP_SUMMARY - if [ -n "$CREATED" ] && [ "$CREATED" != "[]" ] && [ "$CREATED" != "null" ]; then - echo "$CREATED" | jq -r '.[] | "- \(.title) (#\(.issueNumber // "dry-run"))"' >> $GITHUB_STEP_SUMMARY || echo "_Parse error_" >> $GITHUB_STEP_SUMMARY - else - echo "_No issues created_" >> $GITHUB_STEP_SUMMARY - fi + if [ "$DRY_RUN" = "true" ]; then + echo "๐Ÿ” **Dry Run Mode** - No issues were actually created" + echo "" + fi - echo "" >> $GITHUB_STEP_SUMMARY - echo "### Errors" >> $GITHUB_STEP_SUMMARY - if [ -n "$ERRORS" ] && [ "$ERRORS" != "[]" ] && [ "$ERRORS" != "null" ]; then - echo "$ERRORS" | jq -r '.[] | "- โŒ \(.file): \(.error)"' >> $GITHUB_STEP_SUMMARY || echo "_Parse error_" >> $GITHUB_STEP_SUMMARY - else - echo "_No errors_" >> $GITHUB_STEP_SUMMARY - fi + echo "### Created Issues" + if [ -n "$CREATED" ] && [ "$CREATED" != "[]" ] && [ "$CREATED" != "null" ]; then + echo "$CREATED" | jq -r '.[] | "- \(.title) (#\(.issueNumber // "dry-run"))"' || echo "_Parse error_" + else + echo "_No issues created_" + fi + + echo "" + echo "### Errors" + if [ -n "$ERRORS" ] && [ "$ERRORS" != "[]" ] && [ "$ERRORS" != "null" ]; then + echo "$ERRORS" | jq -r '.[] | "- โŒ \(.file): \(.error)"' || echo "_Parse error_" + else + echo "_No errors_" + fi + } >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 59744172..738c4a0b 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -377,15 +377,17 @@ jobs: # Create a summary - name: ๐Ÿ“‹ Create deployment summary run: | - echo "## ๐ŸŽ‰ Documentation Deployed!" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "Your documentation is now live at:" >> $GITHUB_STEP_SUMMARY - echo "๐Ÿ”— ${{ steps.deployment.outputs.page_url }}" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "### ๐Ÿ“š What's Included" >> $GITHUB_STEP_SUMMARY - echo "- Getting Started Guide" >> $GITHUB_STEP_SUMMARY - echo "- Complete README" >> $GITHUB_STEP_SUMMARY - echo "- API Documentation" >> $GITHUB_STEP_SUMMARY - echo "- Database Schema" >> $GITHUB_STEP_SUMMARY - echo "- Import Guide" >> $GITHUB_STEP_SUMMARY - echo "- Contributing Guidelines" >> $GITHUB_STEP_SUMMARY + { + echo "## ๐ŸŽ‰ Documentation Deployed!" + echo "" + echo "Your documentation is now live at:" + echo "๐Ÿ”— ${{ steps.deployment.outputs.page_url }}" + echo "" + echo "### ๐Ÿ“š What's Included" + echo "- Getting Started Guide" + echo "- Complete README" + echo "- API Documentation" + echo "- Database Schema" + echo "- Import Guide" + echo "- Contributing Guidelines" + } >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/e2e-tests-split.yml b/.github/workflows/e2e-tests-split.yml index 312a9b77..d63cbcaf 100644 --- a/.github/workflows/e2e-tests-split.yml +++ b/.github/workflows/e2e-tests-split.yml @@ -112,20 +112,24 @@ jobs: IMAGE_REF="${{ inputs.image_ref }}" IMAGE_TAG="${{ inputs.image_tag || 'charon:e2e-test' }}" if [ -n "$IMAGE_REF" ]; then - echo "image_source=registry" >> "$GITHUB_OUTPUT" - echo "image_ref=$IMAGE_REF" >> "$GITHUB_OUTPUT" - echo "image_tag=$IMAGE_TAG" >> "$GITHUB_OUTPUT" - if [[ "$IMAGE_REF" == *@* ]]; then - echo "image_digest=${IMAGE_REF#*@}" >> "$GITHUB_OUTPUT" - else - echo "image_digest=" >> "$GITHUB_OUTPUT" - fi + { + echo "image_source=registry" + echo "image_ref=$IMAGE_REF" + echo "image_tag=$IMAGE_TAG" + if [[ "$IMAGE_REF" == *@* ]]; then + echo "image_digest=${IMAGE_REF#*@}" + else + echo "image_digest=" + fi + } >> "$GITHUB_OUTPUT" exit 0 fi - echo "image_source=build" >> "$GITHUB_OUTPUT" - echo "image_ref=" >> "$GITHUB_OUTPUT" - echo "image_tag=$IMAGE_TAG" >> "$GITHUB_OUTPUT" - echo "image_digest=" >> "$GITHUB_OUTPUT" + { + echo "image_source=build" + echo "image_ref=" + echo "image_tag=$IMAGE_TAG" + echo "image_digest=" + } >> "$GITHUB_OUTPUT" - name: Checkout repository if: steps.resolve-image.outputs.image_source == 'build' @@ -183,7 +187,7 @@ jobs: - name: Upload Docker image artifact if: steps.resolve-image.outputs.image_source == 'build' - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1 with: name: docker-image path: charon-e2e-image.tar @@ -251,7 +255,7 @@ jobs: exit 1 fi TOKEN_LENGTH=${#CHARON_EMERGENCY_TOKEN} - if [ $TOKEN_LENGTH -lt 64 ]; then + if [ "$TOKEN_LENGTH" -lt 64 ]; then echo "::error title=Invalid Token Length::CHARON_EMERGENCY_TOKEN must be at least 64 characters" exit 1 fi @@ -267,7 +271,7 @@ jobs: docker images | grep charon - name: Generate ephemeral encryption key - run: echo "CHARON_ENCRYPTION_KEY=$(openssl rand -base64 32)" >> $GITHUB_ENV + run: echo "CHARON_ENCRYPTION_KEY=$(openssl rand -base64 32)" >> "$GITHUB_ENV" - name: Start test environment (Security Tests Profile) run: | @@ -302,7 +306,7 @@ jobs: npx playwright install --with-deps chromium EXIT_CODE=$? echo "โœ… Install command completed (exit code: $EXIT_CODE)" - exit $EXIT_CODE + exit "$EXIT_CODE" - name: Run Chromium Security Enforcement Tests run: | @@ -314,7 +318,7 @@ jobs: echo "โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•" SHARD_START=$(date +%s) - echo "SHARD_START=$SHARD_START" >> $GITHUB_ENV + echo "SHARD_START=$SHARD_START" >> "$GITHUB_ENV" npx playwright test \ --project=chromium \ @@ -322,7 +326,7 @@ jobs: tests/security/ SHARD_END=$(date +%s) - echo "SHARD_END=$SHARD_END" >> $GITHUB_ENV + echo "SHARD_END=$SHARD_END" >> "$GITHUB_ENV" SHARD_DURATION=$((SHARD_END - SHARD_START)) echo "โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•" echo "Chromium Security Complete | Duration: ${SHARD_DURATION}s" @@ -333,7 +337,7 @@ jobs: - name: Upload HTML report (Chromium Security) if: always() - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1 with: name: playwright-report-chromium-security path: playwright-report/ @@ -341,7 +345,7 @@ jobs: - name: Upload Chromium Security coverage (if enabled) if: always() && env.PLAYWRIGHT_COVERAGE == '1' - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1 with: name: e2e-coverage-chromium-security path: coverage/e2e/ @@ -349,7 +353,7 @@ jobs: - name: Upload test traces on failure if: failure() - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1 with: name: traces-chromium-security path: test-results/**/*.zip @@ -362,7 +366,7 @@ jobs: - name: Upload Docker logs on failure if: failure() - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1 with: name: docker-logs-chromium-security path: docker-logs-chromium-security.txt @@ -427,7 +431,7 @@ jobs: exit 1 fi TOKEN_LENGTH=${#CHARON_EMERGENCY_TOKEN} - if [ $TOKEN_LENGTH -lt 64 ]; then + if [ "$TOKEN_LENGTH" -lt 64 ]; then echo "::error title=Invalid Token Length::CHARON_EMERGENCY_TOKEN must be at least 64 characters" exit 1 fi @@ -443,7 +447,7 @@ jobs: docker images | grep charon - name: Generate ephemeral encryption key - run: echo "CHARON_ENCRYPTION_KEY=$(openssl rand -base64 32)" >> $GITHUB_ENV + run: echo "CHARON_ENCRYPTION_KEY=$(openssl rand -base64 32)" >> "$GITHUB_ENV" - name: Start test environment (Security Tests Profile) run: | @@ -478,7 +482,7 @@ jobs: npx playwright install --with-deps chromium EXIT_CODE=$? echo "โœ… Install command completed (exit code: $EXIT_CODE)" - exit $EXIT_CODE + exit "$EXIT_CODE" - name: Install Playwright Firefox run: | @@ -486,7 +490,7 @@ jobs: npx playwright install --with-deps firefox EXIT_CODE=$? echo "โœ… Install command completed (exit code: $EXIT_CODE)" - exit $EXIT_CODE + exit "$EXIT_CODE" - name: Run Firefox Security Enforcement Tests run: | @@ -498,7 +502,7 @@ jobs: echo "โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•" SHARD_START=$(date +%s) - echo "SHARD_START=$SHARD_START" >> $GITHUB_ENV + echo "SHARD_START=$SHARD_START" >> "$GITHUB_ENV" npx playwright test \ --project=firefox \ @@ -506,7 +510,7 @@ jobs: tests/security/ SHARD_END=$(date +%s) - echo "SHARD_END=$SHARD_END" >> $GITHUB_ENV + echo "SHARD_END=$SHARD_END" >> "$GITHUB_ENV" SHARD_DURATION=$((SHARD_END - SHARD_START)) echo "โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•" echo "Firefox Security Complete | Duration: ${SHARD_DURATION}s" @@ -517,7 +521,7 @@ jobs: - name: Upload HTML report (Firefox Security) if: always() - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1 with: name: playwright-report-firefox-security path: playwright-report/ @@ -525,7 +529,7 @@ jobs: - name: Upload Firefox Security coverage (if enabled) if: always() && env.PLAYWRIGHT_COVERAGE == '1' - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1 with: name: e2e-coverage-firefox-security path: coverage/e2e/ @@ -533,7 +537,7 @@ jobs: - name: Upload test traces on failure if: failure() - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1 with: name: traces-firefox-security path: test-results/**/*.zip @@ -546,7 +550,7 @@ jobs: - name: Upload Docker logs on failure if: failure() - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1 with: name: docker-logs-firefox-security path: docker-logs-firefox-security.txt @@ -611,7 +615,7 @@ jobs: exit 1 fi TOKEN_LENGTH=${#CHARON_EMERGENCY_TOKEN} - if [ $TOKEN_LENGTH -lt 64 ]; then + if [ "$TOKEN_LENGTH" -lt 64 ]; then echo "::error title=Invalid Token Length::CHARON_EMERGENCY_TOKEN must be at least 64 characters" exit 1 fi @@ -627,7 +631,7 @@ jobs: docker images | grep charon - name: Generate ephemeral encryption key - run: echo "CHARON_ENCRYPTION_KEY=$(openssl rand -base64 32)" >> $GITHUB_ENV + run: echo "CHARON_ENCRYPTION_KEY=$(openssl rand -base64 32)" >> "$GITHUB_ENV" - name: Start test environment (Security Tests Profile) run: | @@ -662,7 +666,7 @@ jobs: npx playwright install --with-deps chromium EXIT_CODE=$? echo "โœ… Install command completed (exit code: $EXIT_CODE)" - exit $EXIT_CODE + exit "$EXIT_CODE" - name: Install Playwright WebKit run: | @@ -670,7 +674,7 @@ jobs: npx playwright install --with-deps webkit EXIT_CODE=$? echo "โœ… Install command completed (exit code: $EXIT_CODE)" - exit $EXIT_CODE + exit "$EXIT_CODE" - name: Run WebKit Security Enforcement Tests run: | @@ -682,7 +686,7 @@ jobs: echo "โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•" SHARD_START=$(date +%s) - echo "SHARD_START=$SHARD_START" >> $GITHUB_ENV + echo "SHARD_START=$SHARD_START" >> "$GITHUB_ENV" npx playwright test \ --project=webkit \ @@ -690,7 +694,7 @@ jobs: tests/security/ SHARD_END=$(date +%s) - echo "SHARD_END=$SHARD_END" >> $GITHUB_ENV + echo "SHARD_END=$SHARD_END" >> "$GITHUB_ENV" SHARD_DURATION=$((SHARD_END - SHARD_START)) echo "โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•" echo "WebKit Security Complete | Duration: ${SHARD_DURATION}s" @@ -701,7 +705,7 @@ jobs: - name: Upload HTML report (WebKit Security) if: always() - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1 with: name: playwright-report-webkit-security path: playwright-report/ @@ -709,7 +713,7 @@ jobs: - name: Upload WebKit Security coverage (if enabled) if: always() && env.PLAYWRIGHT_COVERAGE == '1' - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1 with: name: e2e-coverage-webkit-security path: coverage/e2e/ @@ -717,7 +721,7 @@ jobs: - name: Upload test traces on failure if: failure() - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1 with: name: traces-webkit-security path: test-results/**/*.zip @@ -730,7 +734,7 @@ jobs: - name: Upload Docker logs on failure if: failure() - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1 with: name: docker-logs-webkit-security path: docker-logs-webkit-security.txt @@ -806,7 +810,7 @@ jobs: docker images | grep charon - name: Generate ephemeral encryption key - run: echo "CHARON_ENCRYPTION_KEY=$(openssl rand -base64 32)" >> $GITHUB_ENV + run: echo "CHARON_ENCRYPTION_KEY=$(openssl rand -base64 32)" >> "$GITHUB_ENV" - name: Start test environment (Non-Security Profile) run: | @@ -841,7 +845,7 @@ jobs: npx playwright install --with-deps chromium EXIT_CODE=$? echo "โœ… Install command completed (exit code: $EXIT_CODE)" - exit $EXIT_CODE + exit "$EXIT_CODE" - name: Run Chromium Non-Security Tests (Shard ${{ matrix.shard }}/${{ matrix.total-shards }}) run: | @@ -853,7 +857,7 @@ jobs: echo "โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•" SHARD_START=$(date +%s) - echo "SHARD_START=$SHARD_START" >> $GITHUB_ENV + echo "SHARD_START=$SHARD_START" >> "$GITHUB_ENV" npx playwright test \ --project=chromium \ @@ -869,7 +873,7 @@ jobs: tests/tasks SHARD_END=$(date +%s) - echo "SHARD_END=$SHARD_END" >> $GITHUB_ENV + echo "SHARD_END=$SHARD_END" >> "$GITHUB_ENV" SHARD_DURATION=$((SHARD_END - SHARD_START)) echo "โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•" echo "Chromium Shard ${{ matrix.shard }} Complete | Duration: ${SHARD_DURATION}s" @@ -881,7 +885,7 @@ jobs: - name: Upload HTML report (Chromium shard ${{ matrix.shard }}) if: always() - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1 with: name: playwright-report-chromium-shard-${{ matrix.shard }} path: playwright-report/ @@ -889,7 +893,7 @@ jobs: - name: Upload Chromium coverage (if enabled) if: always() && env.PLAYWRIGHT_COVERAGE == '1' - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1 with: name: e2e-coverage-chromium-shard-${{ matrix.shard }} path: coverage/e2e/ @@ -897,7 +901,7 @@ jobs: - name: Upload test traces on failure if: failure() - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1 with: name: traces-chromium-shard-${{ matrix.shard }} path: test-results/**/*.zip @@ -910,7 +914,7 @@ jobs: - name: Upload Docker logs on failure if: failure() - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1 with: name: docker-logs-chromium-shard-${{ matrix.shard }} path: docker-logs-chromium-shard-${{ matrix.shard }}.txt @@ -979,7 +983,7 @@ jobs: docker images | grep charon - name: Generate ephemeral encryption key - run: echo "CHARON_ENCRYPTION_KEY=$(openssl rand -base64 32)" >> $GITHUB_ENV + run: echo "CHARON_ENCRYPTION_KEY=$(openssl rand -base64 32)" >> "$GITHUB_ENV" - name: Start test environment (Non-Security Profile) run: | @@ -1014,7 +1018,7 @@ jobs: npx playwright install --with-deps chromium EXIT_CODE=$? echo "โœ… Install command completed (exit code: $EXIT_CODE)" - exit $EXIT_CODE + exit "$EXIT_CODE" - name: Install Playwright Firefox run: | @@ -1022,7 +1026,7 @@ jobs: npx playwright install --with-deps firefox EXIT_CODE=$? echo "โœ… Install command completed (exit code: $EXIT_CODE)" - exit $EXIT_CODE + exit "$EXIT_CODE" - name: Run Firefox Non-Security Tests (Shard ${{ matrix.shard }}/${{ matrix.total-shards }}) run: | @@ -1034,7 +1038,7 @@ jobs: echo "โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•" SHARD_START=$(date +%s) - echo "SHARD_START=$SHARD_START" >> $GITHUB_ENV + echo "SHARD_START=$SHARD_START" >> "$GITHUB_ENV" npx playwright test \ --project=firefox \ @@ -1050,7 +1054,7 @@ jobs: tests/tasks SHARD_END=$(date +%s) - echo "SHARD_END=$SHARD_END" >> $GITHUB_ENV + echo "SHARD_END=$SHARD_END" >> "$GITHUB_ENV" SHARD_DURATION=$((SHARD_END - SHARD_START)) echo "โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•" echo "Firefox Shard ${{ matrix.shard }} Complete | Duration: ${SHARD_DURATION}s" @@ -1062,7 +1066,7 @@ jobs: - name: Upload HTML report (Firefox shard ${{ matrix.shard }}) if: always() - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1 with: name: playwright-report-firefox-shard-${{ matrix.shard }} path: playwright-report/ @@ -1070,7 +1074,7 @@ jobs: - name: Upload Firefox coverage (if enabled) if: always() && env.PLAYWRIGHT_COVERAGE == '1' - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1 with: name: e2e-coverage-firefox-shard-${{ matrix.shard }} path: coverage/e2e/ @@ -1078,7 +1082,7 @@ jobs: - name: Upload test traces on failure if: failure() - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1 with: name: traces-firefox-shard-${{ matrix.shard }} path: test-results/**/*.zip @@ -1091,7 +1095,7 @@ jobs: - name: Upload Docker logs on failure if: failure() - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1 with: name: docker-logs-firefox-shard-${{ matrix.shard }} path: docker-logs-firefox-shard-${{ matrix.shard }}.txt @@ -1160,7 +1164,7 @@ jobs: docker images | grep charon - name: Generate ephemeral encryption key - run: echo "CHARON_ENCRYPTION_KEY=$(openssl rand -base64 32)" >> $GITHUB_ENV + run: echo "CHARON_ENCRYPTION_KEY=$(openssl rand -base64 32)" >> "$GITHUB_ENV" - name: Start test environment (Non-Security Profile) run: | @@ -1195,7 +1199,7 @@ jobs: npx playwright install --with-deps chromium EXIT_CODE=$? echo "โœ… Install command completed (exit code: $EXIT_CODE)" - exit $EXIT_CODE + exit "$EXIT_CODE" - name: Install Playwright WebKit run: | @@ -1203,7 +1207,7 @@ jobs: npx playwright install --with-deps webkit EXIT_CODE=$? echo "โœ… Install command completed (exit code: $EXIT_CODE)" - exit $EXIT_CODE + exit "$EXIT_CODE" - name: Run WebKit Non-Security Tests (Shard ${{ matrix.shard }}/${{ matrix.total-shards }}) run: | @@ -1215,7 +1219,7 @@ jobs: echo "โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•" SHARD_START=$(date +%s) - echo "SHARD_START=$SHARD_START" >> $GITHUB_ENV + echo "SHARD_START=$SHARD_START" >> "$GITHUB_ENV" npx playwright test \ --project=webkit \ @@ -1231,7 +1235,7 @@ jobs: tests/tasks SHARD_END=$(date +%s) - echo "SHARD_END=$SHARD_END" >> $GITHUB_ENV + echo "SHARD_END=$SHARD_END" >> "$GITHUB_ENV" SHARD_DURATION=$((SHARD_END - SHARD_START)) echo "โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•" echo "WebKit Shard ${{ matrix.shard }} Complete | Duration: ${SHARD_DURATION}s" @@ -1292,30 +1296,32 @@ jobs: steps: - name: Generate job summary run: | - echo "## ๐Ÿ“Š E2E Test Results (Split: Security + Sharded)" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "### Architecture: 15 Total Jobs" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "#### Security Enforcement (3 jobs)" >> $GITHUB_STEP_SUMMARY - echo "| Browser | Status | Shards | Timeout | Cerberus |" >> $GITHUB_STEP_SUMMARY - echo "|---------|--------|--------|---------|----------|" >> $GITHUB_STEP_SUMMARY - echo "| Chromium | ${{ needs.e2e-chromium-security.result }} | 1 | 30min | ON |" >> $GITHUB_STEP_SUMMARY - echo "| Firefox | ${{ needs.e2e-firefox-security.result }} | 1 | 30min | ON |" >> $GITHUB_STEP_SUMMARY - echo "| WebKit | ${{ needs.e2e-webkit-security.result }} | 1 | 30min | ON |" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "#### Non-Security Tests (12 jobs)" >> $GITHUB_STEP_SUMMARY - echo "| Browser | Status | Shards | Timeout | Cerberus |" >> $GITHUB_STEP_SUMMARY - echo "|---------|--------|--------|---------|----------|" >> $GITHUB_STEP_SUMMARY - echo "| Chromium | ${{ needs.e2e-chromium.result }} | 4 | 20min | OFF |" >> $GITHUB_STEP_SUMMARY - echo "| Firefox | ${{ needs.e2e-firefox.result }} | 4 | 20min | OFF |" >> $GITHUB_STEP_SUMMARY - echo "| WebKit | ${{ needs.e2e-webkit.result }} | 4 | 20min | OFF |" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "### Benefits" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "- โœ… **Isolation:** Security tests run independently without ACL/rate limit interference" >> $GITHUB_STEP_SUMMARY - echo "- โœ… **Performance:** Non-security tests sharded 4-way for faster execution" >> $GITHUB_STEP_SUMMARY - echo "- โœ… **Reliability:** Cerberus OFF by default prevents cross-shard contamination" >> $GITHUB_STEP_SUMMARY - echo "- โœ… **Clarity:** Separate artifacts for security vs non-security test results" >> $GITHUB_STEP_SUMMARY + { + echo "## ๐Ÿ“Š E2E Test Results (Split: Security + Sharded)" + echo "" + echo "### Architecture: 15 Total Jobs" + echo "" + echo "#### Security Enforcement (3 jobs)" + echo "| Browser | Status | Shards | Timeout | Cerberus |" + echo "|---------|--------|--------|---------|----------|" + echo "| Chromium | ${{ needs.e2e-chromium-security.result }} | 1 | 30min | ON |" + echo "| Firefox | ${{ needs.e2e-firefox-security.result }} | 1 | 30min | ON |" + echo "| WebKit | ${{ needs.e2e-webkit-security.result }} | 1 | 30min | ON |" + echo "" + echo "#### Non-Security Tests (12 jobs)" + echo "| Browser | Status | Shards | Timeout | Cerberus |" + echo "|---------|--------|--------|---------|----------|" + echo "| Chromium | ${{ needs.e2e-chromium.result }} | 4 | 20min | OFF |" + echo "| Firefox | ${{ needs.e2e-firefox.result }} | 4 | 20min | OFF |" + echo "| WebKit | ${{ needs.e2e-webkit.result }} | 4 | 20min | OFF |" + echo "" + echo "### Benefits" + echo "" + echo "- โœ… **Isolation:** Security tests run independently without ACL/rate limit interference" + echo "- โœ… **Performance:** Non-security tests sharded 4-way for faster execution" + echo "- โœ… **Reliability:** Cerberus OFF by default prevents cross-shard contamination" + echo "- โœ… **Clarity:** Separate artifacts for security vs non-security test results" + } >> "$GITHUB_STEP_SUMMARY" # Final status check e2e-results: diff --git a/.github/workflows/gh_cache_cleanup.yml b/.github/workflows/gh_cache_cleanup.yml index 6909d315..07c5d79d 100644 --- a/.github/workflows/gh_cache_cleanup.yml +++ b/.github/workflows/gh_cache_cleanup.yml @@ -13,15 +13,14 @@ jobs: - name: Cleanup run: | echo "Fetching list of cache keys" - cacheKeysForPR=$(gh cache list --ref $BRANCH --limit 100 --json id --jq '.[].id') + cacheKeysForPR=$(gh cache list --ref "$BRANCH" --limit 100 --json id --jq '.[].id') ## Setting this to not fail the workflow while deleting cache keys. set +e echo "Deleting caches..." - for cacheKey in $cacheKeysForPR - do - gh cache delete $cacheKey - done + while IFS= read -r cacheKey; do + gh cache delete "$cacheKey" + done <<< "$cacheKeysForPR" echo "Done" env: GH_TOKEN: ${{ github.token }} diff --git a/.github/workflows/nightly-build.yml b/.github/workflows/nightly-build.yml index deaec77c..f5c09a77 100644 --- a/.github/workflows/nightly-build.yml +++ b/.github/workflows/nightly-build.yml @@ -57,7 +57,7 @@ jobs: # Check if there are differences between remote branches if git diff --quiet origin/nightly origin/development; then echo "No changes to sync from development to nightly" - echo "has_changes=false" >> $GITHUB_OUTPUT + echo "has_changes=false" >> "$GITHUB_OUTPUT" else echo "Syncing changes from development to nightly" # Fast-forward merge development into nightly @@ -68,7 +68,7 @@ jobs: } # Force push to handle cases where nightly diverged from development git push --force origin nightly - echo "has_changes=true" >> $GITHUB_OUTPUT + echo "has_changes=true" >> "$GITHUB_OUTPUT" fi build-and-push-nightly: @@ -93,7 +93,7 @@ jobs: fetch-depth: 0 - name: Set lowercase image name - run: echo "IMAGE_NAME_LC=${IMAGE_NAME,,}" >> $GITHUB_ENV + run: echo "IMAGE_NAME_LC=${IMAGE_NAME,,}" >> "$GITHUB_ENV" - name: Set up QEMU uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0 @@ -151,8 +151,8 @@ jobs: - name: Record nightly image digest run: | - echo "## ๐Ÿงพ Nightly Image Digest" >> $GITHUB_STEP_SUMMARY - echo "- ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:nightly@${{ steps.build.outputs.digest }}" >> $GITHUB_STEP_SUMMARY + echo "## ๐Ÿงพ Nightly Image Digest" >> "$GITHUB_STEP_SUMMARY" + echo "- ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:nightly@${{ steps.build.outputs.digest }}" >> "$GITHUB_STEP_SUMMARY" - name: Generate SBOM uses: anchore/sbom-action@28d71544de8eaf1b958d335707167c5f783590ad # v0.22.2 @@ -176,7 +176,7 @@ jobs: - name: Sign GHCR Image run: | echo "Signing GHCR nightly image with keyless signing..." - cosign sign --yes ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.build.outputs.digest }} + cosign sign --yes "${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.build.outputs.digest }}" echo "โœ… GHCR nightly image signed successfully" # Sign Docker Hub image with keyless signing (Sigstore/Fulcio) @@ -184,7 +184,7 @@ jobs: if: env.HAS_DOCKERHUB_TOKEN == 'true' run: | echo "Signing Docker Hub nightly image with keyless signing..." - cosign sign --yes ${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.build.outputs.digest }} + cosign sign --yes "${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.build.outputs.digest }}" echo "โœ… Docker Hub nightly image signed successfully" # Attach SBOM to Docker Hub image @@ -192,7 +192,7 @@ jobs: if: env.HAS_DOCKERHUB_TOKEN == 'true' run: | echo "Attaching SBOM to Docker Hub nightly image..." - cosign attach sbom --sbom sbom-nightly.json ${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.build.outputs.digest }} + cosign attach sbom --sbom sbom-nightly.json "${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.build.outputs.digest }}" echo "โœ… SBOM attached to Docker Hub nightly image" test-nightly-image: @@ -209,7 +209,7 @@ jobs: ref: nightly - name: Set lowercase image name - run: echo "IMAGE_NAME_LC=${IMAGE_NAME,,}" >> $GITHUB_ENV + run: echo "IMAGE_NAME_LC=${IMAGE_NAME,,}" >> "$GITHUB_ENV" - name: Log in to GitHub Container Registry uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 @@ -219,13 +219,13 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} - name: Pull nightly image - run: docker pull ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:nightly@${{ needs.build-and-push-nightly.outputs.digest }} + run: docker pull "${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:nightly@${{ needs.build-and-push-nightly.outputs.digest }}" - name: Run container smoke test run: | docker run --name charon-nightly -d \ -p 8080:8080 \ - ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:nightly@${{ needs.build-and-push-nightly.outputs.digest }} + "${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:nightly@${{ needs.build-and-push-nightly.outputs.digest }}" # Wait for container to start sleep 10 @@ -263,7 +263,7 @@ jobs: ref: nightly - name: Set lowercase image name - run: echo "IMAGE_NAME_LC=${IMAGE_NAME,,}" >> $GITHUB_ENV + run: echo "IMAGE_NAME_LC=${IMAGE_NAME,,}" >> "$GITHUB_ENV" - name: Download SBOM uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 diff --git a/.github/workflows/quality-checks.yml b/.github/workflows/quality-checks.yml index 5fdadb69..4f20fb63 100644 --- a/.github/workflows/quality-checks.yml +++ b/.github/workflows/quality-checks.yml @@ -48,26 +48,27 @@ jobs: CGO_ENABLED: 1 run: | bash scripts/go-test-coverage.sh 2>&1 | tee backend/test-output.txt - exit ${PIPESTATUS[0]} + exit "${PIPESTATUS[0]}" - name: Go Test Summary if: always() working-directory: backend run: | - echo "## ๐Ÿ”ง Backend Test Results" >> $GITHUB_STEP_SUMMARY - if [ "${{ steps.go-tests.outcome }}" == "success" ]; then - echo "โœ… **All tests passed**" >> $GITHUB_STEP_SUMMARY - PASS_COUNT=$(grep -c "^--- PASS" test-output.txt || echo "0") - echo "- Tests passed: $PASS_COUNT" >> $GITHUB_STEP_SUMMARY - else - echo "โŒ **Tests failed**" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "### Failed Tests:" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - grep -E "^--- FAIL|FAIL\s+github" test-output.txt || echo "See logs for details" - grep -E "^--- FAIL|FAIL\s+github" test-output.txt >> $GITHUB_STEP_SUMMARY || echo "See logs for details" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - fi + { + echo "## ๐Ÿ”ง Backend Test Results" + if [ "${{ steps.go-tests.outcome }}" == "success" ]; then + echo "โœ… **All tests passed**" + PASS_COUNT=$(grep -c "^--- PASS" test-output.txt || echo "0") + echo "- Tests passed: $PASS_COUNT" + else + echo "โŒ **Tests failed**" + echo "" + echo "### Failed Tests:" + echo '```' + grep -E "^--- FAIL|FAIL\s+github" test-output.txt || echo "See logs for details" + echo '```' + fi + } >> "$GITHUB_STEP_SUMMARY" # Codecov upload moved to `codecov-upload.yml` (Docker Build-gated). @@ -89,24 +90,26 @@ jobs: - name: GORM Security Scan Summary if: always() run: | - echo "## ๐Ÿ”’ GORM Security Scan Results" >> $GITHUB_STEP_SUMMARY - if [ "${{ steps.gorm-scan.outcome }}" == "success" ]; then - echo "โœ… **No GORM security issues detected**" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "All models follow secure GORM patterns:" >> $GITHUB_STEP_SUMMARY - echo "- โœ… No exposed internal database IDs" >> $GITHUB_STEP_SUMMARY - echo "- โœ… No exposed API keys or secrets" >> $GITHUB_STEP_SUMMARY - echo "- โœ… Response DTOs properly structured" >> $GITHUB_STEP_SUMMARY - else - echo "โŒ **GORM security issues found**" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "Run locally for details:" >> $GITHUB_STEP_SUMMARY - echo '```bash' >> $GITHUB_STEP_SUMMARY - echo "./scripts/scan-gorm-security.sh --report" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "See [GORM Security Scanner docs](docs/implementation/gorm_security_scanner_complete.md) for remediation guidance." >> $GITHUB_STEP_SUMMARY - fi + { + echo "## ๐Ÿ”’ GORM Security Scan Results" + if [ "${{ steps.gorm-scan.outcome }}" == "success" ]; then + echo "โœ… **No GORM security issues detected**" + echo "" + echo "All models follow secure GORM patterns:" + echo "- โœ… No exposed internal database IDs" + echo "- โœ… No exposed API keys or secrets" + echo "- โœ… Response DTOs properly structured" + else + echo "โŒ **GORM security issues found**" + echo "" + echo "Run locally for details:" + echo '```bash' + echo "./scripts/scan-gorm-security.sh --report" + echo '```' + echo "" + echo "See [GORM Security Scanner docs](docs/implementation/gorm_security_scanner_complete.md) for remediation guidance." + fi + } >> "$GITHUB_STEP_SUMMARY" - name: Annotate GORM Security Issues if: failure() && steps.gorm-scan.outcome == 'failure' @@ -121,9 +124,9 @@ jobs: PERF_MAX_MS_GETSTATUS_P95_PARALLEL: 1500ms PERF_MAX_MS_LISTDECISIONS_P95: 2000ms run: | - echo "## ๐Ÿ” Running performance assertions (TestPerf)" >> $GITHUB_STEP_SUMMARY + echo "## ๐Ÿ” Running performance assertions (TestPerf)" >> "$GITHUB_STEP_SUMMARY" go test -run TestPerf -v ./internal/api/handlers -count=1 | tee perf-output.txt - exit ${PIPESTATUS[0]} + exit "${PIPESTATUS[0]}" frontend-quality: name: Frontend (React) @@ -153,7 +156,7 @@ jobs: BASE_REF="${{ github.event.pull_request.base.ref }}" if [ "$EVENT_NAME" = "push" ]; then - echo "frontend_changed=true" >> $GITHUB_OUTPUT + echo "frontend_changed=true" >> "$GITHUB_OUTPUT" exit 0 fi # Try to fetch the PR base ref. This may fail for forked PRs or other cases. @@ -167,25 +170,25 @@ jobs: else CHANGED="" fi - echo "Changed files (base ref):\n$CHANGED" + printf 'Changed files (base ref):\n%s\n' "$CHANGED" if [ -z "$CHANGED" ]; then echo "Base ref diff empty or failed; fetching origin/main for fallback..." git fetch origin main --depth=1 || true CHANGED=$(git diff --name-only origin/main...HEAD 2>/dev/null || echo "") - echo "Changed files (main fallback):\n$CHANGED" + printf 'Changed files (main fallback):\n%s\n' "$CHANGED" fi if [ -z "$CHANGED" ]; then echo "Still empty; falling back to diffing last 10 commits from HEAD..." CHANGED=$(git diff --name-only HEAD~10...HEAD 2>/dev/null || echo "") - echo "Changed files (HEAD~10 fallback):\n$CHANGED" + printf 'Changed files (HEAD~10 fallback):\n%s\n' "$CHANGED" fi if echo "$CHANGED" | grep -q '^frontend/'; then - echo "frontend_changed=true" >> $GITHUB_OUTPUT + echo "frontend_changed=true" >> "$GITHUB_OUTPUT" else - echo "frontend_changed=false" >> $GITHUB_OUTPUT + echo "frontend_changed=false" >> "$GITHUB_OUTPUT" fi - name: Install dependencies @@ -199,28 +202,30 @@ jobs: if: ${{ inputs.run_frontend != false && (github.event_name == 'workflow_dispatch' || steps.check-frontend.outputs.frontend_changed == 'true') }} run: | bash scripts/frontend-test-coverage.sh 2>&1 | tee frontend/test-output.txt - exit ${PIPESTATUS[0]} + exit "${PIPESTATUS[0]}" - name: Frontend Test Summary if: always() working-directory: frontend run: | - echo "## โš›๏ธ Frontend Test Results" >> $GITHUB_STEP_SUMMARY - if [ "${{ steps.frontend-tests.outcome }}" == "success" ]; then - echo "โœ… **All tests passed**" >> $GITHUB_STEP_SUMMARY - # Extract test counts from vitest output - if grep -q "Tests:" test-output.txt; then - grep "Tests:" test-output.txt | tail -1 >> $GITHUB_STEP_SUMMARY + { + echo "## โš›๏ธ Frontend Test Results" + if [ "${{ steps.frontend-tests.outcome }}" == "success" ]; then + echo "โœ… **All tests passed**" + # Extract test counts from vitest output + if grep -q "Tests:" test-output.txt; then + grep "Tests:" test-output.txt | tail -1 + fi + else + echo "โŒ **Tests failed**" + echo "" + echo "### Failed Tests:" + echo '```' + # Extract failed test info from vitest output + grep -E "FAIL|โœ•|ร—|AssertionError|Error:" test-output.txt | head -30 || echo "See logs for details" + echo '```' fi - else - echo "โŒ **Tests failed**" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "### Failed Tests:" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - # Extract failed test info from vitest output - grep -E "FAIL|โœ•|ร—|AssertionError|Error:" test-output.txt | head -30 >> $GITHUB_STEP_SUMMARY || echo "See logs for details" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - fi + } >> "$GITHUB_STEP_SUMMARY" # Codecov upload moved to `codecov-upload.yml` (Docker Build-gated). diff --git a/.github/workflows/rate-limit-integration.yml b/.github/workflows/rate-limit-integration.yml index 8dd24650..ab36a10d 100644 --- a/.github/workflows/rate-limit-integration.yml +++ b/.github/workflows/rate-limit-integration.yml @@ -3,6 +3,9 @@ name: Rate Limit integration # Phase 2-3: Build Once, Test Many - Use registry image instead of building # This workflow now waits for docker-build.yml to complete and pulls the built image on: + workflow_run: + workflows: ["Docker Build, Publish & Test"] + types: [completed] workflow_dispatch: inputs: image_tag: @@ -40,12 +43,15 @@ jobs: # Manual trigger uses provided tag if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then if [[ -n "$MANUAL_TAG" ]]; then - echo "tag=${MANUAL_TAG}" >> $GITHUB_OUTPUT + TAG_VALUE="$MANUAL_TAG" else # Default to latest if no tag provided - echo "tag=latest" >> $GITHUB_OUTPUT + TAG_VALUE="latest" fi - echo "source_type=manual" >> $GITHUB_OUTPUT + { + echo "tag=${TAG_VALUE}" + echo "source_type=manual" + } >> "$GITHUB_OUTPUT" exit 0 fi @@ -72,16 +78,20 @@ jobs: fi # Immutable tag with SHA suffix prevents race conditions - echo "tag=pr-${PR_NUM}-${SHORT_SHA}" >> $GITHUB_OUTPUT - echo "source_type=pr" >> $GITHUB_OUTPUT + { + echo "tag=pr-${PR_NUM}-${SHORT_SHA}" + echo "source_type=pr" + } >> "$GITHUB_OUTPUT" else # Non-PR workflow_run uses short SHA tag (matches docker-build.yml) - echo "tag=sha-${SHORT_SHA}" >> $GITHUB_OUTPUT - echo "source_type=sha" >> $GITHUB_OUTPUT + { + echo "tag=sha-${SHORT_SHA}" + echo "source_type=sha" + } >> "$GITHUB_OUTPUT" fi - echo "sha=${SHORT_SHA}" >> $GITHUB_OUTPUT - echo "Determined image tag: $(cat $GITHUB_OUTPUT | grep tag=)" + echo "sha=${SHORT_SHA}" >> "$GITHUB_OUTPUT" + echo "Determined image tag: $(grep tag= "$GITHUB_OUTPUT")" # Pull image from Docker Hub with retry logic - name: Pull Docker image from registry @@ -119,69 +129,72 @@ jobs: run: | chmod +x scripts/rate_limit_integration.sh scripts/rate_limit_integration.sh 2>&1 | tee ratelimit-test-output.txt - exit ${PIPESTATUS[0]} + exit "${PIPESTATUS[0]}" - name: Dump Debug Info on Failure if: failure() run: | - echo "## ๐Ÿ” Debug Information" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY + { + echo "## ๐Ÿ” Debug Information" + echo "" - echo "### Container Status" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - docker ps -a --filter "name=charon" --filter "name=ratelimit" --filter "name=backend" >> $GITHUB_STEP_SUMMARY 2>&1 || true - echo '```' >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY + echo "### Container Status" + echo '```' + docker ps -a --filter "name=charon" --filter "name=ratelimit" --filter "name=backend" 2>&1 || true + echo '```' + echo "" - echo "### Security Config API" >> $GITHUB_STEP_SUMMARY - echo '```json' >> $GITHUB_STEP_SUMMARY - curl -s http://localhost:8280/api/v1/security/config 2>/dev/null | head -100 >> $GITHUB_STEP_SUMMARY || echo "Could not retrieve security config" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY + echo "### Security Config API" + echo '```json' + curl -s http://localhost:8280/api/v1/security/config 2>/dev/null | head -100 || echo "Could not retrieve security config" + echo '```' + echo "" - echo "### Security Status API" >> $GITHUB_STEP_SUMMARY - echo '```json' >> $GITHUB_STEP_SUMMARY - curl -s http://localhost:8280/api/v1/security/status 2>/dev/null | head -100 >> $GITHUB_STEP_SUMMARY || echo "Could not retrieve security status" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY + echo "### Security Status API" + echo '```json' + curl -s http://localhost:8280/api/v1/security/status 2>/dev/null | head -100 || echo "Could not retrieve security status" + echo '```' + echo "" - echo "### Caddy Admin Config (rate_limit handlers)" >> $GITHUB_STEP_SUMMARY - echo '```json' >> $GITHUB_STEP_SUMMARY - curl -s http://localhost:2119/config 2>/dev/null | grep -A 20 '"handler":"rate_limit"' | head -30 >> $GITHUB_STEP_SUMMARY || echo "Could not retrieve Caddy config" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY + echo "### Caddy Admin Config (rate_limit handlers)" + echo '```json' + curl -s http://localhost:2119/config 2>/dev/null | grep -A 20 '"handler":"rate_limit"' | head -30 || echo "Could not retrieve Caddy config" + echo '```' + echo "" - echo "### Charon Container Logs (last 100 lines)" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - docker logs charon-ratelimit-test 2>&1 | tail -100 >> $GITHUB_STEP_SUMMARY || echo "No container logs available" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY + echo "### Charon Container Logs (last 100 lines)" + echo '```' + docker logs charon-ratelimit-test 2>&1 | tail -100 || echo "No container logs available" + echo '```' + } >> "$GITHUB_STEP_SUMMARY" - name: Rate Limit Integration Summary if: always() run: | - echo "## โฑ๏ธ Rate Limit Integration Test Results" >> $GITHUB_STEP_SUMMARY - if [ "${{ steps.ratelimit-test.outcome }}" == "success" ]; then - echo "โœ… **All rate limit tests passed**" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "### Test Results:" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - grep -E "โœ“|=== ALL|HTTP 429|HTTP 200" ratelimit-test-output.txt | head -30 || echo "See logs for details" - grep -E "โœ“|=== ALL|HTTP 429|HTTP 200" ratelimit-test-output.txt | head -30 >> $GITHUB_STEP_SUMMARY || echo "See logs for details" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "### Verified Behaviors:" >> $GITHUB_STEP_SUMMARY - echo "- Requests within limit return HTTP 200" >> $GITHUB_STEP_SUMMARY - echo "- Requests exceeding limit return HTTP 429" >> $GITHUB_STEP_SUMMARY - echo "- Retry-After header present on blocked responses" >> $GITHUB_STEP_SUMMARY - echo "- Rate limit window resets correctly" >> $GITHUB_STEP_SUMMARY - else - echo "โŒ **Rate limit tests failed**" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "### Failure Details:" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - grep -E "โœ—|FAIL|Error|failed|expected" ratelimit-test-output.txt | head -30 >> $GITHUB_STEP_SUMMARY || echo "See logs for details" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - fi + { + echo "## โฑ๏ธ Rate Limit Integration Test Results" + if [ "${{ steps.ratelimit-test.outcome }}" == "success" ]; then + echo "โœ… **All rate limit tests passed**" + echo "" + echo "### Test Results:" + echo '```' + grep -E "โœ“|=== ALL|HTTP 429|HTTP 200" ratelimit-test-output.txt | head -30 || echo "See logs for details" + echo '```' + echo "" + echo "### Verified Behaviors:" + echo "- Requests within limit return HTTP 200" + echo "- Requests exceeding limit return HTTP 429" + echo "- Retry-After header present on blocked responses" + echo "- Rate limit window resets correctly" + else + echo "โŒ **Rate limit tests failed**" + echo "" + echo "### Failure Details:" + echo '```' + grep -E "โœ—|FAIL|Error|failed|expected" ratelimit-test-output.txt | head -30 || echo "See logs for details" + echo '```' + fi + } >> "$GITHUB_STEP_SUMMARY" - name: Cleanup if: always() diff --git a/.github/workflows/release-goreleaser.yml b/.github/workflows/release-goreleaser.yml index 33cde6b8..2aa4ad3d 100644 --- a/.github/workflows/release-goreleaser.yml +++ b/.github/workflows/release-goreleaser.yml @@ -47,7 +47,7 @@ jobs: run: | # Inject version into frontend build from tag (if present) VERSION=${GITHUB_REF#refs/tags/} - echo "VITE_APP_VERSION=${VERSION}" >> $GITHUB_ENV + echo "VITE_APP_VERSION=${VERSION}" >> "$GITHUB_ENV" npm ci npm run build diff --git a/.github/workflows/renovate_prune.yml b/.github/workflows/renovate_prune.yml index 8757b993..dea92e98 100644 --- a/.github/workflows/renovate_prune.yml +++ b/.github/workflows/renovate_prune.yml @@ -26,10 +26,10 @@ jobs: run: | if [ -n "${{ secrets.GITHUB_TOKEN }}" ]; then echo "Using GITHUB_TOKEN" >&2 - echo "GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }}" >> $GITHUB_ENV + echo "GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }}" >> "$GITHUB_ENV" else echo "Using CHARON_TOKEN fallback" >&2 - echo "GITHUB_TOKEN=${{ secrets.CHARON_TOKEN }}" >> $GITHUB_ENV + echo "GITHUB_TOKEN=${{ secrets.CHARON_TOKEN }}" >> "$GITHUB_ENV" fi - name: Prune renovate branches uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 diff --git a/.github/workflows/security-pr.yml b/.github/workflows/security-pr.yml index f1f27539..b0732348 100644 --- a/.github/workflows/security-pr.yml +++ b/.github/workflows/security-pr.yml @@ -306,23 +306,25 @@ jobs: - name: Create job summary if: always() && (steps.check-artifact.outputs.artifact_exists == 'true' || github.event_name == 'push' || github.event_name == 'pull_request') run: | - if [[ "${{ steps.pr-info.outputs.is_push }}" == "true" ]]; then - echo "## ๐Ÿ”’ Security Scan Results - Branch: ${{ github.event.workflow_run.head_branch }}" >> $GITHUB_STEP_SUMMARY - else - echo "## ๐Ÿ”’ Security Scan Results - PR #${{ steps.pr-info.outputs.pr_number }}" >> $GITHUB_STEP_SUMMARY - fi - echo "" >> $GITHUB_STEP_SUMMARY - echo "**Scan Type**: Trivy Filesystem Scan" >> $GITHUB_STEP_SUMMARY - echo "**Target**: \`/app/charon\` binary" >> $GITHUB_STEP_SUMMARY - echo "**Severity Filter**: CRITICAL, HIGH" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - if [[ "${{ job.status }}" == "success" ]]; then - echo "โœ… **PASSED**: No CRITICAL or HIGH vulnerabilities found" >> $GITHUB_STEP_SUMMARY - else - echo "โŒ **FAILED**: CRITICAL or HIGH vulnerabilities detected" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "Please review the Trivy scan output and address the vulnerabilities." >> $GITHUB_STEP_SUMMARY - fi + { + if [[ "${{ steps.pr-info.outputs.is_push }}" == "true" ]]; then + echo "## ๐Ÿ”’ Security Scan Results - Branch: ${{ github.event.workflow_run.head_branch }}" + else + echo "## ๐Ÿ”’ Security Scan Results - PR #${{ steps.pr-info.outputs.pr_number }}" + fi + echo "" + echo "**Scan Type**: Trivy Filesystem Scan" + echo "**Target**: \`/app/charon\` binary" + echo "**Severity Filter**: CRITICAL, HIGH" + echo "" + if [[ "${{ job.status }}" == "success" ]]; then + echo "โœ… **PASSED**: No CRITICAL or HIGH vulnerabilities found" + else + echo "โŒ **FAILED**: CRITICAL or HIGH vulnerabilities detected" + echo "" + echo "Please review the Trivy scan output and address the vulnerabilities." + fi + } >> "$GITHUB_STEP_SUMMARY" - name: Cleanup if: always() && steps.check-artifact.outputs.artifact_exists == 'true' diff --git a/.github/workflows/security-weekly-rebuild.yml b/.github/workflows/security-weekly-rebuild.yml index 5cd216ff..c1e618cb 100644 --- a/.github/workflows/security-weekly-rebuild.yml +++ b/.github/workflows/security-weekly-rebuild.yml @@ -39,7 +39,7 @@ jobs: - name: Normalize image name run: | - echo "IMAGE_NAME=$(echo "${{ env.IMAGE_NAME }}" | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV + echo "IMAGE_NAME=$(echo "${{ env.IMAGE_NAME }}" | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_ENV" - name: Set up QEMU uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0 @@ -52,7 +52,7 @@ jobs: run: | docker pull debian:trixie-slim DIGEST=$(docker inspect --format='{{index .RepoDigests 0}}' debian:trixie-slim) - echo "digest=$DIGEST" >> $GITHUB_OUTPUT + echo "digest=$DIGEST" >> "$GITHUB_OUTPUT" echo "Base image digest: $DIGEST" - name: Log in to Container Registry @@ -127,28 +127,32 @@ jobs: - name: Check Debian package versions run: | - echo "## ๐Ÿ“ฆ Installed Package Versions" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "Checking key security packages:" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - docker run --rm --entrypoint "" ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.build.outputs.digest }} \ - sh -c "dpkg -l | grep -E 'libc-ares|curl|libcurl|openssl|libssl' || echo 'No matching packages found'" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY + { + echo "## ๐Ÿ“ฆ Installed Package Versions" + echo "" + echo "Checking key security packages:" + echo '```' + docker run --rm --entrypoint "" "${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.build.outputs.digest }}" \ + sh -c "dpkg -l | grep -E 'libc-ares|curl|libcurl|openssl|libssl' || echo 'No matching packages found'" + echo '```' + } >> "$GITHUB_STEP_SUMMARY" - name: Create security scan summary if: always() run: | - echo "## ๐Ÿ”’ Weekly Security Rebuild Complete" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "- **Build Date:** $(date -u +"%Y-%m-%d %H:%M:%S UTC")" >> $GITHUB_STEP_SUMMARY - echo "- **Image:** ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.build.outputs.digest }}" >> $GITHUB_STEP_SUMMARY - echo "- **Cache Used:** No (forced fresh build)" >> $GITHUB_STEP_SUMMARY - echo "- **Trivy Scan:** Completed (see Security tab for details)" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "### Next Steps:" >> $GITHUB_STEP_SUMMARY - echo "1. Review Security tab for new vulnerabilities" >> $GITHUB_STEP_SUMMARY - echo "2. Check Trivy JSON artifact for detailed package info" >> $GITHUB_STEP_SUMMARY - echo "3. If critical CVEs found, trigger production rebuild" >> $GITHUB_STEP_SUMMARY + { + echo "## ๐Ÿ”’ Weekly Security Rebuild Complete" + echo "" + echo "- **Build Date:** $(date -u +"%Y-%m-%d %H:%M:%S UTC")" + echo "- **Image:** ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.build.outputs.digest }}" + echo "- **Cache Used:** No (forced fresh build)" + echo "- **Trivy Scan:** Completed (see Security tab for details)" + echo "" + echo "### Next Steps:" + echo "1. Review Security tab for new vulnerabilities" + echo "2. Check Trivy JSON artifact for detailed package info" + echo "3. If critical CVEs found, trigger production rebuild" + } >> "$GITHUB_STEP_SUMMARY" - name: Notify on security issues (optional) if: failure() diff --git a/.github/workflows/supply-chain-pr.yml b/.github/workflows/supply-chain-pr.yml index e5b4d66e..e3d94dc1 100644 --- a/.github/workflows/supply-chain-pr.yml +++ b/.github/workflows/supply-chain-pr.yml @@ -177,9 +177,11 @@ jobs: exit 0 fi - echo "artifact_found=true" >> "$GITHUB_OUTPUT" - echo "artifact_id=${ARTIFACT_ID}" >> "$GITHUB_OUTPUT" - echo "artifact_name=${ARTIFACT_NAME}" >> "$GITHUB_OUTPUT" + { + echo "artifact_found=true" + echo "artifact_id=${ARTIFACT_ID}" + echo "artifact_name=${ARTIFACT_NAME}" + } >> "$GITHUB_OUTPUT" echo "โœ… Found artifact: ${ARTIFACT_NAME} (ID: ${ARTIFACT_ID})" - name: Skip if no artifact @@ -317,11 +319,13 @@ jobs: LOW_COUNT=$(jq '[.matches[] | select(.vulnerability.severity == "Low")] | length' grype-results.json 2>/dev/null || echo "0") TOTAL_COUNT=$(jq '.matches | length' grype-results.json 2>/dev/null || echo "0") - echo "critical_count=${CRITICAL_COUNT}" >> "$GITHUB_OUTPUT" - echo "high_count=${HIGH_COUNT}" >> "$GITHUB_OUTPUT" - echo "medium_count=${MEDIUM_COUNT}" >> "$GITHUB_OUTPUT" - echo "low_count=${LOW_COUNT}" >> "$GITHUB_OUTPUT" - echo "total_count=${TOTAL_COUNT}" >> "$GITHUB_OUTPUT" + { + echo "critical_count=${CRITICAL_COUNT}" + echo "high_count=${HIGH_COUNT}" + echo "medium_count=${MEDIUM_COUNT}" + echo "low_count=${LOW_COUNT}" + echo "total_count=${TOTAL_COUNT}" + } >> "$GITHUB_OUTPUT" echo "๐Ÿ“Š Vulnerability Summary:" echo " Critical: ${CRITICAL_COUNT}" diff --git a/.github/workflows/supply-chain-verify.yml b/.github/workflows/supply-chain-verify.yml index 024f4f28..03653477 100644 --- a/.github/workflows/supply-chain-verify.yml +++ b/.github/workflows/supply-chain-verify.yml @@ -4,6 +4,15 @@ on: workflow_dispatch: schedule: - cron: '0 0 * * 1' # Mondays 00:00 UTC + workflow_run: + workflows: + - Docker Build, Publish & Test + types: + - completed + release: + types: + - published + - prereleased permissions: contents: read @@ -17,6 +26,8 @@ jobs: verify-sbom: name: Verify SBOM runs-on: ubuntu-latest + outputs: + image_exists: ${{ steps.image-check.outputs.exists }} # Only run on scheduled scans for main branch, or if workflow_run completed successfully # Critical Fix #5: Exclude PR builds to prevent duplicate verification (now handled inline in docker-build.yml) if: | @@ -61,7 +72,7 @@ jobs: TAG="pr-${PR_NUMBER}" else # Fallback to SHA-based tag if PR number not available - TAG="sha-$(echo ${{ github.event.workflow_run.head_sha }} | cut -c1-7)" + TAG="sha-$(echo "${{ github.event.workflow_run.head_sha }}" | cut -c1-7)" fi else # For feature branches and other pushes, sanitize branch name for Docker tag @@ -71,7 +82,7 @@ jobs: else TAG="latest" fi - echo "tag=${TAG}" >> $GITHUB_OUTPUT + echo "tag=${TAG}" >> "$GITHUB_OUTPUT" echo "Determined image tag: ${TAG}" - name: Check Image Availability @@ -83,15 +94,15 @@ jobs: echo "Checking if image exists: ${IMAGE}" # Authenticate with GHCR using GitHub token - echo "${GH_TOKEN}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin + echo "${GH_TOKEN}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin - if docker manifest inspect ${IMAGE} >/dev/null 2>&1; then + if docker manifest inspect "${IMAGE}" >/dev/null 2>&1; then echo "โœ… Image exists and is accessible" - echo "exists=true" >> $GITHUB_OUTPUT + echo "exists=true" >> "$GITHUB_OUTPUT" else echo "โš ๏ธ Image not found - likely not built yet" echo "This is normal for PR workflows before docker-build completes" - echo "exists=false" >> $GITHUB_OUTPUT + echo "exists=false" >> "$GITHUB_OUTPUT" fi # Generate SBOM using official Anchore action (auto-updated by Renovate) @@ -138,21 +149,21 @@ jobs: # Check jq availability if ! command -v jq &> /dev/null; then echo "โŒ jq is not available" - echo "valid=false" >> $GITHUB_OUTPUT + echo "valid=false" >> "$GITHUB_OUTPUT" exit 1 fi # Check file exists if [[ ! -f sbom-verify.cyclonedx.json ]]; then echo "โŒ SBOM file does not exist" - echo "valid=false" >> $GITHUB_OUTPUT + echo "valid=false" >> "$GITHUB_OUTPUT" exit 0 fi # Check file is non-empty if [[ ! -s sbom-verify.cyclonedx.json ]]; then echo "โŒ SBOM file is empty" - echo "valid=false" >> $GITHUB_OUTPUT + echo "valid=false" >> "$GITHUB_OUTPUT" exit 0 fi @@ -161,7 +172,7 @@ jobs: echo "โŒ SBOM file contains invalid JSON" echo "SBOM content:" cat sbom-verify.cyclonedx.json - echo "valid=false" >> $GITHUB_OUTPUT + echo "valid=false" >> "$GITHUB_OUTPUT" exit 0 fi @@ -177,16 +188,16 @@ jobs: if [[ "${BOMFORMAT}" != "CycloneDX" ]]; then echo "โŒ Invalid bomFormat: expected 'CycloneDX', got '${BOMFORMAT}'" - echo "valid=false" >> $GITHUB_OUTPUT + echo "valid=false" >> "$GITHUB_OUTPUT" exit 0 fi if [[ "${COMPONENTS}" == "0" ]]; then echo "โš ๏ธ SBOM has no components - may indicate incomplete scan" - echo "valid=partial" >> $GITHUB_OUTPUT + echo "valid=partial" >> "$GITHUB_OUTPUT" else echo "โœ… SBOM is valid with ${COMPONENTS} components" - echo "valid=true" >> $GITHUB_OUTPUT + echo "valid=true" >> "$GITHUB_OUTPUT" fi echo "SBOM Format: ${BOMFORMAT}" @@ -196,16 +207,16 @@ jobs: if [[ "${BOMFORMAT}" != "CycloneDX" ]]; then echo "โŒ Invalid bomFormat: expected 'CycloneDX', got '${BOMFORMAT}'" - echo "valid=false" >> $GITHUB_OUTPUT + echo "valid=false" >> "$GITHUB_OUTPUT" exit 0 fi if [[ "${COMPONENTS}" == "0" ]]; then echo "โš ๏ธ SBOM has no components - may indicate incomplete scan" - echo "valid=partial" >> $GITHUB_OUTPUT + echo "valid=partial" >> "$GITHUB_OUTPUT" else echo "โœ… SBOM is valid with ${COMPONENTS} components" - echo "valid=true" >> $GITHUB_OUTPUT + echo "valid=true" >> "$GITHUB_OUTPUT" fi # Scan for vulnerabilities using official Anchore action (auto-updated by Renovate) @@ -251,10 +262,12 @@ jobs: fi # Store for PR comment - echo "CRITICAL_VULNS=${CRITICAL}" >> $GITHUB_ENV - echo "HIGH_VULNS=${HIGH}" >> $GITHUB_ENV - echo "MEDIUM_VULNS=${MEDIUM}" >> $GITHUB_ENV - echo "LOW_VULNS=${LOW}" >> $GITHUB_ENV + { + echo "CRITICAL_VULNS=${CRITICAL}" + echo "HIGH_VULNS=${HIGH}" + echo "MEDIUM_VULNS=${MEDIUM}" + echo "LOW_VULNS=${LOW}" + } >> "$GITHUB_ENV" - name: Parse Vulnerability Details if: steps.validate-sbom.outputs.valid == 'true' @@ -314,22 +327,24 @@ jobs: - name: Report Skipped Scan if: steps.image-check.outputs.exists != 'true' || steps.validate-sbom.outputs.valid != 'true' run: | - echo "## โš ๏ธ Vulnerability Scan Skipped" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY + { + echo "## โš ๏ธ Vulnerability Scan Skipped" + echo "" - if [[ "${{ steps.image-check.outputs.exists }}" != "true" ]]; then - echo "**Reason**: Docker image not available yet" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "This is expected for PR workflows. The image will be scanned" >> $GITHUB_STEP_SUMMARY - echo "after it's built by the docker-build workflow." >> $GITHUB_STEP_SUMMARY - elif [[ "${{ steps.validate-sbom.outputs.valid }}" != "true" ]]; then - echo "**Reason**: SBOM validation failed" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "Check the 'Validate SBOM File' step for details." >> $GITHUB_STEP_SUMMARY - fi + if [[ "${{ steps.image-check.outputs.exists }}" != "true" ]]; then + echo "**Reason**: Docker image not available yet" + echo "" + echo "This is expected for PR workflows. The image will be scanned" + echo "after it's built by the docker-build workflow." + elif [[ "${{ steps.validate-sbom.outputs.valid }}" != "true" ]]; then + echo "**Reason**: SBOM validation failed" + echo "" + echo "Check the 'Validate SBOM File' step for details." + fi - echo "" >> $GITHUB_STEP_SUMMARY - echo "โœ… Workflow completed successfully (scan skipped)" >> $GITHUB_STEP_SUMMARY + echo "" + echo "โœ… Workflow completed successfully (scan skipped)" + } >> "$GITHUB_STEP_SUMMARY" - name: Determine PR Number id: pr-number @@ -453,8 +468,6 @@ jobs: " if [[ -f critical-vulns.txt && -s critical-vulns.txt ]]; then - # Count lines in the file - CRIT_COUNT=$(wc -l < critical-vulns.txt) COMMENT_BODY+="$(cat critical-vulns.txt)" # If more than 20, add truncation message @@ -585,6 +598,15 @@ jobs: echo "Generated comment body:" cat /tmp/comment-body.txt + - name: Find Existing PR Comment + id: find-comment + if: steps.pr-number.outputs.result != '' + uses: peter-evans/find-comment@v3.2.0 + with: + issue-number: ${{ steps.pr-number.outputs.result }} + comment-author: 'github-actions[bot]' + body-includes: '' + - name: Update or Create PR Comment if: steps.pr-number.outputs.result != '' uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5.0.0 @@ -592,8 +614,7 @@ jobs: issue-number: ${{ steps.pr-number.outputs.result }} body-path: /tmp/comment-body.txt edit-mode: replace - comment-author: 'github-actions[bot]' - body-includes: '' + comment-id: ${{ steps.find-comment.outputs.comment-id }} verify-docker-image: name: Verify Docker Image Supply Chain @@ -623,7 +644,7 @@ jobs: id: tag run: | TAG="${{ github.event.release.tag_name }}" - echo "tag=${TAG}" >> $GITHUB_OUTPUT + echo "tag=${TAG}" >> "$GITHUB_OUTPUT" - name: Verify Cosign Signature with Rekor Fallback env: @@ -632,7 +653,7 @@ jobs: echo "Verifying Cosign signature for ${IMAGE}..." # Try with Rekor - if cosign verify ${IMAGE} \ + if cosign verify "${IMAGE}" \ --certificate-identity-regexp="https://github.com/${{ github.repository }}" \ --certificate-oidc-issuer="https://token.actions.githubusercontent.com" 2>&1; then echo "โœ… Cosign signature verified (with Rekor)" @@ -640,7 +661,7 @@ jobs: echo "โš ๏ธ Rekor verification failed, trying offline verification..." # Fallback: verify without Rekor - if cosign verify ${IMAGE} \ + if cosign verify "${IMAGE}" \ --certificate-identity-regexp="https://github.com/${{ github.repository }}" \ --certificate-oidc-issuer="https://token.actions.githubusercontent.com" \ --insecure-ignore-tlog 2>&1; then @@ -653,11 +674,11 @@ jobs: fi - name: Verify Docker Hub Image Signature - if: steps.image-check.outputs.exists == 'true' + if: needs.verify-sbom.outputs.image_exists == 'true' continue-on-error: true run: | echo "Verifying Docker Hub image signature..." - cosign verify docker.io/wikid82/charon:${{ steps.tag.outputs.tag }} \ + cosign verify "docker.io/wikid82/charon:${{ steps.tag.outputs.tag }}" \ --certificate-identity-regexp="https://github.com/Wikid82/Charon" \ --certificate-oidc-issuer="https://token.actions.githubusercontent.com" && \ echo "โœ… Docker Hub signature verified" || \ @@ -702,7 +723,7 @@ jobs: 6. Re-run build if signatures/provenance are missing EOF - cat verification-report.md >> $GITHUB_STEP_SUMMARY + cat verification-report.md >> "$GITHUB_STEP_SUMMARY" verify-release-artifacts: name: Verify Release Artifacts @@ -723,9 +744,9 @@ jobs: env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - TAG=${{ github.event.release.tag_name }} + TAG="${{ github.event.release.tag_name }}" mkdir -p ./release-assets - gh release download ${TAG} --dir ./release-assets || { + gh release download "${TAG}" --dir ./release-assets || { echo "โš ๏ธ No release assets found or download failed" exit 0 } @@ -750,11 +771,11 @@ jobs: fi if [[ -f "$artifact" ]]; then - echo "Verifying: $(basename $artifact)" + echo "Verifying: $(basename "$artifact")" # Check if signature files exist if [[ ! -f "${artifact}.sig" ]] || [[ ! -f "${artifact}.pem" ]]; then - echo "โš ๏ธ No signature files found for $(basename $artifact)" + echo "โš ๏ธ No signature files found for $(basename "$artifact")" FAILED_COUNT=$((FAILED_COUNT + 1)) continue fi diff --git a/.github/workflows/update-geolite2.yml b/.github/workflows/update-geolite2.yml index 00d3b130..05d13843 100644 --- a/.github/workflows/update-geolite2.yml +++ b/.github/workflows/update-geolite2.yml @@ -31,8 +31,8 @@ jobs: break else echo "โŒ Download failed on attempt $i" - if [ $i -eq 3 ]; then - echo "error=download_failed" >> $GITHUB_OUTPUT + if [ "$i" -eq 3 ]; then + echo "error=download_failed" >> "$GITHUB_OUTPUT" exit 1 fi sleep 5 @@ -45,7 +45,7 @@ jobs: # Validate checksum format (64 hex characters) if ! [[ "$CURRENT" =~ ^[a-f0-9]{64}$ ]]; then echo "โŒ Invalid checksum format: $CURRENT" - echo "error=invalid_checksum_format" >> $GITHUB_OUTPUT + echo "error=invalid_checksum_format" >> "$GITHUB_OUTPUT" exit 1 fi @@ -55,7 +55,7 @@ jobs: # Validate old checksum format if ! [[ "$OLD" =~ ^[a-f0-9]{64}$ ]]; then echo "โŒ Invalid old checksum format in Dockerfile: $OLD" - echo "error=invalid_dockerfile_checksum" >> $GITHUB_OUTPUT + echo "error=invalid_dockerfile_checksum" >> "$GITHUB_OUTPUT" exit 1 fi @@ -63,14 +63,14 @@ jobs: echo " Current (Dockerfile): $OLD" echo " Latest (Downloaded): $CURRENT" - echo "current=$CURRENT" >> $GITHUB_OUTPUT - echo "old=$OLD" >> $GITHUB_OUTPUT + echo "current=$CURRENT" >> "$GITHUB_OUTPUT" + echo "old=$OLD" >> "$GITHUB_OUTPUT" if [ "$CURRENT" != "$OLD" ]; then - echo "needs_update=true" >> $GITHUB_OUTPUT + echo "needs_update=true" >> "$GITHUB_OUTPUT" echo "โš ๏ธ Checksum mismatch detected - update required" else - echo "needs_update=false" >> $GITHUB_OUTPUT + echo "needs_update=false" >> "$GITHUB_OUTPUT" echo "โœ… Checksum matches - no update needed" fi diff --git a/.github/workflows/waf-integration.yml b/.github/workflows/waf-integration.yml index 11a9ba62..3d862d50 100644 --- a/.github/workflows/waf-integration.yml +++ b/.github/workflows/waf-integration.yml @@ -40,12 +40,15 @@ jobs: # Manual trigger uses provided tag if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then if [[ -n "$MANUAL_TAG" ]]; then - echo "tag=${MANUAL_TAG}" >> $GITHUB_OUTPUT + TAG_VALUE="$MANUAL_TAG" else # Default to latest if no tag provided - echo "tag=latest" >> $GITHUB_OUTPUT + TAG_VALUE="latest" fi - echo "source_type=manual" >> $GITHUB_OUTPUT + { + echo "tag=${TAG_VALUE}" + echo "source_type=manual" + } >> "$GITHUB_OUTPUT" exit 0 fi @@ -71,16 +74,20 @@ jobs: fi # Immutable tag with SHA suffix prevents race conditions - echo "tag=pr-${PR_NUM}-${SHORT_SHA}" >> $GITHUB_OUTPUT - echo "source_type=pr" >> $GITHUB_OUTPUT + { + echo "tag=pr-${PR_NUM}-${SHORT_SHA}" + echo "source_type=pr" + } >> "$GITHUB_OUTPUT" else # Non-PR workflow_run uses short SHA tag (matches docker-build.yml) - echo "tag=sha-${SHORT_SHA}" >> $GITHUB_OUTPUT - echo "source_type=sha" >> $GITHUB_OUTPUT + { + echo "tag=sha-${SHORT_SHA}" + echo "source_type=sha" + } >> "$GITHUB_OUTPUT" fi - echo "sha=${SHORT_SHA}" >> $GITHUB_OUTPUT - echo "Determined image tag: $(cat $GITHUB_OUTPUT | grep tag=)" + echo "sha=${SHORT_SHA}" >> "$GITHUB_OUTPUT" + echo "Determined image tag: $(grep tag= "$GITHUB_OUTPUT")" # Pull image from Docker Hub with retry logic - name: Pull Docker image from registry @@ -118,57 +125,60 @@ jobs: run: | chmod +x scripts/coraza_integration.sh scripts/coraza_integration.sh 2>&1 | tee waf-test-output.txt - exit ${PIPESTATUS[0]} + exit "${PIPESTATUS[0]}" - name: Dump Debug Info on Failure if: failure() run: | - echo "## ๐Ÿ” Debug Information" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY + { + echo "## ๐Ÿ” Debug Information" + echo "" - echo "### Container Status" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - docker ps -a --filter "name=charon" --filter "name=coraza" >> $GITHUB_STEP_SUMMARY 2>&1 || true - echo '```' >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY + echo "### Container Status" + echo '```' + docker ps -a --filter "name=charon" --filter "name=coraza" 2>&1 || true + echo '```' + echo "" - echo "### Caddy Admin Config" >> $GITHUB_STEP_SUMMARY - echo '```json' >> $GITHUB_STEP_SUMMARY - curl -s http://localhost:2019/config 2>/dev/null | head -200 >> $GITHUB_STEP_SUMMARY || echo "Could not retrieve Caddy config" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY + echo "### Caddy Admin Config" + echo '```json' + curl -s http://localhost:2019/config 2>/dev/null | head -200 || echo "Could not retrieve Caddy config" + echo '```' + echo "" - echo "### Charon Container Logs (last 100 lines)" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - docker logs charon-debug 2>&1 | tail -100 >> $GITHUB_STEP_SUMMARY || echo "No container logs available" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY + echo "### Charon Container Logs (last 100 lines)" + echo '```' + docker logs charon-debug 2>&1 | tail -100 || echo "No container logs available" + echo '```' + echo "" - echo "### WAF Ruleset Files" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - docker exec charon-debug sh -c 'ls -la /app/data/caddy/coraza/rulesets/ 2>/dev/null && echo "---" && cat /app/data/caddy/coraza/rulesets/*.conf 2>/dev/null' >> $GITHUB_STEP_SUMMARY || echo "No ruleset files found" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY + echo "### WAF Ruleset Files" + echo '```' + docker exec charon-debug sh -c 'ls -la /app/data/caddy/coraza/rulesets/ 2>/dev/null && echo "---" && cat /app/data/caddy/coraza/rulesets/*.conf 2>/dev/null' || echo "No ruleset files found" + echo '```' + } >> "$GITHUB_STEP_SUMMARY" - name: WAF Integration Summary if: always() run: | - echo "## ๐Ÿ›ก๏ธ WAF Integration Test Results" >> $GITHUB_STEP_SUMMARY - if [ "${{ steps.waf-test.outcome }}" == "success" ]; then - echo "โœ… **All WAF tests passed**" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "### Test Results:" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - grep -E "^โœ“|^===|^Coraza" waf-test-output.txt || echo "See logs for details" - grep -E "^โœ“|^===|^Coraza" waf-test-output.txt >> $GITHUB_STEP_SUMMARY || echo "See logs for details" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - else - echo "โŒ **WAF tests failed**" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "### Failure Details:" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - grep -E "^โœ—|Unexpected|Error|failed" waf-test-output.txt | head -20 >> $GITHUB_STEP_SUMMARY || echo "See logs for details" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - fi + { + echo "## ๐Ÿ›ก๏ธ WAF Integration Test Results" + if [ "${{ steps.waf-test.outcome }}" == "success" ]; then + echo "โœ… **All WAF tests passed**" + echo "" + echo "### Test Results:" + echo '```' + grep -E "^โœ“|^===|^Coraza" waf-test-output.txt || echo "See logs for details" + echo '```' + else + echo "โŒ **WAF tests failed**" + echo "" + echo "### Failure Details:" + echo '```' + grep -E "^โœ—|Unexpected|Error|failed" waf-test-output.txt | head -20 || echo "See logs for details" + echo '```' + fi + } >> "$GITHUB_STEP_SUMMARY" - name: Cleanup if: always() diff --git a/.github/workflows/weekly-nightly-promotion.yml b/.github/workflows/weekly-nightly-promotion.yml index 4a61a328..14b482db 100644 --- a/.github/workflows/weekly-nightly-promotion.yml +++ b/.github/workflows/weekly-nightly-promotion.yml @@ -128,22 +128,22 @@ jobs: - name: Check for Differences id: check-diff run: | - git fetch origin ${{ env.SOURCE_BRANCH }} + git fetch origin "${{ env.SOURCE_BRANCH }}" # Compare the branches - AHEAD_COUNT=$(git rev-list --count origin/${{ env.TARGET_BRANCH }}..origin/${{ env.SOURCE_BRANCH }}) - BEHIND_COUNT=$(git rev-list --count origin/${{ env.SOURCE_BRANCH }}..origin/${{ env.TARGET_BRANCH }}) + AHEAD_COUNT=$(git rev-list --count "origin/${{ env.TARGET_BRANCH }}..origin/${{ env.SOURCE_BRANCH }}") + BEHIND_COUNT=$(git rev-list --count "origin/${{ env.SOURCE_BRANCH }}..origin/${{ env.TARGET_BRANCH }}") echo "Nightly is $AHEAD_COUNT commits ahead of main" echo "Nightly is $BEHIND_COUNT commits behind main" if [ "$AHEAD_COUNT" -eq 0 ]; then echo "No changes to promote - nightly is up-to-date with main" - echo "skipped=true" >> $GITHUB_OUTPUT - echo "skip_reason=No changes to promote" >> $GITHUB_OUTPUT + echo "skipped=true" >> "$GITHUB_OUTPUT" + echo "skip_reason=No changes to promote" >> "$GITHUB_OUTPUT" else - echo "skipped=false" >> $GITHUB_OUTPUT - echo "ahead_count=$AHEAD_COUNT" >> $GITHUB_OUTPUT + echo "skipped=false" >> "$GITHUB_OUTPUT" + echo "ahead_count=$AHEAD_COUNT" >> "$GITHUB_OUTPUT" fi - name: Generate Commit Summary @@ -152,11 +152,11 @@ jobs: run: | # Get the date for the PR title DATE=$(date -u +%Y-%m-%d) - echo "date=$DATE" >> $GITHUB_OUTPUT + echo "date=$DATE" >> "$GITHUB_OUTPUT" # Generate commit log - COMMIT_LOG=$(git log --oneline origin/${{ env.TARGET_BRANCH }}..origin/${{ env.SOURCE_BRANCH }} | head -50) - COMMIT_COUNT=$(git rev-list --count origin/${{ env.TARGET_BRANCH }}..origin/${{ env.SOURCE_BRANCH }}) + COMMIT_LOG=$(git log --oneline "origin/${{ env.TARGET_BRANCH }}..origin/${{ env.SOURCE_BRANCH }}" | head -50) + COMMIT_COUNT=$(git rev-list --count "origin/${{ env.TARGET_BRANCH }}..origin/${{ env.SOURCE_BRANCH }}") # Store commit log in a file to preserve formatting cat > /tmp/commit_log.md << 'COMMITS_EOF' @@ -164,23 +164,25 @@ jobs: COMMITS_EOF - if [ "$COMMIT_COUNT" -gt 50 ]; then - echo "_Showing first 50 of $COMMIT_COUNT commits:_" >> /tmp/commit_log.md - fi + { + if [ "$COMMIT_COUNT" -gt 50 ]; then + echo "_Showing first 50 of $COMMIT_COUNT commits:_" + fi - echo '```' >> /tmp/commit_log.md - echo "$COMMIT_LOG" >> /tmp/commit_log.md - echo '```' >> /tmp/commit_log.md + echo '```' + echo "$COMMIT_LOG" + echo '```' - if [ "$COMMIT_COUNT" -gt 50 ]; then - echo "" >> /tmp/commit_log.md - echo "_...and $((COMMIT_COUNT - 50)) more commits_" >> /tmp/commit_log.md - fi + if [ "$COMMIT_COUNT" -gt 50 ]; then + echo "" + echo "_...and $((COMMIT_COUNT - 50)) more commits_" + fi + } >> /tmp/commit_log.md # Get files changed summary - FILES_CHANGED=$(git diff --stat origin/${{ env.TARGET_BRANCH }}..origin/${{ env.SOURCE_BRANCH }} | tail -1) - echo "files_changed=$FILES_CHANGED" >> $GITHUB_OUTPUT - echo "commit_count=$COMMIT_COUNT" >> $GITHUB_OUTPUT + FILES_CHANGED=$(git diff --stat "origin/${{ env.TARGET_BRANCH }}..origin/${{ env.SOURCE_BRANCH }}" | tail -1) + echo "files_changed=$FILES_CHANGED" >> "$GITHUB_OUTPUT" + echo "commit_count=$COMMIT_COUNT" >> "$GITHUB_OUTPUT" - name: Check for Existing PR id: existing-pr @@ -450,32 +452,34 @@ jobs: steps: - name: Generate Summary run: | - echo "## ๐Ÿ“‹ Weekly Nightly Promotion Summary" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY + { + echo "## ๐Ÿ“‹ Weekly Nightly Promotion Summary" + echo "" - HEALTH="${{ needs.check-nightly-health.outputs.is_healthy }}" - SKIPPED="${{ needs.create-promotion-pr.outputs.skipped }}" - PR_URL="${{ needs.create-promotion-pr.outputs.pr_url }}" - PR_NUMBER="${{ needs.create-promotion-pr.outputs.pr_number }}" - FAILURE_REASON="${{ needs.check-nightly-health.outputs.failure_reason }}" + HEALTH="${{ needs.check-nightly-health.outputs.is_healthy }}" + SKIPPED="${{ needs.create-promotion-pr.outputs.skipped }}" + PR_URL="${{ needs.create-promotion-pr.outputs.pr_url }}" + PR_NUMBER="${{ needs.create-promotion-pr.outputs.pr_number }}" + FAILURE_REASON="${{ needs.check-nightly-health.outputs.failure_reason }}" - echo "| Step | Status |" >> $GITHUB_STEP_SUMMARY - echo "|------|--------|" >> $GITHUB_STEP_SUMMARY + echo "| Step | Status |" + echo "|------|--------|" - if [ "$HEALTH" = "true" ]; then - echo "| Nightly Health Check | โœ… Healthy |" >> $GITHUB_STEP_SUMMARY - else - echo "| Nightly Health Check | โŒ Unhealthy: $FAILURE_REASON |" >> $GITHUB_STEP_SUMMARY - fi + if [ "$HEALTH" = "true" ]; then + echo "| Nightly Health Check | โœ… Healthy |" + else + echo "| Nightly Health Check | โŒ Unhealthy: $FAILURE_REASON |" + fi - if [ "$SKIPPED" = "true" ]; then - echo "| PR Creation | โญ๏ธ Skipped (no changes) |" >> $GITHUB_STEP_SUMMARY - elif [ -n "$PR_URL" ]; then - echo "| PR Creation | โœ… [PR #$PR_NUMBER]($PR_URL) |" >> $GITHUB_STEP_SUMMARY - else - echo "| PR Creation | โŒ Failed |" >> $GITHUB_STEP_SUMMARY - fi + if [ "$SKIPPED" = "true" ]; then + echo "| PR Creation | โญ๏ธ Skipped (no changes) |" + elif [ -n "$PR_URL" ]; then + echo "| PR Creation | โœ… [PR #$PR_NUMBER]($PR_URL) |" + else + echo "| PR Creation | โŒ Failed |" + fi - echo "" >> $GITHUB_STEP_SUMMARY - echo "---" >> $GITHUB_STEP_SUMMARY - echo "_Workflow run: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}_" >> $GITHUB_STEP_SUMMARY + echo "" + echo "---" + echo "_Workflow run: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}_" + } >> "$GITHUB_STEP_SUMMARY"