Skip to content

security: audit remediation #28

security: audit remediation

security: audit remediation #28

name: cross-platform_build
on:
push:
branches:
- main
pull_request:
branches:
- main
env:
APP_NAME: subterfuge
# Pinned FVM CLI version (not just the Flutter SDK pinned in .fvmrc) so the
# `curl | bash` installer below is reproducible instead of always fetching
# whatever is latest at build time.
FVM_VERSION: "4.1.2"
# Pinned commit of ethicnology/paraph (https://github.com/ethicnology/paraph),
# the cross-platform signing wrapper used to code-sign release artifacts
# below (apksigner/osslsigncode/rcodesign/gpg under the hood).
PARAPH_REF: "478e63ea65a04ff5afa3179238e2a967d20f2e3c"
# Code-signing (only runs on push to `main`, never on pull_request, since
# forked-repo PRs don't get secrets) needs these repository secrets. Signing
# is skipped gracefully wherever its secret is absent — everything above
# still builds and publishes an unsigned artifact + checksum.
#
# Android (.apk): ANDROID_KEYSTORE_BASE64 (base64 of the .keystore file),
# ANDROID_ALIAS, ANDROID_PASS
# Windows (.msix): WINDOWS_P12_BASE64 (base64 of the .p12 file), WINDOWS_PASS
# macOS (.dmg): MAC_P12_BASE64 (base64 of the .p12 file), MAC_PASS,
# MAC_CERT_SUBJECT (optional, defaults to "Subterfuge")
# Linux (.AppImage): GPG_PRIVATE_KEY (ASCII-armored secret key), GPG_KEY_ID,
# GPG_PASS
#
# See https://github.com/ethicnology/paraph#configuration for how to
# generate each of these.
jobs:
# Gate every build job on analyze+test: this app handles cryptographic
# secrets (mnemonics/seeds/shares), a broken build must never be shipped.
test:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
- name: Install FVM
run: |
curl -fsSL https://fvm.app/install.sh | bash -s -- "$FVM_VERSION"
echo "$HOME/fvm/bin" >> $GITHUB_PATH
- run: fvm install
- run: fvm flutter pub get
- run: fvm flutter analyze
- run: fvm flutter test
macos-silicon:
needs: test
runs-on: macos-15
# Secrets cannot be referenced directly in `if:` conditionals, so mirror
# the ones needed for that into job-level env vars first (see
# https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions).
env:
MAC_P12_BASE64: ${{ secrets.MAC_P12_BASE64 }}
MAC_PASS: ${{ secrets.MAC_PASS }}
MAC_CERT_SUBJECT: ${{ secrets.MAC_CERT_SUBJECT }}
steps:
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
- name: Install FVM
run: |
curl -fsSL https://fvm.app/install.sh | bash -s -- "$FVM_VERSION"
echo "$HOME/fvm/bin" >> $GITHUB_PATH
- run: fvm install
- run: fvm flutter pub get
- run: fvm flutter precache --macos
- run: cd macos && pod install --repo-update && cd -
- run: fvm flutter config --enable-macos-desktop
- run: fvm flutter build macos --release
- name: Install create-dmg
run: brew install create-dmg
- name: Create DMG
run: |
create-dmg \
--volname "Subterfuge" \
--window-pos 200 120 \
--window-size 800 400 \
--icon-size 100 \
--icon "${{ env.APP_NAME }}.app" 200 190 \
--hide-extension "${{ env.APP_NAME }}.app" \
--app-drop-link 600 185 \
"Subterfuge-silicon.dmg" \
"build/macos/Build/Products/Release/${{ env.APP_NAME }}.app"
# Sign with paraph (rcodesign under the hood) when MAC_P12_BASE64 is
# configured and we're on a push to main. Skipped otherwise (e.g. PRs
# from forks never have secrets) — the checksum step below then just
# publishes the unsigned .dmg instead.
- name: Sign DMG (paraph)
if: github.event_name == 'push' && env.MAC_P12_BASE64 != ''
run: |
curl -fsSL -o "$RUNNER_TEMP/rcodesign.tar.gz" "https://github.com/indygreg/apple-platform-rs/releases/download/apple-codesign%2F0.29.0/apple-codesign-0.29.0-macos-universal.tar.gz"
echo "d98372d5524226ccf9dc0eda03d4e4f5826182dabb2fc3f2bd303ed9113a748d $RUNNER_TEMP/rcodesign.tar.gz" | shasum -a 256 -c -
tar -xzf "$RUNNER_TEMP/rcodesign.tar.gz" -C "$RUNNER_TEMP"
sudo mv "$RUNNER_TEMP"/apple-codesign-*/rcodesign /usr/local/bin/rcodesign
curl -fsSL -o "$RUNNER_TEMP/paraph.sh" "https://raw.githubusercontent.com/ethicnology/paraph/${{ env.PARAPH_REF }}/paraph.sh"
chmod +x "$RUNNER_TEMP/paraph.sh"
echo "$MAC_P12_BASE64" | base64 -d > "$RUNNER_TEMP/mac.p12"
export MAC_P12="$RUNNER_TEMP/mac.p12"
export MAC_CERT_SUBJECT="${MAC_CERT_SUBJECT:-Subterfuge}"
"$RUNNER_TEMP/paraph.sh" sign Subterfuge-silicon.dmg
"$RUNNER_TEMP/paraph.sh" verify Subterfuge-silicon-signed.dmg || true
- name: Checksum
run: |
FILE=Subterfuge-silicon.dmg
if [ -f Subterfuge-silicon-signed.dmg ]; then FILE=Subterfuge-silicon-signed.dmg; fi
shasum -a 256 "$FILE" | tee "$FILE.sha256"
echo "DMG_FILE=$FILE" >> "$GITHUB_ENV"
- name: Upload macOS Silicon DMG
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: ${{ env.APP_NAME }}-macos-silicon.dmg
path: |
${{ env.DMG_FILE }}
${{ env.DMG_FILE }}.sha256
android:
needs: test
runs-on: ubuntu-24.04
env:
ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
ANDROID_ALIAS: ${{ secrets.ANDROID_ALIAS }}
ANDROID_PASS: ${{ secrets.ANDROID_PASS }}
steps:
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
- uses: actions/setup-java@0f481fcb613427c0f801b606911222b5b6f3083a # v5
with:
distribution: "temurin"
java-version: "17"
cache: "gradle"
- name: Install FVM
run: |
curl -fsSL https://fvm.app/install.sh | bash -s -- "$FVM_VERSION"
echo "$HOME/fvm/bin" >> $GITHUB_PATH
- run: fvm install
- run: fvm flutter pub get
- run: fvm flutter build apk --release
# Gradle deliberately produces an unsigned APK (see
# android/app/build.gradle.kts); sign it here with paraph (apksigner
# under the hood) when ANDROID_KEYSTORE_BASE64 is configured and we're
# on a push to main. Skipped otherwise — the checksum step below then
# just publishes the unsigned APK instead.
- name: Sign APK (paraph)
if: github.event_name == 'push' && env.ANDROID_KEYSTORE_BASE64 != ''
run: |
curl -fsSL -o "$RUNNER_TEMP/paraph.sh" "https://raw.githubusercontent.com/ethicnology/paraph/${{ env.PARAPH_REF }}/paraph.sh"
chmod +x "$RUNNER_TEMP/paraph.sh"
echo "$ANDROID_KEYSTORE_BASE64" | base64 -d > "$RUNNER_TEMP/android.keystore"
export ANDROID_KEYSTORE="$RUNNER_TEMP/android.keystore"
BUILD_TOOLS="$(find "$ANDROID_HOME/build-tools" -maxdepth 1 -mindepth 1 -type d | sort -V | tail -1)"
export PATH="$BUILD_TOOLS:$PATH"
"$RUNNER_TEMP/paraph.sh" sign build/app/outputs/flutter-apk/app-release.apk
"$RUNNER_TEMP/paraph.sh" verify build/app/outputs/flutter-apk/app-release-signed.apk || true
- name: Checksum
working-directory: build/app/outputs/flutter-apk
run: |
FILE=app-release.apk
if [ -f app-release-signed.apk ]; then FILE=app-release-signed.apk; fi
sha256sum "$FILE" | tee "$FILE.sha256"
echo "APK_FILE=$FILE" >> "$GITHUB_ENV"
- name: Upload Android APK
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: ${{ env.APP_NAME }}-android.apk
path: |
build/app/outputs/flutter-apk/${{ env.APK_FILE }}
build/app/outputs/flutter-apk/${{ env.APK_FILE }}.sha256
linux-x64:
needs: test
runs-on: ubuntu-24.04
env:
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
GPG_KEY_ID: ${{ secrets.GPG_KEY_ID }}
GPG_PASS: ${{ secrets.GPG_PASS }}
steps:
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
- run: sudo apt-get update
- run: sudo apt-get install build-essential libgtk-3-dev
- name: Install FVM
run: |
curl -fsSL https://fvm.app/install.sh | bash -s -- "$FVM_VERSION"
echo "$HOME/fvm/bin" >> $GITHUB_PATH
- run: fvm install
- run: fvm flutter pub get
- run: fvm flutter config --enable-linux-desktop
- run: fvm flutter build linux --release --target-platform=linux-x64
- name: Install appimage-builder dependencies
run: sudo apt install -y binutils coreutils desktop-file-utils fakeroot fuse libgdk-pixbuf2.0-dev patchelf python3-pip python3-setuptools squashfs-tools strace util-linux zsync
- name: Install appimage-builder
run: |
curl -fsSL -o appimage-builder-x86_64.AppImage https://github.com/AppImageCrafters/appimage-builder/releases/download/v1.1.0/appimage-builder-1.1.0-x86_64.AppImage
echo "4b4f99cae9291d78ba12dbdabca7c0a67c72aa61eb2e5d424089171a9485e96f appimage-builder-x86_64.AppImage" | sha256sum -c -
chmod +x appimage-builder-x86_64.AppImage
sudo mv appimage-builder-x86_64.AppImage /usr/local/bin/appimage-builder
- name: Build AppImage
run: appimage-builder --recipe appimage_builder_x64.yml --skip-test
# Sign with paraph (a detached gpg signature) when GPG_PRIVATE_KEY is
# configured and we're on a push to main. Skipped otherwise — the
# checksum step below then just publishes the unsigned .AppImage.
- name: Sign AppImage (paraph)
if: github.event_name == 'push' && env.GPG_PRIVATE_KEY != ''
run: |
curl -fsSL -o "$RUNNER_TEMP/paraph.sh" "https://raw.githubusercontent.com/ethicnology/paraph/${{ env.PARAPH_REF }}/paraph.sh"
chmod +x "$RUNNER_TEMP/paraph.sh"
echo "$GPG_PRIVATE_KEY" | gpg --batch --yes --import
APPIMAGE_FILE="$(ls ./*.AppImage | head -1)"
"$RUNNER_TEMP/paraph.sh" sign "$APPIMAGE_FILE"
"$RUNNER_TEMP/paraph.sh" verify "$APPIMAGE_FILE" || true
- name: Checksum
run: sha256sum ./*.AppImage | tee AppImage.sha256
- name: Upload Linux x64 AppImage
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: ${{ env.APP_NAME }}-linux-x64.AppImage
path: |
*.AppImage
*.AppImage.sig
AppImage.sha256
if-no-files-found: warn
windows:
needs: test
runs-on: windows-2025
env:
WINDOWS_P12_BASE64: ${{ secrets.WINDOWS_P12_BASE64 }}
WINDOWS_PASS: ${{ secrets.WINDOWS_PASS }}
steps:
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
- run: choco install fvm --version=${{ env.FVM_VERSION }}
- run: fvm install
- run: fvm flutter pub get
- run: fvm flutter config --enable-windows-desktop
- run: fvm flutter build windows --release
- run: fvm dart run msix:create
# msix:create produces an unsigned package (install_certificate: false
# in pubspec.yaml); sign it here with paraph (osslsigncode under the
# hood) when WINDOWS_P12_BASE64 is configured and we're on a push to
# main. Skipped otherwise — the checksum step below then just
# publishes the unsigned .msix instead. Runs under bash (paraph.sh is
# a bash script; Git Bash ships on GitHub's Windows runners).
- name: Sign MSIX (paraph)
if: github.event_name == 'push' && env.WINDOWS_P12_BASE64 != ''
shell: bash
run: |
choco install osslsigncode -y --no-progress
curl -fsSL -o "$RUNNER_TEMP/paraph.sh" "https://raw.githubusercontent.com/ethicnology/paraph/${{ env.PARAPH_REF }}/paraph.sh"
chmod +x "$RUNNER_TEMP/paraph.sh"
echo "$WINDOWS_P12_BASE64" | base64 -d > "$RUNNER_TEMP/windows.p12"
export WINDOWS_P12="$RUNNER_TEMP/windows.p12"
MSIX_FILE="$(ls build/windows/x64/runner/Release/*.msix | head -1)"
"$RUNNER_TEMP/paraph.sh" sign "$MSIX_FILE"
SIGNED_MSIX="${MSIX_FILE%.msix}-signed.msix"
"$RUNNER_TEMP/paraph.sh" verify "$SIGNED_MSIX" || true
- name: Checksum
working-directory: build\windows\x64\runner\Release
shell: pwsh
run: |
$file = Get-ChildItem -Filter "*-signed.msix" | Select-Object -First 1
if (-not $file) { $file = Get-ChildItem -Filter "*.msix" | Select-Object -First 1 }
Get-FileHash -Algorithm SHA256 $file.Name | Format-List | Out-File -Encoding utf8 "$($file.Name).sha256"
echo "MSIX_FILE=$($file.Name)" >> $env:GITHUB_ENV
- name: Upload Windows MSIX
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: ${{ env.APP_NAME }}-windows-x64.msix
path: |
build\windows\x64\runner\Release\${{ env.MSIX_FILE }}
build\windows\x64\runner\Release\${{ env.MSIX_FILE }}.sha256