security(actions): stop evaluating tool-version inputs as shell/pwsh source (#45)

* security(actions): stop evaluating tool-version inputs as shell/pwsh source

The install-wix, install-nfpm, install-trunk, and packaging-lint composite
actions interpolated their *-version (and deb-arch) inputs directly into
Bash/PowerShell source when constructing download URLs. A command substitution
or subexpression in those values executed on the runner before any checksum
verification (CWE-78).

install-trunk additionally placed the version into a URL path component, so
path-traversal segments could point both the archive and its fallback checksum
at an attacker-controlled repository (CWE-23).

Fixes:
- Pass every version/arch input through the step `env:` and read it with quoted
  variable expansions instead of GitHub `${{ }}` interpolation in script bodies.
- Validate a strict version grammar (and an allow-list for deb-arch) before the
  value is used, which also excludes slashes and other URL path-traversal
  characters so the download publisher stays fixed.

No change to default versions, pinned checksums, or install behavior.

* security(actions): anchor version validation against the whole value

Address review feedback:
- The bash version checks used `printf %s | grep -Eq '^...$'`, which matches
  line-by-line. A value like `2.49.0\ninvalid` passed and then flowed into the
  download URL. Switch to a bash `[[ =~ ^...$ ]]` test, which anchors the entire
  string and rejects any value containing a newline. Applied to install-nfpm,
  install-trunk, and packaging-lint.
- The install-wix PowerShell check used `$`, which in .NET also matches before a
  trailing newline. Use `\z` (absolute end of input) so a trailing-newline value
  cannot bypass validation.
This commit is contained in:
Dylan M. Taylor authored and GitHub committed 2026-10-22 11:40:53 -04:00
1 parent 3a54b3bbb8
commit 2446e3bd4f
4 files changed
+52 -12

No files matched your search

+18 -4
View File
@@ -16,11 +16,25 @@ runs:
steps:
- name: Download and verify nfpm
shell: bash
env:
NFPM_VERSION: ${{ inputs.nfpm-version }}
DEB_ARCH: ${{ inputs.deb-arch }}
run: |
deb_url="https://github.com/goreleaser/nfpm/releases/download/v${{ inputs.nfpm-version }}/nfpm_${{ inputs.nfpm-version }}_${{ inputs.deb-arch }}.deb"
version="$NFPM_VERSION"
arch="$DEB_ARCH"
if [[ ! "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+([.-][0-9A-Za-z.-]+)?$ ]]; then
echo "::error::invalid nfpm-version '$version'"
exit 1
fi
case "$arch" in
amd64 | arm64) ;;
*) echo "::error::invalid deb-arch '$arch'"; exit 1 ;;
esac
deb_url="https://github.com/goreleaser/nfpm/releases/download/v${version}/nfpm_${version}_${arch}.deb"
curl --proto '=https' --proto-redir =https -fsSL -o "$RUNNER_TEMP/nfpm.deb" "$deb_url"
case "${{ inputs.nfpm-version }}:${{ inputs.deb-arch }}" in
case "${version}:${arch}" in
"2.48.0:amd64")
expected="787af76e172dd5fc7cedf4d7c5a090a2f62be9001557a4db1103e270f90ddefd"
;;
@@ -34,8 +48,8 @@ runs:
expected="27419eb382695a7942be8ad52259f3ec1854fad001b3ae4baed34ce39a223b97"
;;
*)
curl --proto '=https' --proto-redir =https -fsSL -o "$RUNNER_TEMP/checksums.txt" "https://github.com/goreleaser/nfpm/releases/download/v${{ inputs.nfpm-version }}/checksums.txt"
expected=$(grep -E "nfpm_${{ inputs.nfpm-version }}_${{ inputs.deb-arch }}\.deb" "$RUNNER_TEMP/checksums.txt" | awk '{print $1}')
curl --proto '=https' --proto-redir =https -fsSL -o "$RUNNER_TEMP/checksums.txt" "https://github.com/goreleaser/nfpm/releases/download/v${version}/checksums.txt"
expected=$(grep -E "nfpm_${version}_${arch}\.deb" "$RUNNER_TEMP/checksums.txt" | awk '{print $1}')
;;
esac
+14 -2
View File
@@ -12,10 +12,22 @@ runs:
steps:
- name: Install trunk
shell: bash
env:
TRUNK_VERSION: ${{ inputs.trunk-version }}
run: |
tar_url="https://github.com/trunk-rs/trunk/releases/download/v${{ inputs.trunk-version }}/trunk-x86_64-unknown-linux-gnu.tar.gz"
version="$TRUNK_VERSION"
# Restrict the version to a single safe release-tag component. This keeps
# the value from carrying slashes or other URL path-traversal segments
# that could redirect downloads to an attacker-controlled repository. The
# bash regex anchors the whole string, so values containing a newline are
# rejected (unlike a line-oriented grep check).
if [[ ! "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+([.-][0-9A-Za-z.-]+)?$ ]]; then
echo "::error::invalid trunk-version '$version'"
exit 1
fi
tar_url="https://github.com/trunk-rs/trunk/releases/download/v${version}/trunk-x86_64-unknown-linux-gnu.tar.gz"
curl --proto '=https' --proto-redir =https -fsSL -o "$RUNNER_TEMP/trunk.tar.gz" "$tar_url"
case "${{ inputs.trunk-version }}" in
case "$version" in
"0.21.14")
expected="f2b4680cd239693a646a2795e4633c625328d7b2a044fbe749fa3a2fe9e7036b"
;;
+9 -2
View File
@@ -16,9 +16,16 @@ runs:
steps:
- name: Install WiX
shell: pwsh
env:
WIX_VERSION: ${{ inputs.wix-version }}
INSTALL_EXTENSIONS: ${{ inputs.install-extensions }}
run: |
$version = "${{ inputs.wix-version }}"
$version = $env:WIX_VERSION
if ($version -notmatch '^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?\z') {
Write-Output "::error::invalid wix-version '$version'"
exit 1
}
dotnet tool install --global wix --version $version
if ("${{ inputs.install-extensions }}" -eq "true") {
if ($env:INSTALL_EXTENSIONS -eq "true") {
wix extension add -g "WixToolset.UI.wixext/$version" "WixToolset.Util.wixext/$version"
}
+11 -4
View File
@@ -12,18 +12,25 @@ runs:
steps:
- name: Install tools
shell: bash
env:
ACTIONLINT_VERSION: ${{ inputs.actionlint-version }}
run: |
version="$ACTIONLINT_VERSION"
if [[ ! "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+([.-][0-9A-Za-z.-]+)?$ ]]; then
echo "::error::invalid actionlint-version '$version'"
exit 1
fi
sudo apt-get update
sudo apt-get install -y --no-install-recommends shellcheck desktop-file-utils appstream libxml2-utils python3-yaml
tar_url="https://github.com/rhysd/actionlint/releases/download/v${{ inputs.actionlint-version }}/actionlint_${{ inputs.actionlint-version }}_linux_amd64.tar.gz"
tar_url="https://github.com/rhysd/actionlint/releases/download/v${version}/actionlint_${version}_linux_amd64.tar.gz"
curl --proto '=https' --proto-redir =https -fsSL -o "$RUNNER_TEMP/actionlint.tar.gz" "$tar_url"
case "${{ inputs.actionlint-version }}" in
case "$version" in
"1.7.12")
expected="8aca8db96f1b94770f1b0d72b6dddcb1ebb8123cb3712530b08cc387b349a3d8"
;;
*)
curl --proto '=https' --proto-redir =https -fsSL -o "$RUNNER_TEMP/checksums.txt" "https://github.com/rhysd/actionlint/releases/download/v${{ inputs.actionlint-version }}/actionlint_${{ inputs.actionlint-version }}_checksums.txt"
expected=$(grep -E "actionlint_${{ inputs.actionlint-version }}_linux_amd64\.tar\.gz" "$RUNNER_TEMP/checksums.txt" | awk '{print $1}')
curl --proto '=https' --proto-redir =https -fsSL -o "$RUNNER_TEMP/checksums.txt" "https://github.com/rhysd/actionlint/releases/download/v${version}/actionlint_${version}_checksums.txt"
expected=$(grep -E "actionlint_${version}_linux_amd64\.tar\.gz" "$RUNNER_TEMP/checksums.txt" | awk '{print $1}')
;;
esac
echo "$expected $RUNNER_TEMP/actionlint.tar.gz" | sha256sum -c -