AikidoSec-safe-chain/.github/workflows/build-and-release.yml
2026-05-21 17:05:01 -04:00

357 lines
15 KiB
YAML

name: Create Release
on:
push:
tags:
- "*"
release:
types: [published]
permissions:
id-token: write
contents: write
jobs:
set-version:
name: Set version number
if: github.event_name == 'push'
runs-on: open-source-releaser
outputs:
version: ${{ steps.get_version.outputs.tag }}
steps:
- name: Set version number
id: get_version
run: |
version="${{ github.ref_name }}"
echo "tag=$version" >> $GITHUB_OUTPUT
create-binaries:
if: github.event_name == 'push'
needs: set-version
uses: ./.github/workflows/create-artifact.yml
with:
version: ${{ needs.set-version.outputs.version }}
publish-binaries:
name: Publish to GitHub release
if: github.event_name == 'push'
needs: [set-version, create-binaries]
runs-on: open-source-releaser
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Download all binary artifacts
uses: actions/download-artifact@v4
with:
path: binaries/
pattern: safe-chain-*
merge-multiple: false
- name: Rename binaries to include platform and architecture
run: |
mkdir release-artifacts
mv binaries/safe-chain-macos-x64/safe-chain release-artifacts/safe-chain-macos-x64
mv binaries/safe-chain-macos-arm64/safe-chain release-artifacts/safe-chain-macos-arm64
mv binaries/safe-chain-linux-x64/safe-chain release-artifacts/safe-chain-linux-x64
mv binaries/safe-chain-linux-arm64/safe-chain release-artifacts/safe-chain-linux-arm64
mv binaries/safe-chain-linuxstatic-x64/safe-chain release-artifacts/safe-chain-linuxstatic-x64
mv binaries/safe-chain-linuxstatic-arm64/safe-chain release-artifacts/safe-chain-linuxstatic-arm64
mv binaries/safe-chain-win-x64/safe-chain.exe release-artifacts/safe-chain-win-x64.exe
mv binaries/safe-chain-win-arm64/safe-chain.exe release-artifacts/safe-chain-win-arm64.exe
- name: Move install scripts and hard-code version and checksums
env:
VERSION: ${{ needs.set-version.outputs.version }}
run: |
SHA_MACOS_X64=$(sha256sum release-artifacts/safe-chain-macos-x64 | awk '{print $1}')
SHA_MACOS_ARM64=$(sha256sum release-artifacts/safe-chain-macos-arm64 | awk '{print $1}')
SHA_LINUX_X64=$(sha256sum release-artifacts/safe-chain-linux-x64 | awk '{print $1}')
SHA_LINUX_ARM64=$(sha256sum release-artifacts/safe-chain-linux-arm64 | awk '{print $1}')
SHA_LINUXSTATIC_X64=$(sha256sum release-artifacts/safe-chain-linuxstatic-x64 | awk '{print $1}')
SHA_LINUXSTATIC_ARM64=$(sha256sum release-artifacts/safe-chain-linuxstatic-arm64 | awk '{print $1}')
SHA_WIN_X64=$(sha256sum release-artifacts/safe-chain-win-x64.exe | awk '{print $1}')
SHA_WIN_ARM64=$(sha256sum release-artifacts/safe-chain-win-arm64.exe | awk '{print $1}')
sed \
-e "s/\$(fetch_latest_version)/${VERSION}/" \
-e "s|^SHA256_MACOS_X64=\"\"|SHA256_MACOS_X64=\"${SHA_MACOS_X64}\"|" \
-e "s|^SHA256_MACOS_ARM64=\"\"|SHA256_MACOS_ARM64=\"${SHA_MACOS_ARM64}\"|" \
-e "s|^SHA256_LINUX_X64=\"\"|SHA256_LINUX_X64=\"${SHA_LINUX_X64}\"|" \
-e "s|^SHA256_LINUX_ARM64=\"\"|SHA256_LINUX_ARM64=\"${SHA_LINUX_ARM64}\"|" \
-e "s|^SHA256_LINUXSTATIC_X64=\"\"|SHA256_LINUXSTATIC_X64=\"${SHA_LINUXSTATIC_X64}\"|" \
-e "s|^SHA256_LINUXSTATIC_ARM64=\"\"|SHA256_LINUXSTATIC_ARM64=\"${SHA_LINUXSTATIC_ARM64}\"|" \
-e "s|^SHA256_WIN_X64=\"\"|SHA256_WIN_X64=\"${SHA_WIN_X64}\"|" \
-e "s|^SHA256_WIN_ARM64=\"\"|SHA256_WIN_ARM64=\"${SHA_WIN_ARM64}\"|" \
install-scripts/install-safe-chain.sh > release-artifacts/install-safe-chain.sh
sed \
-e "s/\$Version = Get-LatestVersion/\$Version = \"${VERSION}\"/" \
-e "s|^\$SHA256_MACOS_X64 = \"\"|\$SHA256_MACOS_X64 = \"${SHA_MACOS_X64}\"|" \
-e "s|^\$SHA256_MACOS_ARM64 = \"\"|\$SHA256_MACOS_ARM64 = \"${SHA_MACOS_ARM64}\"|" \
-e "s|^\$SHA256_LINUX_X64 = \"\"|\$SHA256_LINUX_X64 = \"${SHA_LINUX_X64}\"|" \
-e "s|^\$SHA256_LINUX_ARM64 = \"\"|\$SHA256_LINUX_ARM64 = \"${SHA_LINUX_ARM64}\"|" \
-e "s|^\$SHA256_LINUXSTATIC_X64 = \"\"|\$SHA256_LINUXSTATIC_X64 = \"${SHA_LINUXSTATIC_X64}\"|" \
-e "s|^\$SHA256_LINUXSTATIC_ARM64 = \"\"|\$SHA256_LINUXSTATIC_ARM64 = \"${SHA_LINUXSTATIC_ARM64}\"|" \
-e "s|^\$SHA256_WIN_X64 = \"\"|\$SHA256_WIN_X64 = \"${SHA_WIN_X64}\"|" \
-e "s|^\$SHA256_WIN_ARM64 = \"\"|\$SHA256_WIN_ARM64 = \"${SHA_WIN_ARM64}\"|" \
install-scripts/install-safe-chain.ps1 > release-artifacts/install-safe-chain.ps1
cp install-scripts/uninstall-safe-chain.sh release-artifacts/uninstall-safe-chain.sh
cp install-scripts/uninstall-safe-chain.ps1 release-artifacts/uninstall-safe-chain.ps1
cp install-scripts/install-endpoint-mac.sh release-artifacts/install-endpoint-mac.sh
cp install-scripts/install-endpoint-windows.ps1 release-artifacts/install-endpoint-windows.ps1
cp install-scripts/uninstall-endpoint-mac.sh release-artifacts/uninstall-endpoint-mac.sh
cp install-scripts/uninstall-endpoint-windows.ps1 release-artifacts/uninstall-endpoint-windows.ps1
- name: Create draft release and upload assets
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VERSION: ${{ needs.set-version.outputs.version }}
run: |
if ! gh release view "$VERSION" &>/dev/null; then
gh release create "$VERSION" --draft --title "$VERSION" --generate-notes
fi
gh release upload "$VERSION" --clobber \
release-artifacts/safe-chain-macos-x64 \
release-artifacts/safe-chain-macos-arm64 \
release-artifacts/safe-chain-linux-x64 \
release-artifacts/safe-chain-linux-arm64 \
release-artifacts/safe-chain-linuxstatic-x64 \
release-artifacts/safe-chain-linuxstatic-arm64 \
release-artifacts/safe-chain-win-x64.exe \
release-artifacts/safe-chain-win-arm64.exe \
release-artifacts/install-safe-chain.sh \
release-artifacts/install-safe-chain.ps1 \
release-artifacts/uninstall-safe-chain.sh \
release-artifacts/uninstall-safe-chain.ps1 \
release-artifacts/install-endpoint-mac.sh \
release-artifacts/install-endpoint-windows.ps1 \
release-artifacts/uninstall-endpoint-mac.sh \
release-artifacts/uninstall-endpoint-windows.ps1
publish-homebrew:
name: Update Homebrew tap
if: github.event_name == 'release' && !github.event.release.prerelease
runs-on: ubuntu-latest
steps:
- name: Extract SHA256 checksums from the released install script
env:
VERSION: ${{ github.event.release.tag_name }}
run: |
set -euo pipefail
# The release pipeline already bakes per-platform SHA256s into
# install-safe-chain.sh. Parse them out instead of re-downloading
# ~200MB of binaries just to recompute the same hashes.
curl -fsSL --retry 3 \
"https://github.com/AikidoSec/safe-chain/releases/download/${VERSION}/install-safe-chain.sh" \
-o install-safe-chain.sh
extract_sha() {
grep -E "^${1}=\"[a-f0-9]+\"" install-safe-chain.sh \
| head -n1 \
| sed -E "s/^${1}=\"([a-f0-9]+)\"/\1/"
}
{
echo "SHA_MACOS_X64=$(extract_sha SHA256_MACOS_X64)"
echo "SHA_MACOS_ARM64=$(extract_sha SHA256_MACOS_ARM64)"
echo "SHA_LINUX_X64=$(extract_sha SHA256_LINUX_X64)"
echo "SHA_LINUX_ARM64=$(extract_sha SHA256_LINUX_ARM64)"
} >> "$GITHUB_ENV"
- name: Verify all checksums were extracted
run: |
set -euo pipefail
missing=()
for var in SHA_MACOS_X64 SHA_MACOS_ARM64 SHA_LINUX_X64 SHA_LINUX_ARM64; do
if [ -z "${!var:-}" ]; then
missing+=("$var")
fi
done
if [ ${#missing[@]} -gt 0 ]; then
echo "::error::Could not extract checksum(s) from install-safe-chain.sh: ${missing[*]}"
exit 1
fi
- name: Checkout Homebrew tap
uses: actions/checkout@v4
with:
repository: AikidoSec/homebrew-tap
path: homebrew-tap
token: ${{ secrets.HOMEBREW_TAP_TOKEN }}
persist-credentials: true
- name: Render formula from template
env:
VERSION: ${{ github.event.release.tag_name }}
run: |
set -euo pipefail
mkdir -p homebrew-tap/Formula
# Quoted heredoc preserves Ruby `#{version}` interpolation and the
# backticks inside the caveats string. Placeholders are substituted
# with sed below, after the heredoc is written.
cat > homebrew-tap/Formula/safe-chain.rb <<'RUBY'
# typed: strict
# frozen_string_literal: true
# This file is auto-generated by AikidoSec/safe-chain's release
# workflow. Do not edit by hand — changes will be overwritten on
# the next release.
#
# Aikido Safe Chain wraps your package managers (npm, yarn, pnpm,
# pip, poetry, uv, ...) and blocks installations of known-malicious
# packages using the Aikido Intel feed.
class SafeChain < Formula
desc "Block malicious packages from npm, yarn, pnpm, pip, poetry and uv"
homepage "https://github.com/AikidoSec/safe-chain"
version "__VERSION__"
license "AGPL-3.0-or-later"
livecheck do
url :stable
strategy :github_latest
end
on_macos do
on_arm do
url "https://github.com/AikidoSec/safe-chain/releases/download/#{version}/safe-chain-macos-arm64",
using: :nounzip
sha256 "__SHA_MACOS_ARM64__"
end
on_intel do
url "https://github.com/AikidoSec/safe-chain/releases/download/#{version}/safe-chain-macos-x64",
using: :nounzip
sha256 "__SHA_MACOS_X64__"
end
end
on_linux do
on_arm do
url "https://github.com/AikidoSec/safe-chain/releases/download/#{version}/safe-chain-linux-arm64",
using: :nounzip
sha256 "__SHA_LINUX_ARM64__"
end
on_intel do
url "https://github.com/AikidoSec/safe-chain/releases/download/#{version}/safe-chain-linux-x64",
using: :nounzip
sha256 "__SHA_LINUX_X64__"
end
end
def install
binary_name = if OS.mac?
Hardware::CPU.arm? ? "safe-chain-macos-arm64" : "safe-chain-macos-x64"
else
Hardware::CPU.arm? ? "safe-chain-linux-arm64" : "safe-chain-linux-x64"
end
chmod 0755, binary_name
bin.install binary_name => "safe-chain"
end
def caveats
<<~CAVEATS
Aikido Safe Chain works by wrapping your package managers (npm, yarn,
pnpm, pip, poetry, uv, ...) with shell aliases. After installing, run:
safe-chain setup
For CI/non-interactive environments, use:
safe-chain setup-ci
Then restart your shell (or `source ~/.zshrc` / `~/.bashrc`) so the
new aliases are picked up.
CAVEATS
end
test do
assert_match version.to_s, shell_output("#{bin}/safe-chain --version")
assert_match "safe-chain setup", shell_output("#{bin}/safe-chain help")
end
end
RUBY
sed -i \
-e "s|__VERSION__|${VERSION}|g" \
-e "s|__SHA_MACOS_ARM64__|${SHA_MACOS_ARM64}|g" \
-e "s|__SHA_MACOS_X64__|${SHA_MACOS_X64}|g" \
-e "s|__SHA_LINUX_ARM64__|${SHA_LINUX_ARM64}|g" \
-e "s|__SHA_LINUX_X64__|${SHA_LINUX_X64}|g" \
homebrew-tap/Formula/safe-chain.rb
# Fail fast if any placeholder wasn't substituted (e.g. a new
# __XXX__ added to the template without a matching sed rule).
if grep -q "__[A-Z_]*__" homebrew-tap/Formula/safe-chain.rb; then
echo "::error::Unsubstituted placeholder(s) remain in the formula:"
grep -n "__[A-Z_]*__" homebrew-tap/Formula/safe-chain.rb
exit 1
fi
- name: Commit and push formula update
working-directory: homebrew-tap
env:
VERSION: ${{ github.event.release.tag_name }}
run: |
set -euo pipefail
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add Formula/safe-chain.rb
if git diff --staged --quiet; then
echo "No formula changes to commit."
exit 0
fi
git commit -m "safe-chain ${VERSION}"
git push
publish-npm:
name: Publish to npm
if: github.event_name == 'release'
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version: "lts/*"
registry-url: "https://registry.npmjs.org/"
- name: Setup safe-chain
run: curl -fsSL https://github.com/AikidoSec/safe-chain/releases/latest/download/install-safe-chain.sh | sh -s -- --ci
- name: Set the version in safe-chain package
run: npm --no-git-tag-version version ${{ github.event.release.tag_name }} --workspace=packages/safe-chain
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm run test
- name: Copy documentation files to package
run: |
cp README.md packages/safe-chain/
cp LICENSE packages/safe-chain/
cp -r docs packages/safe-chain/
cp npm-shrinkwrap.json packages/safe-chain/
- name: Publish to npm
run: |
VERSION="${{ github.event.release.tag_name }}"
echo "Publishing version $VERSION to NPM"
if [[ "$VERSION" == *"-"* ]]; then
PRERELEASE_TAG=$(echo "$VERSION" | sed 's/.*-\([^-]*\)$/\1/')
npm publish --workspace=packages/safe-chain --access public --provenance --tag "$PRERELEASE_TAG"
else
npm publish --workspace=packages/safe-chain --access public --provenance
fi