mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
Two release-pipeline defects found while pre-flighting 2.2.0, plus the image hardening that the second one exposed. The release job would have died immediately after pushing the v2.2.0 tag. draftRelease was turned on in #649 and never executed, and GitHub's /releases/tags/{tag} endpoint does not return draft releases, so all nine tag lookups in release.yml would have 404'd against the draft semantic-release had just created. Verified against this repo with a throwaway draft: the tag endpoint 404s while gh release view reads it and /releases/{id} returns the same REST shape. Every site now resolves the numeric id first, so existing jq expressions are untouched. The unfixed-vulnerability gate was measuring almost nothing. The blocking Trivy steps run ignore-unfixed, and trivy-unfixed-gate.mjs was meant to cover the remainder but defaults to CRITICAL with neither call site passing --severity. An unfixed HIGH was gated by nothing, and the arm64 image carried 79 of them while the summary read clean. Rather than document 79 findings, the image lost what it did not need: libde265 1.1.1 and libheif 1.23.1 are now built from source (the old libheif pin was itself affected by CVE-2026-3950, and Debian's libde265 1.0.11 was the decoder every .heic upload actually reached), and xvfb, wget and openssh-client are purged. 15 CVEs left the image outright and the HIGH gap fell to 65, each now carrying a rationale verified against the running container. curl gets its own section: bookworm-backports has a fixed 8.14.1, so claiming no fix was available would have been false. It is recorded as a declined fix. Verified on both architectures: gate exits 0, the source-built libde265 is the one libheif links, and HEIC, RAW, ImageMagick, Sharp AVIF and headless chromium all still work after the purge.
148 lines
6.4 KiB
Docker
148 lines
6.4 KiB
Docker
# ============================================
|
|
# SnapOtter - Test Dockerfile
|
|
# Runs the full test suite (unit + integration)
|
|
# ============================================
|
|
|
|
# ============================================
|
|
# Stage 1: Build libheif from source
|
|
# ============================================
|
|
FROM node:22-bookworm@sha256:5647be709086c696ff32edaaf1c70cd26d1da6ab2b39c32f3c7b4c4a31957e37 AS libheif-builder
|
|
|
|
ARG LIBHEIF_VERSION=1.23.1
|
|
ARG LIBDE265_VERSION=1.1.1
|
|
|
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
cmake pkg-config gcc g++ make curl ca-certificates \
|
|
libx265-dev libjpeg-dev libpng-dev \
|
|
&& rm -rf /var/lib/apt/lists/*
|
|
|
|
# Kept in step with docker/Dockerfile: the test image has to exercise the same
|
|
# codec stack that ships, or the docker suite validates decoders we do not use.
|
|
RUN curl --fail --location --silent --show-error --retry 3 \
|
|
--proto '=https' --tlsv1.2 \
|
|
--output /tmp/libde265.tar.gz \
|
|
"https://github.com/strukturag/libde265/releases/download/v${LIBDE265_VERSION}/libde265-${LIBDE265_VERSION}.tar.gz" \
|
|
&& printf '%s %s\n' \
|
|
"fd48a927e94ed74fc7ce8829d222b9d8599fcbfe8b6448ba66705babc56ab219" \
|
|
/tmp/libde265.tar.gz | sha256sum --check --strict - \
|
|
&& tar -xzf /tmp/libde265.tar.gz \
|
|
&& cmake -B build-de265 -S "libde265-${LIBDE265_VERSION}" \
|
|
-DCMAKE_INSTALL_PREFIX=/opt/libheif \
|
|
-DBUILD_SHARED_LIBS=ON \
|
|
-DENABLE_SDL=OFF \
|
|
&& cmake --build build-de265 -j$(nproc) \
|
|
&& cmake --install build-de265
|
|
|
|
RUN curl --fail --location --silent --show-error --retry 3 \
|
|
--proto '=https' --tlsv1.2 \
|
|
--output /tmp/libheif.tar.gz \
|
|
"https://github.com/strukturag/libheif/releases/download/v${LIBHEIF_VERSION}/libheif-${LIBHEIF_VERSION}.tar.gz" \
|
|
&& printf '%s %s\n' \
|
|
"0de0327f60fcd47de90d5654c6fe152232738d60d84fe084ec3e0f35e03b166a" \
|
|
/tmp/libheif.tar.gz | sha256sum --check --strict - \
|
|
&& tar -xzf /tmp/libheif.tar.gz \
|
|
&& PKG_CONFIG_PATH=/opt/libheif/lib/pkgconfig \
|
|
cmake -B build -S "libheif-${LIBHEIF_VERSION}" \
|
|
-DCMAKE_INSTALL_PREFIX=/opt/libheif \
|
|
-DCMAKE_PREFIX_PATH=/opt/libheif \
|
|
-DWITH_EXAMPLES=ON \
|
|
-DWITH_GDK_PIXBUF=OFF \
|
|
-DWITH_AOM_DECODER=OFF \
|
|
-DWITH_AOM_ENCODER=OFF \
|
|
-DWITH_DAV1D=OFF \
|
|
&& cmake --build build -j$(nproc) \
|
|
&& cmake --install build \
|
|
&& test -e /opt/libheif/lib/libde265.so \
|
|
&& readelf -d /opt/libheif/lib/libheif.so | grep -q 'NEEDED.*libde265'
|
|
|
|
# ============================================
|
|
# Stage 2: Test runner
|
|
# ============================================
|
|
FROM node:22-bookworm@sha256:5647be709086c696ff32edaaf1c70cd26d1da6ab2b39c32f3c7b4c4a31957e37
|
|
|
|
RUN corepack enable && corepack prepare pnpm@9.15.4 --activate
|
|
|
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
libde265-0 \
|
|
libimage-exiftool-perl \
|
|
imagemagick \
|
|
libraw-dev \
|
|
libjxl-tools \
|
|
ghostscript \
|
|
qpdf \
|
|
tesseract-ocr \
|
|
tesseract-ocr-eng tesseract-ocr-deu tesseract-ocr-fra \
|
|
tesseract-ocr-spa tesseract-ocr-chi-sim tesseract-ocr-jpn \
|
|
&& if apt-cache show libmagickcore-6.q16-7-extra >/dev/null 2>&1; then \
|
|
apt-get install -y --no-install-recommends libmagickcore-6.q16-7-extra; \
|
|
elif apt-cache show libmagickcore-6.q16-6-extra >/dev/null 2>&1; then \
|
|
apt-get install -y --no-install-recommends libmagickcore-6.q16-6-extra; \
|
|
else \
|
|
echo "No supported ImageMagick EXR coder package found" >&2; exit 1; \
|
|
fi \
|
|
&& convert -list format | grep -Eq '^[[:space:]]*EXR([*[:space:]]|$)' \
|
|
&& if apt-cache show libx265-199 >/dev/null 2>&1; then \
|
|
apt-get install -y --no-install-recommends libx265-199; \
|
|
elif apt-cache show libx265-209 >/dev/null 2>&1; then \
|
|
apt-get install -y --no-install-recommends libx265-209; \
|
|
fi \
|
|
&& rm -rf /var/lib/apt/lists/*
|
|
|
|
COPY --from=libheif-builder /opt/libheif/bin/ /usr/local/bin/
|
|
COPY --from=libheif-builder /opt/libheif/lib/ /usr/local/lib/
|
|
# The base image ships an older system libheif (~1.15) that shadows our built
|
|
# 1.21 without this, so heif-dec fails with an undefined-symbol error.
|
|
ENV LD_LIBRARY_PATH=/usr/local/lib
|
|
RUN ldconfig
|
|
|
|
# Allow ImageMagick's Ghostscript delegate to read EPS. Decoding an EPS goes
|
|
# through the PostScript (PS) coder, so the default Debian policy.xml blocking
|
|
# PS/PS2/PS3 must be opened too, not just EPS -- otherwise `convert` refuses with
|
|
# a policy error before Ghostscript ever runs.
|
|
RUN POLICY_FILE=$(find /etc/ImageMagick* -name policy.xml 2>/dev/null | head -1) && \
|
|
if [ -n "$POLICY_FILE" ]; then \
|
|
for CODER in EPS PS PS2 PS3; do \
|
|
sed -i "s/<policy domain=\"coder\" rights=\"none\" pattern=\"${CODER}\"/<policy domain=\"coder\" rights=\"read|write\" pattern=\"${CODER}\"/" "$POLICY_FILE"; \
|
|
done; \
|
|
fi
|
|
|
|
WORKDIR /app
|
|
|
|
# Copy workspace config first (for layer caching)
|
|
COPY pnpm-workspace.yaml pnpm-lock.yaml package.json turbo.json tsconfig.base.json vitest.config.ts ./
|
|
# patchedDependencies (gray-matter) needs the patch files present at install
|
|
# time, otherwise pnpm exits with ENOENT (exit 254). The prod Dockerfile copies
|
|
# these too; the test image was missing them.
|
|
COPY patches/ ./patches/
|
|
|
|
# Copy all package.json files
|
|
COPY apps/web/package.json apps/web/tsconfig.json apps/web/vite.config.ts ./apps/web/
|
|
COPY apps/api/package.json apps/api/tsconfig.json ./apps/api/
|
|
COPY packages/shared/package.json packages/shared/tsconfig.json ./packages/shared/
|
|
COPY packages/image-engine/package.json packages/image-engine/tsconfig.json ./packages/image-engine/
|
|
COPY packages/ai/package.json packages/ai/tsconfig.json ./packages/ai/
|
|
# enterprise ships @aws-sdk/client-s3, which tests/integration/s3-storage.test.ts
|
|
# imports at module load; without it that suite fails to collect.
|
|
COPY packages/enterprise/package.json packages/enterprise/tsconfig.json ./packages/enterprise/
|
|
|
|
# Install ALL dependencies (including devDependencies for testing).
|
|
RUN pnpm install --frozen-lockfile
|
|
|
|
# Copy source code
|
|
COPY . .
|
|
|
|
# Non-sensitive test defaults. Authentication credentials belong to the test
|
|
# runner or Compose environment rather than the reusable image metadata.
|
|
ENV NODE_ENV=test \
|
|
WORKSPACE_PATH=/tmp/test-workspace \
|
|
MAX_MEGAPIXELS=100 \
|
|
MAX_UPLOAD_SIZE_MB=100 \
|
|
MAX_BATCH_SIZE=200 \
|
|
CONCURRENT_JOBS=3 \
|
|
RATE_LIMIT_PER_MIN=1000 \
|
|
FILE_MAX_AGE_HOURS=1 \
|
|
CLEANUP_INTERVAL_MINUTES=60
|
|
|
|
# Run unit + integration tests with coverage
|
|
CMD ["pnpm", "test:all"]
|