What changed, and why it matters
This commit is a routine build-system update for a cryptocurrency wallet app. It adds a new automated build workflow for Linux and Android, updates the Docker image used for builds, and changes some scripts to download pre-built crypto libraries from GitHub Releases instead of compiling them from source. There is no direct evidence in the diff of a security vulnerability, malicious code, or a backdoor. However, switching to downloaded pre-built binaries introduces a supply-chain risk: if the download source or release artifacts are compromised, the build pipeline could incorporate malicious native libraries into the wallet without reviewers seeing the source code change.
Treat this as a build-hygiene review, not an active vulnerability. Reviewers should inspect the per-plugin `download.sh` scripts and the GitHub Releases they pull from to confirm checksum/signature verification is performed. Verify that the new CI secrets handling does not leak decoded values in logs, and audit the `stackwallet/stackwallet-ci:latest` image supply chain. If no verification exists, require adding reproducible checksums or signatures for all downloaded native libraries before relying on `-d` builds for releases.
Security signals we found
Build pipeline now downloads pre-built native crypto libraries from external releases rather than compiling from source
No visible checksum or signature verification in the added download orchestration scripts
New CI workflow decodes base64 secrets (`CHANGE_NOW`, Android keystore) into build files
Dockerfile adds Android SDK/NDK and OpenJDK, expanding the container's attack surface
Submodules for crypto plugins are referenced but their download/verification logic is not shown in this commit
Evidence from the diff
The patch adds .github/workflows/build.yaml to automate release builds for Linux and Android inside a Docker container, including APK/AAB signing and GitHub release artifact upload. It updates Dockerfile to install OpenJDK 17, the Android SDK command-line tools, NDK 28.2, and Flutter Android precache. It modifies scripts/build_app.sh to add a -d flag that, when set, invokes new download_all.sh scripts for Linux and Android. These scripts download flutter_libepiccash, flutter_libmwc, and frostdart binaries via per-plugin download.sh scripts instead of building them from source. The test workflow is updated to use -d. The diff does not show the contents of the download.sh scripts, the source of the release artifacts, or any integrity verification (checksums/signatures) for the downloaded binaries.
Changed components
.github/workflows/build.yaml.github/workflows/test.yamlDockerfilescripts/build_app.shscripts/linux/download_all.shscripts/android/download_all.shcrypto_plugins/flutter_libepiccashcrypto_plugins/flutter_libmwccrypto_plugins/frostdartInspect captured patch +297 / −23
diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml
new file mode 100644
index 0000000..2e2ed58
--- /dev/null
+++ b/.github/workflows/build.yaml
@@ -0,0 +1,209 @@
+name: Build
+
+on:
+ push:
+ tags:
+ - 'v[0-9]+.[0-9]+.[0-9]+'
+ workflow_dispatch:
+ inputs:
+ version:
+ description: 'App version string (e.g. 1.2.3)'
+ required: true
+ default: '0.0.1'
+ build_number:
+ description: 'Build number (integer)'
+ required: true
+ default: '1'
+
+jobs:
+
+ build-linux:
+ runs-on: ubuntu-24.04
+ container:
+ image: stackwallet/stackwallet-ci:latest
+ credentials:
+ username: ${{ secrets.DOCKERHUB_USERNAME }}
+ password: ${{ secrets.DOCKERHUB_TOKEN }}
+ steps:
+ - uses: actions/checkout@v6
+ with:
+ fetch-depth: 0
+ submodules: recursive
+
+ - name: Set version
+ id: ver
+ run: |
+ if [ "${{ github.ref_type }}" = "tag" ]; then
+ VERSION="${{ github.ref_name }}"
+ VERSION="${VERSION#v}"
+ BUILD_NUMBER="${{ github.run_number }}"
+ else
+ VERSION="${{ inputs.version }}"
+ BUILD_NUMBER="${{ inputs.build_number }}"
+ fi
+ echo "version=${VERSION}" >> $GITHUB_OUTPUT
+ echo "build_number=${BUILD_NUMBER}" >> $GITHUB_OUTPUT
+
+ - name: Configure app
+ run: |
+ cd scripts
+ echo "yes" | ./build_app.sh \
+ -v "${{ steps.ver.outputs.version }}" \
+ -b "${{ steps.ver.outputs.build_number }}" \
+ -p linux -a stack_wallet -d -s
+
+ - name: Get dependencies
+ run: flutter pub get
+
+ - name: Create git_versions.dart stubs
+ run: |
+ mkdir -p crypto_plugins/flutter_libepiccash/lib
+ mkdir -p crypto_plugins/flutter_libmwc/lib
+
+ EPIC_TAG=$(git -C crypto_plugins/flutter_libepiccash describe --tags --exact-match HEAD 2>/dev/null || echo "dev")
+ MWC_TAG=$(git -C crypto_plugins/flutter_libmwc describe --tags --exact-match HEAD 2>/dev/null || echo "dev")
+
+ printf 'String getPluginVersion() => "%s";\n' "$EPIC_TAG" \
+ > crypto_plugins/flutter_libepiccash/lib/git_versions.dart
+ printf 'String getPluginVersion() => "%s";\n' "$MWC_TAG" \
+ > crypto_plugins/flutter_libmwc/lib/git_versions.dart
+
+ - name: Decode secrets
+ env:
+ CHANGE_NOW: ${{ secrets.CHANGE_NOW }}
+ run: echo "$CHANGE_NOW" | base64 --decode > lib/external_api_keys.dart
+
+ - name: Generate app config
+ run: dart run build_runner build --delete-conflicting-outputs
+
+ - name: Build
+ run: flutter build linux --release
+
+ - name: Package
+ run: |
+ tar -czf "stack_wallet-linux-x86_64-${{ steps.ver.outputs.version }}.tar.gz" \
+ -C build/linux/x64/release bundle
+
+ - uses: actions/upload-artifact@v4
+ with:
+ name: stack_wallet-linux-x86_64-${{ steps.ver.outputs.version }}
+ path: stack_wallet-linux-x86_64-${{ steps.ver.outputs.version }}.tar.gz
+
+ build-android:
+ runs-on: ubuntu-24.04
+ container:
+ image: stackwallet/stackwallet-ci:latest
+ credentials:
+ username: ${{ secrets.DOCKERHUB_USERNAME }}
+ password: ${{ secrets.DOCKERHUB_TOKEN }}
+ steps:
+ - uses: actions/checkout@v6
+ with:
+ fetch-depth: 0
+ submodules: recursive
+
+ - name: Set version
+ id: ver
+ run: |
+ if [ "${{ github.ref_type }}" = "tag" ]; then
+ VERSION="${{ github.ref_name }}"
+ VERSION="${VERSION#v}"
+ BUILD_NUMBER="${{ github.run_number }}"
+ else
+ VERSION="${{ inputs.version }}"
+ BUILD_NUMBER="${{ inputs.build_number }}"
+ fi
+ echo "version=${VERSION}" >> $GITHUB_OUTPUT
+ echo "build_number=${BUILD_NUMBER}" >> $GITHUB_OUTPUT
+
+ - name: Configure app
+ run: |
+ cd scripts
+ echo "yes" | ./build_app.sh \
+ -v "${{ steps.ver.outputs.version }}" \
+ -b "${{ steps.ver.outputs.build_number }}" \
+ -p android -a stack_wallet -d -s
+
+ - name: Get dependencies
+ run: flutter pub get
+
+ - name: Create git_versions.dart stubs
+ run: |
+ mkdir -p crypto_plugins/flutter_libepiccash/lib
+ mkdir -p crypto_plugins/flutter_libmwc/lib
+
+ EPIC_TAG=$(git -C crypto_plugins/flutter_libepiccash describe --tags --exact-match HEAD 2>/dev/null || echo "dev")
+ MWC_TAG=$(git -C crypto_plugins/flutter_libmwc describe --tags --exact-match HEAD 2>/dev/null || echo "dev")
+
+ printf 'String getPluginVersion() => "%s";\n' "$EPIC_TAG" \
+ > crypto_plugins/flutter_libepiccash/lib/git_versions.dart
+ printf 'String getPluginVersion() => "%s";\n' "$MWC_TAG" \
+ > crypto_plugins/flutter_libmwc/lib/git_versions.dart
+
+ - name: Decode secrets
+ env:
+ CHANGE_NOW: ${{ secrets.CHANGE_NOW }}
+ run: echo "$CHANGE_NOW" | base64 --decode > lib/external_api_keys.dart
+
+ - name: Generate app config
+ run: dart run build_runner build --delete-conflicting-outputs
+
+ - name: Set up Android local.properties
+ run: |
+ cat > android/local.properties <<EOF
+ sdk.dir=${ANDROID_SDK_ROOT}
+ flutter.sdk=${FLUTTER_HOME}
+ EOF
+
+ - name: Set up signing
+ env:
+ KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
+ run: |
+ echo "$KEYSTORE_BASE64" | base64 --decode > android/keystore.jks
+ cat > android/key.properties <<EOF
+ storeFile=../keystore.jks
+ storePassword=${{ secrets.ANDROID_STORE_PASSWORD }}
+ keyPassword=${{ secrets.ANDROID_KEY_PASSWORD }}
+ keyAlias=${{ secrets.ANDROID_KEY_ALIAS }}
+ EOF
+
+ - name: Build APKs
+ run: flutter build apk --split-per-abi --release
+
+ - name: Build AAB
+ run: flutter build appbundle --release
+
+ - name: Collect artifacts
+ run: |
+ VERSION="${{ steps.ver.outputs.version }}"
+ mkdir -p android-artifacts
+ cp build/app/outputs/flutter-apk/app-arm64-v8a-release.apk \
+ android-artifacts/stack_wallet-android-arm64-v8a-${VERSION}.apk
+ cp build/app/outputs/flutter-apk/app-armeabi-v7a-release.apk \
+ android-artifacts/stack_wallet-android-armeabi-v7a-${VERSION}.apk
+ cp build/app/outputs/flutter-apk/app-x86_64-release.apk \
+ android-artifacts/stack_wallet-android-x86_64-${VERSION}.apk
+ cp build/app/outputs/bundle/release/app-release.aab \
+ android-artifacts/stack_wallet-android-${VERSION}.aab
+
+ - uses: actions/upload-artifact@v4
+ with:
+ name: stack_wallet-android-${{ steps.ver.outputs.version }}
+ path: android-artifacts/
+
+ release:
+ if: github.ref_type == 'tag'
+ needs: [build-linux, build-android]
+ runs-on: ubuntu-latest
+ permissions:
+ contents: write
+ steps:
+ - uses: actions/download-artifact@v4
+ with:
+ path: artifacts
+ merge-multiple: true
+
+ - uses: softprops/action-gh-release@v2
+ with:
+ generate_release_notes: true
+ files: artifacts/*
diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml
index 6ced5c9..622387b 100644
--- a/.github/workflows/test.yaml
+++ b/.github/workflows/test.yaml
@@ -18,7 +18,7 @@ jobs:
- name: Configure app
run: |
cd scripts
- echo "yes" | ./build_app.sh -v "0.0.1" -b "1" -p "linux" -a "stack_wallet" -s
+ echo "yes" | ./build_app.sh -v "0.0.1" -b "1" -p "linux" -a "stack_wallet" -d -s
- name: Get dependencies
run: flutter pub get
diff --git a/Dockerfile b/Dockerfile
index 74440bf..d3bdfde 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -6,6 +6,7 @@ ENV DEBIAN_FRONTEND=noninteractive \
LANG=C.UTF-8 \
LC_ALL=C.UTF-8
+
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
RUN apt-get update && apt-get install -y --no-install-recommends \
@@ -18,6 +19,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
ocl-icd-opencl-dev opencl-headers valac zlib1g-dev \
g++-aarch64-linux-gnu gcc-aarch64-linux-gnu \
g++-mingw-w64-x86-64 gcc-mingw-w64-x86-64 \
+ openjdk-17-jdk-headless \
&& rm -rf /var/lib/apt/lists/*
RUN curl -fsSL https://deb.nodesource.com/setup_24.x | bash - \
@@ -30,19 +32,33 @@ ENV RUSTUP_HOME=/usr/local/rustup \
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \
| sh -s -- -y --default-toolchain 1.89.0 --profile minimal --no-modify-path \
- && rustup install 1.85.1 --profile minimal \
+ && rustup install 1.85.1 1.71.0 --profile minimal \
&& rustup target add x86_64-unknown-linux-gnu --toolchain 1.89.0 \
&& cargo install cargo-ndk \
&& chmod -R a+rwX "$CARGO_HOME" "$RUSTUP_HOME"
-ENV ANDROID_NDK_ROOT=/opt/android-ndk-r28 \
- ANDROID_NDK_HOME=/opt/android-ndk-r28
+ENV JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64
+
+ENV ANDROID_SDK_ROOT=/opt/android-sdk \
+ ANDROID_HOME=/opt/android-sdk \
+ ANDROID_NDK_ROOT=/opt/android-sdk/ndk/28.2.13676358 \
+ ANDROID_NDK_HOME=/opt/android-sdk/ndk/28.2.13676358 \
+ PATH=/opt/android-sdk/cmdline-tools/latest/bin:/opt/android-sdk/platform-tools:$PATH
-RUN curl -fsSL https://dl.google.com/android/repository/android-ndk-r28-linux.zip \
- -o /tmp/android-ndk.zip \
- && echo "a186b67e8810cb949514925e4f7a2255548fb55f5e9b0824a6430d012c1b695b /tmp/android-ndk.zip" | sha256sum -c \
- && unzip -q /tmp/android-ndk.zip -d /opt \
- && rm /tmp/android-ndk.zip
+RUN mkdir -p "$ANDROID_SDK_ROOT/cmdline-tools" \
+ && curl -fsSL https://dl.google.com/android/repository/commandlinetools-linux-14742923_latest.zip \
+ -o /tmp/cmdline-tools.zip \
+ && echo "48833c34b761c10cb20bcd16582129395d121b27 /tmp/cmdline-tools.zip" | sha1sum -c \
+ && unzip -q /tmp/cmdline-tools.zip -d "$ANDROID_SDK_ROOT/cmdline-tools" \
+ && mv "$ANDROID_SDK_ROOT/cmdline-tools/cmdline-tools" "$ANDROID_SDK_ROOT/cmdline-tools/latest" \
+ && rm /tmp/cmdline-tools.zip \
+ && yes | sdkmanager --licenses \
+ && sdkmanager \
+ "platform-tools" \
+ "build-tools;35.0.0" \
+ "platforms;android-35" \
+ "ndk;28.2.13676358" \
+ && chmod -R a+rwX "$ANDROID_SDK_ROOT"
ENV FLUTTER_HOME=/opt/flutter \
PATH=/opt/flutter/bin:/opt/flutter/bin/cache/dart-sdk/bin:$PATH
@@ -50,7 +66,7 @@ ENV FLUTTER_HOME=/opt/flutter \
RUN git clone --depth 1 --branch 3.38.1 https://github.com/flutter/flutter.git "$FLUTTER_HOME" \
&& git config --global --add safe.directory '*' \
&& flutter config --no-analytics \
- && flutter precache --linux \
+ && flutter precache --linux --android \
&& chmod -R a+rwX "$FLUTTER_HOME"
RUN git config --system --add safe.directory '*'
diff --git a/scripts/android/download_all.sh b/scripts/android/download_all.sh
new file mode 100755
index 0000000..1db80d8
--- /dev/null
+++ b/scripts/android/download_all.sh
@@ -0,0 +1,17 @@
+#!/bin/bash
+
+set -x -e
+
+mkdir -p build
+. ./config.sh
+
+PLUGINS_DIR=../../crypto_plugins
+
+(cd "${PLUGINS_DIR}"/flutter_libepiccash/scripts/android && ./download.sh)
+
+(cd "${PLUGINS_DIR}"/flutter_libmwc/scripts/android && ./download.sh)
+
+(cd "${PLUGINS_DIR}"/frostdart/scripts/android && ./download.sh)
+
+wait
+echo "Done"
diff --git a/scripts/build_app.sh b/scripts/build_app.sh
index 3672100..893856e 100755
--- a/scripts/build_app.sh
+++ b/scripts/build_app.sh
@@ -9,7 +9,7 @@ APP_NAMED_IDS=("stack_wallet" "stack_duo" "campfire")
# Function to display usage.
usage() {
- echo "Usage: $0 -v <version> -b <build_number> -p <platform> -a <app> [-i] [-f] [-s]"
+ echo "Usage: $0 -v <version> -b <build_number> -p <platform> -a <app> [-d] [-i] [-f] [-s]"
exit 1
}
@@ -33,17 +33,19 @@ unset -v APP_NAMED_ID
# optional args (with defaults)
BUILD_CRYPTO_PLUGINS=0
+DOWNLOAD_CRYPTO_PLUGINS=0
BUILD_ISAR_FROM_SOURCE=0
USE_SYSTEM_SECURE_STORAGE_DEPS=0
# Parse command-line arguments.
-while getopts "v:b:p:a:i:fs" opt; do
+while getopts "v:b:p:a:idfs" opt; do
case "${opt}" in
v) APP_VERSION_STRING="$OPTARG" ;;
b) APP_BUILD_NUMBER="$OPTARG" ;;
p) APP_BUILD_PLATFORM="$OPTARG" ;;
a) APP_NAMED_ID="$OPTARG" ;;
i) BUILD_CRYPTO_PLUGINS=1 ;;
+ d) DOWNLOAD_CRYPTO_PLUGINS=1 ;;
f) BUILD_ISAR_FROM_SOURCE=1 ;;
s) USE_SYSTEM_SECURE_STORAGE_DEPS=1 ;;
*) usage ;;
@@ -114,15 +116,28 @@ else
fi
if [ "$BUILD_CRYPTO_PLUGINS" -eq 0 ]; then
- if [[ "$APP_NAMED_ID" = "stack_wallet" ]]; then
- ./build_all.sh
- elif [[ "$APP_NAMED_ID" = "stack_duo" ]]; then
- ./build_all_duo.sh
- elif [[ "$APP_NAMED_ID" = "campfire" ]]; then
- ./build_all_campfire.sh
+ if [ "$DOWNLOAD_CRYPTO_PLUGINS" -eq 1 ]; then
+ if [[ "$APP_NAMED_ID" = "stack_wallet" ]]; then
+ ./download_all.sh
+ elif [[ "$APP_NAMED_ID" = "stack_duo" ]]; then
+ ./build_all_duo.sh
+ elif [[ "$APP_NAMED_ID" = "campfire" ]]; then
+ ./build_all_campfire.sh
+ else
+ echo "Invalid app id: ${APP_NAMED_ID}"
+ exit 1
+ fi
else
- echo "Invalid app id: ${APP_NAMED_ID}"
- exit 1
+ if [[ "$APP_NAMED_ID" = "stack_wallet" ]]; then
+ ./build_all.sh
+ elif [[ "$APP_NAMED_ID" = "stack_duo" ]]; then
+ ./build_all_duo.sh
+ elif [[ "$APP_NAMED_ID" = "campfire" ]]; then
+ ./build_all_campfire.sh
+ else
+ echo "Invalid app id: ${APP_NAMED_ID}"
+ exit 1
+ fi
fi
fi
diff --git a/scripts/linux/download_all.sh b/scripts/linux/download_all.sh
new file mode 100755
index 0000000..5e56389
--- /dev/null
+++ b/scripts/linux/download_all.sh
@@ -0,0 +1,17 @@
+#!/bin/bash
+
+set -x -e
+
+mkdir -p build
+./build_secure_storage_deps.sh
+
+(cd ../../crypto_plugins/flutter_libepiccash/scripts/linux && ./download.sh)
+
+(cd ../../crypto_plugins/flutter_libmwc/scripts/linux && ./download.sh)
+
+(cd ../../crypto_plugins/frostdart/scripts/linux && ./download.sh)
+
+./build_secp256k1.sh
+
+wait
+echo "Done"
Why this scored 26/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.