Compare commits

...

4 commits

Author SHA1 Message Date
PabloMK7
308a9b14ea Add attestation support to increase release security (#2117)
* ci: Add sbom and attestation

* tools: Add verify-release.sh

* verify-release.sh: Set executable permission

* verify-release.sh: Put downloads into a gitignored directory

* tools: Make verify-release also download sbom

---------

Co-authored-by: OpenSauce04 <opensauce04@gmail.com>
2026-05-14 13:55:42 +01:00
Eric Warmenhoven
26036ce2d3 libretro: update docker image for mxe github action 2026-05-14 13:44:24 +01:00
OpenSauce04
667850556d ci: Strip libretro cores after building 2026-05-14 13:40:15 +01:00
OpenSauce04
53fae5c168 cmake: Add EXCLUDE_FROM_ALL to targets where applicable 2026-05-14 13:40:00 +01:00
13 changed files with 460 additions and 32 deletions

View file

@ -14,4 +14,7 @@ echo "Tag name is: $TAG_NAME"
docker build -f docker/azahar-room/Dockerfile -t azahar-room:$TAG_NAME .
mkdir -p build
docker save azahar-room:$TAG_NAME > build/azahar-room-$TAG_NAME.dockerimage
FILENAME="azahar-room-$TAG_NAME.dockerimage"
docker save azahar-room:$TAG_NAME > build/$FILENAME
echo "DOCKER_IMAGE_PATH=artifacts/$FILENAME" >> $GITHUB_ENV

View file

@ -7,6 +7,11 @@ on:
pull_request:
branches: [ master ]
permissions:
id-token: write
contents: read
attestations: write
jobs:
source:
if: ${{ !github.head_ref }}
@ -17,11 +22,26 @@ jobs:
submodules: recursive
- name: Pack
run: ./.ci/source.sh
- name: Generate SBOM
if: ${{ github.ref_type == 'tag' }}
uses: anchore/sbom-action@v0
with:
path: ./
format: spdx-json
output-file: artifacts/source.spdx.json
upload-artifact: false
- name: Upload
uses: actions/upload-artifact@v4
with:
name: source
path: artifacts/
- name: Attest artifacts
if: ${{ github.ref_type == 'tag' }}
uses: actions/attest@v4
with:
subject-path: |
artifacts/*.tar.xz
sbom-path: artifacts/source.spdx.json
linux-x86_64:
runs-on: ubuntu-latest
@ -39,13 +59,14 @@ jobs:
OS: linux
TARGET: ${{ matrix.target }}
SHOULD_RUN: ${{ (matrix.target != 'appimage-wayland' || github.ref_type == 'tag') }}
CACHE_ENABLED: ${{ github.ref_type != 'tag' }}
steps:
- uses: actions/checkout@v4
if: ${{ env.SHOULD_RUN == 'true' }}
with:
submodules: recursive
- name: Set up cache
if: ${{ env.SHOULD_RUN == 'true' }}
if: ${{ env.SHOULD_RUN == 'true' && env.CACHE_ENABLED == 'true' }}
uses: actions/cache@v4
with:
path: ${{ env.CCACHE_DIR }}
@ -64,12 +85,27 @@ jobs:
if: ${{ matrix.target == 'appimage-wayland' && env.SHOULD_RUN == 'true' }}
run: |
mv artifacts/azahar.AppImage artifacts/azahar-wayland.AppImage
- name: Generate SBOM
if: ${{ contains(matrix.target, 'appimage') && github.ref_type == 'tag' && env.SHOULD_RUN == 'true' }}
uses: anchore/sbom-action@v0
with:
path: build/
format: spdx-json
output-file: artifacts/linux-x86_64-${{ matrix.target }}.spdx.json
upload-artifact: false
- name: Upload
if: ${{ contains(matrix.target, 'appimage') && env.SHOULD_RUN == 'true' }}
uses: actions/upload-artifact@v4
with:
name: ${{ github.job }}-${{ matrix.target }}
path: artifacts/
- name: Attest artifacts
if: ${{ contains(matrix.target, 'appimage') && github.ref_type == 'tag' && env.SHOULD_RUN == 'true' }}
uses: actions/attest@v4
with:
subject-path: |
artifacts/*.AppImage
sbom-path: artifacts/linux-x86_64-${{ matrix.target }}.spdx.json
linux-arm64:
runs-on: ubuntu-24.04-arm
@ -106,12 +142,14 @@ jobs:
CCACHE_DIR: ${{ github.workspace }}/.ccache
CCACHE_COMPILERCHECK: content
CCACHE_SLOPPINESS: time_macros
CACHE_ENABLED: ${{ github.ref_type != 'tag' }}
OS: macos
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Set up cache
if: ${{ env.CACHE_ENABLED == 'true' }}
uses: actions/cache@v4
with:
path: ${{ env.CCACHE_DIR }}
@ -136,11 +174,26 @@ jobs:
env:
PACK_INDIVIDUALLY: 1
run: ./.ci/pack.sh
- name: Generate SBOM
if: ${{ github.ref_type == 'tag' }}
uses: anchore/sbom-action@v0
with:
path: build/
format: spdx-json
output-file: artifacts/macos.spdx.json
upload-artifact: false
- name: Upload
uses: actions/upload-artifact@v4
with:
name: ${{ env.OS }}
path: artifacts/
- name: Attest artifacts
if: ${{ github.ref_type == 'tag' }}
uses: actions/attest@v4
with:
subject-path: |
artifacts/*.zip
sbom-path: artifacts/macos.spdx.json
windows:
runs-on: windows-latest
@ -155,6 +208,7 @@ jobs:
CCACHE_DIR: ${{ github.workspace }}/.ccache
CCACHE_COMPILERCHECK: content
CCACHE_SLOPPINESS: time_macros
CACHE_ENABLED: ${{ github.ref_type != 'tag' }}
OS: windows
TARGET: ${{ matrix.target }}
steps:
@ -162,6 +216,7 @@ jobs:
with:
submodules: recursive
- name: Set up cache
if: ${{ env.CACHE_ENABLED == 'true' }}
uses: actions/cache@v4
with:
path: ${{ env.CCACHE_DIR }}
@ -214,11 +269,27 @@ jobs:
shell: cmd
- name: Pack
run: ./.ci/pack.sh
- name: Generate SBOM
if: ${{ github.ref_type == 'tag' }}
uses: anchore/sbom-action@v0
with:
path: build/
format: spdx-json
output-file: artifacts/windows-${{ matrix.target }}.spdx.json
upload-artifact: false
- name: Upload
uses: actions/upload-artifact@v4
with:
name: ${{ env.OS }}-${{ env.TARGET }}
path: artifacts/
- name: Attest artifacts
if: ${{ github.ref_type == 'tag' }}
uses: actions/attest@v4
with:
subject-path: |
artifacts/*.zip
artifacts/*.exe
sbom-path: artifacts/windows-${{ matrix.target }}.spdx.json
android:
runs-on: ubuntu-latest
@ -230,6 +301,7 @@ jobs:
CCACHE_DIR: ${{ github.workspace }}/.ccache
CCACHE_COMPILERCHECK: content
CCACHE_SLOPPINESS: time_macros
CACHE_ENABLED: ${{ github.ref_type != 'tag' }}
OS: android
TARGET: ${{ matrix.target }}
SHOULD_RUN: ${{ (matrix.target == 'vanilla' || github.ref_type == 'tag') }}
@ -239,7 +311,7 @@ jobs:
with:
submodules: recursive
- name: Set up cache
if: ${{ env.SHOULD_RUN == 'true' }}
if: ${{ env.SHOULD_RUN == 'true' && env.CACHE_ENABLED == 'true' }}
uses: actions/cache@v4
with:
path: |
@ -278,12 +350,28 @@ jobs:
working-directory: src/android/app
env:
UNPACKED: 1
- name: Generate SBOM
if: ${{ github.ref_type == 'tag' }}
uses: anchore/sbom-action@v0
with:
path: src/android
format: spdx-json
output-file: src/android/app/artifacts/android-${{ matrix.target }}.spdx.json
upload-artifact: false
- name: Upload
if: ${{ env.SHOULD_RUN == 'true' }}
uses: actions/upload-artifact@v4
with:
name: ${{ env.OS }}-${{ env.TARGET }}
path: src/android/app/artifacts/
- name: Attest artifacts
if: ${{ github.ref_type == 'tag' }}
uses: actions/attest@v4
with:
subject-path: |
src/android/app/artifacts/*.apk
src/android/app/artifacts/*.aab
sbom-path: src/android/app/artifacts/android-${{ matrix.target }}.spdx.json
docker:
runs-on: ubuntu-latest
@ -303,8 +391,23 @@ jobs:
run: |
mkdir -p artifacts
mv build/*.dockerimage artifacts/
- name: Generate SBOM
if: ${{ github.ref_type == 'tag' }}
uses: anchore/sbom-action@v0
with:
image: ${{ env.DOCKER_IMAGE_PATH }}
format: spdx-json
output-file: artifacts/docker-room.spdx.json
upload-artifact: false
- name: Upload
uses: actions/upload-artifact@v4
with:
name: docker
path: artifacts/
- name: Attest artifacts
if: ${{ github.ref_type == 'tag' }}
uses: actions/attest@v4
with:
subject-path: |
artifacts/*.dockerimage
sbom-path: artifacts/docker-room.spdx.json

View file

@ -11,6 +11,11 @@ on:
env:
CORE_ARGS: -DENABLE_LIBRETRO=ON
permissions:
id-token: write
contents: read
attestations: write
jobs:
android:
runs-on: ubuntu-22.04
@ -32,6 +37,10 @@ jobs:
echo "GIT_TAG_NAME=$GITHUB_REF_NAME" >> $GITHUB_ENV
fi
echo $GIT_TAG_NAME
- name: Install tools
run: |
sudo apt-get update -y
sudo apt-get install -y llvm
- name: Update Android SDK CMake version
run: |
echo "y" | ${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager "ndk;$ANDROID_NDK_VERSION"
@ -41,13 +50,32 @@ jobs:
export NDK_ROOT=${ANDROID_SDK_ROOT}/ndk/$ANDROID_NDK_VERSION
${ANDROID_SDK_ROOT}/cmake/3.30.3/bin/cmake $CORE_ARGS -DANDROID_PLATFORM=android-$API_LEVEL -DCMAKE_TOOLCHAIN_FILE=$NDK_ROOT/build/cmake/android.toolchain.cmake -DANDROID_STL=c++_static -DANDROID_ABI=$ANDROID_ABI . -B $BUILD_DIR
${ANDROID_SDK_ROOT}/cmake/3.30.3/bin/cmake --build $BUILD_DIR --target azahar_libretro --config Release -j $(nproc)
llvm-strip -s $BUILD_DIR/$EXTRA_PATH/azahar_libretro.*
- name: Pack
run: ./.ci/libretro-pack.sh
- name: Generate SBOM
if: ${{ github.ref_type == 'tag' }}
uses: anchore/sbom-action@v0
with:
path: build/
format: spdx-json
output-file: libretro-android.spdx.json
upload-artifact: false
- name: Upload
uses: actions/upload-artifact@v4
with:
name: ${{ env.OS }}-${{ env.TARGET }}
path: ./*.zip
path: |
./*.zip
./*.spdx.json
- name: Attest artifacts
if: ${{ github.ref_type == 'tag' }}
uses: actions/attest@v4
with:
subject-path: |
./*.zip
sbom-path: libretro-android.spdx.json
linux:
runs-on: ubuntu-22.04
env:
@ -60,17 +88,40 @@ jobs:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Install tools
run: |
sudo apt-get update -y
sudo apt-get install -y llvm
- name: Build
run: |
cmake $CORE_ARGS $EXTRA_CORE_ARGS . -B $BUILD_DIR
cmake --build $BUILD_DIR --target azahar_libretro --config Release -j $(nproc)
llvm-strip -s $BUILD_DIR/$EXTRA_PATH/azahar_libretro.*
- name: Pack
run: ./.ci/libretro-pack.sh
- name: Generate SBOM
if: ${{ github.ref_type == 'tag' }}
uses: anchore/sbom-action@v0
with:
path: build/
format: spdx-json
output-file: libretro-linux.spdx.json
upload-artifact: false
- name: Upload
uses: actions/upload-artifact@v4
with:
name: ${{ env.OS }}-${{ env.TARGET }}
path: ./*.zip
path: |
./*.zip
./*.spdx.json
- name: Attest artifacts
if: ${{ github.ref_type == 'tag' }}
uses: actions/attest@v4
with:
subject-path: |
./*.zip
sbom-path: libretro-linux.spdx.json
windows:
runs-on: ubuntu-latest
env:
@ -79,7 +130,7 @@ jobs:
BUILD_DIR: build/windows-x86_64
EXTRA_CORE_ARGS: -DENABLE_LTO=OFF -G Ninja
CMAKE: x86_64-w64-mingw32.static-cmake
IMAGE: git.libretro.com:5050/libretro-infrastructure/libretro-build-mxe-win-cross-cores:mingw12
IMAGE: reallibretroretroarch/libretro-build-mxe-win-cross-cores:mingw12
EXTRA_PATH: bin/Release
steps:
- uses: actions/checkout@v4
@ -94,14 +145,32 @@ jobs:
$IMAGE \
bash -lc "\
${CMAKE} $CORE_ARGS $EXTRA_CORE_ARGS . -B $BUILD_DIR && \
${CMAKE} --build $BUILD_DIR --target azahar_libretro --config Release -j $(nproc)"
${CMAKE} --build $BUILD_DIR --target azahar_libretro --config Release -j $(nproc) && \
x86_64-w64-mingw32.static-strip -s $BUILD_DIR/$EXTRA_PATH/azahar_libretro.*"
- name: Pack
run: ./.ci/libretro-pack.sh
- name: Generate SBOM
if: ${{ github.ref_type == 'tag' }}
uses: anchore/sbom-action@v0
with:
path: build/
format: spdx-json
output-file: libretro-windows.spdx.json
upload-artifact: false
- name: Upload
uses: actions/upload-artifact@v4
with:
name: ${{ env.OS }}-${{ env.TARGET }}
path: ./*.zip
path: |
./*.zip
./*.spdx.json
- name: Attest artifacts
if: ${{ github.ref_type == 'tag' }}
uses: actions/attest@v4
with:
subject-path: |
./*.zip
sbom-path: libretro-windows.spdx.json
macos:
runs-on: macos-26
strategy:
@ -123,13 +192,32 @@ jobs:
run: |
cmake $CORE_ARGS -DCMAKE_OSX_ARCHITECTURES=$TARGET . -B $BUILD_DIR
cmake --build $BUILD_DIR --target azahar_libretro --config Release
strip -x $BUILD_DIR/$EXTRA_PATH/azahar_libretro.*
- name: Pack
run: ./.ci/libretro-pack.sh
- name: Generate SBOM
if: ${{ github.ref_type == 'tag' }}
uses: anchore/sbom-action@v0
with:
path: build/
format: spdx-json
output-file: libretro-macos-${{ matrix.target }}.spdx.json
upload-artifact: false
- name: Upload
uses: actions/upload-artifact@v4
with:
name: ${{ env.OS }}-${{ env.TARGET }}
path: ./*.zip
path: |
./*.zip
./*.spdx.json
- name: Attest artifacts
if: ${{ github.ref_type == 'tag' }}
uses: actions/attest@v4
with:
subject-path: |
./*.zip
sbom-path: libretro-macos-${{ matrix.target }}.spdx.json
ios:
runs-on: macos-26
env:
@ -146,13 +234,32 @@ jobs:
run: |
cmake $CORE_ARGS $EXTRA_CORE_ARGS . -B $BUILD_DIR
cmake --build $BUILD_DIR --target azahar_libretro --config Release
strip -x $BUILD_DIR/$EXTRA_PATH/azahar_libretro.*
- name: Pack
run: ./.ci/libretro-pack.sh
- name: Generate SBOM
if: ${{ github.ref_type == 'tag' }}
uses: anchore/sbom-action@v0
with:
path: build/
format: spdx-json
output-file: libretro-ios.spdx.json
upload-artifact: false
- name: Upload
uses: actions/upload-artifact@v4
with:
name: ${{ env.OS }}-${{ env.TARGET }}
path: ./*.zip
path: |
./*.zip
./*.spdx.json
- name: Attest artifacts
if: ${{ github.ref_type == 'tag' }}
uses: actions/attest@v4
with:
subject-path: |
./*.zip
sbom-path: libretro-ios.spdx.json
tvos:
runs-on: macos-26
env:
@ -169,10 +276,28 @@ jobs:
run: |
cmake $CORE_ARGS $EXTRA_CORE_ARGS . -B $BUILD_DIR
cmake --build $BUILD_DIR --target azahar_libretro --config Release
strip -x $BUILD_DIR/$EXTRA_PATH/azahar_libretro.*
- name: Pack
run: ./.ci/libretro-pack.sh
- name: Generate SBOM
if: ${{ github.ref_type == 'tag' }}
uses: anchore/sbom-action@v0
with:
path: build/
format: spdx-json
output-file: libretro-tvos.spdx.json
upload-artifact: false
- name: Upload
uses: actions/upload-artifact@v4
with:
name: ${{ env.OS }}-${{ env.TARGET }}
path: ./*.zip
path: |
./*.zip
./*.spdx.json
- name: Attest artifacts
if: ${{ github.ref_type == 'tag' }}
uses: actions/attest@v4
with:
subject-path: |
./*.zip
sbom-path: libretro-tvos.spdx.json

3
.gitignore vendored
View file

@ -60,3 +60,6 @@ VULKAN_SDK/
# Version info files
GIT-COMMIT
GIT-TAG
# verify-release.sh downloads
verify/

View file

@ -57,7 +57,7 @@ if (ENABLE_TESTS)
else()
set(CATCH_INSTALL_DOCS OFF CACHE BOOL "")
set(CATCH_INSTALL_EXTRAS OFF CACHE BOOL "")
add_subdirectory(catch2)
add_subdirectory(catch2 EXCLUDE_FROM_ALL)
endif()
target_link_libraries(catch2 INTERFACE Catch2::Catch2WithMain)
include(Catch)
@ -79,7 +79,7 @@ else()
set(CRYPTOPP_BUILD_TESTING OFF CACHE BOOL "")
set(CRYPTOPP_INSTALL OFF CACHE BOOL "")
set(CRYPTOPP_SOURCES "${CMAKE_SOURCE_DIR}/externals/cryptopp" CACHE STRING "")
add_subdirectory(cryptopp-cmake)
add_subdirectory(cryptopp-cmake EXCLUDE_FROM_ALL)
endif()
# dds-ktx
@ -142,7 +142,7 @@ endif()
# getopt
if (MSVC)
add_subdirectory(getopt)
add_subdirectory(getopt EXCLUDE_FROM_ALL)
endif()
# inih
@ -151,7 +151,7 @@ if(USE_SYSTEM_INIH)
add_library(inih INTERFACE)
target_link_libraries(inih INTERFACE inih::inih inih::inir)
else()
add_subdirectory(inih)
add_subdirectory(inih EXCLUDE_FROM_ALL)
endif()
# MicroProfile
@ -174,7 +174,7 @@ if (NOT MSVC)
endif()
# Open Source Archives
add_subdirectory(open_source_archives)
add_subdirectory(open_source_archives EXCLUDE_FROM_ALL)
# faad2
add_subdirectory(faad2 EXCLUDE_FROM_ALL)
@ -213,12 +213,12 @@ add_subdirectory(teakra EXCLUDE_FROM_ALL)
# SDL2
if (ENABLE_SDL2 AND NOT USE_SYSTEM_SDL2)
add_subdirectory(sdl2)
add_subdirectory(sdl2 EXCLUDE_FROM_ALL)
endif()
# libusb
if (ENABLE_LIBUSB AND NOT USE_SYSTEM_LIBUSB)
add_subdirectory(libusb)
add_subdirectory(libusb EXCLUDE_FROM_ALL)
set(LIBUSB_INCLUDE_DIR "" PARENT_SCOPE)
set(LIBUSB_LIBRARIES usb PARENT_SCOPE)
endif()
@ -262,7 +262,7 @@ if(USE_SYSTEM_ENET)
add_library(enet INTERFACE)
target_link_libraries(enet INTERFACE libenet::libenet)
else()
add_subdirectory(enet)
add_subdirectory(enet EXCLUDE_FROM_ALL)
target_include_directories(enet INTERFACE ./enet/include)
endif()
@ -374,7 +374,7 @@ target_compile_options(httplib INTERFACE -DCPPHTTPLIB_OPENSSL_SUPPORT)
target_link_libraries(httplib INTERFACE ${OPENSSL_LIBRARIES})
if (UNIX AND NOT APPLE)
add_subdirectory(gamemode)
add_subdirectory(gamemode EXCLUDE_FROM_ALL)
endif()
# cpp-jwt
@ -397,13 +397,13 @@ if(USE_SYSTEM_LODEPNG)
find_package(lodepng REQUIRED)
target_link_libraries(lodepng INTERFACE lodepng::lodepng)
else()
add_subdirectory(lodepng)
add_subdirectory(lodepng EXCLUDE_FROM_ALL)
endif()
# (xperia64): Only use libyuv on Android b/c of build issues on Windows and mandatory JPEG
if(ANDROID)
# libyuv
add_subdirectory(libyuv)
add_subdirectory(libyuv EXCLUDE_FROM_ALL)
target_include_directories(yuv INTERFACE ./libyuv/include)
endif()
@ -429,7 +429,7 @@ endif()
# OpenGL dependencies
if (ENABLE_OPENGL)
# Glad
add_subdirectory(glad)
add_subdirectory(glad EXCLUDE_FROM_ALL)
endif()
# Vulkan dependencies
@ -469,7 +469,7 @@ if (ENABLE_VULKAN)
set(ENABLE_CTEST OFF CACHE BOOL "")
set(ENABLE_HLSL OFF CACHE BOOL "")
set(BUILD_EXTERNAL OFF CACHE BOOL "")
add_subdirectory(glslang)
add_subdirectory(glslang EXCLUDE_FROM_ALL)
endif()
# sirit
@ -517,7 +517,7 @@ if (ENABLE_VULKAN)
# adrenotools
if (ANDROID AND "arm64" IN_LIST ARCHITECTURE)
add_subdirectory(libadrenotools)
add_subdirectory(libadrenotools EXCLUDE_FROM_ALL)
endif()
endif()

View file

@ -1,4 +1,4 @@
add_library(audio_core STATIC
add_library(audio_core STATIC EXCLUDE_FROM_ALL
audio_types.h
codec.cpp
codec.h

View file

@ -1,6 +1,6 @@
include(GenerateSCMRev)
add_library(citra_common STATIC
add_library(citra_common STATIC EXCLUDE_FROM_ALL
aarch64/cpu_detect.cpp
aarch64/cpu_detect.h
aarch64/oaknut_abi.h

View file

@ -1,4 +1,4 @@
add_library(citra_core STATIC
add_library(citra_core STATIC EXCLUDE_FROM_ALL
3ds.h
arm/arm_interface.h
arm/dyncom/arm_dyncom.cpp

View file

@ -1,4 +1,4 @@
add_library(input_common STATIC
add_library(input_common STATIC EXCLUDE_FROM_ALL
analog_from_button.cpp
analog_from_button.h
keyboard.cpp

View file

@ -1,4 +1,4 @@
add_library(network STATIC
add_library(network STATIC EXCLUDE_FROM_ALL
announce_multiplayer_session.cpp
announce_multiplayer_session.h
artic_base/artic_base_client.cpp

View file

@ -1,6 +1,6 @@
add_subdirectory(host_shaders)
add_library(video_core STATIC
add_library(video_core STATIC EXCLUDE_FROM_ALL
custom_textures/custom_format.cpp
custom_textures/custom_format.h
custom_textures/custom_tex_manager.cpp

View file

@ -1,4 +1,4 @@
add_library(web_service STATIC
add_library(web_service STATIC EXCLUDE_FROM_ALL
announce_room_json.cpp
announce_room_json.h
precompiled_headers.h

194
tools/verify-release.sh Executable file
View file

@ -0,0 +1,194 @@
#!/usr/bin/env bash
# Copyright Citra Emulator Project / Azahar Emulator Project
# Licensed under GPLv2 or any later version
# Refer to the license.txt file included.
set -euo pipefail
# Usage:
# ./verify-release.sh <owner/repo> <tag>
#
# Example:
# ./verify-release.sh azahar-emu/azahar 2126.0
#
# Behavior:
# - Downloads all release assets
# - Verifies asset is published in the release
# - Verifies SPDX attestations for every asset
# - Extracts SPDX SBOMs
#
# Notes:
# - Requires installation of the GitHub CLI (gh) and jq tools.
# - Draft release support requires authentication with permission
# to view the draft release.
# - gh release verify-asset currently does NOT support draft releases.
if [[ $# -ne 2 ]]; then
echo "Usage: $0 <owner/repo> <tag>"
exit 1
fi
command -v gh >/dev/null 2>&1 || {
echo "ERROR: GitHub CLI (gh) is not installed or not in PATH"
exit 1
}
command -v jq >/dev/null 2>&1 || {
echo "ERROR: jq is not installed or not in PATH"
exit 1
}
REPO="$1"
TAG="$2"
echo "==> Fetching release metadata"
IS_DRAFT=$(
gh release view "$TAG" \
--repo "$REPO" \
--json isDraft \
--jq '.isDraft'
)
WORKDIR="verify/release-${TAG}"
SBOMSUBDIR="sbom"
rm -rf "$WORKDIR"
mkdir -p "$WORKDIR"
cd "$WORKDIR"
mkdir -p "$SBOMSUBDIR"
echo
echo "==> Downloading release assets"
gh release download "$TAG" \
--repo "$REPO"
echo
echo "==> Fetching asset list"
ASSETS=()
while IFS= read -r asset; do
ASSETS+=("$asset")
done < <(
gh release view "$TAG" \
--repo "$REPO" \
--json assets \
--jq '.assets[].name'
)
echo
echo "==> Release type: $(
[[ "$IS_DRAFT" == "true" ]] && echo "draft" || echo "published"
)"
echo
echo "==> Verifying assets"
for asset in "${ASSETS[@]}"; do
# Skip attestation files themselves
if [[ "$asset" == *.intoto.jsonl ]]; then
continue
fi
if [[ ! -f "$asset" ]]; then
echo "ERROR: Missing downloaded asset: $asset"
exit 1
fi
echo
echo "========================================"
echo "Asset: $asset"
echo "========================================"
echo "1/3 Release asset verification"
if [[ "$IS_DRAFT" != "true" ]]; then
gh release verify-asset "$TAG" "$asset" \
--repo "$REPO"
echo
else
echo "SKIPPED (draft releases unsupported)"
echo
fi
echo "2/3 Attestation verification"
if [[ "$asset" == *.sha256sum ]]; then
echo "SKIPPED (sha256sum does not need verification)"
echo "SKIPPED (no SPDX SBOM extraction)"
else
gh attestation verify "$asset" \
--repo "$REPO" \
--predicate-type https://spdx.dev/Document
echo
echo "3/3 SBOM extraction"
BASE_NAME="$(basename "$asset")"
SBOM_FILE="${SBOMSUBDIR}/${BASE_NAME}.spdx.json"
# gh attestation download does not currently support
# specifying the output file, nor it allows piping the
# output. For that reason, we need to find the .jsonl
# in the current directory.
# Exclude any existing .jsonl files from find
# (failsafe, should not happen)
BEFORE_JSONL="$(find . -maxdepth 1 -name '*.jsonl' -print)"
gh attestation download "$asset" \
--repo "$REPO" \
>/dev/null
ATTESTATION_FILE=""
while IFS= read -r file; do
FOUND=false
while IFS= read -r oldfile; do
if [[ "$file" == "$oldfile" ]]; then
FOUND=true
break
fi
done <<< "$BEFORE_JSONL"
# Only consider new jsonl files
if [[ "$FOUND" == "false" ]]; then
ATTESTATION_FILE="$file"
break
fi
done < <(find . -maxdepth 1 -name '*.jsonl' -print)
if [[ -z "$ATTESTATION_FILE" ]]; then
echo "ERROR: Could not locate downloaded attestation jsonl"
exit 1
fi
# Extract and decode the SBOM from the jsonl
jq -r '
.dsseEnvelope.payload
' "$ATTESTATION_FILE" |
while IFS= read -r payload; do
echo "$payload" | base64 -d
done |
jq '.predicate' \
> "$SBOM_FILE"
rm -f "$ATTESTATION_FILE"
echo "Saved SBOM: $SBOM_FILE"
fi
echo
echo "OK: $asset"
done
echo
echo "========================================"
echo "All assets verified successfully"
echo "SBOMs saved in: $WORKDIR/$SBOMSUBDIR"
echo "========================================"