Migrate CI publishing back to trusted publishing (OIDC)¶
Status: active / temporary workaround in place Owner: maintainer (Nehal) Trigger to close: PyPI account access recovered (2FA re-established)
Current (temporary) state¶
release.yml publishes token-based:
| Package set | Auth today | Secret |
|---|---|---|
PyPI: habemus-papadum-rfb, -nvenc, -vtenc |
API token via pypa/gh-action-pypi-publish password: |
PYPI_API_TOKEN |
npm: @habemus-papadum/rfb-widgets + 3 wrappers |
_authToken npmrc + pnpm publish --provenance |
NPM_TOKEN |
The pypi-publish job no longer sets environment: pypi or id-token: write; the
npm-publish job is unchanged (it was already token-based — it keeps id-token: write
only for provenance attestation, which is independent of the publish auth).
Releasing is CI-only — the release workflow (workflow_dispatch); there is no local release
script. scripts/publish.sh (reading .env) remains a token-based manual break-glass fallback.
Uploading the secrets (one-time, from a maintainer box)¶
The token values already live in the maintainer's git-ignored .env
(HATCH_INDEX_AUTH = the PyPI token, NPM_TOKEN = the npm token). Upload them without
printing the values:
cd /path/to/pdum_rfb
set -a; . ./.env; set +a # load .env into the shell (values stay in-process)
printf '%s' "$HATCH_INDEX_AUTH" | gh secret set PYPI_API_TOKEN
printf '%s' "$NPM_TOKEN" | gh secret set NPM_TOKEN
gh secret list # verify both are present
Then a normal release (Actions → release → Run workflow, pick bump) publishes everything
from CI using the secrets.
Security notes (accepted, time-boxed)¶
- Long-lived tokens in CI secrets are the exact risk trusted publishing removes: a workflow compromise (malicious dependency in a build step, a poisoned action) could exfiltrate them. Kept acceptable only because this is short-lived and the project is not mission-critical.
- Scope: the PyPI token is account-wide (it published all three projects at 0.1.0). If PyPI access is recovered enough to mint tokens but not enough to set up trusted publishing, replace it with three project-scoped tokens to limit blast radius.
- Optional gate: move the secrets into a
pypiGitHub Environment with a required reviewer, so every publish needs a human approval. Skipped for now for simplicity. - Rotate on exit: treat both tokens as potentially exposed once this window closes — see the migration checklist.
Migration checklist (do this once PyPI access is back)¶
- PyPI trusted publishers — for each of
habemus-papadum-rfb,habemus-papadum-nvenc,habemus-papadum-vtenc: project → Publishing → GitHub Actions → add:owner=habemus-papadum,repository=pdum_rfb,workflow=release.yml,environment=pypi. - GitHub environment:
gh api -X PUT repos/habemus-papadum/pdum_rfb/environments/pypi(optionally add a required reviewer). - Revert
pypi-publishinrelease.ymlto OIDC: - restore
environment: pypiandpermissions: id-token: write; - remove the
password: ${{ secrets.PYPI_API_TOKEN }}line. - npm trusted publishers (optional, if moving npm off tokens too) — needs npm ≥ 11.5.1:
Then rework
npm install -g npm@latest for pkg in rfb-widgets rfb-react rfb-svelte rfb-solid; do npm trust github "@habemus-papadum/$pkg" --repo habemus-papadum/pdum_rfb \ --file release.yml --allow-publish -y donenpm-publishto OIDC. Caveat: pnpm does not yet do the OIDC handshake reliably (pnpm #9812, #11513 —pnpm publishvia OIDC 404s), so the workflow mustpnpm packeach package (resolves the wrappers'workspace:^→ a concrete range) and thennpm publish <tarball> --provenancewith npm ≥ 11.5.1, which does the OIDC exchange. Validate on av0.1.1rc1tag before a real release. Until this is proven, keeping npm onNPM_TOKENis a reasonable steady state. - Delete the CI secrets:
gh secret delete PYPI_API_TOKEN(andNPM_TOKENif npm moved to OIDC). - Rotate the tokens: mint a fresh PyPI token and revoke the old one; if npm moved
to OIDC, revoke the old npm automation token. Update
.envlocally. - Restore the "trusted publishing" wording in
release.yml's header anddocs/development.md, and mark this proposal completed.
Appendix: the trusted-publishing setup (target state)¶
Exact values for all registries, for when step 2/5 are executed:
- PyPI (web UI, per project): owner
habemus-papadum, repositorypdum_rfb, workflow filenamerelease.yml, environmentpypi. No API exists — it is web-UI only. - npm (
npm trustCLI, per package): repohabemus-papadum/pdum_rfb, workflow filerelease.yml,--allow-publish, no--env(thenpm-publishjob runs in no environment). One trusted-publisher config per package (npm trust revoke --id <id>to replace).