fix(checkers): read real PyPI provenance and authenticate GitHub reads

Merged
YA
1mo ago
+482-22

User description

What

Two checker defects, both verified today against the live registries by a read-only pass before this PR started, and both fixed here:

  1. scripts/release/check_drift.py read attestation coverage from the wrong PyPI API. It called the legacy https://pypi.org/pypi/wave-sdk/<version>/json endpoint and checked urls[].provenance, which PyPI has permanently null on that legacy field. Result: the release-drift check reported wave-sdk 2.2.0 as having no PEP 740 attestation even though attestations are actually published — a healthy state rendered as a false failure.
  2. scripts/ga/check_ver_001.py (via scripts/ga/ga_evidence.py) called the GitHub REST API unauthenticated. https://api.github.com/repos/<repo>/tags and .../releases/tags/<tag> are capped at 60 requests/hour per source IP unauthenticated — a cap CI runners sharing NAT'd IP pools burn through easily. Any non-404 HTTPError (including a 403 rate-limit) was turned into a bare RegistryError with no detail, and ga_evidence.py reported that as exit 2 "could not run" — an absent measurement standing in for a real check, distinct from and worse than a false failure.

Fix

Provenance (drift check): pypi_attestations() now reads the PyPI Simple API (https://pypi.org/simple/wave-sdk/ with Accept: application/vnd.pypi.simple.v1+json), which lists each file's provenance URL, and follows that link to the PyPI Integrity API (https://pypi.org/integrity/wave-sdk/<version>/<filename>/provenance). A 200 response carrying attestation_bundles means provenance is present for that file. A 404 (or a file with no provenance link at all) is treated as genuinely absent — that's a real, meaningful drift finding, not an UnreadableError. Only a real network/parse failure (non-200/404, connection error, bad JSON) still raises UnreadableError → exit 2. Added _files_for_version() with a version-boundary regex so e.g. 2.2.0 never accidentally matches a 2.2.0rc1 file.

GitHub auth (ga-evidence): scripts/ga/ga_common.py gained a _headers_for(url) helper that attaches Authorization: Bearer <token> — sourced from the GITHUB_TOKEN or GH_TOKEN environment variable — only when the request targets api.github.com; it is never sent to pypi.org or any other host (verified by a negative-control unit test). .github/workflows/ga-evidence.yml now passes GITHUB_TOKEN: ${{ github.token }} into the producer step's env — the default, job-scoped, read-only token, already governed by the workflow's existing permissions: contents: read block, no new secret. A genuine 403/429 (rate-limited even with a token, or any other non-404 registry error) still fails loud as a RegistryError with the response body attached for diagnosis — it is never silently downgraded to a detail-free "could not run," and never coerced into looking like a legitimate 404 "absent" result.

Out of scope (operator-only)

The PyPI trusted-publisher environment for wave-sdk is currently null ("unconstrained") — every workflow in this repo, not just release.yml, could in principle publish under that trusted-publisher binding rather than only a scoped release GitHub Environment. That is a PyPI project settings change only the package owner/operator can make (Trusted Publisher management UI), and it is not touched by this PR — noted here per the task brief so it's tracked, not silently left unmentioned.

Tests

Added two new unit-test files, both fully mocked (no real network calls in the suite):

  • tests/test_check_drift.py — 7 tests covering: attestation present via Simple API + Integrity API (the real regression case), genuinely-absent-is-not-UnreadableError (404 negative control), partial coverage, Simple-API network failure raising UnreadableError, Integrity-API non-404 error raising UnreadableError with body detail, no-files-for-version raising UnreadableError, and the version-boundary regex not false-matching a prerelease.
  • tests/test_ga_common_github_auth.py — 6 tests covering: GITHUB_TOKEN attached to api.github.com requests, GH_TOKEN fallback, the token never sent to pypi.org (negative control for credential scoping), no token in env → no Authorization header, a 403 raising RegistryError with the response body (the core regression guard — never silently swallowed), and 404 still being the normal "absent" case.
$ python -m pytest -q
........................................................................ [ 85%]
............                                                             [100%]
84 passed in 2.22s

$ ruff check
All checks passed!

Real-run receipts

scripts/release/check_drift.py --repo-root . against wave-sdk 2.2.0 on live PyPI + GitHub — now goes green (previously would have reported the attestation line as NONE):

[source] latest git tag           : v2.2.0 (version 2.2.0)
[source] pyproject.toml version    : 2.2.0
[source] PyPI latest version        : 2.2.0
[source] GitHub Release for v2.2.0   : exists
[source] PyPI 2.2.0 attestations : present on all files

RESULT: in sync (tag, pyproject, PyPI, GitHub Release, and attestations all agree)

Exit code: 0

scripts/ga/ga_evidence.py --repo wave-av/sdk-python --package wave-sdk run with a real GitHub token in env (job-scoped gh auth token, injected into the process environment only, never printed) — completes and prints its verdict instead of exit-2'ing:

PASS VER-001: wheel-digest-matches-index=True; wheel-metadata-matches-pypi-version=True; newest-tag-exists=True; newest-tag-has-github-release=True; head-version-matches-published=True; newest-tag-matches-published=True
UNKNOWN SUPPLY-001: provenance-present:wave_sdk-2.2.0-py3-none-any.whl=True; provenance-present:wave_sdk-2.2.0.tar.gz=True; sbom-attached=None; known-vuln-resolution=None

evidence fingerprint: 804778014ea46ff44d9b26e3e8aadadec5ba4978f00e66d0b8816db718b02057

Exit code: 0 (SUPPLY-001 is UNKNOWN, not FAIL or a crash — sbom-attached and known-vuln-resolution are explicitly out of scope for that producer and always report unknown, by design, per its docstring)

Not merging

This PR is left open for review per the task's scope — it is not merged here.


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.


[!NOTE]
Cursor Bugbot is generating a summary for commit 47f65f52379dc952daef27c5a6a5b45e7cc328f0. Configure here.


CodeAnt-AI Description

Correct PyPI provenance checks and authenticate GitHub registry reads

What Changed

  • Release-drift checks now verify PyPI attestations through the current Simple and Integrity APIs, correctly identifying present, partial, and genuinely missing provenance.
  • Version matching no longer confuses a release with similarly prefixed versions such as release candidates.
  • GitHub registry requests use the CI-provided read-only token when available, while keeping credentials off non-GitHub requests.
  • GitHub rate-limit and other HTTP failures now include response details; expected 404 responses remain treated as missing resources.
  • Added regression tests covering provenance results, version filtering, credential scoping, and registry error handling.

Impact

✅ Accurate PyPI attestation drift results
✅ Fewer GitHub API rate-limit failures in CI
✅ Clearer registry failure diagnostics

💡 Usage Guide

Checking Your Pull Request

Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.

Talking to CodeAnt AI

Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:

@codeant-ai ask: Your question here

This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.

Example

@codeant-ai ask: Can you suggest a safer alternative to storing this secret?

Preserve Org Learnings with CodeAnt

You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:

@codeant-ai: Your feedback here

This helps CodeAnt AI learn and adapt to your team's coding style and standards.

Example

@codeant-ai: Do not flag unused imports.

Retrigger review

Ask CodeAnt AI to review the PR again, by typing:

@codeant-ai: review

Check Your Repository Health

To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.