security: audit remediation #28
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |