Skip to content

Commit 4ecd9b9

Browse files
aclark4lifeCopilot
andcommitted
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>
1 parent 54489f3 commit 4ecd9b9

File tree

2 files changed

+143
-11
lines changed

2 files changed

+143
-11
lines changed

.github/generate-sbom.py

Lines changed: 124 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#!/usr/bin/env python3
2-
"""Generate a CycloneDX 1.6 SBOM for Pillow's C extensions and their
2+
"""Generate a CycloneDX 1.7 SBOM for Pillow's C extensions and their
33
vendored/optional native library dependencies.
44
55
Usage:
@@ -10,6 +10,8 @@
1010

1111
from __future__ import annotations
1212

13+
import datetime as dt
14+
import hashlib
1315
import json
1416
import sys
1517
import uuid
@@ -21,10 +23,16 @@ def get_version() -> str:
2123
return version_file.read_text(encoding="utf-8").split('"')[1]
2224

2325

26+
def sha256_file(path: Path) -> str:
27+
return hashlib.sha256(path.read_bytes()).hexdigest()
28+
29+
2430
def generate(version: str) -> dict:
2531
serial = str(uuid.uuid4())
26-
now = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
32+
now = dt.datetime.now(dt.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
2733
purl = f"pkg:pypi/pillow@{version}"
34+
root = Path(__file__).parent.parent
35+
thirdparty = root / "src" / "thirdparty"
2836

2937
metadata_component = {
3038
"bom-ref": purl,
@@ -79,8 +87,21 @@ def generate(version: str) -> dict:
7987
"(vendored in src/thirdparty/raqm/)",
8088
"licenses": [{"license": {"id": "MIT"}}],
8189
"purl": "pkg:github/HOST-Oman/libraqm@0.10.3",
90+
"hashes": [
91+
{
92+
"alg": "SHA-256",
93+
"content": sha256_file(thirdparty / "raqm" / "raqm.c"),
94+
}
95+
],
96+
"pedigree": {
97+
"notes": "Vendored unmodified from upstream HOST-Oman/libraqm v0.10.3."
98+
},
8299
"externalReferences": [
83100
{"type": "vcs", "url": "https://github.com/HOST-Oman/libraqm"},
101+
{
102+
"type": "distribution",
103+
"url": "https://github.com/HOST-Oman/libraqm/releases/tag/v0.10.3",
104+
},
84105
],
85106
},
86107
{
@@ -92,6 +113,17 @@ def generate(version: str) -> dict:
92113
"(vendored in src/thirdparty/fribidi-shim/); "
93114
"loads libfribidi dynamically",
94115
"licenses": [{"license": {"id": "LGPL-2.1-or-later"}}],
116+
"hashes": [
117+
{
118+
"alg": "SHA-256",
119+
"content": sha256_file(
120+
thirdparty / "fribidi-shim" / "fribidi.c"
121+
),
122+
}
123+
],
124+
"pedigree": {
125+
"notes": "Pillow-authored shim; not taken from an upstream project."
126+
},
95127
"externalReferences": [
96128
{"type": "website", "url": "https://github.com/fribidi/fribidi"},
97129
],
@@ -103,11 +135,24 @@ def generate(version: str) -> dict:
103135
"description": "Backport header for new CPython C-API functions "
104136
"(vendored in src/thirdparty/pythoncapi_compat.h)",
105137
"licenses": [{"license": {"id": "MIT-0"}}],
138+
"hashes": [
139+
{
140+
"alg": "SHA-256",
141+
"content": sha256_file(thirdparty / "pythoncapi_compat.h"),
142+
}
143+
],
144+
"pedigree": {
145+
"notes": "Vendored unmodified from upstream python/pythoncapi-compat."
146+
},
106147
"externalReferences": [
107148
{
108149
"type": "vcs",
109150
"url": "https://github.com/python/pythoncapi-compat",
110151
},
152+
{
153+
"type": "distribution",
154+
"url": "https://github.com/python/pythoncapi-compat/releases",
155+
},
111156
],
112157
},
113158
]
@@ -120,9 +165,17 @@ def generate(version: str) -> dict:
120165
"description": "JPEG codec (required by default; disable with "
121166
"-C jpeg=disable). Tested with libjpeg 6b/8/9-9d "
122167
"and libjpeg-turbo 8.",
168+
"licenses": [
169+
{"license": {"id": "IJG"}},
170+
{"license": {"id": "BSD-3-Clause"}},
171+
],
123172
"externalReferences": [
124173
{"type": "website", "url": "https://libjpeg-turbo.org"},
125174
{"type": "website", "url": "https://ijg.org"},
175+
{
176+
"type": "distribution",
177+
"url": "https://github.com/libjpeg-turbo/libjpeg-turbo/releases",
178+
},
126179
],
127180
},
128181
{
@@ -131,120 +184,187 @@ def generate(version: str) -> dict:
131184
"name": "zlib",
132185
"description": "Deflate/PNG compression (required by default; "
133186
"disable with -C zlib=disable).",
187+
"licenses": [{"license": {"id": "Zlib"}}],
134188
"externalReferences": [
135189
{"type": "website", "url": "https://zlib.net"},
190+
{"type": "distribution", "url": "https://zlib.net/"},
136191
],
137192
},
138193
{
139194
"bom-ref": "pkg:generic/libtiff",
140195
"type": "library",
141196
"name": "libtiff",
197+
"scope": "optional",
142198
"description": "TIFF codec (optional). Tested with libtiff 4.0-4.7.1.",
199+
"licenses": [{"license": {"id": "HPND"}}],
143200
"externalReferences": [
144201
{"type": "website", "url": "https://libtiff.gitlab.io/libtiff/"},
202+
{
203+
"type": "distribution",
204+
"url": "https://download.osgeo.org/libtiff/",
205+
},
145206
],
146207
},
147208
{
148209
"bom-ref": "pkg:generic/freetype2",
149210
"type": "library",
150211
"name": "FreeType",
212+
"scope": "optional",
151213
"description": "Font rendering (optional, used by PIL._imagingft). "
152214
"Required for text/font support.",
215+
"licenses": [{"license": {"id": "FTL"}}],
153216
"externalReferences": [
154217
{"type": "website", "url": "https://freetype.org"},
218+
{
219+
"type": "distribution",
220+
"url": "https://download.savannah.gnu.org/releases/freetype/",
221+
},
155222
],
156223
},
157224
{
158225
"bom-ref": "pkg:generic/littlecms2",
159226
"type": "library",
160227
"name": "Little CMS 2",
228+
"scope": "optional",
161229
"description": "Colour management (optional, used by PIL._imagingcms). "
162230
"Tested with lcms2 2.7-2.18.",
231+
"licenses": [{"license": {"id": "MIT"}}],
163232
"externalReferences": [
164233
{"type": "website", "url": "https://www.littlecms.com"},
234+
{
235+
"type": "distribution",
236+
"url": "https://github.com/mm2/Little-CMS/releases",
237+
},
165238
],
166239
},
167240
{
168241
"bom-ref": "pkg:generic/libwebp",
169242
"type": "library",
170243
"name": "libwebp",
244+
"scope": "optional",
171245
"description": "WebP codec (optional, used by PIL._webp).",
246+
"licenses": [{"license": {"id": "BSD-3-Clause"}}],
172247
"externalReferences": [
173248
{
174249
"type": "website",
175250
"url": "https://chromium.googlesource.com/webm/libwebp",
176251
},
252+
{
253+
"type": "distribution",
254+
"url": "https://chromium.googlesource.com/webm/libwebp",
255+
},
177256
],
178257
},
179258
{
180259
"bom-ref": "pkg:generic/openjpeg",
181260
"type": "library",
182261
"name": "OpenJPEG",
262+
"scope": "optional",
183263
"description": "JPEG 2000 codec (optional). "
184264
"Tested with openjpeg 2.0.0-2.5.4.",
265+
"licenses": [{"license": {"id": "BSD-2-Clause"}}],
185266
"externalReferences": [
186267
{"type": "website", "url": "https://www.openjpeg.org"},
268+
{
269+
"type": "distribution",
270+
"url": "https://github.com/uclouvain/openjpeg/releases",
271+
},
187272
],
188273
},
189274
{
190275
"bom-ref": "pkg:generic/libavif",
191276
"type": "library",
192277
"name": "libavif",
278+
"scope": "optional",
193279
"description": "AVIF codec (optional, used by PIL._avif). "
194280
"Requires libavif >= 1.0.0.",
281+
"licenses": [{"license": {"id": "BSD-2-Clause"}}],
195282
"externalReferences": [
196283
{"type": "website", "url": "https://github.com/AOMediaCodec/libavif"},
284+
{
285+
"type": "distribution",
286+
"url": "https://github.com/AOMediaCodec/libavif/releases",
287+
},
197288
],
198289
},
199290
{
200291
"bom-ref": "pkg:generic/harfbuzz",
201292
"type": "library",
202293
"name": "HarfBuzz",
294+
"scope": "optional",
203295
"description": "Text shaping (optional, required by libraqm "
204296
"for complex text layout).",
297+
"licenses": [{"license": {"id": "MIT"}}],
205298
"externalReferences": [
206299
{"type": "website", "url": "https://harfbuzz.github.io"},
300+
{
301+
"type": "distribution",
302+
"url": "https://github.com/harfbuzz/harfbuzz/releases",
303+
},
207304
],
208305
},
209306
{
210307
"bom-ref": "pkg:generic/fribidi",
211308
"type": "library",
212309
"name": "FriBiDi",
310+
"scope": "optional",
213311
"description": "Unicode bidi algorithm library (optional, "
214312
"loaded at runtime by fribidi-shim).",
313+
"licenses": [{"license": {"id": "LGPL-2.1-or-later"}}],
215314
"externalReferences": [
216315
{"type": "website", "url": "https://github.com/fribidi/fribidi"},
316+
{
317+
"type": "distribution",
318+
"url": "https://github.com/fribidi/fribidi/releases",
319+
},
217320
],
218321
},
219322
{
220323
"bom-ref": "pkg:generic/libimagequant",
221324
"type": "library",
222325
"name": "libimagequant",
326+
"scope": "optional",
223327
"description": "Improved colour quantization (optional). "
224328
"Tested with 2.6-4.4.1. NOTE: GPLv3 licensed.",
225329
"licenses": [{"license": {"id": "GPL-3.0-only"}}],
226330
"externalReferences": [
227331
{"type": "website", "url": "https://pngquant.org/lib/"},
332+
{
333+
"type": "distribution",
334+
"url": "https://github.com/ImageOptim/libimagequant/releases",
335+
},
228336
],
229337
},
230338
{
231339
"bom-ref": "pkg:generic/libxcb",
232340
"type": "library",
233341
"name": "libxcb",
342+
"scope": "optional",
234343
"description": "X11 screen-grab support (optional, "
235344
"used by PIL._imagingtk on Linux).",
345+
"licenses": [{"license": {"id": "MIT"}}],
236346
"externalReferences": [
237347
{"type": "website", "url": "https://xcb.freedesktop.org"},
348+
{
349+
"type": "distribution",
350+
"url": "https://xcb.freedesktop.org/dist/",
351+
},
238352
],
239353
},
240354
{
241355
"bom-ref": "pkg:pypi/pybind11",
242356
"type": "library",
243357
"name": "pybind11",
358+
"scope": "optional",
244359
"description": "C++/Python binding library "
245360
"(build-time dependency for PIL._imagingmath).",
361+
"licenses": [{"license": {"id": "BSD-3-Clause"}}],
246362
"externalReferences": [
247363
{"type": "website", "url": "https://pybind11.readthedocs.io"},
364+
{
365+
"type": "distribution",
366+
"url": "https://github.com/pybind/pybind11/releases",
367+
},
248368
],
249369
},
250370
]
@@ -300,11 +420,12 @@ def generate(version: str) -> dict:
300420

