psf-libheif-oct2026-check(8)
libheif HEIF/AVIF decoder check (v1)
If this software is helpful, consider a donation to our 501c3 nonprofit:
https://privacysafe.foundation
A heap buffer overflow in libheif's HEIF decoding (reached through
ImageMagick on an attacker-supplied image) was used to get remote code
execution on a Discourse forum and pivot further. The bug class spans many
libheif CVEs with no single CVE for the Discourse case; the upstream commit
shipped without one. Debian fixed a batch in DSA-6417-1 (libheif
1.19.8-1+deb13u1, trixie, 8 Aug 2026); other distributions backport under
their own version strings, and some stable branches stayed vulnerable for a
while. Upstream security releases have continued (1.21.0, 1.22.0, 1.22.1,
1.23.x). See https://www.hacktron.ai/blog/hacking-openai
This is NOT the Ruby Regexp issue. For Ruby/GitLab, use
ruby/ruby-regexp-oct2026-check.sh instead.
Usage:
./libheif-oct2026-check.sh [command]
check (default) read-only: every libheif and libde265 on the host, the
ImageMagick and libvips that reach them, every Discourse
install (whose image-upload path runs an attacker-supplied
file through ImageMagick into libheif), and container
images, with whether each libheif carries the fixes. For
a Discourse container it says whether a rebuild is needed
rather than a restart. Root optional. Exit 0 = nothing
exposed, 1 = a vulnerable libheif is reachable, 2 =
something couldn't be read.
policy read-only: whether ImageMagick's policy.xml still lets
HEIC/HEIF/AVIF through. Disabling those coders protects the system until
you can patch libheif. Exit 0 =
HEIF is blocked everywhere a policy was found, 1 = HEIF
is still allowed, 2 = no policy found.
harden write a managed block into ImageMagick's policy.xml that
refuses the HEIC/HEIF/AVIF coders, so a crafted upload
never reaches libheif through ImageMagick. Backs the file
up first, is reversible with unharden, and is the one
thing this script changes. Needs root. This is defense in
depth, NOT a substitute for updating libheif.
unharden remove that managed block and restore. Needs root. Only
touches a block this script wrote.
mw-harden block HEIF/HEIC/AVIF uploads in every MediaWiki found, by
adding those MIME types to wgMimeTypeExclusions in
LocalSettings.php, so a crafted upload is rejected by its
detected type before the scaler runs. Backs up first,
checks the result still parses as PHP, reversible with
mw-unharden. Needs root. Application-level defense; the
host libheif still needs updating.
mw-unharden remove that MediaWiki block and restore. Needs root.
hunt read-only: the journal and logs for image-processor
crashes (ImageMagick/libheif/libde265 segfaults, HEIF
decoder frames, repeated convert/magick aborts).
Exit 0 = clean, 1 = findings, 2 = no journal or log could
be read, so nothing was searched.
version print the script version.
The real fix is to update libheif (and libde265) and rebuild anything that
bundles it (Discourse containers especially); this script does not run that
for you, because a package upgrade or a container rebuild is an administrator's
call to make and time. The only change it will make is the ImageMagick coder
policy, and only when you ask for it with harden.
Env overrides:
HEIF_FIXED_UPSTREAM=1.22.0 upstream floor the heap-write class is
fixed at (coarse: distro backports under
older numbers are judged by FIXED_BUILDS
and the package changelog first)
FIXED_BUILDS="1.19.8-1+deb13u1 1.17.6-1ubuntu4.6"
distro builds known to carry the fixes,
space-separated full versions
DISCOURSE_ROOT="/var/discourse" where the Docker launcher lives
DISCOURSE_APP="/var/www/discourse" where a bare-metal checkout lives
POLICY_PATHS="/etc/ImageMagick-6 /etc/ImageMagick-7 ..."
where policy looks for policy.xml
HUNT_SINCE="2026-07-01" how far back hunt reads the journal
HUNT_LOGS="/var/log/nginx/*.log" extra log globs for hunt
BACKUP_ROOT=/var/backups/libheif-oct2026
where harden and mw-harden keep the backup
of each config file they change
PSF_LOCK_WAIT seconds a run that changes the host waits for another one that is already
changing it (default 30), then stops with exit 2; the lock is STATE_DIR/.lockThe Security Patches software and this documentation are dedicated to the public domain under CC0 1.0 Universal. Other pages on privacysafe.app/docs are CC BY-SA 4.0 unless the page says otherwise.