Files
DeepSeek-TUI/.github/workflows/release-artifacts.yml
CodeWhale Bot f9519454ca fix(ci): close the CodeQL cache-poisoning Highs in the release workflows
All 19 open actions/cache-poisoning/poisonable-step alerts (#88-#106) sit in
release.yml, release-candidate.yml and release-artifacts.yml: jobs interpolated
the caller SHA into ref/caches and let setup-node's implicit npm cache key on
run identity. Now caller source_sha is pinned against github.sha by a pin job
(refusing any retarget), checkout and build identity use GITHUB_SHA through
env indirection instead of template interpolation, rust-cache keys use stable
prefix-key values, and setup-node's implicit package cache is off where it
cannot be keyed safely.

Also carries the CodeQL #107 fix from the same lane: catalog_models_dev.py
prints remote limit values as numbers/null/redacted only and drops query and
fragment material from source URLs before logging.

Harvested from #5401 (workflow+script files; CHANGELOG edit intentionally
excluded here and lands with the release branch; GHSA advisory text split to
a later PR as advised).

No-Issue: CodeQL alert remediation (alerts #88-#107); no single user-facing issue tracks these

Signed-off-by: Hunter Bown <hunter@hmbown.com>
2026-08-16 22:52:15 -07:00

414 lines
17 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
name: Release artifacts
on:
workflow_call:
inputs:
source_sha:
description: Exact 40-character source commit to build
required: true
type: string
version:
description: Workspace version without a v prefix
required: true
type: string
retention_days:
description: Retention for Actions-only intermediate and assembled artifacts
required: false
default: 7
type: number
permissions:
contents: read
env:
CARGO_TERM_COLOR: always
CARGO_INCREMENTAL: 0
RUSTFLAGS: -Dwarnings
# Build identity is the trusted workflow SHA. Callers pass source_sha only
# so `pin` can refuse a mismatch; it must not retarget checkout or caches.
DEEPSEEK_BUILD_SHA: ${{ github.sha }}
jobs:
pin:
name: Pin caller SHA to this run
runs-on: ubuntu-latest
steps:
- name: Require source_sha equals github.sha
env:
SOURCE_SHA: ${{ inputs.source_sha }}
run: |
set -euo pipefail
if [[ "${#SOURCE_SHA}" -ne 40 || "${SOURCE_SHA}" =~ [^0-9a-fA-F] ]]; then
echo "::error::source_sha must be a full 40-character commit SHA." >&2
exit 1
fi
expected="$(printf '%s' "${SOURCE_SHA}" | tr '[:upper:]' '[:lower:]')"
actual="$(printf '%s' "${GITHUB_SHA}" | tr '[:upper:]' '[:lower:]')"
if [[ "${actual}" != "${expected}" ]]; then
echo "::error::Reusable workflow SHA ${actual} does not match source_sha ${SOURCE_SHA}." >&2
exit 1
fi
build:
name: Build ${{ matrix.platform }}
# FreeBSD is a source-build target validated via `cargo check --target x86_64-unknown-freebsd -p codewhale-cli --locked`
# (see packaging/freebsd/README.md and docs/INSTALL.md#freebsd). The 7×1 prebuilt matrix stays 7 targets;
# FreeBSD has no prebuilt asset, no npm binary, and no matrix bloat — it builds from source.
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
target: x86_64-unknown-linux-musl
platform: linux-x64
cli_binary: codewhale
shim_binary: codew
cli_artifact: codewhale-linux-x64
shim_artifact: codew-linux-x64
tui_artifact: codewhale-tui-linux-x64
- os: ubuntu-24.04-arm
target: aarch64-unknown-linux-musl
platform: linux-arm64
cli_binary: codewhale
shim_binary: codew
cli_artifact: codewhale-linux-arm64
shim_artifact: codew-linux-arm64
tui_artifact: codewhale-tui-linux-arm64
- os: ubuntu-latest
target: aarch64-linux-android
platform: android-arm64
cli_binary: codewhale
shim_binary: codew
cli_artifact: codewhale-android-arm64
shim_artifact: codew-android-arm64
tui_artifact: codewhale-tui-android-arm64
- os: macos-latest
target: x86_64-apple-darwin
platform: macos-x64
cli_binary: codewhale
shim_binary: codew
cli_artifact: codewhale-macos-x64
shim_artifact: codew-macos-x64
tui_artifact: codewhale-tui-macos-x64
- os: macos-latest
target: aarch64-apple-darwin
platform: macos-arm64
cli_binary: codewhale
shim_binary: codew
cli_artifact: codewhale-macos-arm64
shim_artifact: codew-macos-arm64
tui_artifact: codewhale-tui-macos-arm64
- os: windows-latest
target: x86_64-pc-windows-msvc
platform: windows-x64
cli_binary: codewhale.exe
shim_binary: codew.exe
cli_artifact: codewhale-windows-x64.exe
shim_artifact: codew-windows-x64.exe
tui_artifact: codewhale-tui-windows-x64.exe
- os: windows-11-arm
target: aarch64-pc-windows-msvc
platform: windows-arm64
cli_binary: codewhale.exe
shim_binary: codew.exe
cli_artifact: codewhale-windows-arm64.exe
shim_artifact: codew-windows-arm64.exe
tui_artifact: codewhale-tui-windows-arm64.exe
runs-on: ${{ matrix.os }}
needs: pin
steps:
# No ref: — GITHUB_SHA only. CodeQL treats workflow_call checkout-with-ref
# and any ref named *sha* as an untrusted checkout (cache-poisoning).
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- uses: dtolnay/rust-toolchain@6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772 # master 2026-07-18
with:
toolchain: stable
targets: ${{ matrix.target }}
- uses: mozilla-actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11
id: sccache
continue-on-error: true
- name: Enable sccache
if: steps.sccache.outcome == 'success'
shell: bash
run: |
{
echo "SCCACHE_GHA_ENABLED=true"
echo "RUSTC_WRAPPER=sccache"
echo "SCCACHE_IGNORE_SERVER_IO_ERROR=1"
} >> "${GITHUB_ENV}"
# Restore after the trusted lockfile is on disk. Key is OS + arch +
# explicit stable toolchain + rust-cache's Cargo.lock / rust-toolchain
# hash. Never interpolate github.event, github.ref, github.sha, or inputs.
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
with:
cache-bin: false
prefix-key: v1-${{ runner.os }}-${{ runner.arch }}-stable
- name: Build static Linux binaries (musl)
if: endsWith(matrix.target, '-unknown-linux-musl')
shell: bash
run: |
sudo apt-get update
sudo apt-get install -y binutils musl-tools
rustup target add --toolchain stable ${{ matrix.target }}
cargo build --profile dist --locked --target ${{ matrix.target }} -p codewhale-cli
- name: Configure Android NDK linker
if: matrix.target == 'aarch64-linux-android' && runner.os == 'Linux'
shell: bash
env:
ANDROID_NDK_VERSION: 27.2.12479018
run: |
set -euo pipefail
sudo apt-get update
sudo apt-get install -y libclang-dev
ndk="${ANDROID_NDK_ROOT:-${ANDROID_NDK_HOME:-}}"
linker=""
if [[ -n "${ndk}" ]]; then
linker="${ndk}/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android24-clang"
fi
if [[ -z "${linker}" || ! -x "${linker}" ]]; then
if ! command -v sdkmanager >/dev/null 2>&1; then
echo "sdkmanager is required to install Android NDK ${ANDROID_NDK_VERSION}" >&2
exit 1
fi
android_home="${ANDROID_HOME:-${ANDROID_SDK_ROOT:-}}"
if [[ -z "${android_home}" ]]; then
echo "ANDROID_HOME or ANDROID_SDK_ROOT is required to install Android NDK ${ANDROID_NDK_VERSION}" >&2
exit 1
fi
yes | sdkmanager --licenses >/dev/null || true
sdkmanager --install "ndk;${ANDROID_NDK_VERSION}"
ndk="${android_home}/ndk/${ANDROID_NDK_VERSION}"
linker="${ndk}/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android24-clang"
fi
ar="${ndk}/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar"
if [[ ! -x "${linker}" ]]; then
echo "Android linker not found: ${linker}" >&2
exit 1
fi
if [[ ! -x "${ar}" ]]; then
echo "Android archiver not found: ${ar}" >&2
exit 1
fi
{
echo "ANDROID_NDK_ROOT=${ndk}"
echo "ANDROID_NDK_HOME=${ndk}"
echo "CC_aarch64_linux_android=${linker}"
echo "AR_aarch64_linux_android=${ar}"
echo "CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER=${linker}"
echo "BINDGEN_EXTRA_CLANG_ARGS_aarch64_linux_android=--target=aarch64-linux-android24 --sysroot=${ndk}/toolchains/llvm/prebuilt/linux-x86_64/sysroot"
} >> "${GITHUB_ENV}"
- name: Build
if: ${{ !endsWith(matrix.target, '-unknown-linux-musl') }}
shell: bash
run: cargo build --profile dist --locked --target ${{ matrix.target }} -p codewhale-cli
- name: Materialize codew command alias
shell: bash
run: |
bin_dir="target/${{ matrix.target }}/dist"
cp "${bin_dir}/${{ matrix.cli_binary }}" "${bin_dir}/${{ matrix.shim_binary }}"
cmp "${bin_dir}/${{ matrix.cli_binary }}" "${bin_dir}/${{ matrix.shim_binary }}"
- name: Verify static Linux binaries and launch on matching native runners
if: >-
endsWith(matrix.target, '-unknown-linux-musl') &&
((startsWith(matrix.target, 'x86_64-') && runner.arch == 'X64') ||
(startsWith(matrix.target, 'aarch64-') && runner.arch == 'ARM64'))
shell: bash
run: |
set -euo pipefail
bin_dir="target/${{ matrix.target }}/dist"
for binary in "${{ matrix.cli_binary }}" "${{ matrix.shim_binary }}"; do
bin_path="${bin_dir}/${binary}"
if readelf -l "${bin_path}" | grep -Fq 'INTERP'; then
echo "Expected a static musl binary, but ${bin_path} has an ELF interpreter" >&2
exit 1
fi
"${bin_path}" --version
done
- name: Smoke binaries on matching native runners
if: >-
matrix.target != 'aarch64-linux-android' &&
((startsWith(matrix.target, 'x86_64-') && runner.arch == 'X64') ||
(startsWith(matrix.target, 'aarch64-') && runner.arch == 'ARM64'))
shell: bash
run: |
bin_dir="target/${{ matrix.target }}/dist"
"${bin_dir}/${{ matrix.cli_binary }}" --version
"${bin_dir}/${{ matrix.shim_binary }}" --version
- name: Stage binaries
shell: bash
run: |
stage_binary() {
local binary="$1"
local artifact="$2"
local bin_path="target/${{ matrix.target }}/dist/${binary}"
if [[ ! -f "${bin_path}" ]]; then
echo "Binary not at ${bin_path}; searching target/ for ${binary}:" >&2
find target -name "${binary}" -type f
exit 1
fi
cp "${bin_path}" "${artifact}"
}
stage_binary "${{ matrix.cli_binary }}" "${{ matrix.cli_artifact }}"
stage_binary "${{ matrix.shim_binary }}" "${{ matrix.shim_artifact }}"
# One-release compatibility bridge for v0.9.4's hard-coded release
# completeness/updater contract. This is the same runtime, not a
# separately compiled TUI binary.
stage_binary "${{ matrix.cli_binary }}" "${{ matrix.tui_artifact }}"
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: ${{ matrix.cli_artifact }}
path: ${{ matrix.cli_artifact }}
if-no-files-found: error
retention-days: ${{ inputs.retention_days }}
overwrite: true
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: ${{ matrix.shim_artifact }}
path: ${{ matrix.shim_artifact }}
if-no-files-found: error
retention-days: ${{ inputs.retention_days }}
overwrite: true
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: ${{ matrix.tui_artifact }}
path: ${{ matrix.tui_artifact }}
if-no-files-found: error
retention-days: ${{ inputs.retention_days }}
overwrite: true
bundle:
needs: build
if: ${{ !cancelled() && needs.build.result == 'success' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
path: artifacts
pattern: '*'
- name: Create and checksum platform archives
shell: bash
env:
SOURCE_SHA: ${{ github.sha }}
run: |
set -euo pipefail
source_date_epoch="$(git show -s --format=%ct "${SOURCE_SHA}")"
if [[ ! "${source_date_epoch}" =~ ^[0-9]+$ ]]; then
echo "Could not read a Unix timestamp for source commit ${SOURCE_SHA}" >&2
exit 1
fi
SOURCE_DATE_EPOCH="${source_date_epoch}" \
bash scripts/release/create-release-bundles.sh artifacts bundles
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: codewhale-bundles
path: |
bundles/*.tar.gz
bundles/*.zip
bundles/codewhale-bundles-sha256.txt
if-no-files-found: error
retention-days: ${{ inputs.retention_days }}
overwrite: true
windows-installer:
needs: build
if: ${{ !cancelled() && needs.build.result == 'success' }}
runs-on: windows-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
path: artifacts
pattern: '*windows-x64.exe'
- name: Install NSIS
shell: pwsh
run: choco install nsis -y --no-progress
- name: Build NSIS installer
shell: pwsh
run: |
$ErrorActionPreference = "Stop"
Copy-Item "artifacts\codewhale-windows-x64.exe\codewhale-windows-x64.exe" "scripts\installer\codewhale.exe"
Copy-Item "artifacts\codew-windows-x64.exe\codew-windows-x64.exe" "scripts\installer\codew.exe"
$makensis = "${env:ProgramFiles(x86)}\NSIS\makensis.exe"
if (!(Test-Path $makensis)) {
$makensis = "${env:ProgramFiles}\NSIS\makensis.exe"
}
if (!(Test-Path $makensis)) {
throw "makensis.exe not found after NSIS install"
}
Push-Location scripts\installer
& $makensis "/DVERSION=${{ inputs.version }}" "codewhale.nsi"
Pop-Location
if (!(Test-Path "scripts\installer\CodeWhaleSetup.exe")) {
throw "CodeWhaleSetup.exe was not produced"
}
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: CodeWhaleSetup.exe
path: scripts/installer/CodeWhaleSetup.exe
if-no-files-found: error
retention-days: ${{ inputs.retention_days }}
overwrite: true
assemble:
needs: [bundle, windows-installer]
if: ${{ !cancelled() && needs.bundle.result == 'success' && needs.windows-installer.result == 'success' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 20
package-manager-cache: false
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
path: intermediate-artifacts
pattern: '*'
- name: Assemble exact authoritative release inventory
run: node scripts/release/assemble-release-assets.js intermediate-artifacts release-assets
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: codewhale-release-assets
path: release-assets/*
if-no-files-found: error
retention-days: ${{ inputs.retention_days }}
compression-level: 0
overwrite: true
smoke:
needs: assemble
if: ${{ !cancelled() && needs.assemble.result == 'success' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 20
package-manager-cache: false
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: codewhale-release-assets
path: release-assets
- name: Verify 34-asset bridge inventory and checksum manifests (single binary)
run: node scripts/release/assemble-release-assets.js --verify release-assets
- name: Test release inventory contracts
run: |
node --test scripts/release/assemble-release-assets.test.js
node --test npm/codewhale/test/artifacts.test.js npm/codewhale/test/release-assets.test.js
- name: Smoke packed npm wrapper against candidate assets
env:
CODEWHALE_SMOKE_ASSETS_DIR: ${{ github.workspace }}/release-assets
run: node scripts/release/npm-wrapper-smoke.js
- name: Record non-public candidate identity
shell: bash
run: |
{
echo "### Release artifact candidate"
echo ""
echo "- Source: \`${{ github.sha }}\`"
echo "- Version metadata: \`${{ inputs.version }}\`"
echo "- Inventory: 7 targets / 34 files (single binary; 7 legacy alias assets)"
echo "- Publication: none (Actions artifact \`codewhale-release-assets\` only)"
} >> "${GITHUB_STEP_SUMMARY}"