feat: add SHA256 verification for direct downloads

Store SHA256 hashes when running update scripts and verify them
when downloading files at build time. This improves security by
detecting unexpected changes to downloaded artifacts.

Platforms covered: macOS (CPython, PyPy, GraalPy), iOS, Android,
virtualenv, and python-build-standalone. Windows (nuget) and
Linux (Docker) are excluded.

SHA256 sources per platform:
- macOS/iOS/Android CPython (python.org): sha256_sum from API
- GraalPy: .sha256 sidecar assets from GitHub releases
- python-build-standalone: SHA256SUMS file in release
- PyPy, BeeWare iOS, Maven (Chaquopy): stream-download and compute

Changes:
- cibuildwheel/util/file.py: add sha256 param to download()
- cibuildwheel/platforms/{macos,ios,android}.py: add sha256 to
  PythonConfiguration and pass to download()
- cibuildwheel/venv.py: read sha256 from toml and pass to download()
- cibuildwheel/util/python_build_standalone.py: add sha256 to
  PythonBuildStandaloneAsset and pass to download()
- cibuildwheel/resources/build-platforms.toml: add sha256 fields
- cibuildwheel/resources/virtualenv.toml: add sha256 field
- cibuildwheel/resources/python-build-standalone-releases.json: add sha256
- bin/update_pythons.py: compute/store sha256 per source strategy
- bin/update_virtualenv.py: compute sha256 by streaming download
- bin/update_python_build_standalone.py: parse SHA256SUMS file

Closes #908

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Assisted-by: copilot-cli:claude-sonnet-4.6
This commit is contained in:
Henry Schreiner
2026-05-28 11:03:52 -04:00
co-authored by Copilot
parent e3e7cc9e07
commit 97537fe937
12 changed files with 536 additions and 259 deletions
+14 -1
View File
@@ -4,6 +4,7 @@
# dependencies = [
# "click",
# "packaging",
# "requests",
# "rich",
# "cibuildwheel",
# ]
@@ -15,12 +16,14 @@
import dataclasses
import difflib
import hashlib
import logging
import tomllib
from pathlib import Path
from typing import Final
import click
import requests
import rich
from packaging.version import Version
from rich.logging import RichHandler
@@ -92,16 +95,26 @@ def update_virtualenv(force: bool, level: str) -> None:
if latest_release.version > Version(local_version):
version = latest_release.name
url = latest_release.download_url
# Compute sha256 by streaming the new download
log.info("Computing sha256 for %s...", url)
response = requests.get(url, stream=True)
response.raise_for_status()
hasher = hashlib.sha256()
for chunk in response.iter_content(65536):
hasher.update(chunk)
sha256 = hasher.hexdigest()
else:
version = local_version
url = default["url"]
sha256 = default.get("sha256", "")
configurations["default"] = {
"version": version,
"url": url,
"sha256": sha256,
}
result_toml = "".join(
f'{key} = {{ version = "{value["version"]}", url = "{value["url"]}" }}\n'
f'{key} = {{ version = "{value["version"]}", url = "{value["url"]}", sha256 = "{value.get("sha256", "")}" }}\n'
for key, value in configurations.items()
)