Skip to content

docs(licenses): pillow-heif facts match its 1.8.0 wheel — x265, libheif 1.23.4 - #187

Merged
MrChengLen merged 11 commits into
mainfrom
pr-pillow-heif-docs
Oct 8, 2026
Merged

MrChengLen merged 11 commits into
mainfrom
pr-pillow-heif-docs

Conversation

@MrChengLen

Copy link
Copy Markdown
Owner

Two findings from the code review of #164 (pillow-heif 1.7.0 → 1.8.0). Both were already wrong on main before that PR.

What changes

1. Licence (docs/third-party-licenses.md, docs/tech-stack-rationale.md)
The docs said pillow-heif's wheel metadata declares GPLv2 and that pip-licenses or the SBOM will flag it. Since pillow-heif 1.6.0 (upstream #455) the metadata is BSD-3-Clause only. The GPLv2 part, the x265 HEVC encoder that the binary wheel bundles, is stated only in the wheel's LICENSES_bundled.txt, and metadata scanners don't read that file. The docs now say that and spell out what it means for operators: running isn't distribution; redistributing the image or wheel passes x265 on under GPLv2; SBOM-driven reviews must add it by hand; the GPL-free route needs libheif ≥ 1.23.4 without x265. They also say that x265 is loaded into the process with the bundled libheif, though FileMorph never calls an encoder. The libheif row drops libaom, which is no longer in the 1.8.0 wheel.

