What changed, and why it matters
This commit improves build reproducibility: it pins exact software versions (Docker images, source-code commits, Android NDK), uses fixed build paths, and removes non-deterministic timestamps. These are defensive hardening changes that make it easier for anyone to independently verify that the published app was built from the claimed source code. There is no direct vulnerability fix here, but the changes reduce supply-chain risk and make future tampering harder to hide.
No immediate action required; this is a hardening change. Users and auditors should verify that future releases are reproducible from the pinned source commits and container digests, and continue to monitor the monero_c dependency for security updates.
Security signals we found
Pinned container image digests to prevent tag-rolling supply-chain attacks
Pinned monero_c dependency to a commit hash instead of a mutable branch
Added deterministic SOURCE_DATE_EPOCH and fixed build paths for reproducible builds
Added -ffile-prefix-map to strip absolute paths from binaries
Removed dynamic pubspec.lock commit-hash overwrite in CI
Evidence from the diff
The patch switches CI container images from floating tags (debian:bookworm, debian:trixie) to pinned SHA256 digests, pins the monero_c dependency to a specific commit (8de1614734007782bb2701ab16c49adf0df8e850) read from pubspec.lock instead of a branch (lwsf), sets SOURCE_DATE_EPOCH and GIT_COMMITTER_DATE for deterministic builds, uses a fixed /tmp/monero_c build path with -ffile-prefix-map to strip absolute paths, updates Android NDK to 28.1.13356709, and adds a reproducible.patch that forces make jobserver-style=pipe and adds prefix maps for Android and CMake builds. The release workflow also mounts the workspace at /tmp/skylight and exports SOURCE_DATE_EPOCH into the builder container.
Changed components
GitHub Actions CI workflows (.github/workflows/build-monero-c.yml, .github/workflows/release.yml)Docker builder image (Dockerfile.builder)Dependency lock files (pubspec.lock, pubspec.yaml)monero_c build script (scripts/build-moneroc.sh)Reproducibility patch for monero_c (scripts/reproducible.patch)Inspect captured patch +112 / −36
diff --git a/.github/workflows/build-monero-c.yml b/.github/workflows/build-monero-c.yml
index 2160439..e4e4a8e 100644
--- a/.github/workflows/build-monero-c.yml
+++ b/.github/workflows/build-monero-c.yml
@@ -12,7 +12,7 @@ jobs:
name: Build x86_64-linux-gnu
runs-on: ubuntu-latest
container:
- image: debian:bookworm
+ image: debian:bookworm@sha256:30482e873082e906a4908c10529180aefb6f77620aea7404b909829fadc5d168
steps:
- name: Checkout repository
uses: actions/checkout@v6
@@ -37,7 +37,7 @@ jobs:
name: Build x86_64-linux-android
runs-on: ubuntu-latest
container:
- image: debian:bookworm
+ image: debian:trixie@sha256:d07d1b51c39f51188e60be9b64e6bf769fa94e187f092bc32b91305cfa34ba5a
steps:
- name: Checkout repository
uses: actions/checkout@v6
@@ -62,7 +62,7 @@ jobs:
name: Build aarch64-linux-android
runs-on: ubuntu-latest
container:
- image: debian:bookworm
+ image: debian:trixie@sha256:d07d1b51c39f51188e60be9b64e6bf769fa94e187f092bc32b91305cfa34ba5a
steps:
- name: Checkout repository
uses: actions/checkout@v6
@@ -87,7 +87,7 @@ jobs:
name: Build armv7a-linux-androideabi
runs-on: ubuntu-latest
container:
- image: debian:bookworm
+ image: debian:trixie@sha256:d07d1b51c39f51188e60be9b64e6bf769fa94e187f092bc32b91305cfa34ba5a
steps:
- name: Checkout repository
uses: actions/checkout@v6
@@ -112,7 +112,7 @@ jobs:
name: Build x86_64-w64-mingw32
runs-on: ubuntu-latest
container:
- image: debian:bookworm
+ image: debian:bookworm@sha256:30482e873082e906a4908c10529180aefb6f77620aea7404b909829fadc5d168
steps:
- name: Checkout repository
uses: actions/checkout@v6
@@ -144,13 +144,8 @@ jobs:
- aarch64-apple-ios
- aarch64-apple-iossimulator
steps:
- - name: Checkout monero_c repo
+ - name: Checkout app repo (for the monero_c commit pinned in pubspec.lock)
uses: actions/checkout@v6
- with:
- repository: vtnerd/monero_c
- ref: lwsf
- fetch-depth: 0
- submodules: recursive
- name: Setup Xcode
uses: maxim-lobanov/setup-xcode@v1
@@ -161,22 +156,27 @@ jobs:
run: |
brew install ccache cmake autoconf automake libtool
- - name: Patch sources
+ - name: Build monero_c for ${{ matrix.target }} (pinned to pubspec.lock)
run: |
+ MONEROC_COMMIT=$(awk '/^ monero:/{f=1} f&&/resolved-ref:/{gsub(/"/,"",$2);print $2;exit}' pubspec.lock)
+ [ -n "$MONEROC_COMMIT" ] || { echo "no monero resolved-ref in pubspec.lock"; exit 1; }
git config --global --add safe.directory '*'
- git config --global user.email "ci@mrcyjanek.net"
- git config --global user.name "CI mrcyjanek.net"
+ git config --global user.email "info@magicgrants.org"
+ git config --global user.name "MAGIC Grants"
+ git clone https://github.com/vtnerd/monero_c.git /tmp/monero_c
+ cd /tmp/monero_c
+ git checkout "$MONEROC_COMMIT"
+ git submodule update --init --recursive
+ export SOURCE_DATE_EPOCH=$(git log -1 --format=%ct)
+ export GIT_COMMITTER_DATE="@$SOURCE_DATE_EPOCH"
./apply_patches.sh monero
-
- - name: Build monero_c for ${{ matrix.target }}
- run: |
./build_single.sh monero ${{ matrix.target }} -j$(sysctl -n hw.logicalcpu)
- name: Upload built library
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.target }}
- path: monero_libwallet2_api_c/build/${{ matrix.target }}/libwallet2_api_c.dylib
+ path: /tmp/monero_c/monero_libwallet2_api_c/build/${{ matrix.target }}/libwallet2_api_c.dylib
create-ios-xcframework:
name: Create iOS XCFramework
@@ -329,11 +329,6 @@ jobs:
- name: Fix Linux library executable stack
run: scripts/fix-linux-moneroc-execstack.sh
- - name: Update pubspec.lock with monero_c commit hash
- run: |
- COMMIT_HASH=$(git ls-remote https://github.com/vtnerd/monero_c refs/heads/lwsf | cut -f1)
- sed -i "s/resolved-ref: \"[a-f0-9]*\"/resolved-ref: \"${COMMIT_HASH}\"/" pubspec.lock
-
- name: Clean up artifacts
run: rm -rf artifacts
@@ -355,7 +350,8 @@ jobs:
- `windows/libssp-0.dll` (x86_64-w64-mingw32)
- `ios/Frameworks/MoneroWallet.xcframework` (aarch64-apple-ios + aarch64-apple-iossimulator)
- Built from [monero_c](https://github.com/vtnerd/monero_c) branch `lwsf`.
+ Built from [monero_c](https://github.com/vtnerd/monero_c) at the commit pinned in `pubspec.lock`.
+ To bump monero_c: edit the `monero` ref in `pubspec.yaml`, run `flutter pub get`, then re-run this workflow.
branch: update-moneroc-libs
delete-branch: true
reviewers: SamsungGalaxyPlayer,Keeqler
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 0672361..06c2832 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -136,17 +136,21 @@ jobs:
storePassword=${{ secrets.ANDROID_STORE_PASSWORD }}
keyPassword=${{ secrets.ANDROID_KEY_PASSWORD }}
keyAlias=${{ secrets.ANDROID_KEY_ALIAS }}
- storeFile=/workspace/android/keystore.jks
+ storeFile=/tmp/skylight/android/keystore.jks
EOF
- name: Build APKs and AAB
run: |
VERSION='${{ needs.version.outputs.version }}'
+ # Deterministic timestamp for reproducible builds
+ export SOURCE_DATE_EPOCH=$(git log -1 --format=%ct)
+ # Fixed build path for reproducibility
docker run --rm \
- -v "$PWD:/workspace" \
- -w /workspace \
+ -v "$PWD:/tmp/skylight" \
+ -w /tmp/skylight \
+ -e SOURCE_DATE_EPOCH \
ghcr.io/magicgrants/skylight-wallet-builder:latest \
- 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"
+ bash -c "export CARGO_HOME=/tmp/skylight-cargo && flutter pub get && flutter build apk --dart-define=DEMO_MODE=true --release --split-per-abi && flutter build appbundle --dart-define=DEMO_MODE=true --release"
mkdir -p dist
cp -v build/app/outputs/flutter-apk/app-arm64-v8a-release.apk "dist/skylight-wallet-${VERSION}-arm64-v8a.apk"
diff --git a/.gitignore b/.gitignore
index 79c113f..166b3e0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -43,3 +43,6 @@ app.*.map.json
/android/app/debug
/android/app/profile
/android/app/release
+
+# Local reproducibility test outputs (.so builds + logs)
+/repro-out/
diff --git a/Dockerfile.builder b/Dockerfile.builder
index b5eafad..d69fb85 100644
--- a/Dockerfile.builder
+++ b/Dockerfile.builder
@@ -7,7 +7,7 @@ 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
+ARG ANDROID_NDK_VERSION=28.1.13356709
# Install system dependencies with pinned versions
RUN apt-get update && \
diff --git a/pubspec.lock b/pubspec.lock
index 53308b6..1e58ad0 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -657,8 +657,8 @@ packages:
dependency: "direct main"
description:
path: "impls/monero.dart"
- ref: lwsf
- resolved-ref: e3645fc466b429c976aa5c4e6abc94db65aebcf1
+ ref: "8de1614734007782bb2701ab16c49adf0df8e850"
+ resolved-ref: "8de1614734007782bb2701ab16c49adf0df8e850"
url: "https://github.com/vtnerd/monero_c"
source: git
version: "0.0.0"
diff --git a/pubspec.yaml b/pubspec.yaml
index b86dbe7..a53bd10 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -57,7 +57,7 @@ dependencies:
monero:
git:
url: https://github.com/vtnerd/monero_c
- ref: lwsf
+ ref: 8de1614734007782bb2701ab16c49adf0df8e850
path: impls/monero.dart
cupertino_icons: 1.0.8
path: 1.9.1
diff --git a/scripts/build-moneroc.sh b/scripts/build-moneroc.sh
index d6504e2..1b40cf1 100755
--- a/scripts/build-moneroc.sh
+++ b/scripts/build-moneroc.sh
@@ -1,8 +1,9 @@
#!/bin/bash
set -e
+export DEBIAN_FRONTEND=noninteractive DEBCONF_NOWARNINGS=yes
apt update
-apt upgrade -y
+apt install -y apt-utils
apt install -y build-essential pkg-config autoconf libtool ccache make cmake gcc g++ git curl \
lbzip2 libtinfo5 gperf unzip python-is-python3 llvm gcc-mingw-w64-x86-64 g++-mingw-w64-x86-64
@@ -13,9 +14,27 @@ git config --global --add safe.directory '*'
git config --global user.email "info@magicgrants.org"
git config --global user.name "MAGIC Grants"
-git clone https://github.com/vtnerd/monero_c.git
-cd monero_c
-git checkout lwsf
+REPO="$PWD"
+
+# Reproducible builds: build at a FIXED canonical path so the depends prefix baked
+# into openssl/unbound match.
+# Symlink it back into the workspace so this workflow's later `cp monero_c/...` steps resolve.
+# Single source of truth: the monero_c commit pinned in pubspec.lock.
+MONEROC_COMMIT=$(awk '/^ monero:/{f=1} f&&/resolved-ref:/{gsub(/"/,"",$2);print $2;exit}' "$REPO/pubspec.lock")
+[ -n "$MONEROC_COMMIT" ] || { echo "could not read monero resolved-ref from pubspec.lock"; exit 1; }
+
+rm -rf /tmp/monero_c "$REPO/monero_c"
+git clone https://github.com/vtnerd/monero_c.git /tmp/monero_c
+ln -s /tmp/monero_c "$REPO/monero_c"
+cd /tmp/monero_c
+git checkout "$MONEROC_COMMIT"
git submodule update --init
+
+# Pin timestamps: apply_patches' `git am` commit SHA (baked into Monero's version
+# string) and __DATE__/__TIME__ must be deterministic
+export SOURCE_DATE_EPOCH="$(git log -1 --format=%ct)"
+export GIT_COMMITTER_DATE="@$SOURCE_DATE_EPOCH"
+
./apply_patches.sh monero
+patch -p1 < "$REPO/scripts/reproducible.patch"
./build_single.sh monero $TARGET_ARCH -j$(nproc)
\ No newline at end of file
diff --git a/scripts/reproducible.patch b/scripts/reproducible.patch
new file mode 100644
index 0000000..ec6459c
--- /dev/null
+++ b/scripts/reproducible.patch
@@ -0,0 +1,54 @@
+diff --git a/build_single.sh b/build_single.sh
+index 77d5a52..cbff2ef 100755
+--- a/build_single.sh
++++ b/build_single.sh
+@@ -54,12 +54,18 @@ then
+ fi
+ cd $(dirname $0)
+ WDIR=$PWD
++# make 4.4 defaults to a fifo jobserver that breaks depends' recursive makes with
++# -jN ("invalid --jobserver-auth"). Force the classic pipe jobserver where the flag
++# exists (make >= 4.4); on make 4.3 it's already the default, so this stays empty.
++if make --help 2>/dev/null | grep -q -- '--jobserver-style'; then
++ export MAKEFLAGS="${MAKEFLAGS:+$MAKEFLAGS }--jobserver-style=pipe"
++fi
+ pushd contrib/depends
+ if [[ -d $HOST_ABI ]];
+ then
+ echo "Not building depends, directory exists"
+ else
+- env -i PATH="$PATH" CC=gcc CXX=g++ make "$NPROC" HOST="$HOST_ABI" DEPENDS_UNTRUSTED_FAST_BUILDS=$DEPENDS_UNTRUSTED_FAST_BUILDS
++ env -i PATH="$PATH" MAKEFLAGS="$MAKEFLAGS" CC=gcc CXX=g++ make "$NPROC" HOST="$HOST_ABI" DEPENDS_UNTRUSTED_FAST_BUILDS=$DEPENDS_UNTRUSTED_FAST_BUILDS
+ fi
+ popd
+
+diff --git a/contrib/depends/hosts/android.mk b/contrib/depends/hosts/android.mk
+index 827103c..2e186ce 100644
+--- a/contrib/depends/hosts/android.mk
++++ b/contrib/depends/hosts/android.mk
+@@ -18,7 +18,8 @@ android_CXX=$(host_toolchain)clang++
+ android_RANLIB=llvm-ranlib
+ android_AR=llvm-ar
+
+-android_CFLAGS=-pipe
++# -ffile-prefix-map: reproducible builds, strip depends work-dir paths (BASEDIR=contrib/depends)
++android_CFLAGS=-pipe -ffile-prefix-map=$(BASEDIR)=.
+ android_CXXFLAGS=$(android_CFLAGS)
+ android_ARFLAGS=crsD
+
+diff --git a/contrib/depends/toolchain.cmake.in b/contrib/depends/toolchain.cmake.in
+index cb94cf3..aa44d39 100644
+--- a/contrib/depends/toolchain.cmake.in
++++ b/contrib/depends/toolchain.cmake.in
+@@ -206,3 +206,11 @@ endif()
+
+ #Create a new global cmake flag that indicates building with depends
+ set (DEPENDS true)
++
++# Reproducible builds: strip absolute build paths from binaries.
++# Rewrites __FILE__ (epee logging -> .rodata, survives strip) and debug info so
++# output is identical regardless of the checkout directory. Each build maps its
++# own monero_c root to the same sentinel ".".
++get_filename_component(_MONEROC_ROOT "${CMAKE_CURRENT_LIST_DIR}/../../../.." ABSOLUTE)
++string(APPEND CMAKE_C_FLAGS_INIT " -ffile-prefix-map=${_MONEROC_ROOT}=.")
++string(APPEND CMAKE_CXX_FLAGS_INIT " -ffile-prefix-map=${_MONEROC_ROOT}=.")
Why this scored 18/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.