Skip to content

Split PyPI publishing workflow: isolate OIDC-privileged upload into its own job#230

Draft
Copilot wants to merge 4 commits into
mainfrom
copilot/split-pypi-publishing-workflow
Draft

Split PyPI publishing workflow: isolate OIDC-privileged upload into its own job#230
Copilot wants to merge 4 commits into
mainfrom
copilot/split-pypi-publishing-workflow

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 23, 2026

PyPI's trusted publisher security model recommends confining the OIDC-privileged upload step to a dedicated job, keeping the build step unprivileged. Granting id-token: write to a job that also checks out and builds code unnecessarily widens the blast radius if that job is compromised.

Changes

  • New workflow .github/workflows/publish-to-pypi.yml triggered on release: published

  • build jobcontents: read only

    • Installs hatch, runs hatch build
    • Uploads dist/ as a GitHub Actions artifact
  • publish-to-pypi jobid-token: write only

    • Downloads the pre-built artifact (never touches source)
    • Publishes via pypa/gh-action-pypi-publish using OIDC trusted publishing
    • Gated behind the pypi GitHub environment for additional approval control
jobs:
  build:
    permissions:
      contents: read        # no OIDC
    steps:
      - run: hatch build
      - uses: actions/upload-artifact@v4

  publish-to-pypi:
    needs: build
    permissions:
      id-token: write       # OIDC only, no source access
    steps:
      - uses: actions/download-artifact@v4
      - uses: pypa/gh-action-pypi-publish@release/v1

Copilot AI self-assigned this Apr 23, 2026
Copilot AI review requested due to automatic review settings April 23, 2026 20:34
Copilot AI review requested due to automatic review settings April 23, 2026 20:34
Copilot AI requested review from Copilot and removed request for Copilot April 23, 2026 20:36
Copilot AI changed the title [WIP] Update PyPI publishing workflow to separate upload job Split PyPI publishing workflow: isolate OIDC-privileged upload into its own job Apr 23, 2026
Copilot AI requested a review from kevinbackhouse April 23, 2026 20:37
Comment thread .github/workflows/publish-to-pypi.yml Outdated
needs: build
runs-on: ubuntu-latest
environment:
name: pypi
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check the repo settings and make sure this is behind an approval button so that when the dists are built, there's that last line of defense where a human can double-check the artifacts if needed.

Comment thread .github/workflows/publish-to-pypi.yml Outdated
path: dist/

publish-to-pypi:
name: Publish to PyPI
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you'll want the same for testpypi I imagine?

Comment thread .github/workflows/publish-to-pypi.yml Outdated
Comment on lines +23 to +27
- name: Install Hatch
run: pip install --upgrade hatch

- name: Build distribution packages
run: hatch build
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for a custom frontend really. Just use a PyPA project.

Suggested change
- name: Install Hatch
run: pip install --upgrade hatch
- name: Build distribution packages
run: hatch build
- name: Install the build frontend
run: python -Im pip install --upgrade build
- name: Build distribution packages
run: python -Im build

Although, I must say, it may be a good idea to make this somewhat more predicatable by having a build-deps.in and build-deps.txt managed by pip-tools (supported by Dependabot natively). With just build in build-deps.in.

It's usable as follows:

Suggested change
- name: Install Hatch
run: pip install --upgrade hatch
- name: Build distribution packages
run: hatch build
- name: Install the build frontend
run: python -Im pip install -r build-deps.in -c build-deps.txt
- name: Build distribution packages
run: python -Im build

I often take this a bit farther by also pinning the isolated build env:

pip-compile --all-build-deps --only-build-deps --output-file=build-lock.txt --strip-extras pyproject.toml

