From f3a204347c2cafe43810394e3d5c7ab0c8f7a462 Mon Sep 17 00:00:00 2001 From: Hongming Wang Date: Sun, 26 Apr 2026 13:14:47 -0700 Subject: [PATCH] fix(publish-runtime): use PyPI Trusted Publisher (OIDC) instead of PYPI_TOKEN (#2113) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drops the static PYPI_TOKEN secret in favor of OIDC trusted publishing. PyPI now mints a short-lived upload credential after verifying the workflow's OIDC claim against the trusted-publisher config registered for molecule-ai-workspace-runtime (Molecule-AI/molecule-core, publish-runtime.yml, environment pypi-publish). Why: - A leaked PYPI_TOKEN would let any holder publish arbitrary versions of molecule-ai-workspace-runtime to PyPI from anywhere — bypassing the monorepo's review and CI gates entirely. The 8 template repos pull this package; a malicious publish poisons all of them. - Trusted Publisher (OIDC) makes that exfil path moot: no long-lived credential exists to leak. Only this exact workflow, on this repo, in the pypi-publish environment, can upload. After this lands and the first OIDC publish succeeds, the PYPI_TOKEN repo secret should be deleted (it becomes dead weight + a leak surface with no purpose). Belt-and-suspenders companion to PR #56 in molecule-ai-workspace-runtime (sibling repo lockdown). Without OIDC, the sibling lockdown alone doesn't prevent local `python -m build && twine upload` from a laptop with a personal PyPI maintainer credential. Co-authored-by: Hongming Wang Co-authored-by: Claude Opus 4.7 (1M context) --- .github/workflows/publish-runtime.yml | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/.github/workflows/publish-runtime.yml b/.github/workflows/publish-runtime.yml index f2e6fe25..d46af65f 100644 --- a/.github/workflows/publish-runtime.yml +++ b/.github/workflows/publish-runtime.yml @@ -16,7 +16,11 @@ name: publish-runtime # build/molecule_runtime/ with imports rewritten (`a2a_client` → # `molecule_runtime.a2a_client`). # 2. Builds wheel + sdist with `python -m build`. -# 3. Publishes to PyPI via twine + repo secret PYPI_TOKEN. +# 3. Publishes to PyPI via the PyPA Trusted Publisher action (OIDC). +# No static API token is stored — PyPI verifies the workflow's +# OIDC claim against the trusted-publisher config registered for +# molecule-ai-workspace-runtime (Molecule-AI/molecule-core, +# publish-runtime.yml, environment pypi-publish). # # After publish: the 8 template repos pick up the new version on their # next image rebuild (their requirements.txt pin @@ -42,6 +46,9 @@ jobs: publish: runs-on: ubuntu-latest environment: pypi-publish + permissions: + contents: read + id-token: write # PyPI Trusted Publisher (OIDC) — no PYPI_TOKEN needed steps: - uses: actions/checkout@v4 @@ -106,12 +113,15 @@ jobs: print('✓ smoke import passed') " - - name: Publish to PyPI - working-directory: ${{ runner.temp }}/runtime-build - env: - TWINE_USERNAME: __token__ - TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }} - run: python -m twine upload dist/* + - name: Publish to PyPI (Trusted Publisher / OIDC) + # PyPI side is configured: project molecule-ai-workspace-runtime → + # publisher Molecule-AI/molecule-core, workflow publish-runtime.yml, + # environment pypi-publish. The action mints a short-lived OIDC + # token and exchanges it for a PyPI upload credential — no static + # API token in this repo's secrets. + uses: pypa/gh-action-pypi-publish@release/v1 + with: + packages-dir: ${{ runner.temp }}/runtime-build/dist/ cascade: # After PyPI accepts the upload, fan out a repository_dispatch to each