From f527aca7a4d6666cfa8383e677ad213ee7c51c8c Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Fri, 3 Jul 2026 12:11:50 +0530 Subject: [PATCH 1/3] Ship hardened ImageMagick policy to mitigate image-bomb DoS The storage/avatars preview pipeline decodes untrusted images through Imagick with no bound on decoded dimensions. A crafted image (small on disk, enormous dimensions) decodes unbounded and spills ImageMagick's pixel cache to disk, filling the volume and killing MongoDB or the container. Install a hardened policy.xml capping width/height (hard reject before raster allocation), disk spill, and memory/map/area, and disabling the delegate coders never used for previews. Placed in ImageMagick's configure dir (discovered at build time); the build fails if it does not load. Added a tests.yaml assertion. --- CHANGES.md | 6 ++++++ Dockerfile | 12 +++++++++++ policy.xml | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ tests.yaml | 8 ++++++++ 4 files changed, 86 insertions(+) create mode 100644 policy.xml diff --git a/CHANGES.md b/CHANGES.md index ad9ad1f..b36e22f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,11 @@ # CHANGELOG +## Version 1.3.2 + +### Security + +* Ship a hardened ImageMagick `policy.xml` in the final image to mitigate image-decompression-bomb DoS via the Appwrite storage/avatars preview pipeline. A crafted image (small on disk, huge dimensions) previously decoded unbounded, spilling ImageMagick's pixel cache to disk and filling the volume — killing MongoDB or the container. The policy caps `width`/`height` (16KP, hard reject before raster allocation) and `disk` (1GiB spill limit), sets `memory`/`map`/`area`/`thread` limits, and disables delegate coders never used for previews (PS/EPS/PDF/XPS/MSL/MVG/HTTP/etc.). Installed into ImageMagick's configure dir (discovered at build time); the build fails if the policy does not load. Added a `tests.yaml` assertion verifying the policy is active. + ## Version 1.3.1 ### Fix diff --git a/Dockerfile b/Dockerfile index 3c88a0f..86e6171 100644 --- a/Dockerfile +++ b/Dockerfile @@ -244,6 +244,18 @@ RUN apk update && \ zstd-libs \ && rm -rf /var/cache/apk/* +# Hardened ImageMagick policy — bounds per-decode width/height/disk so a crafted +# "image bomb" (small on disk, enormous when decoded) cannot exhaust memory or +# fill the disk and take down the container or its neighbours. Installed into +# ImageMagick's configure dir, discovered at build time so it survives package +# path changes; the build fails loudly if the policy does not load. +COPY policy.xml /tmp/policy.xml +RUN set -eux; \ + POLICY_DIR="$(identify -list configure | awk '/^CONFIGURE_PATH/ {print $2}')"; \ + cp /tmp/policy.xml "${POLICY_DIR}policy.xml"; \ + rm /tmp/policy.xml; \ + identify -list policy | grep -q '16KP' + WORKDIR /usr/src/code COPY --from=core-extensions /artifacts/ /tmp/exts/ diff --git a/policy.xml b/policy.xml new file mode 100644 index 0000000..06c8afb --- /dev/null +++ b/policy.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + +]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests.yaml b/tests.yaml index 926e132..48a9432 100644 --- a/tests.yaml +++ b/tests.yaml @@ -97,6 +97,14 @@ commandTests: args: ["-i"] expectedOutput: - "ImageMagick supported formats .*WEBP.*" + - name: 'ImageMagick hardened policy loaded' + command: "identify" + args: ["-list", "policy"] + expectedOutput: + - "name: width" + - "name: height" + - "name: disk" + - "16KP" - name: 'PHP intl' command: "php" args: ["-r", 'print(\Normalizer::FORM_D);'] From 4bd6f26b09bf402c7deb56dac2620b0bac71fed3 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Fri, 3 Jul 2026 12:44:32 +0530 Subject: [PATCH 2/3] Address review: harden policy path handling, block SVG via module deny - Dockerfile: strip trailing slash and take first entry of a colon-separated CONFIGURE_PATH so the policy is always written to a valid path. - policy.xml: deny the SVG/SVGZ/MSVG module. A coder deny alone leaves the rsvg delegate route open; the module deny closes SVG's SSRF/XXE vector entirely. Verified jpg/png/gif/heic/avif still round-trip. --- CHANGES.md | 2 +- Dockerfile | 4 ++-- policy.xml | 7 ++++++- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index b36e22f..66f1c3c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,7 +4,7 @@ ### Security -* Ship a hardened ImageMagick `policy.xml` in the final image to mitigate image-decompression-bomb DoS via the Appwrite storage/avatars preview pipeline. A crafted image (small on disk, huge dimensions) previously decoded unbounded, spilling ImageMagick's pixel cache to disk and filling the volume — killing MongoDB or the container. The policy caps `width`/`height` (16KP, hard reject before raster allocation) and `disk` (1GiB spill limit), sets `memory`/`map`/`area`/`thread` limits, and disables delegate coders never used for previews (PS/EPS/PDF/XPS/MSL/MVG/HTTP/etc.). Installed into ImageMagick's configure dir (discovered at build time); the build fails if the policy does not load. Added a `tests.yaml` assertion verifying the policy is active. +* Ship a hardened ImageMagick `policy.xml` in the final image to mitigate image-decompression-bomb DoS via the Appwrite storage/avatars preview pipeline. A crafted image (small on disk, huge dimensions) previously decoded unbounded, spilling ImageMagick's pixel cache to disk and filling the volume — killing MongoDB or the container. The policy caps `width`/`height` (16KP, hard reject before raster allocation) and `disk` (1GiB spill limit), sets `memory`/`map`/`area`/`thread` limits, and disables coders/modules never used for previews (PS/EPS/PDF/XPS/MSL/MVG/HTTP/etc., plus SVG/SVGZ/MSVG via a `module` deny that also closes the SVG SSRF/XXE delegate route). Installed into ImageMagick's configure dir (discovered at build time); the build fails if the policy does not load. Added a `tests.yaml` assertion verifying the policy is active. ## Version 1.3.1 diff --git a/Dockerfile b/Dockerfile index 86e6171..fded13f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -251,8 +251,8 @@ RUN apk update && \ # path changes; the build fails loudly if the policy does not load. COPY policy.xml /tmp/policy.xml RUN set -eux; \ - POLICY_DIR="$(identify -list configure | awk '/^CONFIGURE_PATH/ {print $2}')"; \ - cp /tmp/policy.xml "${POLICY_DIR}policy.xml"; \ + POLICY_DIR="$(identify -list configure | awk '/^CONFIGURE_PATH/ {print $2}' | cut -d: -f1)"; \ + cp /tmp/policy.xml "${POLICY_DIR%/}/policy.xml"; \ rm /tmp/policy.xml; \ identify -list policy | grep -q '16KP' diff --git a/policy.xml b/policy.xml index 06c8afb..75a93a5 100644 --- a/policy.xml +++ b/policy.xml @@ -52,9 +52,14 @@ - + + + + From 1e1370c9eeaf474b9747a7165eee5a24cb5cfe21 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Fri, 3 Jul 2026 13:42:38 +0530 Subject: [PATCH 3/3] Raise dimension/disk limits so high-res phone photos are not rejected 16KP (16000px) rejected legitimate high-res phone photos - a 200MP sensor is ~16320px on its long side. Raise width/height to 50KP as a fast-fail backstop only, and make disk (4GiB) the primary DoS control. ImageMagick in the base image is Q16-HDRI (16 bytes/pixel), so a 150MP photo needs ~2.4GB of cache; 4GiB disk gives headroom while memory stays at 256MiB to bound per-worker RAM. Verified a 9180x16320 PNG and a realistic 150MP JPEG both preview-resize successfully, while a 60000px image is rejected on dimensions and a ~1.6GP image is bounded by the disk cap. --- CHANGES.md | 2 +- Dockerfile | 2 +- policy.xml | 26 +++++++++++++++++--------- tests.yaml | 2 +- 4 files changed, 20 insertions(+), 12 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 66f1c3c..4bbcdac 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,7 +4,7 @@ ### Security -* Ship a hardened ImageMagick `policy.xml` in the final image to mitigate image-decompression-bomb DoS via the Appwrite storage/avatars preview pipeline. A crafted image (small on disk, huge dimensions) previously decoded unbounded, spilling ImageMagick's pixel cache to disk and filling the volume — killing MongoDB or the container. The policy caps `width`/`height` (16KP, hard reject before raster allocation) and `disk` (1GiB spill limit), sets `memory`/`map`/`area`/`thread` limits, and disables coders/modules never used for previews (PS/EPS/PDF/XPS/MSL/MVG/HTTP/etc., plus SVG/SVGZ/MSVG via a `module` deny that also closes the SVG SSRF/XXE delegate route). Installed into ImageMagick's configure dir (discovered at build time); the build fails if the policy does not load. Added a `tests.yaml` assertion verifying the policy is active. +* Ship a hardened ImageMagick `policy.xml` in the final image to mitigate image-decompression-bomb DoS via the Appwrite storage/avatars preview pipeline. A crafted image (small on disk, huge dimensions) previously decoded unbounded, spilling ImageMagick's pixel cache to disk and filling the volume — killing MongoDB or the container. The policy caps `disk` (4GiB spill limit — the primary control; sized for a 150MP Q16-HDRI photo) plus generous `width`/`height` backstops (50KP, well above any real camera so legitimate high-res photos are never rejected), sets `memory`/`map`/`area`/`thread` limits, and disables coders/modules never used for previews (PS/EPS/PDF/XPS/MSL/MVG/HTTP/etc., plus SVG/SVGZ/MSVG via a `module` deny that also closes the SVG SSRF/XXE delegate route). Installed into ImageMagick's configure dir (discovered at build time); the build fails if the policy does not load. Added a `tests.yaml` assertion verifying the policy is active. ## Version 1.3.1 diff --git a/Dockerfile b/Dockerfile index fded13f..9332369 100644 --- a/Dockerfile +++ b/Dockerfile @@ -254,7 +254,7 @@ RUN set -eux; \ POLICY_DIR="$(identify -list configure | awk '/^CONFIGURE_PATH/ {print $2}' | cut -d: -f1)"; \ cp /tmp/policy.xml "${POLICY_DIR%/}/policy.xml"; \ rm /tmp/policy.xml; \ - identify -list policy | grep -q '16KP' + identify -list policy | grep -q '50KP' WORKDIR /usr/src/code diff --git a/policy.xml b/policy.xml index 75a93a5..ea431ba 100644 --- a/policy.xml +++ b/policy.xml @@ -30,22 +30,30 @@ - - - - + + + + - + - + - + - - + + diff --git a/tests.yaml b/tests.yaml index 48a9432..69482b2 100644 --- a/tests.yaml +++ b/tests.yaml @@ -104,7 +104,7 @@ commandTests: - "name: width" - "name: height" - "name: disk" - - "16KP" + - "50KP" - name: 'PHP intl' command: "php" args: ["-r", 'print(\Normalizer::FORM_D);']