301421
return {
302422
"bomFormat": "CycloneDX",
303-
"specVersion": "1.6",
423+
"specVersion": "1.7",
304424
"serialNumber": f"urn:uuid:{serial}",
305425
"version": 1,
306426
"metadata": {
307427
"timestamp": now,
428+
"lifecycles": [{"phase": "build"}],
308429
"tools": [
309430
{
310431
"type": "application",

.github/workflows/wheels.yml

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ on:
1212
push:
1313
paths:
1414
- ".ci/requirements-cibw.txt"
15+
- ".github/generate-sbom.py"
1516
- ".github/workflows/wheel*"
1617
- "pyproject.toml"
1718
- "setup.py"
@@ -23,6 +24,7 @@ on:
2324
pull_request:
2425
paths:
2526
- ".ci/requirements-cibw.txt"
27+
- ".github/generate-sbom.py"
2628
- ".github/workflows/wheel*"
2729
- "pyproject.toml"
2830
- "setup.py"
@@ -277,15 +279,8 @@ jobs:
277279
anaconda_nightly_upload_token: ${{ secrets.ANACONDA_ORG_UPLOAD_TOKEN }}
278280

279281
sbom:
280-
if: |
281-
github.event.repository.fork == false
282-
&& github.event_name == 'push'
283-
&& startsWith(github.ref, 'refs/tags')
284-
needs: count-dists
285282
runs-on: ubuntu-latest
286-
name: Generate and publish SBOM
287-
permissions:
288-
contents: write
283+
name: Generate SBOM
289284
steps:
290285
- uses: actions/checkout@v6
291286
with:
@@ -304,6 +299,22 @@ jobs:
304299
name: sbom
305300
path: "*.cdx.json"
306301

302+
sbom-publish:
303+
if: |
304+
github.event.repository.fork == false
305+
&& github.event_name == 'push'
306+
&& startsWith(github.ref, 'refs/tags')
307+
needs: [count-dists, sbom]
308+
runs-on: ubuntu-latest
309+
name: Publish SBOM to GitHub release
310+
permissions:
311+
contents: write
312+
steps:
313+
- uses: actions/download-artifact@v8
314+
with:
315+
name: sbom
316+
path: .
317+
307318
- name: Attach SBOM to GitHub release
308319
env:
309320
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

0 commit comments

Comments
 (0)