(https://pip-tools.rtfd.io/en/latest/#maximizing-reproducibility)

This would integrate as follows:

Suggested change
- name: Install Hatch
run: pip install --upgrade hatch
- name: Build distribution packages
run: hatch build
- name: Install the build frontend
run: python -Im pip install --requirement=build-deps.in --constraint=build-deps.txt
- name: Build distribution packages
env:
PIP_CONSTRAINT: build-lock.txt
run: python -Im build

Copilot AI review requested due to automatic review settings May 12, 2026 15:59
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new GitHub Actions workflow to publish the Python package to PyPI using trusted publishing, splitting the build and the OIDC-privileged upload into separate jobs to reduce credential blast radius.

Changes:

  • Introduces .github/workflows/publish-to-pypi.yml triggered on release: published.
  • Builds distributions in an unprivileged build job and uploads dist/ as an artifact.
  • Downloads the artifact in a separate publish-to-pypi job gated by the pypi environment and publishes via OIDC.
Show a summary per file
File Description
.github/workflows/publish-to-pypi.yml Adds a two-job (build → publish) PyPI release workflow using artifacts and OIDC trusted publishing.

Copilot's findings

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comments suppressed due to low confidence (4)

.github/workflows/publish-to-pypi.yml:21

  • This workflow uses floating major-version refs for GitHub Actions (actions/checkout@v4, actions/setup-python@v5). Elsewhere in this repo’s release/publishing workflows, actions are pinned to full commit SHAs (e.g., .github/workflows/publish-to-pypi.yaml). For supply-chain hardening (especially in a release pipeline), please pin these actions to commit SHAs.
        uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.11'

.github/workflows/publish-to-pypi.yml:33

  • actions/upload-artifact@v4 / actions/download-artifact@v4 are referenced by a floating tag. Since these steps are part of the release supply chain, consider pinning them to specific commit SHAs (and preferably aligning to the same major/version used in the existing publish workflows) to reduce the risk of a compromised upstream tag affecting releases.
      - name: Upload distribution artifacts
        uses: actions/upload-artifact@v4
        with:
          name: python-package-distributions
          path: dist/

.github/workflows/publish-to-pypi.yml:53

  • The publish step uses pypa/gh-action-pypi-publish@release/v1, which is a moving ref. The existing workflow pins this action to a specific commit SHA. For a security-sensitive publishing workflow, pin this action to a commit SHA (or a fully qualified version tag) to prevent upstream tag movement from impacting releases.
      - name: Publish to PyPI
        uses: pypa/gh-action-pypi-publish@release/v1

.github/workflows/publish-to-pypi.yml:34

  • Compared to the existing PyPI publishing workflow in this repo, this workflow does not generate build provenance attestations (e.g., via actions/attest-build-provenance). If attestations are relied on for release integrity/auditing, consider adding an attestation step in the build job and granting only the minimum required attestations: write permission there.
      - name: Build distribution packages
        run: hatch build

      - name: Upload distribution artifacts
        uses: actions/upload-artifact@v4
        with:
          name: python-package-distributions
          path: dist/

  • Files reviewed: 1/1 changed files
  • Comments generated: 2

Comment thread .github/workflows/publish-to-pypi.yml Outdated
Comment on lines +4 to +5
release:
types: [published]
Comment thread .github/workflows/publish-to-pypi.yml Outdated

steps:
- name: Checkout code
uses: actions/checkout@v4
Comment thread .github/workflows/publish-to-pypi.yml Outdated
path: dist/

- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
@kevinbackhouse
Copy link
Copy Markdown
Collaborator

@copilot: rename the file to .yaml.

Copy link
Copy Markdown
Contributor Author

Copilot AI commented May 12, 2026

@copilot: rename the file to .yaml.

Done — the old publish-to-pypi.yaml (tag-push trigger) has been removed and the new workflow has been renamed to publish-to-pypi.yaml in commit $(git -C /tmp/workspace/GitHubSecurityLab/seclab-taskflow-agent rev-parse --short HEAD).

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.

Split the PyPI publishing workflow to only have the upload in the publishing job

5 participants