From 87f1488e417376e4600887688848899eec877bb6 Mon Sep 17 00:00:00 2001 From: Jeffrey 'Alex' Clark Date: Tue, 7 Apr 2026 12:11:32 -0400 Subject: [PATCH 01/10] Generate CycloneDX SBOM at release time via CI - Add .github/generate-sbom.py: reads the real version from src/PIL/_version.py and emits a CycloneDX 1.6 JSON SBOM covering the 8 C extension modules, 3 vendored thirdparty libraries, and 13 optional native library dependencies. - Add 'sbom' job to wheels.yml: runs on tag pushes, generates the SBOM, uploads it as a workflow artifact, and attaches it to the GitHub release via 'gh release upload'. - Remove the static pillow.cdx.json; CI now owns the generated file. Can also be run locally: python .github/generate-sbom.py Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/generate-sbom.py | 330 +++++++++++++++++++++++++++++++++++ .github/workflows/wheels.yml | 30 ++++ 2 files changed, 360 insertions(+) create mode 100644 .github/generate-sbom.py diff --git a/.github/generate-sbom.py b/.github/generate-sbom.py new file mode 100644 index 00000000000..29b1029d0fb --- /dev/null +++ b/.github/generate-sbom.py @@ -0,0 +1,330 @@ +#!/usr/bin/env python3 +"""Generate a CycloneDX 1.6 SBOM for Pillow's C extensions and their +vendored/optional native library dependencies. + +Usage: + python .github/generate-sbom.py [output-file] + +Output defaults to pillow-{version}.cdx.json in the current directory. +""" + +from __future__ import annotations + +import json +import sys +import uuid +from datetime import datetime, timezone +from pathlib import Path + + +def get_version() -> str: + version_file = Path(__file__).parent.parent / "src" / "PIL" / "_version.py" + return version_file.read_text(encoding="utf-8").split('"')[1] + + +def generate(version: str) -> dict: + serial = str(uuid.uuid4()) + now = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ") + purl = f"pkg:pypi/pillow@{version}" + + metadata_component = { + "bom-ref": purl, + "type": "library", + "name": "Pillow", + "version": version, + "description": "Python Imaging Library (fork)", + "licenses": [{"license": {"id": "MIT-CMU"}}], + "purl": purl, + "externalReferences": [ + {"type": "website", "url": "https://python-pillow.github.io"}, + {"type": "vcs", "url": "https://github.com/python-pillow/Pillow"}, + {"type": "documentation", "url": "https://pillow.readthedocs.io"}, + ], + } + + c_extensions = [ + ( + "PIL._imaging", + "Core image processing extension " + "(decode, encode, map, display, outline, path, libImaging)", + ), + ("PIL._imagingft", "FreeType font rendering extension"), + ("PIL._imagingcms", "LittleCMS2 colour management extension"), + ("PIL._webp", "WebP image format extension"), + ("PIL._avif", "AVIF image format extension"), + ("PIL._imagingtk", "Tk/Tcl display extension"), + ("PIL._imagingmath", "Image math operations extension (via pybind11)"), + ("PIL._imagingmorph", "Image morphology extension"), + ] + + ext_components = [ + { + "bom-ref": f"{purl}#c-ext/{name}", + "type": "library", + "name": name, + "version": version, + "description": desc, + "licenses": [{"license": {"id": "MIT-CMU"}}], + "purl": f"{purl}#c-ext/{name}", + } + for name, desc in c_extensions + ] + + vendored_components = [ + { + "bom-ref": "pkg:github/HOST-Oman/libraqm@0.10.3", + "type": "library", + "name": "raqm", + "version": "0.10.3", + "description": "Complex text layout library " + "(vendored in src/thirdparty/raqm/)", + "licenses": [{"license": {"id": "MIT"}}], + "purl": "pkg:github/HOST-Oman/libraqm@0.10.3", + "externalReferences": [ + {"type": "vcs", "url": "https://github.com/HOST-Oman/libraqm"}, + ], + }, + { + "bom-ref": f"{purl}#thirdparty/fribidi-shim", + "type": "library", + "name": "fribidi-shim", + "version": "1.x", + "description": "FriBiDi runtime-loading shim " + "(vendored in src/thirdparty/fribidi-shim/); " + "loads libfribidi dynamically", + "licenses": [{"license": {"id": "LGPL-2.1-or-later"}}], + "externalReferences": [ + {"type": "website", "url": "https://github.com/fribidi/fribidi"}, + ], + }, + { + "bom-ref": "pkg:github/python/pythoncapi-compat", + "type": "library", + "name": "pythoncapi_compat", + "description": "Backport header for new CPython C-API functions " + "(vendored in src/thirdparty/pythoncapi_compat.h)", + "licenses": [{"license": {"id": "MIT-0"}}], + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/python/pythoncapi-compat", + }, + ], + }, + ] + + native_deps = [ + { + "bom-ref": "pkg:generic/libjpeg", + "type": "library", + "name": "libjpeg / libjpeg-turbo", + "description": "JPEG codec (required by default; disable with " + "-C jpeg=disable). Tested with libjpeg 6b/8/9-9d " + "and libjpeg-turbo 8.", + "externalReferences": [ + {"type": "website", "url": "https://libjpeg-turbo.org"}, + {"type": "website", "url": "https://ijg.org"}, + ], + }, + { + "bom-ref": "pkg:generic/zlib", + "type": "library", + "name": "zlib", + "description": "Deflate/PNG compression (required by default; " + "disable with -C zlib=disable).", + "externalReferences": [ + {"type": "website", "url": "https://zlib.net"}, + ], + }, + { + "bom-ref": "pkg:generic/libtiff", + "type": "library", + "name": "libtiff", + "description": "TIFF codec (optional). Tested with libtiff 4.0-4.7.1.", + "externalReferences": [ + {"type": "website", "url": "https://libtiff.gitlab.io/libtiff/"}, + ], + }, + { + "bom-ref": "pkg:generic/freetype2", + "type": "library", + "name": "FreeType", + "description": "Font rendering (optional, used by PIL._imagingft). " + "Required for text/font support.", + "externalReferences": [ + {"type": "website", "url": "https://freetype.org"}, + ], + }, + { + "bom-ref": "pkg:generic/littlecms2", + "type": "library", + "name": "Little CMS 2", + "description": "Colour management (optional, used by PIL._imagingcms). " + "Tested with lcms2 2.7-2.18.", + "externalReferences": [ + {"type": "website", "url": "https://www.littlecms.com"}, + ], + }, + { + "bom-ref": "pkg:generic/libwebp", + "type": "library", + "name": "libwebp", + "description": "WebP codec (optional, used by PIL._webp).", + "externalReferences": [ + { + "type": "website", + "url": "https://chromium.googlesource.com/webm/libwebp", + }, + ], + }, + { + "bom-ref": "pkg:generic/openjpeg", + "type": "library", + "name": "OpenJPEG", + "description": "JPEG 2000 codec (optional). " + "Tested with openjpeg 2.0.0-2.5.4.", + "externalReferences": [ + {"type": "website", "url": "https://www.openjpeg.org"}, + ], + }, + { + "bom-ref": "pkg:generic/libavif", + "type": "library", + "name": "libavif", + "description": "AVIF codec (optional, used by PIL._avif). " + "Requires libavif >= 1.0.0.", + "externalReferences": [ + {"type": "website", "url": "https://github.com/AOMediaCodec/libavif"}, + ], + }, + { + "bom-ref": "pkg:generic/harfbuzz", + "type": "library", + "name": "HarfBuzz", + "description": "Text shaping (optional, required by libraqm " + "for complex text layout).", + "externalReferences": [ + {"type": "website", "url": "https://harfbuzz.github.io"}, + ], + }, + { + "bom-ref": "pkg:generic/fribidi", + "type": "library", + "name": "FriBiDi", + "description": "Unicode bidi algorithm library (optional, " + "loaded at runtime by fribidi-shim).", + "externalReferences": [ + {"type": "website", "url": "https://github.com/fribidi/fribidi"}, + ], + }, + { + "bom-ref": "pkg:generic/libimagequant", + "type": "library", + "name": "libimagequant", + "description": "Improved colour quantization (optional). " + "Tested with 2.6-4.4.1. NOTE: GPLv3 licensed.", + "licenses": [{"license": {"id": "GPL-3.0-only"}}], + "externalReferences": [ + {"type": "website", "url": "https://pngquant.org/lib/"}, + ], + }, + { + "bom-ref": "pkg:generic/libxcb", + "type": "library", + "name": "libxcb", + "description": "X11 screen-grab support (optional, " + "used by PIL._imagingtk on Linux).", + "externalReferences": [ + {"type": "website", "url": "https://xcb.freedesktop.org"}, + ], + }, + { + "bom-ref": "pkg:pypi/pybind11", + "type": "library", + "name": "pybind11", + "description": "C++/Python binding library " + "(build-time dependency for PIL._imagingmath).", + "externalReferences": [ + {"type": "website", "url": "https://pybind11.readthedocs.io"}, + ], + }, + ] + + dependencies = [ + { + "ref": purl, + "dependsOn": [e["bom-ref"] for e in ext_components], + }, + { + "ref": f"{purl}#c-ext/PIL._imaging", + "dependsOn": [ + "pkg:generic/libjpeg", + "pkg:generic/zlib", + "pkg:generic/libtiff", + "pkg:generic/openjpeg", + ], + }, + { + "ref": f"{purl}#c-ext/PIL._imagingft", + "dependsOn": [ + "pkg:generic/freetype2", + "pkg:github/HOST-Oman/libraqm@0.10.3", + f"{purl}#thirdparty/fribidi-shim", + "pkg:generic/harfbuzz", + "pkg:generic/fribidi", + ], + }, + { + "ref": f"{purl}#c-ext/PIL._imagingcms", + "dependsOn": ["pkg:generic/littlecms2"], + }, + { + "ref": f"{purl}#c-ext/PIL._webp", + "dependsOn": ["pkg:generic/libwebp"], + }, + { + "ref": f"{purl}#c-ext/PIL._avif", + "dependsOn": ["pkg:generic/libavif"], + }, + { + "ref": f"{purl}#c-ext/PIL._imagingmath", + "dependsOn": ["pkg:pypi/pybind11"], + }, + { + "ref": "pkg:github/HOST-Oman/libraqm@0.10.3", + "dependsOn": [ + f"{purl}#thirdparty/fribidi-shim", + "pkg:generic/harfbuzz", + ], + }, + ] + + return { + "bomFormat": "CycloneDX", + "specVersion": "1.6", + "serialNumber": f"urn:uuid:{serial}", + "version": 1, + "metadata": { + "timestamp": now, + "tools": [ + { + "type": "application", + "name": "generate-sbom.py", + "vendor": "Pillow", + } + ], + "component": metadata_component, + }, + "components": ext_components + vendored_components + native_deps, + "dependencies": dependencies, + } + + +if __name__ == "__main__": + version = get_version() + output = ( + Path(sys.argv[1]) if len(sys.argv) > 1 else Path(f"pillow-{version}.cdx.json") + ) + sbom = generate(version) + output.write_text(json.dumps(sbom, indent=2) + "\n", encoding="utf-8") + print(f"Wrote {output} (Pillow {version}, {len(sbom['components'])} components)") diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index d16c80323cd..ff33af192dc 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -276,6 +276,36 @@ jobs: artifacts_path: dist anaconda_nightly_upload_token: ${{ secrets.ANACONDA_ORG_UPLOAD_TOKEN }} + sbom: + if: github.repository_owner == 'python-pillow' && github.event_name == 'push' && startsWith(github.ref, 'refs/tags') + needs: count-dists + runs-on: ubuntu-latest + name: Generate and publish SBOM + permissions: + contents: write + steps: + - uses: actions/checkout@v6 + with: + persist-credentials: false + + - uses: actions/setup-python@v6 + with: + python-version: "3.x" + + - name: Generate CycloneDX SBOM + run: python .github/generate-sbom.py + + - name: Upload SBOM as workflow artifact + uses: actions/upload-artifact@v7 + with: + name: sbom + path: "*.cdx.json" + + - name: Attach SBOM to GitHub release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: gh release upload "${{ github.ref_name }}" *.cdx.json + pypi-publish: if: github.repository_owner == 'python-pillow' && github.event_name == 'push' && startsWith(github.ref, 'refs/tags') needs: count-dists From 123cb5b99256ddb40e0f2601613a6d1538a0d301 Mon Sep 17 00:00:00 2001 From: Jeffrey 'Alex' Clark Date: Tue, 7 Apr 2026 19:31:19 -0400 Subject: [PATCH 02/10] Update .github/workflows/wheels.yml Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com> --- .github/workflows/wheels.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index ff33af192dc..6164f87f04d 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -293,7 +293,7 @@ jobs: python-version: "3.x" - name: Generate CycloneDX SBOM - run: python .github/generate-sbom.py + run: python3 .github/generate-sbom.py - name: Upload SBOM as workflow artifact uses: actions/upload-artifact@v7 From adbec65299bca28dc18f36de607a7204a35d08e0 Mon Sep 17 00:00:00 2001 From: Jeffrey 'Alex' Clark Date: Tue, 7 Apr 2026 19:31:28 -0400 Subject: [PATCH 03/10] Update .github/generate-sbom.py Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com> --- .github/generate-sbom.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/generate-sbom.py b/.github/generate-sbom.py index 29b1029d0fb..4e31d04b483 100644 --- a/.github/generate-sbom.py +++ b/.github/generate-sbom.py @@ -3,7 +3,7 @@ vendored/optional native library dependencies. Usage: - python .github/generate-sbom.py [output-file] + python3 .github/generate-sbom.py [output-file] Output defaults to pillow-{version}.cdx.json in the current directory. """ From 1ae1d72e25adbf4d6125be2e5bce4cb4aff5699c Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 8 Apr 2026 19:37:06 +1000 Subject: [PATCH 04/10] Set executable flag on script with shebang line --- .github/generate-sbom.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 .github/generate-sbom.py diff --git a/.github/generate-sbom.py b/.github/generate-sbom.py old mode 100644 new mode 100755 From 7cebafc91f3d687c2986369066d12be5c71a2cfa Mon Sep 17 00:00:00 2001 From: Jeffrey 'Alex' Clark Date: Wed, 8 Apr 2026 07:34:48 -0400 Subject: [PATCH 05/10] Update .github/workflows/wheels.yml Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com> --- .github/workflows/wheels.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 6164f87f04d..e98f869623f 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -304,7 +304,7 @@ jobs: - name: Attach SBOM to GitHub release env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: gh release upload "${{ github.ref_name }}" *.cdx.json + run: gh release upload "$GITHUB_REF_NAME" *.cdx.json pypi-publish: if: github.repository_owner == 'python-pillow' && github.event_name == 'push' && startsWith(github.ref, 'refs/tags') From ba6c04655bdab22c0814b28efb0bd8dbce0f0b41 Mon Sep 17 00:00:00 2001 From: Jeffrey 'Alex' Clark Date: Wed, 8 Apr 2026 07:35:28 -0400 Subject: [PATCH 06/10] Update .github/generate-sbom.py Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> --- .github/generate-sbom.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/generate-sbom.py b/.github/generate-sbom.py index 4e31d04b483..3093b64ce14 100755 --- a/.github/generate-sbom.py +++ b/.github/generate-sbom.py @@ -13,7 +13,7 @@ import json import sys import uuid -from datetime import datetime, timezone +import datetime as dt from pathlib import Path From fbf8425bf606c23b04c6663b6cd43e3be15c7ecf Mon Sep 17 00:00:00 2001 From: Jeffrey 'Alex' Clark Date: Wed, 8 Apr 2026 07:35:51 -0400 Subject: [PATCH 07/10] Update .github/workflows/wheels.yml Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> --- .github/workflows/wheels.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index e98f869623f..282416b32d5 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -277,7 +277,10 @@ jobs: anaconda_nightly_upload_token: ${{ secrets.ANACONDA_ORG_UPLOAD_TOKEN }} sbom: - if: github.repository_owner == 'python-pillow' && github.event_name == 'push' && startsWith(github.ref, 'refs/tags') + if: | + github.event.repository.fork == false + && github.event_name == 'push' + && startsWith(github.ref, 'refs/tags') needs: count-dists runs-on: ubuntu-latest name: Generate and publish SBOM From 54489f363e4cda298a1863688f5f00fdf3af24b0 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 8 Apr 2026 11:36:05 +0000 Subject: [PATCH 08/10] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .github/generate-sbom.py | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/generate-sbom.py b/.github/generate-sbom.py index 3093b64ce14..4ef7eeb0694 100755 --- a/.github/generate-sbom.py +++ b/.github/generate-sbom.py @@ -13,7 +13,6 @@ import json import sys import uuid -import datetime as dt from pathlib import Path From 4ecd9b9db5c1b313bd14755c65aa8afe714a17a5 Mon Sep 17 00:00:00 2001 From: Jeffrey 'Alex' Clark Date: Wed, 8 Apr 2026 20:34:50 -0400 Subject: [PATCH 09/10] Address CycloneDX SBOM review feedback - Fix missing datetime/hashlib imports; use datetime as dt - Bump specVersion from 1.6 to 1.7 - Add metadata.lifecycles = [{"phase": "build"}] - Add SHA-256 hashes to vendored components (raqm, fribidi-shim, pythoncapi_compat) - Add pedigree notes clarifying whether vendored components are patched - Add distribution externalReferences to vendored and native dep components - Add SPDX license IDs to all native dependencies - Mark optional native dependencies with scope: optional - Split sbom job: generate early (no bottleneck), publish separately after count-dists - Add .github/generate-sbom.py to push/PR trigger paths Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/generate-sbom.py | 127 ++++++++++++++++++++++++++++++++++- .github/workflows/wheels.yml | 27 +++++--- 2 files changed, 143 insertions(+), 11 deletions(-) diff --git a/.github/generate-sbom.py b/.github/generate-sbom.py index 4ef7eeb0694..179079c4224 100755 --- a/.github/generate-sbom.py +++ b/.github/generate-sbom.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Generate a CycloneDX 1.6 SBOM for Pillow's C extensions and their +"""Generate a CycloneDX 1.7 SBOM for Pillow's C extensions and their vendored/optional native library dependencies. Usage: @@ -10,6 +10,8 @@ from __future__ import annotations +import datetime as dt +import hashlib import json import sys import uuid @@ -21,10 +23,16 @@ def get_version() -> str: return version_file.read_text(encoding="utf-8").split('"')[1] +def sha256_file(path: Path) -> str: + return hashlib.sha256(path.read_bytes()).hexdigest() + + def generate(version: str) -> dict: serial = str(uuid.uuid4()) - now = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ") + now = dt.datetime.now(dt.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ") purl = f"pkg:pypi/pillow@{version}" + root = Path(__file__).parent.parent + thirdparty = root / "src" / "thirdparty" metadata_component = { "bom-ref": purl, @@ -79,8 +87,21 @@ def generate(version: str) -> dict: "(vendored in src/thirdparty/raqm/)", "licenses": [{"license": {"id": "MIT"}}], "purl": "pkg:github/HOST-Oman/libraqm@0.10.3", + "hashes": [ + { + "alg": "SHA-256", + "content": sha256_file(thirdparty / "raqm" / "raqm.c"), + } + ], + "pedigree": { + "notes": "Vendored unmodified from upstream HOST-Oman/libraqm v0.10.3." + }, "externalReferences": [ {"type": "vcs", "url": "https://github.com/HOST-Oman/libraqm"}, + { + "type": "distribution", + "url": "https://github.com/HOST-Oman/libraqm/releases/tag/v0.10.3", + }, ], }, { @@ -92,6 +113,17 @@ def generate(version: str) -> dict: "(vendored in src/thirdparty/fribidi-shim/); " "loads libfribidi dynamically", "licenses": [{"license": {"id": "LGPL-2.1-or-later"}}], + "hashes": [ + { + "alg": "SHA-256", + "content": sha256_file( + thirdparty / "fribidi-shim" / "fribidi.c" + ), + } + ], + "pedigree": { + "notes": "Pillow-authored shim; not taken from an upstream project." + }, "externalReferences": [ {"type": "website", "url": "https://github.com/fribidi/fribidi"}, ], @@ -103,11 +135,24 @@ def generate(version: str) -> dict: "description": "Backport header for new CPython C-API functions " "(vendored in src/thirdparty/pythoncapi_compat.h)", "licenses": [{"license": {"id": "MIT-0"}}], + "hashes": [ + { + "alg": "SHA-256", + "content": sha256_file(thirdparty / "pythoncapi_compat.h"), + } + ], + "pedigree": { + "notes": "Vendored unmodified from upstream python/pythoncapi-compat." + }, "externalReferences": [ { "type": "vcs", "url": "https://github.com/python/pythoncapi-compat", }, + { + "type": "distribution", + "url": "https://github.com/python/pythoncapi-compat/releases", + }, ], }, ] @@ -120,9 +165,17 @@ def generate(version: str) -> dict: "description": "JPEG codec (required by default; disable with " "-C jpeg=disable). Tested with libjpeg 6b/8/9-9d " "and libjpeg-turbo 8.", + "licenses": [ + {"license": {"id": "IJG"}}, + {"license": {"id": "BSD-3-Clause"}}, + ], "externalReferences": [ {"type": "website", "url": "https://libjpeg-turbo.org"}, {"type": "website", "url": "https://ijg.org"}, + { + "type": "distribution", + "url": "https://github.com/libjpeg-turbo/libjpeg-turbo/releases", + }, ], }, { @@ -131,120 +184,187 @@ def generate(version: str) -> dict: "name": "zlib", "description": "Deflate/PNG compression (required by default; " "disable with -C zlib=disable).", + "licenses": [{"license": {"id": "Zlib"}}], "externalReferences": [ {"type": "website", "url": "https://zlib.net"}, + {"type": "distribution", "url": "https://zlib.net/"}, ], }, { "bom-ref": "pkg:generic/libtiff", "type": "library", "name": "libtiff", + "scope": "optional", "description": "TIFF codec (optional). Tested with libtiff 4.0-4.7.1.", + "licenses": [{"license": {"id": "HPND"}}], "externalReferences": [ {"type": "website", "url": "https://libtiff.gitlab.io/libtiff/"}, + { + "type": "distribution", + "url": "https://download.osgeo.org/libtiff/", + }, ], }, { "bom-ref": "pkg:generic/freetype2", "type": "library", "name": "FreeType", + "scope": "optional", "description": "Font rendering (optional, used by PIL._imagingft). " "Required for text/font support.", + "licenses": [{"license": {"id": "FTL"}}], "externalReferences": [ {"type": "website", "url": "https://freetype.org"}, + { + "type": "distribution", + "url": "https://download.savannah.gnu.org/releases/freetype/", + }, ], }, { "bom-ref": "pkg:generic/littlecms2", "type": "library", "name": "Little CMS 2", + "scope": "optional", "description": "Colour management (optional, used by PIL._imagingcms). " "Tested with lcms2 2.7-2.18.", + "licenses": [{"license": {"id": "MIT"}}], "externalReferences": [ {"type": "website", "url": "https://www.littlecms.com"}, + { + "type": "distribution", + "url": "https://github.com/mm2/Little-CMS/releases", + }, ], }, { "bom-ref": "pkg:generic/libwebp", "type": "library", "name": "libwebp", + "scope": "optional", "description": "WebP codec (optional, used by PIL._webp).", + "licenses": [{"license": {"id": "BSD-3-Clause"}}], "externalReferences": [ { "type": "website", "url": "https://chromium.googlesource.com/webm/libwebp", }, + { + "type": "distribution", + "url": "https://chromium.googlesource.com/webm/libwebp", + }, ], }, { "bom-ref": "pkg:generic/openjpeg", "type": "library", "name": "OpenJPEG", + "scope": "optional", "description": "JPEG 2000 codec (optional). " "Tested with openjpeg 2.0.0-2.5.4.", + "licenses": [{"license": {"id": "BSD-2-Clause"}}], "externalReferences": [ {"type": "website", "url": "https://www.openjpeg.org"}, + { + "type": "distribution", + "url": "https://github.com/uclouvain/openjpeg/releases", + }, ], }, { "bom-ref": "pkg:generic/libavif", "type": "library", "name": "libavif", + "scope": "optional", "description": "AVIF codec (optional, used by PIL._avif). " "Requires libavif >= 1.0.0.", + "licenses": [{"license": {"id": "BSD-2-Clause"}}], "externalReferences": [ {"type": "website", "url": "https://github.com/AOMediaCodec/libavif"}, + { + "type": "distribution", + "url": "https://github.com/AOMediaCodec/libavif/releases", + }, ], }, { "bom-ref": "pkg:generic/harfbuzz", "type": "library", "name": "HarfBuzz", + "scope": "optional", "description": "Text shaping (optional, required by libraqm " "for complex text layout).", + "licenses": [{"license": {"id": "MIT"}}], "externalReferences": [ {"type": "website", "url": "https://harfbuzz.github.io"}, + { + "type": "distribution", + "url": "https://github.com/harfbuzz/harfbuzz/releases", + }, ], }, { "bom-ref": "pkg:generic/fribidi", "type": "library", "name": "FriBiDi", + "scope": "optional", "description": "Unicode bidi algorithm library (optional, " "loaded at runtime by fribidi-shim).", + "licenses": [{"license": {"id": "LGPL-2.1-or-later"}}], "externalReferences": [ {"type": "website", "url": "https://github.com/fribidi/fribidi"}, + { + "type": "distribution", + "url": "https://github.com/fribidi/fribidi/releases", + }, ], }, { "bom-ref": "pkg:generic/libimagequant", "type": "library", "name": "libimagequant", + "scope": "optional", "description": "Improved colour quantization (optional). " "Tested with 2.6-4.4.1. NOTE: GPLv3 licensed.", "licenses": [{"license": {"id": "GPL-3.0-only"}}], "externalReferences": [ {"type": "website", "url": "https://pngquant.org/lib/"}, + { + "type": "distribution", + "url": "https://github.com/ImageOptim/libimagequant/releases", + }, ], }, { "bom-ref": "pkg:generic/libxcb", "type": "library", "name": "libxcb", + "scope": "optional", "description": "X11 screen-grab support (optional, " "used by PIL._imagingtk on Linux).", + "licenses": [{"license": {"id": "MIT"}}], "externalReferences": [ {"type": "website", "url": "https://xcb.freedesktop.org"}, + { + "type": "distribution", + "url": "https://xcb.freedesktop.org/dist/", + }, ], }, { "bom-ref": "pkg:pypi/pybind11", "type": "library", "name": "pybind11", + "scope": "optional", "description": "C++/Python binding library " "(build-time dependency for PIL._imagingmath).", + "licenses": [{"license": {"id": "BSD-3-Clause"}}], "externalReferences": [ {"type": "website", "url": "https://pybind11.readthedocs.io"}, + { + "type": "distribution", + "url": "https://github.com/pybind/pybind11/releases", + }, ], }, ] @@ -300,11 +420,12 @@ def generate(version: str) -> dict: return { "bomFormat": "CycloneDX", - "specVersion": "1.6", + "specVersion": "1.7", "serialNumber": f"urn:uuid:{serial}", "version": 1, "metadata": { "timestamp": now, + "lifecycles": [{"phase": "build"}], "tools": [ { "type": "application", diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 282416b32d5..bb1f20f8cf4 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -12,6 +12,7 @@ on: push: paths: - ".ci/requirements-cibw.txt" + - ".github/generate-sbom.py" - ".github/workflows/wheel*" - "pyproject.toml" - "setup.py" @@ -23,6 +24,7 @@ on: pull_request: paths: - ".ci/requirements-cibw.txt" + - ".github/generate-sbom.py" - ".github/workflows/wheel*" - "pyproject.toml" - "setup.py" @@ -277,15 +279,8 @@ jobs: anaconda_nightly_upload_token: ${{ secrets.ANACONDA_ORG_UPLOAD_TOKEN }} sbom: - if: | - github.event.repository.fork == false - && github.event_name == 'push' - && startsWith(github.ref, 'refs/tags') - needs: count-dists runs-on: ubuntu-latest - name: Generate and publish SBOM - permissions: - contents: write + name: Generate SBOM steps: - uses: actions/checkout@v6 with: @@ -304,6 +299,22 @@ jobs: name: sbom path: "*.cdx.json" + sbom-publish: + if: | + github.event.repository.fork == false + && github.event_name == 'push' + && startsWith(github.ref, 'refs/tags') + needs: [count-dists, sbom] + runs-on: ubuntu-latest + name: Publish SBOM to GitHub release + permissions: + contents: write + steps: + - uses: actions/download-artifact@v8 + with: + name: sbom + path: . + - name: Attach SBOM to GitHub release env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} From e9b4b9fa2bd5e319ea41f16336f04991ead796f8 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 9 Apr 2026 00:35:45 +0000 Subject: [PATCH 10/10] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .github/generate-sbom.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/generate-sbom.py b/.github/generate-sbom.py index 179079c4224..e213bb39a75 100755 --- a/.github/generate-sbom.py +++ b/.github/generate-sbom.py @@ -116,9 +116,7 @@ def generate(version: str) -> dict: "hashes": [ { "alg": "SHA-256", - "content": sha256_file( - thirdparty / "fribidi-shim" / "fribidi.c" - ), + "content": sha256_file(thirdparty / "fribidi-shim" / "fribidi.c"), } ], "pedigree": {