diff --git a/.changeset/patch-secure-install-assets.md b/.changeset/patch-secure-install-assets.md new file mode 100644 index 0000000000..910b905d12 --- /dev/null +++ b/.changeset/patch-secure-install-assets.md @@ -0,0 +1,5 @@ +--- +"gh-aw": patch +--- + +Harden the Copilot CLI installer to download release binaries directly and verify SHA256 checksums before running, and avoid shell-interpreted `exec.exec` calls in `upload_assets.cjs` by using the array argument form. diff --git a/actions/setup/js/upload_assets.cjs b/actions/setup/js/upload_assets.cjs index f116c773da..e3f4936306 100644 --- a/actions/setup/js/upload_assets.cjs +++ b/actions/setup/js/upload_assets.cjs @@ -95,8 +95,8 @@ async function main() { try { // Check if orphaned branch already exists, if not create it try { - await exec.exec(`git rev-parse --verify origin/${normalizedBranchName}`); - await exec.exec(`git checkout -B ${normalizedBranchName} origin/${normalizedBranchName}`); + await exec.exec("git", ["rev-parse", "--verify", `origin/${normalizedBranchName}`]); + await exec.exec("git", ["checkout", "-B", normalizedBranchName, `origin/${normalizedBranchName}`]); core.info(`Checked out existing branch from origin: ${normalizedBranchName}`); } catch (originError) { // Validate that branch starts with "assets/" prefix before creating orphaned branch @@ -111,9 +111,9 @@ async function main() { // Branch doesn't exist on origin and has valid prefix, create orphaned branch core.info(`Creating new orphaned branch: ${normalizedBranchName}`); - await exec.exec(`git checkout --orphan ${normalizedBranchName}`); - await exec.exec(`git rm -rf .`); - await exec.exec(`git clean -fdx`); + await exec.exec("git", ["checkout", "--orphan", normalizedBranchName]); + await exec.exec("git", ["rm", "-rf", "."]); + await exec.exec("git", ["clean", "-fdx"]); } // Process each asset @@ -171,7 +171,7 @@ async function main() { if (isStaged) { core.summary.addRaw("## 🎭 Staged Mode: Asset Publication Preview"); } else { - await exec.exec(`git push origin ${normalizedBranchName}`); + await exec.exec("git", ["push", "origin", normalizedBranchName]); core.summary.addRaw("## Assets").addRaw(`Successfully uploaded **${uploadCount}** assets to branch \`${normalizedBranchName}\``).addRaw(""); core.info(`Successfully uploaded ${uploadCount} assets to branch ${normalizedBranchName}`); } diff --git a/actions/setup/sh/install_copilot_cli.sh b/actions/setup/sh/install_copilot_cli.sh index e21de647f5..3a508e19ea 100755 --- a/actions/setup/sh/install_copilot_cli.sh +++ b/actions/setup/sh/install_copilot_cli.sh @@ -1,26 +1,25 @@ #!/usr/bin/env bash -# Install GitHub Copilot CLI with retry logic +# Install GitHub Copilot CLI with SHA256 checksum verification # Usage: install_copilot_cli.sh [VERSION] # -# This script downloads and installs the GitHub Copilot CLI from the official -# installer script with retry logic to handle transient network failures. +# This script downloads and installs the GitHub Copilot CLI directly from GitHub +# releases with SHA256 checksum verification, following the secure pattern from +# install_awf_binary.sh to avoid executing unverified downloaded scripts. # # Arguments: -# VERSION - Optional Copilot CLI version to install (default: latest from installer) +# VERSION - Optional Copilot CLI version to install (default: latest release) # -# Features: -# - Retries download up to 3 times with exponential backoff -# - Verifies installation after completion -# - Downloads to temp file for security -# - Cleans up temp files after installation +# Security features: +# - Downloads binary directly from GitHub releases (no installer script execution) +# - Verifies SHA256 checksum against official SHA256SUMS.txt +# - Fails fast if checksum verification fails set -euo pipefail # Configuration VERSION="${1:-}" -INSTALLER_URL="https://raw.githubusercontent.com/github/copilot-cli/main/install.sh" -INSTALLER_TEMP="/tmp/copilot-install.sh" -MAX_ATTEMPTS=3 +COPILOT_REPO="github/copilot-cli" +INSTALL_DIR="/usr/local/bin" COPILOT_DIR="/home/runner/.copilot" # Fix directory ownership before installation @@ -32,52 +31,93 @@ echo "Ensuring correct ownership of $COPILOT_DIR..." mkdir -p "$COPILOT_DIR" sudo chown -R runner:runner "$COPILOT_DIR" -# Function to download installer with retry logic -download_installer_with_retry() { - local attempt=1 - local wait_time=5 - - while [ $attempt -le $MAX_ATTEMPTS ]; do - echo "Attempt $attempt of $MAX_ATTEMPTS: Downloading Copilot CLI installer..." - - if curl -fsSL "$INSTALLER_URL" -o "$INSTALLER_TEMP" 2>&1; then - echo "Successfully downloaded installer" - return 0 - fi - - if [ $attempt -lt $MAX_ATTEMPTS ]; then - echo "Failed to download installer. Retrying in ${wait_time}s..." - sleep $wait_time - wait_time=$((wait_time * 2)) # Exponential backoff - else - echo "ERROR: Failed to download installer after $MAX_ATTEMPTS attempts" - return 1 - fi - attempt=$((attempt + 1)) - done +# Detect OS and architecture +OS="$(uname -s)" +ARCH="$(uname -m)" + +# Map architecture to Copilot CLI naming +case "$ARCH" in + x86_64|amd64) ARCH_NAME="x64" ;; + aarch64|arm64) ARCH_NAME="arm64" ;; + *) echo "ERROR: Unsupported architecture: ${ARCH}"; exit 1 ;; +esac + +# Map OS to Copilot CLI naming +case "$OS" in + Linux) PLATFORM="linux" ;; + Darwin) PLATFORM="darwin" ;; + *) echo "ERROR: Unsupported operating system: ${OS}"; exit 1 ;; +esac + +TARBALL_NAME="copilot-${PLATFORM}-${ARCH_NAME}.tar.gz" + +# Build download URLs +if [ -z "$VERSION" ]; then + BASE_URL="https://github.com/${COPILOT_REPO}/releases/latest/download" +else + # Prefix version with 'v' if not already present + case "$VERSION" in + v*) ;; + *) VERSION="v$VERSION" ;; + esac + BASE_URL="https://github.com/${COPILOT_REPO}/releases/download/${VERSION}" +fi + +TARBALL_URL="${BASE_URL}/${TARBALL_NAME}" +CHECKSUMS_URL="${BASE_URL}/SHA256SUMS.txt" + +echo "Installing GitHub Copilot CLI${VERSION:+ version $VERSION} (os: ${OS}, arch: ${ARCH})..." + +# Platform-portable SHA256 function +sha256_hash() { + local file="$1" + if command -v sha256sum &>/dev/null; then + sha256sum "$file" | awk '{print $1}' + elif command -v shasum &>/dev/null; then + shasum -a 256 "$file" | awk '{print $1}' + else + echo "ERROR: No sha256sum or shasum found" >&2 + exit 1 + fi } -# Main installation flow -echo "Installing GitHub Copilot CLI${VERSION:+ version $VERSION}..." +# Create temp directory with cleanup on exit +TEMP_DIR=$(mktemp -d) +trap 'rm -rf "$TEMP_DIR"' EXIT + +# Download checksums +echo "Downloading checksums from ${CHECKSUMS_URL}..." +curl -fsSL -o "${TEMP_DIR}/SHA256SUMS.txt" "${CHECKSUMS_URL}" + +# Download binary tarball +echo "Downloading binary from ${TARBALL_URL}..." +curl -fsSL -o "${TEMP_DIR}/${TARBALL_NAME}" "${TARBALL_URL}" + +# Verify checksum +echo "Verifying SHA256 checksum for ${TARBALL_NAME}..." +EXPECTED_CHECKSUM=$(awk -v fname="${TARBALL_NAME}" '$2 == fname {print $1; exit}' "${TEMP_DIR}/SHA256SUMS.txt" | tr 'A-F' 'a-f') -# Download installer with retry logic -if ! download_installer_with_retry; then - echo "ERROR: Could not download Copilot CLI installer" +if [ -z "$EXPECTED_CHECKSUM" ]; then + echo "ERROR: Could not find checksum for ${TARBALL_NAME} in SHA256SUMS.txt" exit 1 fi -# Execute the installer with the specified version -# Pass VERSION directly to sudo to ensure it's available to the installer script -if [ -n "$VERSION" ]; then - echo "Installing Copilot CLI version $VERSION..." - sudo VERSION="$VERSION" bash "$INSTALLER_TEMP" -else - echo "Installing latest Copilot CLI version..." - sudo bash "$INSTALLER_TEMP" +ACTUAL_CHECKSUM=$(sha256_hash "${TEMP_DIR}/${TARBALL_NAME}" | tr 'A-F' 'a-f') + +if [ "$EXPECTED_CHECKSUM" != "$ACTUAL_CHECKSUM" ]; then + echo "ERROR: Checksum verification failed!" + echo " Expected: $EXPECTED_CHECKSUM" + echo " Got: $ACTUAL_CHECKSUM" + echo " The downloaded file may be corrupted or tampered with" + exit 1 fi -# Cleanup temp file -rm -f "$INSTALLER_TEMP" +echo "✓ Checksum verification passed for ${TARBALL_NAME}" + +# Extract and install binary +echo "Installing binary to ${INSTALL_DIR}..." +sudo tar -xz -C "${INSTALL_DIR}" -f "${TEMP_DIR}/${TARBALL_NAME}" +sudo chmod +x "${INSTALL_DIR}/copilot" # Verify installation echo "Verifying Copilot CLI installation..."