Skip to content

Commit febb158

Browse files
committed
build(ci): hash-pin the toolchain bootstrap in the tools image
The image that produces the release APK bootstrapped its whole toolchain by executing remote code with no integrity check beyond TLS: rustup piped from sh.rustup.rs, FVM piped from an unversioned fvm.app/install.sh, and the Android cmdline-tools zip fetched from a _latest.zip URL. Everything downstream is built by those tools, including the Rust FFI crypto, so a compromised or swapped asset reaches the signed artifact. The rest of the repo already pins carefully, the base image by digest and the reproducibility jars by SHA256; this closes the remaining gap using the same sha256sum -c pattern. rustup now comes from its immutable archive path at a pinned rustup version, FVM from its release tarball reproducing the layout install.sh produces, and the Android zip is checked before use. The base image is pinned to linux/amd64, so single x86_64 digests are correct. Each digest was downloaded and computed here; the rustup one also matches the checksum rustup publishes beside the binary. NOT VERIFIED: the image was not built. This host is aarch64 with no x86 emulation, so the amd64 image cannot run here. The URLs, digests, archive layouts and the sha256sum -c pattern were each checked directly, but the first real build will be the first end-to-end proof.
1 parent 3b80444 commit febb158

1 file changed

Lines changed: 39 additions & 9 deletions

File tree

Containerfile.tools

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -107,10 +107,22 @@ RUN adduser $USER sudo
107107
RUN echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers
108108
USER $USER
109109

110-
# Install Rust (pinned via RUST_VERSION arg)
111-
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs -o /tmp/rustup.sh
112-
RUN sh /tmp/rustup.sh -y --no-modify-path --default-toolchain ${RUST_VERSION}
113-
RUN rm /tmp/rustup.sh
110+
# Install Rust (pinned via RUST_VERSION arg).
111+
# rustup-init is fetched from the immutable archive path and checked against
112+
# the SHA256 rustup publishes next to it, rather than piping sh.rustup.rs into
113+
# a shell: everything downstream — the FFI crypto in bull_sdk, payjoin and tor
114+
# — is built by whatever this step installs, so it has to be tamper-evident.
115+
# The base image is pinned to linux/amd64, so a single x86_64 digest is right.
116+
# Refresh: curl -fsSL https://static.rust-lang.org/rustup/archive/${RUSTUP_VERSION}/x86_64-unknown-linux-gnu/rustup-init.sha256
117+
ARG RUSTUP_VERSION="1.29.0"
118+
ARG RUSTUP_INIT_SHA256="4acc9acc76d5079515b46346a485974457b5a79893cfb01112423c89aeb5aa10"
119+
RUN curl --proto '=https' --tlsv1.2 -fsSL \
120+
https://static.rust-lang.org/rustup/archive/${RUSTUP_VERSION}/x86_64-unknown-linux-gnu/rustup-init \
121+
-o /tmp/rustup-init \
122+
&& echo "${RUSTUP_INIT_SHA256} /tmp/rustup-init" | sha256sum -c - \
123+
&& chmod +x /tmp/rustup-init \
124+
&& /tmp/rustup-init -y --no-modify-path --default-toolchain ${RUST_VERSION} \
125+
&& rm /tmp/rustup-init
114126
ENV PATH="/home/$USER/.cargo/bin:${PATH}"
115127

116128
# Pre-install clippy and rustfmt for the pinned toolchain.
@@ -173,10 +185,22 @@ RUN if [ -n "$EXPECTED_RUST_VERSION" ]; then \
173185
fi; \
174186
fi
175187

176-
# Install FVM
177-
RUN curl -fsSL https://fvm.app/install.sh -o /tmp/fvm-install.sh
178-
RUN bash /tmp/fvm-install.sh ${FVM_VERSION}
179-
RUN rm /tmp/fvm-install.sh
188+
# Install FVM from its pinned release tarball rather than piping fvm.app's
189+
# install.sh into bash: that script is itself unversioned, so only the FVM
190+
# version was pinned and never the installer fetching it. The layout below is
191+
# what install.sh produces — it copies the contents of the archive's fvm/
192+
# directory into ${INSTALL_BASE}/bin, which is the directory added to PATH.
193+
# Refresh: sha256sum of the linux-x64 asset on the FVM release for this version.
194+
ARG FVM_SHA256="2d7fdfc3e77591806efe38fab23098a79567a89a9570de713c43f30cc54348d7"
195+
RUN curl -fsSL \
196+
https://github.com/leoafarias/fvm/releases/download/${FVM_VERSION}/fvm-${FVM_VERSION}-linux-x64.tar.gz \
197+
-o /tmp/fvm.tar.gz \
198+
&& echo "${FVM_SHA256} /tmp/fvm.tar.gz" | sha256sum -c - \
199+
&& mkdir -p /home/$USER/fvm/bin /tmp/fvm-extract \
200+
&& tar -xzf /tmp/fvm.tar.gz -C /tmp/fvm-extract \
201+
&& cp -a /tmp/fvm-extract/fvm/. /home/$USER/fvm/bin/ \
202+
&& chmod +x /home/$USER/fvm/bin/fvm \
203+
&& rm -rf /tmp/fvm.tar.gz /tmp/fvm-extract
180204
ENV PATH="/home/$USER/fvm/bin:${PATH}"
181205

182206
# Install Flutter via FVM.
@@ -196,7 +220,13 @@ ENV PATH="/home/$USER/fvm/default/bin:${PATH}"
196220
# podman user namespaces — sudo fails with "account validation failure" —
197221
# and are unnecessary at build time since USER switching achieves the same.)
198222
USER root
199-
RUN wget -q https://dl.google.com/android/repository/commandlinetools-linux-${ANDROID_CMDLINE_TOOLS_VERSION}_latest.zip -O /tmp/android-cmdline-tools.zip
223+
# The zip is versioned but Google serves it from a _latest.zip URL, so verify
224+
# the digest: this archive provides sdkmanager, which installs the rest of the
225+
# Android SDK used to build the release APK.
226+
# Refresh: sha256sum of the downloaded zip for this ANDROID_CMDLINE_TOOLS_VERSION.
227+
ARG ANDROID_CMDLINE_TOOLS_SHA256="04453066b540409d975c676d781da1477479dde3761310f1a7eb92a1dfb15af7"
228+
RUN wget -q https://dl.google.com/android/repository/commandlinetools-linux-${ANDROID_CMDLINE_TOOLS_VERSION}_latest.zip -O /tmp/android-cmdline-tools.zip \
229+
&& echo "${ANDROID_CMDLINE_TOOLS_SHA256} /tmp/android-cmdline-tools.zip" | sha256sum -c -
200230

201231
# Set up Android SDK
202232
RUN mkdir -p ${ANDROID_HOME}/cmdline-tools

0 commit comments

Comments
 (0)