diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 1503a49e..88dc47b3 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -84,6 +84,16 @@ jobs: echo "is_push=false" >> "$GITHUB_OUTPUT" fi + - name: Sanitize branch name + id: sanitize + run: | + # Sanitize branch name for use in Docker tags and artifact names + # Replace / with - to avoid invalid reference format errors + BRANCH="${{ github.event.workflow_run.head_branch || github.head_ref || github.ref_name }}" + SANITIZED=$(echo "$BRANCH" | tr '/' '-') + echo "branch=${SANITIZED}" >> "$GITHUB_OUTPUT" + echo "📋 Sanitized branch name: ${BRANCH} -> ${SANITIZED}" + - name: Check for PR image artifact id: check-artifact if: steps.pr-info.outputs.pr_number != '' || steps.pr-info.outputs.is_push == 'true' @@ -170,7 +180,8 @@ jobs: # Normalize image name (GitHub lowercases repository owner names in GHCR) IMAGE_NAME=$(echo "${{ github.repository_owner }}/charon" | tr '[:upper:]' '[:lower:]') if [[ "${{ steps.pr-info.outputs.is_push }}" == "true" ]]; then - IMAGE_REF="ghcr.io/${IMAGE_NAME}:${{ github.event.workflow_run.head_branch }}" + # Use sanitized branch name for Docker tag (/ is invalid in tags) + IMAGE_REF="ghcr.io/${IMAGE_NAME}:${{ steps.sanitize.outputs.branch }}" else IMAGE_REF="ghcr.io/${IMAGE_NAME}:pr-${{ steps.pr-info.outputs.pr_number }}" fi @@ -237,7 +248,7 @@ jobs: # actions/upload-artifact v4.4.3 uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f with: - name: ${{ steps.pr-info.outputs.is_push == 'true' && format('playwright-report-{0}', github.event.workflow_run.head_branch) || format('playwright-report-pr-{0}', steps.pr-info.outputs.pr_number) }} + name: ${{ steps.pr-info.outputs.is_push == 'true' && format('playwright-report-{0}', steps.sanitize.outputs.branch) || format('playwright-report-pr-{0}', steps.pr-info.outputs.pr_number) }} path: playwright-report/ retention-days: 14 diff --git a/.github/workflows/supply-chain-pr.yml b/.github/workflows/supply-chain-pr.yml index 6d9089c5..487e158b 100644 --- a/.github/workflows/supply-chain-pr.yml +++ b/.github/workflows/supply-chain-pr.yml @@ -105,6 +105,16 @@ jobs: echo "is_push=false" >> "$GITHUB_OUTPUT" fi + - name: Sanitize branch name + id: sanitize + run: | + # Sanitize branch name for use in artifact names + # Replace / with - to avoid invalid reference format errors + BRANCH="${{ github.event.workflow_run.head_branch || github.head_ref || github.ref_name }}" + SANITIZED=$(echo "$BRANCH" | tr '/' '-') + echo "branch=${SANITIZED}" >> "$GITHUB_OUTPUT" + echo "📋 Sanitized branch name: ${BRANCH} -> ${SANITIZED}" + - name: Check for PR image artifact id: check-artifact if: steps.pr-number.outputs.pr_number != '' || steps.pr-number.outputs.is_push == 'true' @@ -297,7 +307,7 @@ jobs: # actions/upload-artifact v4.6.0 uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f with: - name: ${{ steps.pr-number.outputs.is_push == 'true' && format('supply-chain-{0}', github.event.workflow_run.head_branch) || format('supply-chain-pr-{0}', steps.pr-number.outputs.pr_number) }} + name: ${{ steps.pr-number.outputs.is_push == 'true' && format('supply-chain-{0}', steps.sanitize.outputs.branch) || format('supply-chain-pr-{0}', steps.pr-number.outputs.pr_number) }} path: | sbom.cyclonedx.json grype-results.json diff --git a/.github/workflows/supply-chain-verify.yml b/.github/workflows/supply-chain-verify.yml index 87f1cb2f..5579d94e 100644 --- a/.github/workflows/supply-chain-verify.yml +++ b/.github/workflows/supply-chain-verify.yml @@ -71,15 +71,14 @@ jobs: if [[ "${{ github.event_name }}" == "release" ]]; then TAG="${{ github.event.release.tag_name }}" elif [[ "${{ github.event_name }}" == "workflow_run" ]]; then + BRANCH="${{ github.event.workflow_run.head_branch }}" # Extract tag from the workflow that triggered us - if [[ "${{ github.event.workflow_run.head_branch }}" == "main" ]]; then + if [[ "${BRANCH}" == "main" ]]; then TAG="latest" - elif [[ "${{ github.event.workflow_run.head_branch }}" == "development" ]]; then + elif [[ "${BRANCH}" == "development" ]]; then TAG="dev" - elif [[ "${{ github.event.workflow_run.head_branch }}" == "nightly" ]]; then + elif [[ "${BRANCH}" == "nightly" ]]; then TAG="nightly" - elif [[ "${{ github.event.workflow_run.head_branch }}" == "feature/beta-release" ]]; then - TAG="beta" elif [[ "${{ github.event.workflow_run.event }}" == "pull_request" ]]; then # Extract PR number from workflow_run context with null handling PR_NUMBER=$(jq -r '.pull_requests[0].number // empty' <<< '${{ toJson(github.event.workflow_run.pull_requests) }}') @@ -90,7 +89,9 @@ jobs: TAG="sha-$(echo ${{ github.event.workflow_run.head_sha }} | cut -c1-7)" fi else - TAG="sha-$(echo ${{ github.event.workflow_run.head_sha }} | cut -c1-7)" + # For feature branches and other pushes, sanitize branch name for Docker tag + # Replace / with - to avoid invalid reference format errors + TAG=$(echo "${BRANCH}" | tr '/' '-') fi else TAG="latest" diff --git a/CHANGELOG.md b/CHANGELOG.md index 243c4847..07b835b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- **GitHub Actions workflows failing with 'invalid reference format' for feature branches containing slashes**: Branch names like `feature/beta-release` now properly sanitized (replacing `/` with `-`) in Docker image tags and artifact names across `playwright.yml`, `supply-chain-verify.yml`, and `supply-chain-pr.yml` workflows - **PermissionsModal State Synchronization**: Fixed React anti-pattern where `useState` was used like `useEffect`, causing potential stale state when editing different users' permissions ### Added diff --git a/docs/plans/current_spec.md b/docs/plans/current_spec.md index c358686f..fa7bb39b 100644 --- a/docs/plans/current_spec.md +++ b/docs/plans/current_spec.md @@ -1,7 +1,258 @@ -# WAF Integration Workflow Fix: wget-style curl Syntax Migration +# WAF-2026-002: Docker Tag Sanitization for Branch Names + +**Plan ID**: WAF-2026-002 +**Status**: ✅ COMPLETED +**Priority**: High +**Created**: 2026-01-25 +**Completed**: 2026-01-25 +**Scope**: Fix Docker image tag construction to handle branch names containing forward slashes + +--- + +## Problem Summary + +GitHub Actions workflows are failing with "invalid reference format" errors when building/pulling Docker images for feature branches. The root cause is that branch names like `feature/beta-release` contain forward slashes (`/`), which are **invalid characters in Docker image tags**. + +### Docker Tag Naming Rules + +Docker image tags must match the regex: `[a-zA-Z0-9_][a-zA-Z0-9._-]{0,127}` + +Invalid characters include: +- Forward slash (`/`) - **causes "invalid reference format" error** +- Colon (`:`) - reserved for tag separator +- Spaces and special characters + +--- + +## Files Affected + +### 1. `.github/workflows/playwright.yml` (Line 103) + +**Location**: [playwright.yml](.github/workflows/playwright.yml#L103) + +**Current (broken):** +```yaml +- name: Start Charon container + run: | + ... + if [[ "${{ steps.pr-info.outputs.is_push }}" == "true" ]]; then + IMAGE_REF="ghcr.io/${IMAGE_NAME}:${{ github.event.workflow_run.head_branch }}" + else +``` + +**Issue**: `github.event.workflow_run.head_branch` can contain `/` (e.g., `feature/beta-release`) + +**Fix:** +```yaml +- name: Start Charon container + run: | + ... + if [[ "${{ steps.pr-info.outputs.is_push }}" == "true" ]]; then + # Sanitize branch name: replace / with - + SANITIZED_BRANCH=$(echo "${{ github.event.workflow_run.head_branch }}" | tr '/' '-') + IMAGE_REF="ghcr.io/${IMAGE_NAME}:${SANITIZED_BRANCH}" + else +``` + +--- + +### 2. `.github/workflows/playwright.yml` (Line 161) - Artifact Naming + +**Location**: [playwright.yml](.github/workflows/playwright.yml#L161) + +**Current:** +```yaml +- name: Upload Playwright report + uses: actions/upload-artifact@... + with: + name: ${{ steps.pr-info.outputs.is_push == 'true' && format('playwright-report-{0}', github.event.workflow_run.head_branch) || format('playwright-report-pr-{0}', steps.pr-info.outputs.pr_number) }} +``` + +**Issue**: Artifact names also cannot contain `/` + +**Fix:** +Add a step to sanitize the branch name first and use an environment variable: +```yaml +- name: Sanitize branch name for artifact + id: sanitize + run: | + SANITIZED=$(echo "${{ github.event.workflow_run.head_branch }}" | tr '/' '-') + echo "branch=${SANITIZED}" >> $GITHUB_OUTPUT + +- name: Upload Playwright report + uses: actions/upload-artifact@... + with: + name: ${{ steps.pr-info.outputs.is_push == 'true' && format('playwright-report-{0}', steps.sanitize.outputs.branch) || format('playwright-report-pr-{0}', steps.pr-info.outputs.pr_number) }} +``` + +--- + +### 3. `.github/workflows/supply-chain-verify.yml` (Lines 64-90) - Tag Determination + +**Location**: [supply-chain-verify.yml](.github/workflows/supply-chain-verify.yml#L64-L90) + +**Current (partial):** +```yaml +- name: Determine Image Tag + id: tag + run: | + if [[ "${{ github.event_name }}" == "release" ]]; then + TAG="${{ github.event.release.tag_name }}" + elif [[ "${{ github.event_name }}" == "workflow_run" ]]; then + if [[ "${{ github.event.workflow_run.head_branch }}" == "main" ]]; then + TAG="latest" + elif [[ "${{ github.event.workflow_run.head_branch }}" == "development" ]]; then + TAG="dev" + elif [[ "${{ github.event.workflow_run.head_branch }}" == "nightly" ]]; then + TAG="nightly" + elif [[ "${{ github.event.workflow_run.head_branch }}" == "feature/beta-release" ]]; then + TAG="beta" + elif [[ "${{ github.event.workflow_run.event }}" == "pull_request" ]]; then + ... + else + TAG="sha-$(echo ${{ github.event.workflow_run.head_sha }} | cut -c1-7)" + fi +``` + +**Issue**: Only `feature/beta-release` is explicitly mapped. Other feature branches fall through to SHA-based tags which works, BUT there's an implicit assumption that docker-build.yml creates tags that match. The docker-build.yml uses `type=ref,event=branch` which DOES sanitize branch names. + +**Analysis**: The logic here is complex. The `docker/metadata-action` in docker-build.yml uses: +```yaml +type=ref,event=branch,enable=${{ startsWith(github.ref, 'refs/heads/feature/') }} +``` + +According to [docker/metadata-action docs](https://github.com/docker/metadata-action#typeref), `type=ref,event=branch` produces a tag like `feature-beta-release` (slashes replaced with dashes). + +**Fix**: Align supply-chain-verify.yml with docker-build.yml's tag sanitization: +```yaml +- name: Determine Image Tag + id: tag + run: | + if [[ "${{ github.event_name }}" == "release" ]]; then + TAG="${{ github.event.release.tag_name }}" + elif [[ "${{ github.event_name }}" == "workflow_run" ]]; then + BRANCH="${{ github.event.workflow_run.head_branch }}" + if [[ "${BRANCH}" == "main" ]]; then + TAG="latest" + elif [[ "${BRANCH}" == "development" ]]; then + TAG="dev" + elif [[ "${BRANCH}" == "nightly" ]]; then + TAG="nightly" + elif [[ "${BRANCH}" == feature/* ]]; then + # Match docker/metadata-action behavior: type=ref,event=branch replaces / with - + TAG=$(echo "${BRANCH}" | tr '/' '-') + elif [[ "${{ github.event.workflow_run.event }}" == "pull_request" ]]; then + ... + else + TAG="sha-$(echo ${{ github.event.workflow_run.head_sha }} | cut -c1-7)" + fi +``` + +--- + +### 4. `.github/workflows/supply-chain-pr.yml` (Line 196) - Artifact Naming + +**Location**: [supply-chain-pr.yml](.github/workflows/supply-chain-pr.yml#L196) + +**Current:** +```yaml +- name: Upload supply chain artifacts + uses: actions/upload-artifact@... + with: + name: ${{ steps.pr-number.outputs.is_push == 'true' && format('supply-chain-{0}', github.event.workflow_run.head_branch) || format('supply-chain-pr-{0}', steps.pr-number.outputs.pr_number) }} +``` + +**Issue**: Same artifact naming issue with unsanitized branch names + +**Fix:** +```yaml +- name: Sanitize branch name + id: sanitize + if: steps.pr-number.outputs.is_push == 'true' + run: | + SANITIZED=$(echo "${{ github.event.workflow_run.head_branch }}" | tr '/' '-') + echo "branch=${SANITIZED}" >> $GITHUB_OUTPUT + +- name: Upload supply chain artifacts + uses: actions/upload-artifact@... + with: + name: ${{ steps.pr-number.outputs.is_push == 'true' && format('supply-chain-{0}', steps.sanitize.outputs.branch) || format('supply-chain-pr-{0}', steps.pr-number.outputs.pr_number) }} +``` + +--- + +## How docker/metadata-action Handles This + +The `docker/metadata-action` correctly handles this via `type=ref,event=branch`: + +From [docker-build.yml](.github/workflows/docker-build.yml#L89-L95): +```yaml +- name: Extract metadata (tags, labels) + id: meta + uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + ... + type=ref,event=branch,enable=${{ startsWith(github.ref, 'refs/heads/feature/') }} +``` + +The `type=ref,event=branch` option automatically sanitizes the branch name, replacing `/` with `-`. + +**Result**: Feature branch `feature/beta-release` produces tag `feature-beta-release` + +--- + +## Summary Table + +| Workflow | Line | Issue | Fix Strategy | +|----------|------|-------|--------------| +| [playwright.yml](.github/workflows/playwright.yml) | 103 | `head_branch` used directly as tag | `tr '/' '-'` sanitization | +| [playwright.yml](.github/workflows/playwright.yml) | 161 | `head_branch` in artifact name | Add sanitize step | +| [supply-chain-verify.yml](.github/workflows/supply-chain-verify.yml) | 74 | Only hardcodes `feature/beta-release` | Generic feature/* handling with `tr '/' '-'` | +| [supply-chain-pr.yml](.github/workflows/supply-chain-pr.yml) | 196 | `head_branch` in artifact name | Add sanitize step | + +--- + +## Execution Checklist + +- [ ] **Fix 1**: Update `playwright.yml` line 103 - sanitize branch name for Docker tag +- [ ] **Fix 2**: Update `playwright.yml` line 161 - sanitize branch name for artifact +- [ ] **Fix 3**: Update `supply-chain-verify.yml` lines 74-75 - generic feature branch handling +- [ ] **Fix 4**: Update `supply-chain-pr.yml` line 196 - sanitize branch name for artifact +- [ ] **Verify**: Push to `feature/beta-release` and confirm workflows pass +- [ ] **CI**: All affected workflows should complete without "invalid reference format" + +--- + +## Verification + +After applying fixes: + +```bash +# Test sanitization logic locally +echo "feature/beta-release" | tr '/' '-' +# Expected output: feature-beta-release + +# Verify Docker accepts the sanitized tag +docker pull ghcr.io/owner/charon:feature-beta-release +# Should work (or fail with 404 if not published yet, but NOT "invalid reference format") +``` + +--- + +## References + +- [Docker tag naming rules](https://docs.docker.com/engine/reference/commandline/tag/) +- [docker/metadata-action type=ref behavior](https://github.com/docker/metadata-action#typeref) +- GitHub Issue: Workflow failures on `feature/beta-release` branch + +--- + +# WAF-2026-001: wget-style curl Syntax Migration (Archived) **Plan ID**: WAF-2026-001 -**Status**: 📋 PENDING +**Status**: ✅ ARCHIVED (Superseded by WAF-2026-002 as current active plan) **Priority**: High **Created**: 2026-01-25 **Scope**: Fix integration test scripts using incorrect wget-style curl syntax