From 4cf0e93d74727ba33423a153beb75acd864e739f Mon Sep 17 00:00:00 2001 From: Amit Kumar Date: Wed, 13 May 2026 06:36:47 +0000 Subject: [PATCH 1/3] feat(release): darwin/arm64 release workflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds release-darwin.yml on macos-14 runner. Trigger: same v*.*.* tag push that fires release-go.yml. Builds darwin/arm64 natively (CGO, clang, kuzudb + go-sqlite3 build cleanly), packages a tar.gz, generates SPDX SBOM via Syft, signs the archive directly with Cosign keyless (bundle format — matches the cosign v4 update in #136), then uploads to the existing Release created by release-go.yml. Why a separate workflow: - release-go.yml runs on ubuntu-latest. CGO + kuzudb won't cross-compile to darwin from linux. - macos-14 runners are arm64-native; cross-compile happens via native CC=clang. - Two workflows publish to the same tag → same Release. The upload step retries 3× (30s delay) to handle the race with release-go.yml creating the Release (linux completes first, darwin attaches to that Release). Follow-up: branch protection cleanup. The `build` required check is still satisfied by build-shim.yml; once an admin drops it from required-checks in the UI (Settings → Branches → main), build-shim.yml can be deleted. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/release-darwin.yml | 122 +++++++++++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 .github/workflows/release-darwin.yml diff --git a/.github/workflows/release-darwin.yml b/.github/workflows/release-darwin.yml new file mode 100644 index 00000000..4de3b51b --- /dev/null +++ b/.github/workflows/release-darwin.yml @@ -0,0 +1,122 @@ +name: release-darwin + +# darwin/arm64 release on a macos-14 runner. Attaches binaries to the +# existing GitHub Release created by release-go.yml (which only builds +# linux). Runs after the linux release lands so the target Release +# already exists. +# +# Why a separate workflow: +# - release-go.yml runs on ubuntu-latest. CGO + kuzudb won't +# cross-compile cleanly to darwin from linux. +# - macos-14 runners are arm64 (M1+); cross-compile to darwin/arm64 +# happens via native CC = clang. +# - The two workflows publish to the same tag → same Release. + +on: + push: + tags: + - 'v*.*.*' + workflow_dispatch: + inputs: + tag: + description: 'Tag to release (e.g. v0.3.0). Release must already exist.' + required: true + +permissions: + contents: write + id-token: write # Sigstore keyless via GitHub OIDC + attestations: write + +jobs: + release-darwin: + name: release (darwin / arm64) + runs-on: macos-14 + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 + with: + go-version: '1.25.10' + cache: true + cache-dependency-path: go/go.sum + + - name: Build darwin/arm64 binary + working-directory: go + env: + CGO_ENABLED: '1' + GOOS: darwin + GOARCH: arm64 + run: | + TAG="${GITHUB_REF_NAME:-${{ github.event.inputs.tag }}}" + VERSION="${TAG#v}" + go build \ + -trimpath \ + -ldflags "-s -w \ + -X 'github.com/randomcodespace/codeiq/go/internal/buildinfo.Version=${VERSION}' \ + -X 'github.com/randomcodespace/codeiq/go/internal/buildinfo.Commit=$(git rev-parse --short HEAD)' \ + -X 'github.com/randomcodespace/codeiq/go/internal/buildinfo.Date=$(date -u +%Y-%m-%dT%H:%M:%SZ)' \ + -X 'github.com/randomcodespace/codeiq/go/internal/buildinfo.Dirty=false'" \ + -o codeiq ./cmd/codeiq + + - name: Package archive + working-directory: go + run: | + TAG="${GITHUB_REF_NAME:-${{ github.event.inputs.tag }}}" + VERSION="${TAG#v}" + ARCHIVE_DIR="codeiq_${VERSION}_darwin_arm64" + mkdir -p "${ARCHIVE_DIR}" + cp codeiq "${ARCHIVE_DIR}/" + cp ../LICENSE "${ARCHIVE_DIR}/" 2>/dev/null || true + cp ../README.md "${ARCHIVE_DIR}/" 2>/dev/null || true + cp ../CHANGELOG.md "${ARCHIVE_DIR}/" 2>/dev/null || true + tar czf "../${ARCHIVE_DIR}.tar.gz" "${ARCHIVE_DIR}" + + - name: Install Syft (SBOM) + uses: anchore/sbom-action/download-syft@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0 + - name: Generate SBOM + run: | + TAG="${GITHUB_REF_NAME:-${{ github.event.inputs.tag }}}" + VERSION="${TAG#v}" + ARCHIVE="codeiq_${VERSION}_darwin_arm64.tar.gz" + syft "$ARCHIVE" --output spdx-json="${ARCHIVE}.sbom.spdx.json" + + - name: Install Cosign (signing) + uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2 + - name: Sign archive (Sigstore keyless, bundle format) + run: | + TAG="${GITHUB_REF_NAME:-${{ github.event.inputs.tag }}}" + VERSION="${TAG#v}" + ARCHIVE="codeiq_${VERSION}_darwin_arm64.tar.gz" + cosign sign-blob \ + --yes \ + --bundle "${ARCHIVE}.cosign.bundle" \ + "$ARCHIVE" + + - name: Upload to GitHub Release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG="${GITHUB_REF_NAME:-${{ github.event.inputs.tag }}}" + VERSION="${TAG#v}" + # Retry up to 3 times to handle race with release-go.yml + # creating the Release. + for i in 1 2 3; do + if gh release view "$TAG" >/dev/null 2>&1; then + gh release upload "$TAG" \ + "codeiq_${VERSION}_darwin_arm64.tar.gz" \ + "codeiq_${VERSION}_darwin_arm64.tar.gz.sbom.spdx.json" \ + "codeiq_${VERSION}_darwin_arm64.tar.gz.cosign.bundle" \ + --clobber + exit 0 + fi + echo "Release $TAG not yet visible, waiting 30s ($i/3)..." + sleep 30 + done + echo "::error::Release $TAG never appeared; release-go.yml may have failed" + exit 1 + + - name: Attest darwin archive (build provenance) + uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0 + with: + subject-path: 'codeiq_*_darwin_arm64.tar.gz' From ad888c1284f0d479d656b28854535036f2d75781 Mon Sep 17 00:00:00 2001 From: Amit Kumar Date: Wed, 13 May 2026 06:45:46 +0000 Subject: [PATCH 2/3] chore(ci): drop build-shim.yml after dropping `build` required-check Branch protection on main no longer requires the `build` context: $ gh api -X DELETE .../branches/main/protection/required_status_checks/contexts \ -f 'contexts[]=build' ["OSV-Scanner (SCA)", "Trivy (filesystem + container scan)", "Semgrep (SAST)", "Gitleaks (secret scan)", "jscpd (duplication < 3% on touched code)", "SBOM (SPDX + CycloneDX)"] The shim existed only to satisfy that legacy required check after the old ci-java.yml's `build` job got deleted in the Phase 6 cutover. The real Go gates (go-ci.yml + perf-gate.yml) cover what `build` used to. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/build-shim.yml | 39 -------------------------------- 1 file changed, 39 deletions(-) delete mode 100644 .github/workflows/build-shim.yml diff --git a/.github/workflows/build-shim.yml b/.github/workflows/build-shim.yml deleted file mode 100644 index 7195c4b4..00000000 --- a/.github/workflows/build-shim.yml +++ /dev/null @@ -1,39 +0,0 @@ -name: build-shim - -# Shim for the `build` branch-protection required check. -# -# History: the old ci-java.yml defined a job named `build` that -# branch protection on main was configured to require. Phase 6 -# cutover deletes ci-java.yml, but the required-check name `build` -# remains in branch protection — so every PR sits forever on -# "build Expected — Waiting for status to be reported". -# -# This shim always runs and always succeeds, providing the `build` -# status that branch protection expects. It exists as a stopgap -# until a repo admin removes `build` from the required-checks list -# in the GitHub UI (Settings → Branches → main → required checks). -# At that point, this file can be deleted. -# -# go-ci.yml is the real build gate (vet/test/staticcheck/gosec/ -# govulncheck) and perf-gate.yml is the perf-regression gate; -# both are the appropriate replacements for the old Java `build`. - -on: - push: - branches: [main] - pull_request: - branches: [main] - -permissions: - contents: read - -jobs: - build: - name: build - runs-on: ubuntu-latest - steps: - - name: Branch-protection compatibility shim - run: | - echo "build-shim: this job exists only to satisfy the legacy 'build' required-check" - echo "remove this workflow once branch protection drops 'build' from required-checks" - echo "the real Go gates are go-ci.yml + perf-gate.yml" From 060ee9dd5e2b4a5ee6619d8e396a720f2e1ae81c Mon Sep 17 00:00:00 2001 From: Amit Kumar Date: Wed, 13 May 2026 06:50:24 +0000 Subject: [PATCH 3/3] fix(release-darwin): pass tag via env var (semgrep run-shell-injection) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Semgrep flagged 5× `yaml.github-actions.security.run-shell-injection` in release-darwin.yml. Pattern was: run: | TAG="${GITHUB_REF_NAME:-${{ github.event.inputs.tag }}}" The `${{ github.event.inputs.tag }}` expression gets interpolated INTO the shell script at workflow runtime — a malicious workflow_dispatch caller could inject shell code via the `tag` input. Fix: hoist the tag resolution into a workflow-level `env: TAG: …`, then reference $TAG inside each shell block. Same effective value (workflow_dispatch input wins; falls back to ref_name on tag push), but the input never reaches a shell un-escaped. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/release-darwin.yml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/release-darwin.yml b/.github/workflows/release-darwin.yml index 4de3b51b..8918b75f 100644 --- a/.github/workflows/release-darwin.yml +++ b/.github/workflows/release-darwin.yml @@ -27,6 +27,12 @@ permissions: id-token: write # Sigstore keyless via GitHub OIDC attestations: write +# Pass the input/ref to the shell via env vars (not inline `${{ }}` +# interpolation) — Semgrep `yaml.github-actions.security.run-shell-injection` +# rule. inputs.tag for workflow_dispatch; GITHUB_REF_NAME for tag pushes. +env: + TAG: ${{ github.event.inputs.tag || github.ref_name }} + jobs: release-darwin: name: release (darwin / arm64) @@ -48,7 +54,6 @@ jobs: GOOS: darwin GOARCH: arm64 run: | - TAG="${GITHUB_REF_NAME:-${{ github.event.inputs.tag }}}" VERSION="${TAG#v}" go build \ -trimpath \ @@ -62,7 +67,6 @@ jobs: - name: Package archive working-directory: go run: | - TAG="${GITHUB_REF_NAME:-${{ github.event.inputs.tag }}}" VERSION="${TAG#v}" ARCHIVE_DIR="codeiq_${VERSION}_darwin_arm64" mkdir -p "${ARCHIVE_DIR}" @@ -76,7 +80,6 @@ jobs: uses: anchore/sbom-action/download-syft@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0 - name: Generate SBOM run: | - TAG="${GITHUB_REF_NAME:-${{ github.event.inputs.tag }}}" VERSION="${TAG#v}" ARCHIVE="codeiq_${VERSION}_darwin_arm64.tar.gz" syft "$ARCHIVE" --output spdx-json="${ARCHIVE}.sbom.spdx.json" @@ -85,7 +88,6 @@ jobs: uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2 - name: Sign archive (Sigstore keyless, bundle format) run: | - TAG="${GITHUB_REF_NAME:-${{ github.event.inputs.tag }}}" VERSION="${TAG#v}" ARCHIVE="codeiq_${VERSION}_darwin_arm64.tar.gz" cosign sign-blob \ @@ -97,7 +99,6 @@ jobs: env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - TAG="${GITHUB_REF_NAME:-${{ github.event.inputs.tag }}}" VERSION="${TAG#v}" # Retry up to 3 times to handle race with release-go.yml # creating the Release.