Scope releaser permissions and bind the tag expression #12998
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Security Scan | |
| on: | |
| # Called by run-on-main.yml on push to main, where image-build-and-push gates | |
| # on the result. There is deliberately no `push:` trigger here as well — that | |
| # would run every scan twice for each push to main. | |
| workflow_call: | |
| workflow_dispatch: | |
| # PRs trigger this directly rather than through run-on-pr.yml, for the same | |
| # reason. Unfiltered by branch, to match the coverage run-on-pr.yml gave. | |
| pull_request: | |
| schedule: | |
| # Run daily at 2 AM UTC | |
| - cron: '0 2 * * *' | |
| # security-events: write is granted per job rather than here, so only the two | |
| # jobs that upload SARIF can write security events. | |
| permissions: | |
| contents: read | |
| jobs: | |
| grype-repo-scan: | |
| name: Grype Repository Scan | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| security-events: write | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 | |
| with: | |
| persist-credentials: false | |
| - name: Run Grype vulnerability scanner | |
| id: grype-scan | |
| uses: anchore/scan-action@e1165082ffb1fe366ebaf02d8526e7c4989ea9d2 # v7.4.0 | |
| with: | |
| path: "." | |
| output-format: "sarif" | |
| fail-build: false | |
| - name: Upload Grype scan results to GitHub Security tab | |
| uses: github/codeql-action/upload-sarif@5595ccaf912efad79be6eef63a5619ff05969be3 # v4 | |
| if: always() | |
| with: | |
| sarif_file: ${{ steps.grype-scan.outputs.sarif }} | |
| category: "grype" | |
| zizmor: | |
| name: GitHub Actions Static Analysis | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| security-events: write | |
| # --no-exit-codes stops *findings* failing the job; this stops a tool or | |
| # network failure doing so. Both come off when the gate goes blocking. | |
| continue-on-error: true | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 | |
| with: | |
| # This job only reads the workflow files; it never pushes. | |
| persist-credentials: false | |
| # Advisory only for now: --no-exit-codes keeps the job green while the | |
| # existing backlog is worked through, so this reports without blocking. | |
| # Remove it (and add --min-severity) once the backlog is clear. | |
| # | |
| # Results are written straight to SARIF and never echoed. Workflow logs | |
| # and job summaries are world-readable on a public repository, so | |
| # printing findings would publish them; the SARIF upload keeps them in | |
| # the Security tab, which requires write access to read. | |
| - name: Run zizmor | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| pipx run zizmor==1.29.0 \ | |
| --persona=regular \ | |
| --format=sarif \ | |
| --no-exit-codes \ | |
| --no-progress \ | |
| .github/ > zizmor.sarif | |
| # Pull requests from forks get a read-only token, so the upload is | |
| # skipped there; those workflows are still scanned on push to main. | |
| - name: Upload zizmor results to GitHub Security tab | |
| uses: github/codeql-action/upload-sarif@5595ccaf912efad79be6eef63a5619ff05969be3 # v4 | |
| if: always() && !github.event.pull_request.head.repo.fork | |
| with: | |
| sarif_file: zizmor.sarif | |
| category: "zizmor" | |
| govulncheck: | |
| name: Go Vulnerability Check | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 | |
| with: | |
| persist-credentials: false | |
| - name: Run govulncheck | |
| uses: golang/govulncheck-action@032d45514ae346b1db93c04b0c90b841c370344f # v1 | |
| with: | |
| go-version-input: 'stable' | |
| go-package: ./... | |
| repo-checkout: false | |
| output-format: json | |
| output-file: govulncheck-output.json | |
| - name: Check for vulnerabilities (with exclusions) | |
| run: | | |
| # Ignored vulnerabilities with justification: | |
| # Go stdlib advisories published 2026-06-02, all fixed in go1.26.4 / | |
| # go1.25.11 (DoS / log-injection class, no RCE): | |
| # GO-2026-5037 (CVE-2026-27145, crypto/x509 VerifyHostname) | |
| # GO-2026-5038 (CVE-2026-42504, mime WordDecoder.DecodeHeader) | |
| # GO-2026-5039 (CVE-2026-42507, net/textproto error messages) | |
| # CI's `setup-go: stable` still resolves to go1.26.3 because the | |
| # actions/go-versions manifest lags the Go release. Temporary | |
| # exclusion; remove once CI builds on go1.26.4 or later. | |
| # | |
| # GO-2026-5932: golang.org/x/crypto/openpgp is deprecated-by-design | |
| # ("unsafe, not maintained, should not be used"). No fixed version | |
| # exists and none is planned. ToolHive does not import openpgp | |
| # directly; it is pulled transitively via sigstore (rekor/sigstore-go) | |
| # for backward-compatible OpenPGP verification. Remove when sigstore | |
| # drops the openpgp dependency. | |
| IGNORED_VULNS="GO-2026-5037 GO-2026-5038 GO-2026-5039 GO-2026-5932" | |
| # Show the raw output for debugging | |
| echo "::group::govulncheck raw output" | |
| cat govulncheck-output.json | |
| echo "::endgroup::" | |
| # Extract vulnerability IDs that have actual findings (called symbols) | |
| # The JSON has "finding" objects with "osv" field only for vulnerabilities | |
| # where vulnerable code paths are actually called | |
| FOUND_VULNS=$(jq -r 'select(.finding != null) | .finding.osv' govulncheck-output.json | sort -u | grep -E '^GO-' || true) | |
| if [ -z "$FOUND_VULNS" ]; then | |
| echo "✅ No vulnerabilities found" | |
| exit 0 | |
| fi | |
| echo "Found vulnerabilities: $FOUND_VULNS" | |
| # Check if all found vulnerabilities are in the ignore list | |
| UNIGNORED="" | |
| for vuln in $FOUND_VULNS; do | |
| if ! echo "$IGNORED_VULNS" | grep -qw "$vuln"; then | |
| UNIGNORED="$UNIGNORED $vuln" | |
| fi | |
| done | |
| UNIGNORED=$(echo "$UNIGNORED" | xargs) | |
| if [ -z "$UNIGNORED" ]; then | |
| echo "⚠️ All vulnerabilities are ignored: $FOUND_VULNS" | |
| exit 0 | |
| fi | |
| echo "❌ Vulnerabilities need attention: $UNIGNORED" | |
| exit 1 |