From d9b3a7d5e0d51b1665f580ecc51760495b64ade3 Mon Sep 17 00:00:00 2001 From: archipelago Date: Sun, 2 Aug 2026 09:48:19 -0400 Subject: [PATCH] fix(10-03): quote the Dockerfile heredoc so comments cannot execute MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `cat > "$WORK_DIR/Dockerfile.rootfs" < --- .../_archived/build-auto-installer-iso.sh | 51 +++++++++++++------ tests/first-boot-secrets/run-tests.sh | 50 ++++++++++++++++++ 2 files changed, 86 insertions(+), 15 deletions(-) diff --git a/image-recipe/_archived/build-auto-installer-iso.sh b/image-recipe/_archived/build-auto-installer-iso.sh index e6ada1e8..6e9d3332 100755 --- a/image-recipe/_archived/build-auto-installer-iso.sh +++ b/image-recipe/_archived/build-auto-installer-iso.sh @@ -268,7 +268,23 @@ if [ ! -f "$ROOTFS_TAR" ] || [ "${1:-}" == "--rebuild" ] || [ "$(cat "$ROOTFS_ST echo " Using Docker to create Debian root filesystem..." # Create a Dockerfile for building the rootfs - cat > "$WORK_DIR/Dockerfile.rootfs" < "$WORK_DIR/Dockerfile.rootfs" <<'DOCKERFILE_HEAD' # ─── Stage 1: Build the FIPS mesh daemon .deb at a pinned tag ──────────── # # FIPS (github.com/jmcorgan/fips) is a fast Nostr-keyed mesh routing @@ -288,14 +304,14 @@ ENV DEBIAN_FRONTEND=noninteractive # - libnftnl-dev, libmnl-dev, clang, libclang-dev: rustables → # bindgen (the gateway feature enables rustables for nftables # integration). bindgen panics without libclang.so. -RUN apt-get update && apt-get install -y --no-install-recommends \\ - git ca-certificates build-essential pkg-config dpkg-dev \\ - libdbus-1-dev libssl-dev \\ - clang libclang-dev libnftnl-dev libmnl-dev \\ +RUN apt-get update && apt-get install -y --no-install-recommends \ + git ca-certificates build-essential pkg-config dpkg-dev \ + libdbus-1-dev libssl-dev \ + clang libclang-dev libnftnl-dev libmnl-dev \ && rm -rf /var/lib/apt/lists/* RUN cargo install --locked cargo-deb ARG FIPS_VERSION=v0.4.1 -RUN git clone --depth 1 --branch "\$FIPS_VERSION" \\ +RUN git clone --depth 1 --branch "$FIPS_VERSION" \ https://github.com/jmcorgan/fips.git /src/fips WORKDIR /src/fips # fips-gateway is gated behind the `gateway` Cargo feature (depends on @@ -328,10 +344,15 @@ RUN echo "deb http://deb.debian.org/debian trixie main non-free-firmware" > /etc # Install all packages we need including nginx, podman, tor, and openssl (for self-signed certs) RUN apt-get update && apt-get -y full-upgrade && apt-get install -y --no-install-recommends \ - ${LINUX_IMAGE_PKG} \ - ${GRUB_EFI_PKG} \ - ${GRUB_EFI_SIGNED_PKG} \ - ${GRUB_PC_PKG} \ +DOCKERFILE_HEAD + + # The ONLY build-time interpolation in the entire Dockerfile: the kernel and + # GRUB package names, which vary by architecture and Debian suite. + printf ' %s \\\n' \ + "$LINUX_IMAGE_PKG" "$GRUB_EFI_PKG" "$GRUB_EFI_SIGNED_PKG" "$GRUB_PC_PKG" \ + >> "$WORK_DIR/Dockerfile.rootfs" + + cat >> "$WORK_DIR/Dockerfile.rootfs" <<'DOCKERFILE_TAIL' systemd \ systemd-sysv \ dbus \ @@ -438,7 +459,7 @@ RUN useradd -m -s /bin/bash -G sudo,dialout,audio archipelago && \ echo "root:archipelago" | chpasswd && \ echo "archipelago ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/archipelago # Verify password hash was set (not locked) -RUN grep -q "^archipelago:\$" /etc/shadow && echo "Password set OK" || echo "WARNING: password may not be set" +RUN grep -q "^archipelago:$" /etc/shadow && echo "Password set OK" || echo "WARNING: password may not be set" # Set hostname RUN echo "archipelago" > /etc/hostname @@ -482,8 +503,8 @@ RUN mkdir -p /etc/archipelago/ssl # anyone edits that package list. RUN set -e; \ for bin in /usr/bin/openssl /usr/bin/ssh-keygen; do \ - if [ ! -x "\$bin" ]; then \ - echo "FATAL: \$bin missing or not executable in the rootfs." >&2; \ + if [ ! -x "$bin" ]; then \ + echo "FATAL: $bin missing or not executable in the rootfs." >&2; \ echo "first-boot-secrets.sh cannot generate per-device SSH host keys" >&2; \ echo "or the TLS keypair without it, and that failure is permanent." >&2; \ echo "Restore openssl / openssh-server in the package list above." >&2; \ @@ -572,7 +593,7 @@ RUN systemctl enable archipelago-fips.service || true # (env file doesn't exist until onboarding) so we mask it outright. # `systemctl mask` alone doesn't stick because the real .service file is # already in place — explicit rm + /dev/null symlink is what sticks. -RUN rm -f /etc/systemd/system/nostr-vpn.service && \\ +RUN rm -f /etc/systemd/system/nostr-vpn.service && \ ln -sf /dev/null /etc/systemd/system/nostr-vpn.service # Remove policy-rc.d so services can start on first boot @@ -640,7 +661,7 @@ RUN rm -f /etc/ssh/ssh_host_* && \ { [ -L /var/lib/dbus/machine-id ] || rm -f /var/lib/dbus/machine-id ; } && \ mkdir -p /opt/archipelago && \ printf 'F-03 identity strip: this rootfs was built with the identity-strip layer.\nRemoved:\n /etc/ssh/ssh_host_*\n /etc/archipelago/ssl/archipelago.key\n /etc/archipelago/ssl/archipelago.crt\nTruncated:\n /etc/machine-id\nRecreated per device by archipelago-first-boot-secrets.service on first boot.\n' > /opt/archipelago/rootfs-identity-stripped -DOCKERFILE +DOCKERFILE_TAIL # Copy nginx snippets for HTTPS (PWA, app proxies) if [ -d "$SCRIPT_DIR/../configs/snippets" ]; then diff --git a/tests/first-boot-secrets/run-tests.sh b/tests/first-boot-secrets/run-tests.sh index 9157acbe..fd6809c8 100755 --- a/tests/first-boot-secrets/run-tests.sh +++ b/tests/first-boot-secrets/run-tests.sh @@ -364,6 +364,56 @@ else echo " generator heredoc spans lines $SS_START-$SS_END of $BUILDER" fi +# ── Case 7: the Dockerfile heredoc delimiter must be quoted ────────────── +# Lives in this harness rather than a sibling because it guards the same file +# and the same failure mode the rest of these cases exist for: a build-side +# defect that is invisible to `bash -n` and only shows up as damage on a build +# host. Splitting it into its own runner would mean two commands to remember +# and one of them getting skipped. +# +# The bug: `cat > ... <>? "\$WORK_DIR/Dockerfile\.rootfs" <<' "$BUILDER" || true) +if [ -z "$DF_HEREDOCS" ]; then + c7="$c7 no-dockerfile-heredoc-found" +else + while IFS= read -r hd; do + [ -z "$hd" ] && continue + ln=${hd%%:*} + delim=$(printf '%s' "$hd" | sed -E 's/.*<<-?[[:space:]]*//') + case "$delim" in + \'*\'|\"*\") + : ;; # quoted — the body is emitted verbatim, nothing executes + *) + c7="$c7 UNQUOTED-DELIMITER-at-line-$ln" + # Only meaningful when unquoted: report what would actually run. + bare=$(printf '%s' "$delim" | tr -d "\"'") + endln=$(awk -v s="$ln" -v d="$bare" 'NR>s && $0==d { print NR; exit }' "$BUILDER") + if [ -n "$endln" ]; then + subs=$(awk -v s="$ln" -v e="$endln" 'NR>s && NR$c7" +fi + # ── Summary ─────────────────────────────────────────────────────────────── echo echo "──────── first-boot-secrets summary ────────"