-
Notifications
You must be signed in to change notification settings - Fork 260
fix: supply chain and shell injection security findings #17183
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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}`); | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -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}" | ||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 This new approach of downloading checksums separately and verifying before execution is a solid supply chain security improvement over the old pattern of running a downloaded installer script directly with |
||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| # Download binary tarball | ||||||||||||||||||||||||||||||||||||||
| echo "Downloading binary from ${TARBALL_URL}..." | ||||||||||||||||||||||||||||||||||||||
| curl -fsSL -o "${TEMP_DIR}/${TARBALL_NAME}" "${TARBALL_URL}" | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+90
to
+94
|
||||||||||||||||||||||||||||||||||||||
| 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}" | |
| curl -fsSL -o "${TEMP_DIR}/SHA256SUMS.txt" "${CHECKSUMS_URL}" || { echo "ERROR: Failed to download checksums from ${CHECKSUMS_URL}"; exit 1; } | |
| # Download binary tarball | |
| echo "Downloading binary from ${TARBALL_URL}..." | |
| curl -fsSL -o "${TEMP_DIR}/${TARBALL_NAME}" "${TARBALL_URL}" || { echo "ERROR: Failed to download Copilot CLI tarball from ${TARBALL_URL}"; exit 1; } |
Copilot
AI
Feb 20, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The AWK pattern assumes the checksum file format has the filename in the second field (space-separated), but this should be verified against the actual format of copilot-cli's SHA256SUMS.txt file. Common formats include:
- BSD format:
SHA256 (filename) = checksum - GNU format:
checksum filename(two spaces) - Simple format:
checksum filename(single space)
The current pattern '$2 == fname' will only work if the format is exactly checksum filename with a single space. If the format uses two spaces (GNU style) or has the filename in parentheses (BSD style), this will fail to extract the checksum.
| EXPECTED_CHECKSUM=$(awk -v fname="${TARBALL_NAME}" '$2 == fname {print $1; exit}' "${TEMP_DIR}/SHA256SUMS.txt" | tr 'A-F' 'a-f') | |
| EXPECTED_CHECKSUM=$(awk -v fname="${TARBALL_NAME}" ' | |
| # BSD format: SHA256 (filename) = checksum | |
| $1 == "SHA256" { | |
| fname_field = $2 | |
| gsub(/^\(/, "", fname_field) | |
| gsub(/\)$/, "", fname_field) | |
| if (fname_field == fname) { | |
| print $4 | |
| exit | |
| } | |
| } | |
| # GNU/simple format: checksum[ ]filename | |
| $NF == fname { | |
| print $1 | |
| exit | |
| } | |
| ' "${TEMP_DIR}/SHA256SUMS.txt" | tr 'A-F' 'a-f') |
Copilot
AI
Feb 20, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The script uses sudo tar to extract directly to /usr/local/bin, but this will extract all contents of the tarball to that directory. If the tarball contains multiple files or has a directory structure (e.g., copilot-linux-x64/copilot), this could fail or extract files to unexpected locations. Consider extracting to the temp directory first, then moving only the copilot binary to the install directory, similar to how install_awf_binary.sh handles this with chmod +x and sudo mv separately.
| sudo tar -xz -C "${INSTALL_DIR}" -f "${TEMP_DIR}/${TARBALL_NAME}" | |
| tar -xz -C "${TEMP_DIR}" -f "${TEMP_DIR}/${TARBALL_NAME}" | |
| # Locate the copilot binary within the extracted contents | |
| COPILOT_SRC="" | |
| if [ -f "${TEMP_DIR}/copilot" ]; then | |
| COPILOT_SRC="${TEMP_DIR}/copilot" | |
| else | |
| COPILOT_SRC="$(find "${TEMP_DIR}" -maxdepth 3 -type f -name 'copilot' | head -n 1 || true)" | |
| fi | |
| if [ -z "${COPILOT_SRC}" ] || [ ! -f "${COPILOT_SRC}" ]; then | |
| echo "ERROR: Could not find 'copilot' binary in extracted tarball" | |
| exit 1 | |
| fi | |
| sudo mv "${COPILOT_SRC}" "${INSTALL_DIR}/copilot" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
✅ Great fix: using the array form of
exec.exec()prevents shell injection by ensuringnormalizedBranchNameis passed as a literal argument rather than being interpolated into a shell command string.