2. libheif minimum (docs/formats.md, docs/installation.md, README.md, docs/security-pentest-report.md, Dockerfile comments)
pillow-heif 1.8.0 refuses to compile against libheif < 1.23.4 (an #error in its C source; upstream #480). The docs sent Linux source installs to the distribution's libheif-dev. The Dockerfile said pillow-heif dlopens the system libheif1, but the wheel loads its own bundled copy via RPATH. The Dockerfile's packages are unchanged; only its comments change, so the image stays the same.

Two guards in tests/test_docs_match_code.py:

  • test_licence_docs_describe_the_installed_pillow_heif_wheel: reads the installed wheel's metadata and LICENSES_bundled.txt and checks the licence docs against them.
  • test_docs_quote_the_libheif_minimum_of_the_locked_pillow_heif: fails when requirements.lock moves pillow-heif away from the version docs/formats.md names the minimum for, or when two files quote different minimums. A future pillow-heif lock bump turns this red on purpose: check upstream's changelog for the libheif minimum, then update formats.md.

How it was verified

  • Downloaded the locked pillow-heif 1.8.0 cp314 manylinux wheels (x86_64 and aarch64; SHA-256 in requirements.lock) and read METADATA, LICENSES_bundled.txt and the ELF dynamic sections (_pillow_heif → libheif-….so.1.23.4 via RPATH $ORIGIN/pillow_heif.libs; bundled libheif → libx265, libde265).
  • Compared metadata for 1.5.0 → 1.8.0: the GPLv2 classifier is present in 1.5.0 and gone from 1.6.0.
  • Ran cyclonedx-py environment --PEP-639 (cyclonedx-bom 5.5.0, as in release.yml) and pip-licenses --from=mixed over the wheel, and the SBOM over all 77 locked Linux wheels: pillow-heif = BSD-3-Clause, with no GPL anywhere in the output.
  • Debian: the pinned python:3.14-slim digest is trixie. libheif is 1.19.8-1+deb13u1 in the release archive and 1.23.4-1~deb13u1 in trixie-security (DSA-6523-1, 2026-09-28). I computed the apt dependency closure of both image stages from the Packages indices: nothing depends on libheif1, and listing it adds only libheif1, libheif-plugin-libde265, libde265-0 and libheif-plugin-dav1d.
  • Mutation-checked both guards: a lock bump to 1.9.0, a different minimum in the Dockerfile, and the old doc section each fail.
  • Local full suite on top of b2deb14: 1501 passed, 72 skipped (Linux-only tests). ruff 0.16.9, gitleaks and scope-guard are clean. Reviewed by the code-reviewer and security-auditor agents; their findings are addressed.

Left open on purpose

  • libheif1 / libheif-dev in the image: unused on amd64/arm64. They are the two halves of the from-source fallback for architectures without a pillow-heif wheel, and that fallback compiles today only because of DSA-6523-1. Removing them would shrink the image by four Debian packages and drop the fallback. That changes the image, so it's a separate decision.
  • CI: ci.yml and deps-latest.yml still apt-get install libheif-dev. Tests install the pillow-heif wheel, so it's unused there, and the runner's Ubuntu 24.04 package (1.17.6) is too old for a source build anyway. Not touched here.

🤖 Generated with Claude Code

MrChengLen and others added 11 commits October 1, 2026 12:22
…if 1.23.4

Two claims the #164 review found, both already wrong on main.

Licence: third-party-licenses.md and tech-stack-rationale.md said the
wheel metadata declares GPLv2 and that pip-licenses or the SBOM would flag
it. pillow-heif 1.6.0 dropped that classifier (upstream #455); the locked
1.8.0 declares BSD-3-Clause only. Checked against the locked cp314 wheels
(x86_64 and aarch64): cyclonedx-py 5.5.0 with --PEP-639, as release.yml
runs it, and pip-licenses --from=mixed both report BSD-3-Clause; the
GPLv2 x265 is stated only in the wheel's LICENSES_bundled.txt. The docs
now say so, spell out what it means for operators (running vs
redistributing x265 under GPLv2 and libheif/libde265 under LGPL-3.0,
SBOM-driven reviews, the GPL-free route), and say that x265 is loaded
with the bundled libheif, which _pillow_heif links through its RPATH. The
libheif row drops libaom, gone from the 1.8.0 wheel, and names the copy
HEIC decoding uses. The dated scan paragraph is re-run against today's
lock, and the pentest report's correction adds that dpkg-based image
scanners see Debian's unused libheif1/libde265-0, not the bundled copies.

libheif minimum: pillow-heif 1.8.0 will not compile against libheif older
than 1.23.4 (#error in its C source, upstream #480). formats.md,
installation.md, README.md and the pentest report sent source installs to
the distribution's libheif-dev or a system libheif, and the Dockerfile
said pillow-heif dlopens libheif1. Debian 13 shipped 1.19.8; 1.23.4 came
as a security update (DSA-6523-1, 2026-09-28) — which is why the
builder's libheif-dev fallback for architectures without a wheel still
meets that minimum. Only the Dockerfile's comments change: nothing in the
image's apt closure depends on libheif1 and the amd64/arm64 wheels never
load it, but dropping it would drop that fallback too, so that stays a
separate decision.

Guards in tests/test_docs_match_code.py: one reads the installed wheel's
metadata and LICENSES_bundled.txt and checks the licence docs against
them; the other ties the pillow-heif version formats.md gives the libheif
minimum for to requirements.lock and allows one minimum across docs/,
README.md and the Dockerfile. A lock bump, a stray minimum and the old doc
section each fail them.

Full suite 1501 green (72 skipped); ruff 0.16.9, gitleaks and scope-guard
clean. No templates or dependencies touched, so i18n-drift and pip-audit
do not apply.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ommits

The entry conflicts with main's newest one at the top of [Unreleased].
CHANGELOG.md goes back to the merge base here, GitHub merges main in,
and the next commit puts the entry back on top.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Restores this PR's entry above the entries main gained meanwhile.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ommits

The entry conflicts with main's newest one at the top of [Unreleased].
CHANGELOG.md goes back to the merge base here, GitHub merges main in,
and the next commit puts the entry back on top.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Restores this PR's entry above the entries main gained meanwhile.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ommits

The entry conflicts with main's newest one at the top of [Unreleased].
CHANGELOG.md goes back to the merge base here, GitHub merges main in,
and the next commit puts the entry back on top.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Restores this PR's entry above the entries main gained meanwhile.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e merge base

Since #196 each entry is its own file in changelog.d/, and a guard fails
when an entry is inserted at the top of CHANGELOG.md. CHANGELOG.md goes
back to the merge base's bytes, so this PR no longer touches it, and the
entry moves unchanged to changelog.d/2026-10-06-pillow-heif-docs.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MrChengLen
MrChengLen merged commit f39e8c7 into main Oct 8, 2026
7 checks passed
@MrChengLen
MrChengLen deleted the pr-pillow-heif-docs branch October 8, 2026 07:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant