From fee1a4ba0cf25cc36b880b257da808fa6b26db32 Mon Sep 17 00:00:00 2001 From: Zohayb Bhatti Date: Tue, 28 Oct 2025 13:35:54 -0500 Subject: [PATCH 1/5] feat: Add comprehensive CI/CD workflow with multi-platform builds - Add ci-cd.yml workflow with automated build, test, and deployment - Implement security scanning with Trivy and Gosec - Add multi-platform binary builds (Linux, macOS, Windows - amd64/arm64) - Add Docker multi-arch builds with GHCR and Docker Hub support - Include integration testing and artifact uploads - Update README with CI/CD information --- .github/workflows/ci-cd.yml | 351 ++++++++++++++++++++++++++++++++++++ README.md | 10 +- 2 files changed, 359 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/ci-cd.yml diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml new file mode 100644 index 0000000..2dd9fea --- /dev/null +++ b/.github/workflows/ci-cd.yml @@ -0,0 +1,351 @@ +name: CI/CD Pipeline + +on: + push: + branches: + - main + - develop + pull_request: + branches: + - main + - develop + workflow_dispatch: + +permissions: + contents: write + pull-requests: read + packages: write + id-token: write + attestations: write + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + GO_VERSION: "1.25.1" + TESTCOVERAGE_THRESHOLD: 26 + +jobs: + # Job 1: Build and Test + build-and-test: + name: Build and Test + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v5 + with: + persist-credentials: false + fetch-depth: 0 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: ${{ env.GO_VERSION }} + + - name: Cache Go modules + uses: actions/cache@v4 + with: + path: | + ~/.cache/go-build + ~/go/pkg/mod + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- + + - name: Tidy dependencies + run: go mod tidy + + - name: Verify dependencies + run: go mod verify + + - name: Vet code + run: go vet ./... + + - name: Build binary + run: make build + + - name: Run tests with coverage + run: make test-cov + + - name: Check test coverage threshold + run: | + echo "Quality Gate: checking test coverage is above threshold ..." + echo "Threshold : $TESTCOVERAGE_THRESHOLD %" + totalCoverage=$(go tool cover -func=coverage.out | grep total | grep -Eo '[0-9]+\.[0-9]+') + echo "Current test coverage : $totalCoverage %" + if (( $(echo "$totalCoverage $TESTCOVERAGE_THRESHOLD" | awk '{print ($1 >= $2)}') )); then + echo "Coverage check passed" + else + echo "Current test coverage is below threshold" + echo "Please add more unit tests or adjust threshold to a lower value." + exit 1 + fi + + - name: Upload coverage reports + uses: codecov/codecov-action@v5 + if: always() + with: + files: ./coverage.out + flags: unittests + name: codecov-umbrella + fail_ci_if_error: false + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: github-repo-binary + path: github-repo + retention-days: 7 + + # Job 2: Lint + lint: + name: Lint Code + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v5 + with: + persist-credentials: false + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: ${{ env.GO_VERSION }} + + - name: golangci-lint + uses: golangci/golangci-lint-action@v8 + with: + version: latest + args: --timeout=5m + + # Job 3: Security Scanning + security-scan: + name: Security Scan + runs-on: ubuntu-latest + permissions: + security-events: write + contents: read + actions: read + steps: + - name: Checkout code + uses: actions/checkout@v5 + with: + persist-credentials: false + + - name: Run Trivy vulnerability scanner + uses: aquasecurity/trivy-action@master + with: + scan-type: 'fs' + scan-ref: '.' + format: 'sarif' + output: 'trivy-results.sarif' + severity: 'CRITICAL,HIGH' + + - name: Upload Trivy results to GitHub Security tab + uses: github/codeql-action/upload-sarif@v3 + if: always() + with: + sarif_file: 'trivy-results.sarif' + + - name: Run Gosec Security Scanner + uses: securego/gosec@master + with: + args: '-no-fail -fmt sarif -out gosec-results.sarif ./...' + + - name: Upload Gosec results to GitHub Security tab + uses: github/codeql-action/upload-sarif@v3 + if: always() + with: + sarif_file: 'gosec-results.sarif' + + # Job 4: Build Multi-Platform Binaries + build-binaries: + name: Build Multi-Platform Binaries + runs-on: ubuntu-latest + needs: [build-and-test, lint] + if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' + strategy: + matrix: + include: + - os: linux + arch: amd64 + output: github-repo-linux-amd64 + - os: linux + arch: arm64 + output: github-repo-linux-arm64 + - os: darwin + arch: amd64 + output: github-repo-darwin-amd64 + - os: darwin + arch: arm64 + output: github-repo-darwin-arm64 + - os: windows + arch: amd64 + output: github-repo-windows-amd64.exe + steps: + - name: Checkout code + uses: actions/checkout@v5 + with: + persist-credentials: false + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: ${{ env.GO_VERSION }} + + - name: Build binary for ${{ matrix.os }}/${{ matrix.arch }} + env: + GOOS: ${{ matrix.os }} + GOARCH: ${{ matrix.arch }} + CGO_ENABLED: 0 + run: | + go build -ldflags="-s -w -X 'main.Version=${{ github.ref_name }}' -X 'main.GitCommitHash=${{ github.sha }}' -X 'main.BuiltAt=$(date -u +%Y-%m-%dT%H:%M:%SZ)'" -o ${{ matrix.output }} + + - name: Upload binary artifact + uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.output }} + path: ${{ matrix.output }} + retention-days: 30 + + # Job 5: Build and Push Docker Image + docker-build-push: + name: Build and Push Docker Image + runs-on: ubuntu-latest + needs: [build-and-test, lint] + if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop') + steps: + - name: Checkout code + uses: actions/checkout@v5 + with: + persist-credentials: false + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Log in to Docker Hub + uses: docker/login-action@v3 + if: secrets.DOCKERHUB_TOKEN != '' + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Extract metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: | + ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + ${{ secrets.DOCKERHUB_USERNAME }}/pvtr-github-repo + tags: | + type=ref,event=branch + type=ref,event=pr + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=sha,prefix={{branch}}- + type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }} + + - name: Build and push Docker image + uses: docker/build-push-action@v6 + with: + context: . + platforms: linux/amd64,linux/arm64 + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + provenance: true + sbom: true + + - name: Attest Build Provenance + uses: actions/attest-build-provenance@v2 + if: github.ref == 'refs/heads/main' + with: + subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + subject-digest: ${{ steps.build.outputs.digest }} + push-to-registry: true + + # Job 6: Integration Test (Self-Test) + integration-test: + name: Integration Test + runs-on: ubuntu-latest + needs: [docker-build-push] + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + steps: + - name: Checkout code + uses: actions/checkout@v5 + + - name: Create test config + run: | + cat > test-config.yml << EOF + loglevel: info + write-directory: evaluation_results + write: true + output: yaml + services: + self-test: + plugin: github-repo + policy: + catalogs: + - OSPS_B + applicability: + - Maturity Level 1 + vars: + owner: ${{ github.repository_owner }} + repo: ${{ github.event.repository.name }} + token: \${{ secrets.GITHUB_TOKEN }} + EOF + + - name: Run self-assessment using Docker image + run: | + docker run --rm \ + -v $(pwd)/test-config.yml:/.privateer/config.yml \ + -v $(pwd)/evaluation_results:/evaluation_results \ + ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest + + - name: Upload evaluation results + uses: actions/upload-artifact@v4 + if: always() + with: + name: evaluation-results + path: evaluation_results/ + retention-days: 30 + + # Job 7: Notify on Success/Failure + notify: + name: Notify Status + runs-on: ubuntu-latest + needs: [build-and-test, lint, security-scan, docker-build-push] + if: always() && (github.event_name == 'push' && github.ref == 'refs/heads/main') + steps: + - name: Check job statuses + id: check + run: | + if [ "${{ needs.build-and-test.result }}" == "failure" ] || \ + [ "${{ needs.lint.result }}" == "failure" ] || \ + [ "${{ needs.security-scan.result }}" == "failure" ] || \ + [ "${{ needs.docker-build-push.result }}" == "failure" ]; then + echo "status=failure" >> $GITHUB_OUTPUT + else + echo "status=success" >> $GITHUB_OUTPUT + fi + + - name: Post to Slack (if configured) + if: env.SLACK_WEBHOOK_URL != '' + uses: slackapi/slack-github-action@v2 + with: + payload: | + { + "text": "GitHub Actions build result: ${{ steps.check.outputs.status }}\nRepository: ${{ github.repository }}\nBranch: ${{ github.ref_name }}\nCommit: ${{ github.sha }}\nAuthor: ${{ github.actor }}\nWorkflow: ${{ github.workflow }}\nRun: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" + } + env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} diff --git a/README.md b/README.md index 73d150a..01f91d2 100644 --- a/README.md +++ b/README.md @@ -25,9 +25,9 @@ docker run \ ## GitHub Actions Usage -We've pushed an image to docker hub for use in GitHub Actions. +We've pushed images to GitHub Container Registry and Docker Hub for use in GitHub Actions. -You will also need to set up a GitHub personal access token with the repository read permissions. This token should be added to your config file, or — if using the example pipeline below — as a secret in your repository. +You will need a GitHub personal access token with repository read permissions. This token should be added to your config file or as a secret in your repository. ### Example GHA Setup @@ -35,6 +35,12 @@ You will also need to set up a GitHub personal access token with the repository - [Workflow Definition](https://github.com/privateerproj/.github/blob/main/.github/workflows/osps-baseline.yml) - [Action Results](https://github.com/privateerproj/.github/actions/runs/13691384519/job/38285134201) +### CI/CD Pipeline + +This repository uses GitHub Actions for continuous integration and deployment. The workflow automatically builds, tests, performs security scanning, and publishes multi-platform binaries and Docker images. + +For Docker Hub publishing, add `DOCKERHUB_USERNAME` and `DOCKERHUB_TOKEN` secrets to your repository settings. + ## Contributing Contributions are welcome! Please see our [Contributing Guidelines](.github/CONTRIBUTING.md) for more information. From 68cab443fb4f51b0f68835b17e801e9395f4a3b6 Mon Sep 17 00:00:00 2001 From: Zohayb Bhatti Date: Tue, 28 Oct 2025 13:53:34 -0500 Subject: [PATCH 2/5] fix: Address security issues and apply workflow optimizations Security Fixes (Kusari Inspector findings): - Move permissions from workflow level to individual jobs (least privilege) - Fix template injection vulnerabilities using environment variables - Add persist-credentials: false to integration-test checkout - Add proper permissions to all jobs Optimizations (ChatGPT suggestions): - Add id: build to Docker step for attestation digest reference - Pin action versions: trivy@0.28.0, gosec@v2.21.4 (prevent breaking changes) - Add concurrency control to cancel old runs on new push - Add explicit permissions: {} at workflow level for clarity All 16 high severity Kusari issues resolved. --- .github/workflows/ci-cd.yml | 47 ++++++++++++++++++++++++++++--------- 1 file changed, 36 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 2dd9fea..9da4e1f 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -11,12 +11,13 @@ on: - develop workflow_dispatch: -permissions: - contents: write - pull-requests: read - packages: write - id-token: write - attestations: write +# Prevent concurrent runs on same branch +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +# Explicit no permissions at workflow level (set per-job) +permissions: {} env: REGISTRY: ghcr.io @@ -29,6 +30,8 @@ jobs: build-and-test: name: Build and Test runs-on: ubuntu-latest + permissions: + contents: read steps: - name: Checkout code uses: actions/checkout@v5 @@ -102,6 +105,8 @@ jobs: lint: name: Lint Code runs-on: ubuntu-latest + permissions: + contents: read steps: - name: Checkout code uses: actions/checkout@v5 @@ -134,7 +139,7 @@ jobs: persist-credentials: false - name: Run Trivy vulnerability scanner - uses: aquasecurity/trivy-action@master + uses: aquasecurity/trivy-action@0.28.0 with: scan-type: 'fs' scan-ref: '.' @@ -149,7 +154,7 @@ jobs: sarif_file: 'trivy-results.sarif' - name: Run Gosec Security Scanner - uses: securego/gosec@master + uses: securego/gosec@v2.21.4 with: args: '-no-fail -fmt sarif -out gosec-results.sarif ./...' @@ -163,6 +168,8 @@ jobs: build-binaries: name: Build Multi-Platform Binaries runs-on: ubuntu-latest + permissions: + contents: read needs: [build-and-test, lint] if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' strategy: @@ -199,8 +206,11 @@ jobs: GOOS: ${{ matrix.os }} GOARCH: ${{ matrix.arch }} CGO_ENABLED: 0 + VERSION: ${{ github.ref_name }} + COMMIT_HASH: ${{ github.sha }} + OUTPUT_NAME: ${{ matrix.output }} run: | - go build -ldflags="-s -w -X 'main.Version=${{ github.ref_name }}' -X 'main.GitCommitHash=${{ github.sha }}' -X 'main.BuiltAt=$(date -u +%Y-%m-%dT%H:%M:%SZ)'" -o ${{ matrix.output }} + go build -ldflags="-s -w -X 'main.Version=${VERSION}' -X 'main.GitCommitHash=${COMMIT_HASH}' -X 'main.BuiltAt=$(date -u +%Y-%m-%dT%H:%M:%SZ)'" -o "${OUTPUT_NAME}" - name: Upload binary artifact uses: actions/upload-artifact@v4 @@ -213,6 +223,11 @@ jobs: docker-build-push: name: Build and Push Docker Image runs-on: ubuntu-latest + permissions: + contents: read + packages: write + id-token: write + attestations: write needs: [build-and-test, lint] if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop') steps: @@ -254,6 +269,7 @@ jobs: type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }} - name: Build and push Docker image + id: build uses: docker/build-push-action@v6 with: context: . @@ -278,13 +294,20 @@ jobs: integration-test: name: Integration Test runs-on: ubuntu-latest + permissions: + contents: read needs: [docker-build-push] if: github.event_name == 'push' && github.ref == 'refs/heads/main' steps: - name: Checkout code uses: actions/checkout@v5 + with: + persist-credentials: false - name: Create test config + env: + REPO_OWNER: ${{ github.repository_owner }} + REPO_NAME: ${{ github.event.repository.name }} run: | cat > test-config.yml << EOF loglevel: info @@ -300,8 +323,8 @@ jobs: applicability: - Maturity Level 1 vars: - owner: ${{ github.repository_owner }} - repo: ${{ github.event.repository.name }} + owner: ${REPO_OWNER} + repo: ${REPO_NAME} token: \${{ secrets.GITHUB_TOKEN }} EOF @@ -324,6 +347,8 @@ jobs: notify: name: Notify Status runs-on: ubuntu-latest + permissions: + contents: read needs: [build-and-test, lint, security-scan, docker-build-push] if: always() && (github.event_name == 'push' && github.ref == 'refs/heads/main') steps: From 9b705700a2abf3f874c1e166c6a8024c1c58540f Mon Sep 17 00:00:00 2001 From: Zohayb Bhatti Date: Tue, 4 Nov 2025 23:34:00 -0600 Subject: [PATCH 3/5] refactor: Split CI/CD workflow into separate workflows per review feedback Based on Jason's feedback: - Split large ci-cd.yml into focused workflows - Use workflow_run to trigger CD/security workflows after build/lint pass - Reuse existing build.yaml and lint.yaml workflows - Verify both build and lint pass before triggering CD workflows New workflows: - security-scan.yml: Security scanning (Trivy + Gosec) - build-binaries.yml: Multi-platform binary builds - docker-push.yml: Docker build and push to GHCR/DockerHub - integration-test.yml: Integration testing after Docker push Dependencies: - security-scan, build-binaries, docker-push: Trigger after build workflow - integration-test: Triggers after docker-push workflow - All workflows verify both build AND lint passed before running Also updates gemara dependency to v0.13.0 --- .github/workflows/build-binaries.yml | 83 ++++++ .github/workflows/ci-cd.yml | 376 ------------------------- .github/workflows/docker-push.yml | 98 +++++++ .github/workflows/integration-test.yml | 67 +++++ .github/workflows/security-scan.yml | 66 +++++ go.mod | 4 +- go.sum | 4 +- 7 files changed, 318 insertions(+), 380 deletions(-) create mode 100644 .github/workflows/build-binaries.yml delete mode 100644 .github/workflows/ci-cd.yml create mode 100644 .github/workflows/docker-push.yml create mode 100644 .github/workflows/integration-test.yml create mode 100644 .github/workflows/security-scan.yml diff --git a/.github/workflows/build-binaries.yml b/.github/workflows/build-binaries.yml new file mode 100644 index 0000000..a74acad --- /dev/null +++ b/.github/workflows/build-binaries.yml @@ -0,0 +1,83 @@ +name: Build Binaries + +on: + workflow_run: + workflows: ["build"] + types: [completed] + branches: [main, develop] + workflow_dispatch: + +permissions: + contents: read + actions: read + checks: read + +env: + GO_VERSION: "1.25.1" + +jobs: + build-binaries: + name: Build Multi-Platform Binaries + runs-on: ubuntu-latest + if: ${{ github.event_name == 'workflow_dispatch' || (github.event.workflow_run.conclusion == 'success' && (github.event.workflow_run.event == 'push' || github.event.workflow_run.event == 'workflow_dispatch')) }} + strategy: + matrix: + include: + - os: linux + arch: amd64 + output: github-repo-linux-amd64 + - os: linux + arch: arm64 + output: github-repo-linux-arm64 + - os: darwin + arch: amd64 + output: github-repo-darwin-amd64 + - os: darwin + arch: arm64 + output: github-repo-darwin-arm64 + - os: windows + arch: amd64 + output: github-repo-windows-amd64.exe + steps: + - name: Check if lint workflow passed + if: github.event_name == 'workflow_run' + uses: actions/github-script@v7 + with: + script: | + const { data: runs } = await github.rest.actions.listWorkflowRuns({ + owner: context.repo.owner, + repo: context.repo.repo, + workflow_id: 'lint.yaml', + head_sha: '${{ github.event.workflow_run.head_sha }}', + per_page: 1 + }); + if (runs.workflow_runs.length > 0 && runs.workflow_runs[0].conclusion !== 'success') { + core.setFailed('Lint workflow did not pass'); + } + - name: Checkout code + uses: actions/checkout@v5 + with: + persist-credentials: false + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: ${{ env.GO_VERSION }} + + - name: Build binary for ${{ matrix.os }}/${{ matrix.arch }} + env: + GOOS: ${{ matrix.os }} + GOARCH: ${{ matrix.arch }} + CGO_ENABLED: 0 + VERSION: ${{ github.event_name == 'workflow_dispatch' && github.ref_name || github.event.workflow_run.head_branch || 'unknown' }} + COMMIT_HASH: ${{ github.event_name == 'workflow_dispatch' && github.sha || github.event.workflow_run.head_sha || github.sha }} + OUTPUT_NAME: ${{ matrix.output }} + run: | + go build -ldflags="-s -w -X 'main.Version=${VERSION}' -X 'main.GitCommitHash=${COMMIT_HASH}' -X 'main.BuiltAt=$(date -u +%Y-%m-%dT%H:%M:%SZ)'" -o "${OUTPUT_NAME}" + + - name: Upload binary artifact + uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.output }} + path: ${{ matrix.output }} + retention-days: 30 diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml deleted file mode 100644 index 9da4e1f..0000000 --- a/.github/workflows/ci-cd.yml +++ /dev/null @@ -1,376 +0,0 @@ -name: CI/CD Pipeline - -on: - push: - branches: - - main - - develop - pull_request: - branches: - - main - - develop - workflow_dispatch: - -# Prevent concurrent runs on same branch -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -# Explicit no permissions at workflow level (set per-job) -permissions: {} - -env: - REGISTRY: ghcr.io - IMAGE_NAME: ${{ github.repository }} - GO_VERSION: "1.25.1" - TESTCOVERAGE_THRESHOLD: 26 - -jobs: - # Job 1: Build and Test - build-and-test: - name: Build and Test - runs-on: ubuntu-latest - permissions: - contents: read - steps: - - name: Checkout code - uses: actions/checkout@v5 - with: - persist-credentials: false - fetch-depth: 0 - - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version: ${{ env.GO_VERSION }} - - - name: Cache Go modules - uses: actions/cache@v4 - with: - path: | - ~/.cache/go-build - ~/go/pkg/mod - key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - restore-keys: | - ${{ runner.os }}-go- - - - name: Tidy dependencies - run: go mod tidy - - - name: Verify dependencies - run: go mod verify - - - name: Vet code - run: go vet ./... - - - name: Build binary - run: make build - - - name: Run tests with coverage - run: make test-cov - - - name: Check test coverage threshold - run: | - echo "Quality Gate: checking test coverage is above threshold ..." - echo "Threshold : $TESTCOVERAGE_THRESHOLD %" - totalCoverage=$(go tool cover -func=coverage.out | grep total | grep -Eo '[0-9]+\.[0-9]+') - echo "Current test coverage : $totalCoverage %" - if (( $(echo "$totalCoverage $TESTCOVERAGE_THRESHOLD" | awk '{print ($1 >= $2)}') )); then - echo "Coverage check passed" - else - echo "Current test coverage is below threshold" - echo "Please add more unit tests or adjust threshold to a lower value." - exit 1 - fi - - - name: Upload coverage reports - uses: codecov/codecov-action@v5 - if: always() - with: - files: ./coverage.out - flags: unittests - name: codecov-umbrella - fail_ci_if_error: false - env: - CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} - - - name: Upload build artifacts - uses: actions/upload-artifact@v4 - with: - name: github-repo-binary - path: github-repo - retention-days: 7 - - # Job 2: Lint - lint: - name: Lint Code - runs-on: ubuntu-latest - permissions: - contents: read - steps: - - name: Checkout code - uses: actions/checkout@v5 - with: - persist-credentials: false - - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version: ${{ env.GO_VERSION }} - - - name: golangci-lint - uses: golangci/golangci-lint-action@v8 - with: - version: latest - args: --timeout=5m - - # Job 3: Security Scanning - security-scan: - name: Security Scan - runs-on: ubuntu-latest - permissions: - security-events: write - contents: read - actions: read - steps: - - name: Checkout code - uses: actions/checkout@v5 - with: - persist-credentials: false - - - name: Run Trivy vulnerability scanner - uses: aquasecurity/trivy-action@0.28.0 - with: - scan-type: 'fs' - scan-ref: '.' - format: 'sarif' - output: 'trivy-results.sarif' - severity: 'CRITICAL,HIGH' - - - name: Upload Trivy results to GitHub Security tab - uses: github/codeql-action/upload-sarif@v3 - if: always() - with: - sarif_file: 'trivy-results.sarif' - - - name: Run Gosec Security Scanner - uses: securego/gosec@v2.21.4 - with: - args: '-no-fail -fmt sarif -out gosec-results.sarif ./...' - - - name: Upload Gosec results to GitHub Security tab - uses: github/codeql-action/upload-sarif@v3 - if: always() - with: - sarif_file: 'gosec-results.sarif' - - # Job 4: Build Multi-Platform Binaries - build-binaries: - name: Build Multi-Platform Binaries - runs-on: ubuntu-latest - permissions: - contents: read - needs: [build-and-test, lint] - if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' - strategy: - matrix: - include: - - os: linux - arch: amd64 - output: github-repo-linux-amd64 - - os: linux - arch: arm64 - output: github-repo-linux-arm64 - - os: darwin - arch: amd64 - output: github-repo-darwin-amd64 - - os: darwin - arch: arm64 - output: github-repo-darwin-arm64 - - os: windows - arch: amd64 - output: github-repo-windows-amd64.exe - steps: - - name: Checkout code - uses: actions/checkout@v5 - with: - persist-credentials: false - - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version: ${{ env.GO_VERSION }} - - - name: Build binary for ${{ matrix.os }}/${{ matrix.arch }} - env: - GOOS: ${{ matrix.os }} - GOARCH: ${{ matrix.arch }} - CGO_ENABLED: 0 - VERSION: ${{ github.ref_name }} - COMMIT_HASH: ${{ github.sha }} - OUTPUT_NAME: ${{ matrix.output }} - run: | - go build -ldflags="-s -w -X 'main.Version=${VERSION}' -X 'main.GitCommitHash=${COMMIT_HASH}' -X 'main.BuiltAt=$(date -u +%Y-%m-%dT%H:%M:%SZ)'" -o "${OUTPUT_NAME}" - - - name: Upload binary artifact - uses: actions/upload-artifact@v4 - with: - name: ${{ matrix.output }} - path: ${{ matrix.output }} - retention-days: 30 - - # Job 5: Build and Push Docker Image - docker-build-push: - name: Build and Push Docker Image - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - id-token: write - attestations: write - needs: [build-and-test, lint] - if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop') - steps: - - name: Checkout code - uses: actions/checkout@v5 - with: - persist-credentials: false - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Log in to GitHub Container Registry - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Log in to Docker Hub - uses: docker/login-action@v3 - if: secrets.DOCKERHUB_TOKEN != '' - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Extract metadata - id: meta - uses: docker/metadata-action@v5 - with: - images: | - ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - ${{ secrets.DOCKERHUB_USERNAME }}/pvtr-github-repo - tags: | - type=ref,event=branch - type=ref,event=pr - type=semver,pattern={{version}} - type=semver,pattern={{major}}.{{minor}} - type=sha,prefix={{branch}}- - type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }} - - - name: Build and push Docker image - id: build - uses: docker/build-push-action@v6 - with: - context: . - platforms: linux/amd64,linux/arm64 - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - cache-from: type=gha - cache-to: type=gha,mode=max - provenance: true - sbom: true - - - name: Attest Build Provenance - uses: actions/attest-build-provenance@v2 - if: github.ref == 'refs/heads/main' - with: - subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - subject-digest: ${{ steps.build.outputs.digest }} - push-to-registry: true - - # Job 6: Integration Test (Self-Test) - integration-test: - name: Integration Test - runs-on: ubuntu-latest - permissions: - contents: read - needs: [docker-build-push] - if: github.event_name == 'push' && github.ref == 'refs/heads/main' - steps: - - name: Checkout code - uses: actions/checkout@v5 - with: - persist-credentials: false - - - name: Create test config - env: - REPO_OWNER: ${{ github.repository_owner }} - REPO_NAME: ${{ github.event.repository.name }} - run: | - cat > test-config.yml << EOF - loglevel: info - write-directory: evaluation_results - write: true - output: yaml - services: - self-test: - plugin: github-repo - policy: - catalogs: - - OSPS_B - applicability: - - Maturity Level 1 - vars: - owner: ${REPO_OWNER} - repo: ${REPO_NAME} - token: \${{ secrets.GITHUB_TOKEN }} - EOF - - - name: Run self-assessment using Docker image - run: | - docker run --rm \ - -v $(pwd)/test-config.yml:/.privateer/config.yml \ - -v $(pwd)/evaluation_results:/evaluation_results \ - ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest - - - name: Upload evaluation results - uses: actions/upload-artifact@v4 - if: always() - with: - name: evaluation-results - path: evaluation_results/ - retention-days: 30 - - # Job 7: Notify on Success/Failure - notify: - name: Notify Status - runs-on: ubuntu-latest - permissions: - contents: read - needs: [build-and-test, lint, security-scan, docker-build-push] - if: always() && (github.event_name == 'push' && github.ref == 'refs/heads/main') - steps: - - name: Check job statuses - id: check - run: | - if [ "${{ needs.build-and-test.result }}" == "failure" ] || \ - [ "${{ needs.lint.result }}" == "failure" ] || \ - [ "${{ needs.security-scan.result }}" == "failure" ] || \ - [ "${{ needs.docker-build-push.result }}" == "failure" ]; then - echo "status=failure" >> $GITHUB_OUTPUT - else - echo "status=success" >> $GITHUB_OUTPUT - fi - - - name: Post to Slack (if configured) - if: env.SLACK_WEBHOOK_URL != '' - uses: slackapi/slack-github-action@v2 - with: - payload: | - { - "text": "GitHub Actions build result: ${{ steps.check.outputs.status }}\nRepository: ${{ github.repository }}\nBranch: ${{ github.ref_name }}\nCommit: ${{ github.sha }}\nAuthor: ${{ github.actor }}\nWorkflow: ${{ github.workflow }}\nRun: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" - } - env: - SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} diff --git a/.github/workflows/docker-push.yml b/.github/workflows/docker-push.yml new file mode 100644 index 0000000..62d9e70 --- /dev/null +++ b/.github/workflows/docker-push.yml @@ -0,0 +1,98 @@ +name: Docker Build and Push + +on: + workflow_run: + workflows: ["build"] + types: [completed] + branches: [main, develop] + workflow_dispatch: + +permissions: + contents: read + packages: write + id-token: write + attestations: write + actions: read + checks: read + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +jobs: + docker-build-push: + name: Build and Push Docker Image + runs-on: ubuntu-latest + if: ${{ github.event_name == 'workflow_dispatch' || (github.event.workflow_run.conclusion == 'success' && (github.event.workflow_run.head_branch == 'main' || github.event.workflow_run.head_branch == 'develop')) }} + steps: + - name: Check if lint workflow passed + if: github.event_name == 'workflow_run' + uses: actions/github-script@v7 + with: + script: | + const { data: runs } = await github.rest.actions.listWorkflowRuns({ + owner: context.repo.owner, + repo: context.repo.repo, + workflow_id: 'lint.yaml', + head_sha: '${{ github.event.workflow_run.head_sha }}', + per_page: 1 + }); + if (runs.workflow_runs.length > 0 && runs.workflow_runs[0].conclusion !== 'success') { + core.setFailed('Lint workflow did not pass'); + } + - name: Checkout code + uses: actions/checkout@v5 + with: + persist-credentials: false + ref: ${{ github.event_name == 'workflow_dispatch' && github.sha || github.event.workflow_run.head_sha }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Log in to Docker Hub + uses: docker/login-action@v3 + if: secrets.DOCKERHUB_TOKEN != '' + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Extract metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: | + ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + ${{ secrets.DOCKERHUB_USERNAME }}/pvtr-github-repo + tags: | + type=raw,value=latest,enable=${{ github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main' || github.event.workflow_run.head_branch == 'main' }} + type=ref,event=branch + type=sha,prefix=${{ github.event_name == 'workflow_dispatch' && github.ref_name || github.event.workflow_run.head_branch }}- + + - name: Build and push Docker image + id: build + uses: docker/build-push-action@v6 + with: + context: . + platforms: linux/amd64,linux/arm64 + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + provenance: true + sbom: true + + - name: Attest Build Provenance + uses: actions/attest-build-provenance@v2 + if: ${{ github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main' || github.event.workflow_run.head_branch == 'main' }} + with: + subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + subject-digest: ${{ steps.build.outputs.digest }} + push-to-registry: true diff --git a/.github/workflows/integration-test.yml b/.github/workflows/integration-test.yml new file mode 100644 index 0000000..0b3ad71 --- /dev/null +++ b/.github/workflows/integration-test.yml @@ -0,0 +1,67 @@ +name: Integration Test + +on: + workflow_run: + workflows: ["Docker Build and Push"] + types: [completed] + branches: [main] + workflow_dispatch: + +permissions: + contents: read + packages: read + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +jobs: + integration-test: + name: Integration Test + runs-on: ubuntu-latest + if: ${{ github.event_name == 'workflow_dispatch' || (github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.head_branch == 'main') }} + steps: + - name: Checkout code + uses: actions/checkout@v5 + with: + persist-credentials: false + ref: ${{ github.event_name == 'workflow_dispatch' && github.sha || github.event.workflow_run.head_sha }} + + - name: Create test config + env: + REPO_OWNER: ${{ github.repository_owner }} + REPO_NAME: ${{ github.event.repository.name }} + run: | + cat > test-config.yml << EOF + loglevel: info + write-directory: evaluation_results + write: true + output: yaml + services: + self-test: + plugin: github-repo + policy: + catalogs: + - OSPS_B + applicability: + - Maturity Level 1 + vars: + owner: ${REPO_OWNER} + repo: ${REPO_NAME} + token: \${{ secrets.GITHUB_TOKEN }} + EOF + + - name: Run self-assessment using Docker image + run: | + docker run --rm \ + -v $(pwd)/test-config.yml:/.privateer/config.yml \ + -v $(pwd)/evaluation_results:/evaluation_results \ + ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest + + - name: Upload evaluation results + uses: actions/upload-artifact@v4 + if: always() + with: + name: evaluation-results + path: evaluation_results/ + retention-days: 30 diff --git a/.github/workflows/security-scan.yml b/.github/workflows/security-scan.yml new file mode 100644 index 0000000..eeda427 --- /dev/null +++ b/.github/workflows/security-scan.yml @@ -0,0 +1,66 @@ +name: Security Scan + +on: + workflow_run: + workflows: ["build"] + types: [completed] + branches: [main, develop] + workflow_dispatch: + +permissions: + security-events: write + contents: read + actions: read + checks: read + +jobs: + security-scan: + name: Security Scan + runs-on: ubuntu-latest + if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }} + steps: + - name: Check if lint workflow passed + if: github.event_name == 'workflow_run' + uses: actions/github-script@v7 + with: + script: | + const { data: runs } = await github.rest.actions.listWorkflowRuns({ + owner: context.repo.owner, + repo: context.repo.repo, + workflow_id: 'lint.yaml', + head_sha: '${{ github.event.workflow_run.head_sha }}', + per_page: 1 + }); + if (runs.workflow_runs.length > 0 && runs.workflow_runs[0].conclusion !== 'success') { + core.setFailed('Lint workflow did not pass'); + } + - name: Checkout code + uses: actions/checkout@v5 + with: + persist-credentials: false + + - name: Run Trivy vulnerability scanner + uses: aquasecurity/trivy-action@0.28.0 + with: + scan-type: "fs" + scan-ref: "." + format: "sarif" + output: "trivy-results.sarif" + severity: "CRITICAL,HIGH" + + - name: Upload Trivy results to GitHub Security tab + uses: github/codeql-action/upload-sarif@v3 + if: always() + with: + sarif_file: "trivy-results.sarif" + + - name: Run Gosec Security Scanner + uses: securego/gosec@v2.21.4 + with: + args: "-no-fail -fmt sarif -out gosec-results.sarif ./..." + + - name: Upload Gosec results to GitHub Security tab + uses: github/codeql-action/upload-sarif@v3 + if: always() + with: + sarif_file: "gosec-results.sarif" diff --git a/go.mod b/go.mod index 2518449..1bf2206 100644 --- a/go.mod +++ b/go.mod @@ -5,11 +5,12 @@ go 1.25.1 require ( github.com/google/go-github/v74 v74.0.0 github.com/migueleliasweb/go-github-mock v1.4.0 - github.com/ossf/gemara v0.12.1 + github.com/ossf/gemara v0.13.0 github.com/ossf/si-tooling/v2 v2.0.5-0.20250508212737-7ddcc8c43db9 github.com/privateerproj/privateer-sdk v1.10.0 github.com/rhysd/actionlint v1.7.8 github.com/shurcooL/githubv4 v0.0.0-20240727222349-48295856cce7 + golang.org/x/oauth2 v0.32.0 ) require ( @@ -26,7 +27,6 @@ require ( github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect go.yaml.in/yaml/v4 v4.0.0-rc.2 // indirect - golang.org/x/oauth2 v0.32.0 // indirect golang.org/x/time v0.11.0 // indirect ) diff --git a/go.sum b/go.sum index 6459002..344ff4a 100644 --- a/go.sum +++ b/go.sum @@ -75,8 +75,8 @@ github.com/migueleliasweb/go-github-mock v1.4.0 h1:pQ6K8r348m2q79A8Khb0PbEeNQV7t github.com/migueleliasweb/go-github-mock v1.4.0/go.mod h1:/DUmhXkxrgVlDOVBqGoUXkV4w0ms5n1jDQHotYm135o= github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= -github.com/ossf/gemara v0.12.1 h1:Cyiytndw3HnyrctXE/iV4OzZURwypie2lmI7bf1bLAs= -github.com/ossf/gemara v0.12.1/go.mod h1:rY4YvaWvOSJthTE2jHudjwcCRIQ31Y7GpEc3pyJPIPM= +github.com/ossf/gemara v0.13.0 h1:ewhAliVpaq4DcNo25tngpFoFVJ1Lfd2qbtT972tDDbE= +github.com/ossf/gemara v0.13.0/go.mod h1:rY4YvaWvOSJthTE2jHudjwcCRIQ31Y7GpEc3pyJPIPM= github.com/ossf/si-tooling/v2 v2.0.5-0.20250508212737-7ddcc8c43db9 h1:H8zbVnZ1dbVhoQVGZanbDOSOX91KiCSsge4+GLrcFms= github.com/ossf/si-tooling/v2 v2.0.5-0.20250508212737-7ddcc8c43db9/go.mod h1:I7UDEAfNwoT2iwZrvORukgkGLKeD/cgVhHtcLPpaS6c= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= From 6fa2a83b11f1a2abe1e898e41c3502193665c468 Mon Sep 17 00:00:00 2001 From: Zohayb Bhatti Date: Tue, 4 Nov 2025 23:55:51 -0600 Subject: [PATCH 4/5] fix: Correct Docker Hub image handling and fix indentation - Fix Docker Hub credentials check to use proper step output - Conditionally include Docker Hub image in metadata only when credentials exist - Fix indentation consistency in build-binaries.yml and docker-push.yml - Prevents empty image entries in metadata action when Docker Hub not configured --- .github/workflows/build-binaries.yml | 136 +++++++++++++-------------- .github/workflows/docker-push.yml | 48 +++++++--- 2 files changed, 101 insertions(+), 83 deletions(-) diff --git a/.github/workflows/build-binaries.yml b/.github/workflows/build-binaries.yml index a74acad..1219855 100644 --- a/.github/workflows/build-binaries.yml +++ b/.github/workflows/build-binaries.yml @@ -1,11 +1,11 @@ name: Build Binaries on: - workflow_run: - workflows: ["build"] - types: [completed] - branches: [main, develop] - workflow_dispatch: + workflow_run: + workflows: ["build"] + types: [completed] + branches: [main, develop] + workflow_dispatch: permissions: contents: read @@ -13,71 +13,71 @@ permissions: checks: read env: - GO_VERSION: "1.25.1" + GO_VERSION: "1.25.1" jobs: - build-binaries: - name: Build Multi-Platform Binaries - runs-on: ubuntu-latest - if: ${{ github.event_name == 'workflow_dispatch' || (github.event.workflow_run.conclusion == 'success' && (github.event.workflow_run.event == 'push' || github.event.workflow_run.event == 'workflow_dispatch')) }} - strategy: - matrix: - include: - - os: linux - arch: amd64 - output: github-repo-linux-amd64 - - os: linux - arch: arm64 - output: github-repo-linux-arm64 - - os: darwin - arch: amd64 - output: github-repo-darwin-amd64 - - os: darwin - arch: arm64 - output: github-repo-darwin-arm64 - - os: windows - arch: amd64 - output: github-repo-windows-amd64.exe - steps: - - name: Check if lint workflow passed - if: github.event_name == 'workflow_run' - uses: actions/github-script@v7 - with: - script: | - const { data: runs } = await github.rest.actions.listWorkflowRuns({ - owner: context.repo.owner, - repo: context.repo.repo, - workflow_id: 'lint.yaml', - head_sha: '${{ github.event.workflow_run.head_sha }}', - per_page: 1 - }); - if (runs.workflow_runs.length > 0 && runs.workflow_runs[0].conclusion !== 'success') { - core.setFailed('Lint workflow did not pass'); - } - - name: Checkout code - uses: actions/checkout@v5 - with: - persist-credentials: false + build-binaries: + name: Build Multi-Platform Binaries + runs-on: ubuntu-latest + if: ${{ github.event_name == 'workflow_dispatch' || (github.event.workflow_run.conclusion == 'success' && (github.event.workflow_run.event == 'push' || github.event.workflow_run.event == 'workflow_dispatch')) }} + strategy: + matrix: + include: + - os: linux + arch: amd64 + output: github-repo-linux-amd64 + - os: linux + arch: arm64 + output: github-repo-linux-arm64 + - os: darwin + arch: amd64 + output: github-repo-darwin-amd64 + - os: darwin + arch: arm64 + output: github-repo-darwin-arm64 + - os: windows + arch: amd64 + output: github-repo-windows-amd64.exe + steps: + - name: Check if lint workflow passed + if: github.event_name == 'workflow_run' + uses: actions/github-script@v7 + with: + script: | + const { data: runs } = await github.rest.actions.listWorkflowRuns({ + owner: context.repo.owner, + repo: context.repo.repo, + workflow_id: 'lint.yaml', + head_sha: '${{ github.event.workflow_run.head_sha }}', + per_page: 1 + }); + if (runs.workflow_runs.length > 0 && runs.workflow_runs[0].conclusion !== 'success') { + core.setFailed('Lint workflow did not pass'); + } + - name: Checkout code + uses: actions/checkout@v5 + with: + persist-credentials: false - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version: ${{ env.GO_VERSION }} + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: ${{ env.GO_VERSION }} - - name: Build binary for ${{ matrix.os }}/${{ matrix.arch }} - env: - GOOS: ${{ matrix.os }} - GOARCH: ${{ matrix.arch }} - CGO_ENABLED: 0 - VERSION: ${{ github.event_name == 'workflow_dispatch' && github.ref_name || github.event.workflow_run.head_branch || 'unknown' }} - COMMIT_HASH: ${{ github.event_name == 'workflow_dispatch' && github.sha || github.event.workflow_run.head_sha || github.sha }} - OUTPUT_NAME: ${{ matrix.output }} - run: | - go build -ldflags="-s -w -X 'main.Version=${VERSION}' -X 'main.GitCommitHash=${COMMIT_HASH}' -X 'main.BuiltAt=$(date -u +%Y-%m-%dT%H:%M:%SZ)'" -o "${OUTPUT_NAME}" + - name: Build binary for ${{ matrix.os }}/${{ matrix.arch }} + env: + GOOS: ${{ matrix.os }} + GOARCH: ${{ matrix.arch }} + CGO_ENABLED: 0 + VERSION: ${{ github.event_name == 'workflow_dispatch' && github.ref_name || github.event.workflow_run.head_branch || 'unknown' }} + COMMIT_HASH: ${{ github.event_name == 'workflow_dispatch' && github.sha || github.event.workflow_run.head_sha || github.sha }} + OUTPUT_NAME: ${{ matrix.output }} + run: | + go build -ldflags="-s -w -X 'main.Version=${VERSION}' -X 'main.GitCommitHash=${COMMIT_HASH}' -X 'main.BuiltAt=$(date -u +%Y-%m-%dT%H:%M:%SZ)'" -o "${OUTPUT_NAME}" - - name: Upload binary artifact - uses: actions/upload-artifact@v4 - with: - name: ${{ matrix.output }} - path: ${{ matrix.output }} - retention-days: 30 + - name: Upload binary artifact + uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.output }} + path: ${{ matrix.output }} + retention-days: 30 diff --git a/.github/workflows/docker-push.yml b/.github/workflows/docker-push.yml index 62d9e70..64c1a94 100644 --- a/.github/workflows/docker-push.yml +++ b/.github/workflows/docker-push.yml @@ -1,11 +1,11 @@ name: Docker Build and Push on: - workflow_run: - workflows: ["build"] - types: [completed] - branches: [main, develop] - workflow_dispatch: + workflow_run: + workflows: ["build"] + types: [completed] + branches: [main, develop] + workflow_dispatch: permissions: contents: read @@ -16,14 +16,14 @@ permissions: checks: read env: - REGISTRY: ghcr.io - IMAGE_NAME: ${{ github.repository }} + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} jobs: - docker-build-push: - name: Build and Push Docker Image - runs-on: ubuntu-latest - if: ${{ github.event_name == 'workflow_dispatch' || (github.event.workflow_run.conclusion == 'success' && (github.event.workflow_run.head_branch == 'main' || github.event.workflow_run.head_branch == 'develop')) }} + docker-build-push: + name: Build and Push Docker Image + runs-on: ubuntu-latest + if: ${{ github.event_name == 'workflow_dispatch' || (github.event.workflow_run.conclusion == 'success' && (github.event.workflow_run.head_branch == 'main' || github.event.workflow_run.head_branch == 'develop')) }} steps: - name: Check if lint workflow passed if: github.event_name == 'workflow_run' @@ -56,20 +56,38 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Prepare Docker Hub image name + id: dockerhub + run: | + if [ -n "${{ secrets.DOCKERHUB_TOKEN }}" ] && [ -n "${{ secrets.DOCKERHUB_USERNAME }}" ]; then + echo "image=${{ secrets.DOCKERHUB_USERNAME }}/pvtr-github-repo" >> $GITHUB_OUTPUT + echo "has_credentials=true" >> $GITHUB_OUTPUT + else + echo "has_credentials=false" >> $GITHUB_OUTPUT + fi + - name: Log in to Docker Hub uses: docker/login-action@v3 - if: secrets.DOCKERHUB_TOKEN != '' + if: steps.dockerhub.outputs.has_credentials == 'true' with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Prepare images list for metadata + id: prepare-images + run: | + echo "images<> $GITHUB_OUTPUT + echo "${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}" >> $GITHUB_OUTPUT + if [ "${{ steps.dockerhub.outputs.has_credentials }}" = "true" ]; then + echo "${{ steps.dockerhub.outputs.image }}" >> $GITHUB_OUTPUT + fi + echo "EOF" >> $GITHUB_OUTPUT + - name: Extract metadata id: meta uses: docker/metadata-action@v5 with: - images: | - ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - ${{ secrets.DOCKERHUB_USERNAME }}/pvtr-github-repo + images: ${{ steps.prepare-images.outputs.images }} tags: | type=raw,value=latest,enable=${{ github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main' || github.event.workflow_run.head_branch == 'main' }} type=ref,event=branch From bfb7e8cbb7a45bd07657a55fb2b374daf23fa633 Mon Sep 17 00:00:00 2001 From: Zohayb Bhatti Date: Thu, 6 Nov 2025 00:19:01 -0600 Subject: [PATCH 5/5] chore: Update SDK and fix security vulnerabilities - Update privateer-sdk from v1.10.0 to v1.13.0 to include ToSARIF fixes - Fix template injection vulnerabilities using context.payload instead of template strings - Add repository and branch validation for workflow_run triggers - All security validations ensure workflows only run from same repo and allowed branches --- .github/workflows/build-binaries.yml | 27 +++++++- .github/workflows/docker-push.yml | 27 +++++++- .github/workflows/integration-test.yml | 26 ++++++++ .github/workflows/security-scan.yml | 27 +++++++- go.mod | 22 ++++++- go.sum | 87 ++++++++++++++++++++++++-- 6 files changed, 202 insertions(+), 14 deletions(-) diff --git a/.github/workflows/build-binaries.yml b/.github/workflows/build-binaries.yml index 1219855..31587e1 100644 --- a/.github/workflows/build-binaries.yml +++ b/.github/workflows/build-binaries.yml @@ -39,16 +39,39 @@ jobs: arch: amd64 output: github-repo-windows-amd64.exe steps: - - name: Check if lint workflow passed + - name: Validate workflow_run security if: github.event_name == 'workflow_run' uses: actions/github-script@v7 with: script: | + const workflowRun = context.payload.workflow_run; + + // Validate workflow_run is from the same repository (not a fork) + if (!workflowRun || workflowRun.repository.full_name !== context.repo.owner + '/' + context.repo.repo) { + core.setFailed('Security: workflow_run must be from the same repository'); + return; + } + + // Validate head SHA format + const headSha = workflowRun.head_sha; + if (!headSha || !/^[a-f0-9]{40}$/i.test(headSha)) { + core.setFailed('Invalid head SHA format'); + return; + } + + // Validate branch is allowed + const allowedBranches = ['main', 'develop']; + if (!allowedBranches.includes(workflowRun.head_branch)) { + core.setFailed(`Security: workflow_run from branch '${workflowRun.head_branch}' is not allowed. Allowed branches: ${allowedBranches.join(', ')}`); + return; + } + + // Check if lint workflow passed const { data: runs } = await github.rest.actions.listWorkflowRuns({ owner: context.repo.owner, repo: context.repo.repo, workflow_id: 'lint.yaml', - head_sha: '${{ github.event.workflow_run.head_sha }}', + head_sha: headSha, per_page: 1 }); if (runs.workflow_runs.length > 0 && runs.workflow_runs[0].conclusion !== 'success') { diff --git a/.github/workflows/docker-push.yml b/.github/workflows/docker-push.yml index 64c1a94..8d487ec 100644 --- a/.github/workflows/docker-push.yml +++ b/.github/workflows/docker-push.yml @@ -25,16 +25,39 @@ jobs: runs-on: ubuntu-latest if: ${{ github.event_name == 'workflow_dispatch' || (github.event.workflow_run.conclusion == 'success' && (github.event.workflow_run.head_branch == 'main' || github.event.workflow_run.head_branch == 'develop')) }} steps: - - name: Check if lint workflow passed + - name: Validate workflow_run security if: github.event_name == 'workflow_run' uses: actions/github-script@v7 with: script: | + const workflowRun = context.payload.workflow_run; + + // Validate workflow_run is from the same repository (not a fork) + if (!workflowRun || workflowRun.repository.full_name !== context.repo.owner + '/' + context.repo.repo) { + core.setFailed('Security: workflow_run must be from the same repository'); + return; + } + + // Validate head SHA format + const headSha = workflowRun.head_sha; + if (!headSha || !/^[a-f0-9]{40}$/i.test(headSha)) { + core.setFailed('Invalid head SHA format'); + return; + } + + // Validate branch is allowed + const allowedBranches = ['main', 'develop']; + if (!allowedBranches.includes(workflowRun.head_branch)) { + core.setFailed(`Security: workflow_run from branch '${workflowRun.head_branch}' is not allowed. Allowed branches: ${allowedBranches.join(', ')}`); + return; + } + + // Check if lint workflow passed const { data: runs } = await github.rest.actions.listWorkflowRuns({ owner: context.repo.owner, repo: context.repo.repo, workflow_id: 'lint.yaml', - head_sha: '${{ github.event.workflow_run.head_sha }}', + head_sha: headSha, per_page: 1 }); if (runs.workflow_runs.length > 0 && runs.workflow_runs[0].conclusion !== 'success') { diff --git a/.github/workflows/integration-test.yml b/.github/workflows/integration-test.yml index 0b3ad71..7ec532b 100644 --- a/.github/workflows/integration-test.yml +++ b/.github/workflows/integration-test.yml @@ -21,6 +21,32 @@ jobs: runs-on: ubuntu-latest if: ${{ github.event_name == 'workflow_dispatch' || (github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.head_branch == 'main') }} steps: + - name: Validate workflow_run security + if: github.event_name == 'workflow_run' + uses: actions/github-script@v7 + with: + script: | + const workflowRun = context.payload.workflow_run; + + // Validate workflow_run is from the same repository (not a fork) + if (!workflowRun || workflowRun.repository.full_name !== context.repo.owner + '/' + context.repo.repo) { + core.setFailed('Security: workflow_run must be from the same repository'); + return; + } + + // Validate head SHA format + const headSha = workflowRun.head_sha; + if (!headSha || !/^[a-f0-9]{40}$/i.test(headSha)) { + core.setFailed('Invalid head SHA format'); + return; + } + + // Validate branch is allowed + const allowedBranches = ['main']; + if (!allowedBranches.includes(workflowRun.head_branch)) { + core.setFailed(`Security: workflow_run from branch '${workflowRun.head_branch}' is not allowed. Allowed branches: ${allowedBranches.join(', ')}`); + return; + } - name: Checkout code uses: actions/checkout@v5 with: diff --git a/.github/workflows/security-scan.yml b/.github/workflows/security-scan.yml index eeda427..6b0b99f 100644 --- a/.github/workflows/security-scan.yml +++ b/.github/workflows/security-scan.yml @@ -19,16 +19,39 @@ jobs: runs-on: ubuntu-latest if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }} steps: - - name: Check if lint workflow passed + - name: Validate workflow_run security if: github.event_name == 'workflow_run' uses: actions/github-script@v7 with: script: | + const workflowRun = context.payload.workflow_run; + + // Validate workflow_run is from the same repository (not a fork) + if (!workflowRun || workflowRun.repository.full_name !== context.repo.owner + '/' + context.repo.repo) { + core.setFailed('Security: workflow_run must be from the same repository'); + return; + } + + // Validate head SHA format + const headSha = workflowRun.head_sha; + if (!headSha || !/^[a-f0-9]{40}$/i.test(headSha)) { + core.setFailed('Invalid head SHA format'); + return; + } + + // Validate branch is allowed + const allowedBranches = ['main', 'develop']; + if (!allowedBranches.includes(workflowRun.head_branch)) { + core.setFailed(`Security: workflow_run from branch '${workflowRun.head_branch}' is not allowed. Allowed branches: ${allowedBranches.join(', ')}`); + return; + } + + // Check if lint workflow passed const { data: runs } = await github.rest.actions.listWorkflowRuns({ owner: context.repo.owner, repo: context.repo.repo, workflow_id: 'lint.yaml', - head_sha: '${{ github.event.workflow_run.head_sha }}', + head_sha: headSha, per_page: 1 }); if (runs.workflow_runs.length > 0 && runs.workflow_runs[0].conclusion !== 'success') { diff --git a/go.mod b/go.mod index 1bf2206..4015a3f 100644 --- a/go.mod +++ b/go.mod @@ -7,27 +7,45 @@ require ( github.com/migueleliasweb/go-github-mock v1.4.0 github.com/ossf/gemara v0.13.0 github.com/ossf/si-tooling/v2 v2.0.5-0.20250508212737-7ddcc8c43db9 - github.com/privateerproj/privateer-sdk v1.10.0 + github.com/privateerproj/privateer-sdk v1.13.0 github.com/rhysd/actionlint v1.7.8 github.com/shurcooL/githubv4 v0.0.0-20240727222349-48295856cce7 golang.org/x/oauth2 v0.32.0 ) require ( + dario.cat/mergo v1.0.0 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/ProtonMail/go-crypto v1.1.6 // indirect + github.com/cloudflare/circl v1.6.1 // indirect + github.com/cyphar/filepath-securejoin v0.4.1 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/defenseunicorns/go-oscal v0.7.0 // indirect + github.com/emirpasic/gods v1.18.1 // indirect + github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect + github.com/go-git/go-billy/v5 v5.6.2 // indirect + github.com/go-git/go-git/v5 v5.16.3 // indirect github.com/go-viper/mapstructure/v2 v2.4.0 // indirect github.com/goccy/go-yaml v1.18.0 // indirect + github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/google/go-github/v71 v71.0.0 // indirect github.com/google/go-github/v73 v73.0.0 // indirect github.com/google/go-querystring v1.1.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/mux v1.8.1 // indirect + github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect + github.com/kevinburke/ssh_config v1.2.0 // indirect + github.com/pjbgf/sha1cd v0.3.2 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect + github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect + github.com/skeema/knownhosts v1.3.1 // indirect + github.com/xanzy/ssh-agent v0.3.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect go.yaml.in/yaml/v4 v4.0.0-rc.2 // indirect + golang.org/x/crypto v0.37.0 // indirect golang.org/x/time v0.11.0 // indirect + gopkg.in/warnings.v0 v0.1.2 // indirect ) require ( @@ -58,7 +76,7 @@ require ( github.com/spf13/viper v1.21.0 // indirect github.com/stretchr/testify v1.11.1 github.com/subosito/gotenv v1.6.0 // indirect - golang.org/x/net v0.38.0 // indirect + golang.org/x/net v0.39.0 // indirect golang.org/x/sync v0.17.0 // indirect golang.org/x/sys v0.36.0 // indirect golang.org/x/text v0.28.0 // indirect diff --git a/go.sum b/go.sum index 344ff4a..75aa716 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,23 @@ +dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= +dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw= +github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/bmatcuk/doublestar/v4 v4.9.1 h1:X8jg9rRZmJd4yRy7ZeNDRnM+T3ZfHv15JiBJ/avrEXE= github.com/bmatcuk/doublestar/v4 v4.9.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= github.com/bufbuild/protocompile v0.14.1 h1:iA73zAf/fyljNjQKwYzUHD6AD4R8KMasmwa/FBatYVw= github.com/bufbuild/protocompile v0.14.1/go.mod h1:ppVdAIhbr2H8asPk6k4pY7t9zB1OU5DoEw9xY/FUi1c= +github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= +github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s= +github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -11,6 +26,10 @@ github.com/defenseunicorns/go-oscal v0.7.0 h1:Ji9Yw3zEkbUfKZ8Gotoi9ExjUV/h3jmFLJ github.com/defenseunicorns/go-oscal v0.7.0/go.mod h1:OPuLRz6v7qhSaKIUgr+bK6ykhYq7FpZozSn2cVZJhMs= github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o= +github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE= +github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= @@ -18,6 +37,16 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= +github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= +github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM= +github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= +github.com/go-git/go-git/v5 v5.16.3 h1:Z8BtvxZ09bYm/yYNgPKCzgWtaRqDTgIKRgIRHBfU6Z8= +github.com/go-git/go-git/v5 v5.16.3/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= @@ -26,6 +55,8 @@ github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9L github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/gomarkdown/markdown v0.0.0-20250311123330-531bef5e742b h1:EY/KpStFl60qA17CptGXhwfZ+k1sFNJIUNR8DdbcuUk= @@ -53,10 +84,17 @@ github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8 github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jhump/protoreflect v1.17.0 h1:qOEr613fac2lOuTgWN4tPAtLL7fUSbuJL5X5XumQh94= github.com/jhump/protoreflect v1.17.0/go.mod h1:h9+vUUL38jiBzck8ck+6G/aeMX8Z4QUY/NiJPwPNi+8= +github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= +github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= @@ -75,17 +113,23 @@ github.com/migueleliasweb/go-github-mock v1.4.0 h1:pQ6K8r348m2q79A8Khb0PbEeNQV7t github.com/migueleliasweb/go-github-mock v1.4.0/go.mod h1:/DUmhXkxrgVlDOVBqGoUXkV4w0ms5n1jDQHotYm135o= github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= +github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= +github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= github.com/ossf/gemara v0.13.0 h1:ewhAliVpaq4DcNo25tngpFoFVJ1Lfd2qbtT972tDDbE= github.com/ossf/gemara v0.13.0/go.mod h1:rY4YvaWvOSJthTE2jHudjwcCRIQ31Y7GpEc3pyJPIPM= github.com/ossf/si-tooling/v2 v2.0.5-0.20250508212737-7ddcc8c43db9 h1:H8zbVnZ1dbVhoQVGZanbDOSOX91KiCSsge4+GLrcFms= github.com/ossf/si-tooling/v2 v2.0.5-0.20250508212737-7ddcc8c43db9/go.mod h1:I7UDEAfNwoT2iwZrvORukgkGLKeD/cgVhHtcLPpaS6c= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4= +github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/privateerproj/privateer-sdk v1.10.0 h1:LlD8TxozQmx20N9jkuixTQIPSgSfxFrFbP38FivhADM= -github.com/privateerproj/privateer-sdk v1.10.0/go.mod h1:eDst4232KyZd98dduG534GLxBfN1VtIZR6w5lUOiigY= +github.com/privateerproj/privateer-sdk v1.13.0 h1:xDIEuii/ly32k/U61WfSUYNxV7WtCWdE7xpMfJHgbLg= +github.com/privateerproj/privateer-sdk v1.13.0/go.mod h1:aMI49XFbLjvdXznpSh3pkuZ8crTeKZ1CLQSA2bOOSv4= github.com/rhysd/actionlint v1.7.8 h1:3d+N9ourgAxVYG4z2IFxFIk/YiT6V+VnKASfXGwT60E= github.com/rhysd/actionlint v1.7.8/go.mod h1:3kiS6egcbXG+vQsJIhFxTz+UKaF1JprsE0SKrpCZKvU= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= @@ -93,17 +137,22 @@ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= -github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc= github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik= github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ= github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/shurcooL/githubv4 v0.0.0-20240727222349-48295856cce7 h1:cYCy18SHPKRkvclm+pWm1Lk4YrREb4IOIb/YdFO0p2M= github.com/shurcooL/githubv4 v0.0.0-20240727222349-48295856cce7/go.mod h1:zqMwyHmnN/eDOZOdiTohqIUKUrTFX62PNlu7IJdu0q8= github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466 h1:17JxqqJY66GmZVHkmAsGEkcIu0oCe3AM420QDgGwZx0= github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466/go.mod h1:9dIRpgIY7hVhoqfe0/FcYp0bpInZaT7dc3BYOprrIUE= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8= +github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY= github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw= github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U= github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= @@ -118,11 +167,15 @@ github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3A github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU= github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= +github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= @@ -139,24 +192,41 @@ go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= go.yaml.in/yaml/v4 v4.0.0-rc.2 h1:/FrI8D64VSr4HtGIlUtlFMGsm7H7pWTbj6vOLVZcA6s= go.yaml.in/yaml/v4 v4.0.0-rc.2/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0= -golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= -golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= +golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= +golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= +golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= +golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= golang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY= golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o= +golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 h1:e0AIkUUhxyBKh6ssZNrAMeqhA7RKUj42346d1y02i2g= google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= @@ -165,7 +235,12 @@ google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=