From 9139b8a5cc04464f0ee4a575b6516bebdec73ab0 Mon Sep 17 00:00:00 2001 From: KevinR Date: Sat, 11 Jul 2026 15:26:09 +0200 Subject: [PATCH] add immutable output and sha256 digest verification for asset downloads Entire-Checkpoint: 08bcc7b9fe01 --- README.md | 4 ++++ action.yml | 33 ++++++++++++++++++++++++++++++--- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 4b368f4..45611dc 100644 --- a/README.md +++ b/README.md @@ -136,6 +136,8 @@ The action supports the following inputs:

Example:
Download all .PNG and .JPEG files: `*.PNG,*.JPEG`
Download release asset with a static name: `myfile-to-download.extension` + + >Downloaded files are verified against the sha256 [asset digest](https://github.blog/changelog/2025-06-03-releases-now-expose-digests-for-release-assets/) reported by the release API. The action fails on a digest mismatch. Assets without a digest (uploaded before April 2025) are downloaded without verification. **Required:** *false* @@ -164,9 +166,11 @@ The action supports the following inputs: | `node_id` | Release node id | | `tag_name` | Release tag name | | `target_commitish` | Target from which the release is created | +| `name` | Release name | | `body` | The `body` output from release. It's a multiline output, which is packed in a JSON object.
The output can be used with the `fromJSON` function `(steps..outputs.body)`. | | `draft` | Shows if the release is a draft release or not (true/false) | | `prerelease` | Shows if the release is a pre-release (true/false) | +| `immutable` | Shows if the release is [immutable](https://docs.github.com/en/code-security/concepts/supply-chain-security/immutable-releases) (true/false) - can be `null` on GitHub Enterprise Server versions without immutable releases | | `created_at` | Shows the release creation date | | `published_at` | Shows the published date of the release | | `assets_array_json` | The `assets_array_json` output from release. It's a multiline output, which is packed in a JSON object.
The output can be used with the `fromJSON` function `(steps..outputs.assets_array_json)`. | diff --git a/action.yml b/action.yml index 4a36504..a565e7e 100644 --- a/action.yml +++ b/action.yml @@ -113,6 +113,9 @@ outputs: prerelease: description: 'Shows if the release is a pre-release (true/false)' value: ${{ steps.release_data.outputs.prerelease }} + immutable: + description: 'Shows if the release is immutable (true/false)' + value: ${{ steps.release_data.outputs.immutable }} created_at: description: 'Shows the release creation date' value: ${{ steps.release_data.outputs.created_at }} @@ -160,7 +163,7 @@ runs: gh_api_url="https://api.github.com/repos/${{ inputs.repository }}/releases/tags/${{ inputs.version }}" fi - release_json_data=$(curl ${token:+"-H"} ${token:+"Authorization: token ${token}"} "$gh_api_url") + release_json_data=$(curl ${token:+"-H"} ${token:+"Authorization: token ${token}"} -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" "$gh_api_url") url=$(jq -r '.url' <<< "$release_json_data") html_url=$(jq -r '.html_url' <<< "$release_json_data") @@ -178,6 +181,7 @@ runs: body_packed_in_json=$(printf '%s' "$body" | jq --raw-input --slurp '.') draft=$(jq -r '.draft' <<< "$release_json_data") prerelease=$(jq -r '.prerelease' <<< "$release_json_data") + immutable=$(jq -r '.immutable' <<< "$release_json_data") created_at=$(jq -r '.created_at' <<< "$release_json_data") published_at=$(jq -r '.published_at' <<< "$release_json_data") author=$(jq -r '.author' <<< "$release_json_data") @@ -204,6 +208,7 @@ runs: echo "body=$body_packed_in_json" >> $GITHUB_OUTPUT echo "draft=$draft" >> $GITHUB_OUTPUT echo "prerelease=$prerelease" >> $GITHUB_OUTPUT + echo "immutable=$immutable" >> $GITHUB_OUTPUT echo "created_at=$created_at" >> $GITHUB_OUTPUT echo "published_at=$published_at" >> $GITHUB_OUTPUT echo "author_json=$author_packed_in_json" >> $GITHUB_OUTPUT @@ -242,6 +247,8 @@ runs: - name: Download release assets id: release_asset_download if: ${{ inputs.asset-file != '' }} + env: + ASSETS_JSON: ${{ steps.release_data.outputs.assets_array_json }} shell: bash run: | # shell echo "::group::Download release artifacts" @@ -281,9 +288,11 @@ runs: done regex_search=${regex_search:1} - jq -c '.[]' <<< '${{ fromJSON(steps.release_data.outputs.assets_array_json) }}' | while read i; do + digest_mismatch="" + while IFS= read -r i; do asset_name=$(jq -r '.name' <<<"$i") asset_id=$(jq -r '.id' <<<"$i") + asset_digest=$(jq -r '.digest // empty' <<<"$i") should_download=false if [[ $(echo "$asset_name" | grep -E -o "$regex_search") != "" ]]; then should_download=true @@ -294,12 +303,29 @@ runs: -L \ -H "${auth_header}" \ -H "Accept:application/octet-stream" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ -o "${{ inputs.asset-output }}/${asset_name}" \ "${download_url}" + if [[ "$asset_digest" == sha256:* ]]; then + expected_hash="${asset_digest#sha256:}" + if [[ "$RUNNER_OS" == "Linux" ]]; then + actual_hash=$(sha256sum "${{ inputs.asset-output }}/${asset_name}" | head -c 64) + else + actual_hash=$(shasum -a 256 "${{ inputs.asset-output }}/${asset_name}" | head -c 64) + fi + if [[ "$actual_hash" != "$expected_hash" ]]; then + digest_mismatch="$digest_mismatch $asset_name" + fi + fi fi - done + done < <(printf '%s' "$ASSETS_JSON" | jq -c 'fromjson | .[]') echo "::$log_token_toggle::" + if [[ -n "$digest_mismatch" ]]; then + echo "::error title=Asset digest mismatch::Downloaded asset(s) do not match the sha256 digest from the release API:$digest_mismatch" + exit 1 + fi + echo "::endgroup::" - name: show output values @@ -322,6 +348,7 @@ runs: echo "INFO: Release body ${{ fromJSON(steps.release_data.outputs.body) }}" echo "INFO: Release draft ${{ steps.release_data.outputs.draft }}" echo "INFO: Release prerelease ${{ steps.release_data.outputs.prerelease }}" + echo "INFO: Release immutable ${{ steps.release_data.outputs.immutable }}" echo "INFO: Release created_at ${{ steps.release_data.outputs.created_at }}" echo "INFO: Release published_at ${{ steps.release_data.outputs.published_at }}" echo "INFO: Release assets_array_json ${{ fromJSON(steps.release_data.outputs.assets_array_json) }}"