Skip to content

security: verify published PyPI artifact bytes - #497

Merged
codeforester merged 3 commits into
mainfrom
security/489-20261028-pypi-byte-verification
Oct 28, 2026
Merged

codeforester merged 3 commits into
mainfrom
security/489-20261028-pypi-byte-verification

Conversation

@codeforester

Copy link
Copy Markdown
Contributor

Summary

  • compare PyPI file names and SHA-256 digests with the reviewed wheel/sdist
  • validate the reviewed SHA256SUMS file in the same gate
  • retain machine-readable PYPI-BYTE-VERIFICATION.json evidence as a workflow artifact
  • keep the existing GitHub Release byte-for-byte verification as the companion release-assets check

Security rationale

Version and README checks alone do not prove that users received the reviewed bytes. The PyPI verification job now fails on missing, extra, renamed, or hash-mismatched files and records the result.

Validation

  • UV_CACHE_DIR=/private/tmp/base-cli-uv-cache uv run --extra dev pytest -q tests/test_verify_pypi_metadata.py tests/test_package_workflow.py
  • Ruff and git diff --check pass

Relates to #489.

Comment thread .github/workflows/package.yml Resolved
Comment thread scripts/verify_pypi_metadata.py Resolved
Comment thread scripts/verify_pypi_metadata.py Resolved
Comment thread scripts/verify_pypi_metadata.py Resolved
@codeforester

Copy link
Copy Markdown
Contributor Author

Implemented the review fix in commit ee0f4989.

  • The PyPI byte-verification evidence upload now uses if: ${{ always() }}, so the forensic JSON is retained when the byte comparison fails, which is the failure path it documents.

@codeforester

Copy link
Copy Markdown
Contributor Author

Follow-up after the latest push (ee0f4989):

  • The if: always() fix landed, but only half of what I suggested: if-no-files-found is still error. If verify_pypi_metadata.py crashes before writing any evidence file (e.g. _fetch() exhausts its retries), this step now runs but finds nothing at dist/PYPI-BYTE-VERIFICATION.json, and if-no-files-found: error will raise a misleading "no files found" failure that masks the real root cause instead of surfacing it. Pairing with if-no-files-found: ignore (as tests.yml already does) would close this.
  • The other three findings in scripts/verify_pypi_metadata.py — the inline SHA-256 reimplementation instead of release_metadata_helpers.sha256_file(), the swapped expected/observed labels in the mismatch message, and the malformed/missing urls field discarding already-computed local hashes — are all still present in the current diff; none were addressed by this push.

Leaving these threads open since the underlying code is unchanged for 3 of 4, and the 4th is only partially fixed.

@codeforester

Copy link
Copy Markdown
Contributor Author

Addressed the remaining evidence/hash-validation feedback in commit f409ced7.

  • Evidence upload now uses if: always() with if-no-files-found: ignore, preserving the primary failure when no evidence file can be produced.
  • Reused the streaming sha256_file() release helper.
  • Renamed local/published hash sets for unambiguous diagnostics.
  • Preserved reviewed artifact hashes and recorded validation errors when PyPI returns malformed urls data.
  • Added regression coverage for malformed PyPI metadata.

@codeforester

Copy link
Copy Markdown
Contributor Author

Confirmed — this push (f409ced7) addresses all four remaining points:

  • if-no-files-found: ignore now pairs correctly with if: always().
  • Hashing now goes through release_metadata_helpers.sha256_file() instead of a local reimplementation.
  • observed/published were renamed to reviewed/published, which now matches the "expected {reviewed}, observed {published}" wording in the error message — no more backwards labeling.
  • A malformed/missing urls field no longer short-circuits before the local hashes are computed; evidence now records the reviewed hashes even when PyPI's response is malformed, and the new test (test_preserves_reviewed_hashes_when_pypi_urls_are_malformed) covers exactly that case.

No remaining concerns from me on this PR. The two "Benchmark" check failures on this run aren't required status checks, so they shouldn't block merge.

@codeforester
codeforester force-pushed the security/489-20261028-pypi-byte-verification branch from f409ced to 0d10b6c Compare October 28, 2026 15:09
@codeforester
codeforester merged commit 92f5eb1 into main Oct 28, 2026
112 of 113 checks passed
@codeforester
codeforester deleted the security/489-20261028-pypi-byte-verification branch October 28, 2026 15:14
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