What changed, and why it matters
This commit adds an automated GitHub Actions release pipeline for a wallet app. It builds Android and Linux packages, signs them with GPG and Android release keys, and publishes them on GitHub Releases. There is no direct evidence of a security vulnerability in the code changes themselves, but the pipeline handles sensitive secrets and includes a prebuilt binary library that changed without source diff visibility.
Verify the integrity and provenance of the updated `linux/monero_libwallet2_api_c.so` binary against an auditable source build. Review GitHub Actions secret access controls, restrict `contents: write` to the release job only if possible, and ensure the Android keystore and GPG private key are stored with appropriate repository/organization-level protections and rotation policies. Consider pinning third-party Actions to commit SHAs instead of floating tags.
Security signals we found
Workflow uses repository secrets (ANDROID_KEYSTORE_BASE64, ANDROID_STORE_PASSWORD, ANDROID_KEY_PASSWORD, ANDROID_KEY_ALIAS, GPG_PRIVATE_KEY, GITHUB_TOKEN)
Precompiled native library `linux/monero_libwallet2_api_c.so` changed without corresponding source diff
GPG private key imported into CI runner for artifact signing
Android release keystore decoded and written to disk inside CI container
Build runs with `contents: write` permission and publishes releases automatically on tag push
Evidence from the diff
The commit introduces .github/workflows/release.yml to build Docker-based Flutter/Android and Linux AppImage artifacts on version tags, sign them with a GPG key and Android keystore, and attach them to a GitHub release. It also adds a Dockerfile with pinned toolchains, adjusts android/app/build.gradle.kts to conditionally create a release signing config only when key.properties exists, updates the AppImage build script to extract appimagetool to avoid FUSE, adds a PGP public key file, and updates a precompiled linux/monero_libwallet2_api_c.so binary. No malicious code is visible in the diff, but the workflow relies on repository secrets and the binary library change cannot be source-reviewed from this commit.
Changed components
.github/workflows/release.ymlDockerfileandroid/app/build.gradle.ktsappimage/build_appimage.shlinux/monero_libwallet2_api_c.sopubkey.ascInspect captured patch +312 / −10
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 0000000..9d8a7f0
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,164 @@
+name: Build and Release
+
+on:
+ push:
+ tags:
+ - 'v*'
+
+permissions:
+ contents: write
+
+jobs:
+ build:
+ name: Build All Platforms
+ runs-on: ubuntu-latest
+ environment: Release
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+ with:
+ submodules: recursive
+
+ - name: Free up disk space
+ run: |
+ echo "Disk space before cleanup:"
+ df -h
+ sudo rm -rf /usr/share/dotnet
+ sudo rm -rf /usr/local/lib/android
+ sudo rm -rf /opt/ghc
+ sudo rm -rf /opt/hostedtoolcache/CodeQL
+ sudo docker image prune --all --force
+ echo "Disk space after cleanup:"
+ df -h
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
+
+ - name: Extract version from tag
+ id: version
+ run: |
+ echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
+ echo "TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
+
+ - name: Build Docker image for x86_64
+ run: |
+ docker buildx build \
+ --platform linux/amd64 \
+ --load \
+ -t skylight-builder \
+ .
+
+ - name: Set up Android signing
+ run: |
+ # Decode keystore from base64
+ echo "${{ secrets.ANDROID_KEYSTORE_BASE64 }}" | base64 -d > ./android/keystore.jks
+ readlink -f ./android/keystore.jks
+ stat -c %s ./android/keystore.jks
+
+ # Create key.properties file
+ cat > android/key.properties << EOF
+ storePassword=${{ secrets.ANDROID_STORE_PASSWORD }}
+ keyPassword=${{ secrets.ANDROID_KEY_PASSWORD }}
+ keyAlias=${{ secrets.ANDROID_KEY_ALIAS }}
+ storeFile=/workspace/android/keystore.jks
+ EOF
+
+ - name: Build Android APKs and App Bundle
+ run: |
+ docker run --rm \
+ -v $PWD:/workspace \
+ -w /workspace \
+ skylight-builder \
+ bash -c '
+ flutter pub get && \
+ flutter build apk --dart-define=DEMO_MODE=true --release --split-per-abi && \
+ flutter build appbundle --dart-define=DEMO_MODE=true --release && \
+ cd build/app/outputs/flutter-apk && \
+ mv app-x86_64-release.apk skylight-wallet-${{ steps.version.outputs.VERSION }}-x86_64.apk || true && \
+ mv app-armeabi-v7a-release.apk skylight-wallet-${{ steps.version.outputs.VERSION }}-armeabi-v7a.apk || true && \
+ mv app-arm64-v8a-release.apk skylight-wallet-${{ steps.version.outputs.VERSION }}-arm64-v8a.apk || true && \
+ cd ../bundle/release && \
+ mv app-release.aab skylight-wallet-${{ steps.version.outputs.VERSION }}.aab
+ '
+
+ - name: Build Linux x86_64
+ run: |
+ docker run --rm \
+ -v $PWD:/workspace \
+ -w /workspace \
+ skylight-builder \
+ bash -c '
+ flutter pub get && \
+ ./appimage/build_appimage.sh --version ${{ steps.version.outputs.VERSION }}
+ '
+
+ - name: Fix permissions on build artifacts
+ run: |
+ sudo chown -R $USER:$USER build/ appimage/
+
+ - name: List built artifacts
+ run: |
+ echo "=== Android APKs ==="
+ ls -lh build/app/outputs/flutter-apk/*.apk || true
+ echo "=== Android App Bundle ==="
+ ls -lh build/app/outputs/bundle/release/*.aab || true
+ echo "=== Linux AppImage ==="
+ ls -lh appimage/skylight-wallet-*.AppImage || true
+
+ - name: Import GPG key
+ run: |
+ echo "${{ secrets.GPG_PRIVATE_KEY }}" | gpg --batch --import
+ # Get the key ID for signing
+ GPG_KEY_ID=$(gpg --list-secret-keys --keyid-format LONG | grep sec | head -n1 | awk '{print $2}' | cut -d'/' -f2)
+ echo "GPG_KEY_ID=$GPG_KEY_ID" >> $GITHUB_ENV
+
+ - name: Sign artifacts with GPG
+ run: |
+ # Sign Android APKs
+ for file in build/app/outputs/flutter-apk/*.apk; do
+ if [ -f "$file" ]; then
+ gpg --batch --yes --pinentry-mode loopback --detach-sign --armor "$file"
+ echo "Signed: $file"
+ fi
+ done
+
+ # Sign Android App Bundle
+ for file in build/app/outputs/bundle/release/*.aab; do
+ if [ -f "$file" ]; then
+ gpg --batch --yes --pinentry-mode loopback --detach-sign --armor "$file"
+ echo "Signed: $file"
+ fi
+ done
+
+ # Sign Linux AppImage
+ for file in appimage/skylight-wallet-*.AppImage; do
+ if [ -f "$file" ]; then
+ gpg --batch --yes --pinentry-mode loopback --detach-sign --armor "$file"
+ echo "Signed: $file"
+ fi
+ done
+
+ echo "=== Generated signatures ==="
+ find . -name "*.asc" -type f
+
+ - name: Create Release
+ uses: softprops/action-gh-release@v1
+ with:
+ name: ${{ steps.version.outputs.TAG }}
+ draft: false
+ prerelease: false
+ generate_release_notes: true
+ files: |
+ build/app/outputs/flutter-apk/*-x86_64.apk
+ build/app/outputs/flutter-apk/*-x86_64.apk.asc
+ build/app/outputs/flutter-apk/*-armeabi-v7a.apk
+ build/app/outputs/flutter-apk/*-armeabi-v7a.apk.asc
+ build/app/outputs/flutter-apk/*-arm64-v8a.apk
+ build/app/outputs/flutter-apk/*-arm64-v8a.apk.asc
+ build/app/outputs/bundle/release/*.aab
+ build/app/outputs/bundle/release/*.aab.asc
+ appimage/skylight-wallet-*.AppImage
+ appimage/skylight-wallet-*.AppImage.asc
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..cb886cd
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,81 @@
+# Using Debian Bullseye for maximum AppImage compatibility (GLIBC 2.31)
+FROM debian:bullseye-20251117@sha256:ee239c601913c0d3962208299eef70dcffcb7aac1787f7a02f6d3e2b518755e6
+
+ARG TARGETARCH
+
+ARG FLUTTER_VERSION=3.27.1
+ARG RUST_VERSION=1.83.0
+ARG ANDROID_CMDLINE_TOOLS_VERSION=11076708
+ARG ANDROID_BUILD_TOOLS_VERSION=36.0.0
+ARG ANDROID_PLATFORM_VERSION=36
+ARG ANDROID_NDK_VERSION=28.0.13004108
+
+# Install system dependencies with pinned versions
+RUN apt-get update && \
+ apt-get install -y --no-install-recommends \
+ curl=7.74.0-1.3+deb11u15 \
+ wget=1.21-1+deb11u2 \
+ git=1:2.30.2-1+deb11u5 \
+ unzip=6.0-26+deb11u1 \
+ xz-utils=5.2.5-2.1~deb11u1 \
+ zip=3.0-12 \
+ libglu1-mesa=9.0.1-1 \
+ clang=1:11.0-51+nmu5 \
+ cmake=3.18.4-2+deb11u1 \
+ ninja-build=1.10.1-1 \
+ pkg-config=0.29.2-1 \
+ libgtk-3-dev=3.24.24-4+deb11u4 \
+ liblzma-dev=5.2.5-2.1~deb11u1 \
+ libstdc++-10-dev=10.2.1-6 \
+ openjdk-17-jdk=17.0.17+10-1~deb11u1 \
+ ca-certificates=20210119 \
+ build-essential=12.9 \
+ make=4.3-4.1 \
+ perl=5.32.1-4+deb11u4 \
+ libssl-dev=1.1.1w-0+deb11u4 \
+ libsecret-1-dev=0.20.4-2 \
+ libsecret-1-0=0.20.4-2 \
+ file=1:5.39-3+deb11u1 && \
+ apt-get clean && \
+ rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
+
+ENV JAVA_HOME=/usr/lib/jvm/java-17-openjdk-${TARGETARCH}
+ENV ANDROID_HOME=/opt/android-sdk
+ENV ANDROID_SDK_ROOT=/opt/android-sdk
+ENV CARGO_HOME=/opt/cargo
+ENV RUSTUP_HOME=/opt/rustup
+ENV PATH="/flutter/bin:${ANDROID_HOME}/cmdline-tools/latest/bin:${ANDROID_HOME}/platform-tools:${CARGO_HOME}/bin:${PATH}"
+ENV FLUTTER_ROOT="/flutter"
+
+# Install Rust with pinned version
+RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain ${RUST_VERSION} --profile minimal && \
+ . ${CARGO_HOME}/env && \
+ rustup target add aarch64-linux-android armv7-linux-androideabi x86_64-linux-android i686-linux-android
+
+# Install Android SDK command-line tools with pinned version
+RUN mkdir -p ${ANDROID_HOME}/cmdline-tools && \
+ cd ${ANDROID_HOME}/cmdline-tools && \
+ curl -o cmdtools.zip https://dl.google.com/android/repository/commandlinetools-linux-${ANDROID_CMDLINE_TOOLS_VERSION}_latest.zip && \
+ unzip cmdtools.zip && \
+ mv cmdline-tools latest && \
+ rm cmdtools.zip
+
+# Install Android SDK components with pinned versions
+RUN yes | sdkmanager --licenses && \
+ sdkmanager --install \
+ "platform-tools" \
+ "platforms;android-${ANDROID_PLATFORM_VERSION}" \
+ "build-tools;${ANDROID_BUILD_TOOLS_VERSION}" \
+ "ndk;${ANDROID_NDK_VERSION}" && \
+ rm -rf ${ANDROID_HOME}/.android/cache
+
+# Install Flutter with pinned version
+RUN git clone https://github.com/flutter/flutter.git -b ${FLUTTER_VERSION} --depth 1 /flutter && \
+ flutter doctor -v && \
+ flutter config --enable-linux-desktop && \
+ flutter config --no-analytics && \
+ flutter precache --linux --android && \
+ find /flutter -name "*.zip" -delete
+
+WORKDIR /workspace
+
diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts
index 6a323d5..cf0ad9f 100644
--- a/android/app/build.gradle.kts
+++ b/android/app/build.gradle.kts
@@ -42,11 +42,19 @@ android {
}
signingConfigs {
- create("release") {
- keyAlias = keystoreProperties["keyAlias"] as String
- keyPassword = keystoreProperties["keyPassword"] as String
- storeFile = keystoreProperties["storeFile"]?.let { file(it) }
- storePassword = keystoreProperties["storePassword"] as String
+ if (keystorePropertiesFile.exists()) {
+ create("release") {
+ keyAlias = keystoreProperties["keyAlias"] as String
+ keyPassword = keystoreProperties["keyPassword"] as String
+ val storeFilePath = keystoreProperties["storeFile"] as String
+ storeFile = if (storeFilePath.startsWith("/")) {
+ file(storeFilePath) // Absolute path (local build)
+ } else {
+ file(storeFilePath) // Relative path (CI build)
+ }
+ storePassword = keystoreProperties["storePassword"] as String
+ storeType = "JKS" // Explicitly specify keystore type
+ }
}
}
@@ -54,7 +62,9 @@ android {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
- signingConfig = signingConfigs.getByName("release")
+ if (keystorePropertiesFile.exists()) {
+ signingConfig = signingConfigs.getByName("release")
+ }
}
}
}
diff --git a/appimage/.gitignore b/appimage/.gitignore
index 27d63e0..99ca01c 100644
--- a/appimage/.gitignore
+++ b/appimage/.gitignore
@@ -1,5 +1,6 @@
# AppImage build artifacts
appimagetool-*.AppImage
AppDir/
+squashfs-root/
*.AppImage
.last_build_id
\ No newline at end of file
diff --git a/appimage/build_appimage.sh b/appimage/build_appimage.sh
index 4668c7a..307194e 100755
--- a/appimage/build_appimage.sh
+++ b/appimage/build_appimage.sh
@@ -49,7 +49,7 @@ done
if [ -z "$VERSION" ]; then
echo "Error: --version is required"
echo "Usage: $0 --version <version>"
- echo "Example: $0 --version 1.0.0"
+ echo "Example: $0 --version v1.0.0"
exit 1
fi
@@ -128,14 +128,20 @@ fi
echo "✓ Integrity verified"
+# Extract appimagetool if not already extracted (avoids FUSE requirement)
+if [ ! -d "squashfs-root" ]; then
+ echo "Extracting appimagetool..."
+ ./$APPIMAGETOOL_FILENAME --appimage-extract > /dev/null
+fi
+
# Build the AppImage
echo "Packaging AppImage..."
export VERSION
export ARCH=x86_64
-./$APPIMAGETOOL_FILENAME AppDir
+./squashfs-root/AppRun AppDir
GENERATED_APPIMAGE=$(ls -1 Skylight_Wallet-${VERSION}-*.AppImage 2>/dev/null | head -n1)
-DESIRED_NAME="skylight-wallet-v${VERSION}-x86_64.AppImage"
+DESIRED_NAME="skylight-wallet-${VERSION}-x86_64.AppImage"
if [ -n "$GENERATED_APPIMAGE" ]; then
echo "Renaming to: $DESIRED_NAME"
@@ -144,7 +150,7 @@ fi
# Clean up temporary files
echo "Cleaning up..."
-rm -rf AppDir
+rm -rf AppDir squashfs-root
echo ""
echo "✓ AppImage created successfully!"
diff --git a/linux/monero_libwallet2_api_c.so b/linux/monero_libwallet2_api_c.so
index 6094274..d3814aa 100755
Binary files a/linux/monero_libwallet2_api_c.so and b/linux/monero_libwallet2_api_c.so differ
diff --git a/pubkey.asc b/pubkey.asc
new file mode 100644
index 0000000..67135f4
--- /dev/null
+++ b/pubkey.asc
@@ -0,0 +1,40 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+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
+LmYj4LVvUXYp+Fw3t1OX
+=yljw
+-----END PGP PUBLIC KEY BLOCK-----
\ No newline at end of file
Why this scored 11/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.