Skip to content

ci: switch npm and PyPI releases to Trusted Publishing (OIDC)#47

Closed
thebenignhacker wants to merge 1 commit into
mainfrom
ci/trusted-publishing-rollout
Closed

ci: switch npm and PyPI releases to Trusted Publishing (OIDC)#47
thebenignhacker wants to merge 1 commit into
mainfrom
ci/trusted-publishing-rollout

Conversation

@thebenignhacker

Copy link
Copy Markdown
Contributor

Summary

  • Drop NODE_AUTH_TOKEN: secrets.NPM_TOKEN from publish-npm.yml and bump runner Node to 24 so npm Trusted Publishing handles auth via OIDC.
  • Drop password: secrets.PYPI_API_TOKEN from all four PyPI release jobs in publish.yml. The pypa/gh-action-pypi-publish action silently disables attestations whenever a password: is supplied alongside id-token: write, so every PyPI release since 0a31816 (2026-02-07) shipped without sigstore signatures despite the attestations: true default.
  • Harden the GitHub Release step so workflow re-runs against an existing tag (the actual root cause of the v1.4.3 release-job failure) update assets via gh release upload --clobber instead of crashing the whole job.

DRAFT until admin actions complete

This PR will break the next release if merged before Trusted Publishing is configured on the registries. Steps to land it:

  1. npm: npmjs.com -> cryptoserve -> Settings -> Publishing access -> Trusted Publisher -> GitHub Actions
    • Org: ecolibria
    • Repo: cryptoserve
    • Workflow filename: publish-npm.yml
    • Environment: blank
  2. PyPI (4 packages, one each): visit the URLs below while logged in as a project owner. The pypa/gh-action-pypi-publish action prints these in every run.
  3. Mark this PR ready for review and merge.
  4. Delete NPM_TOKEN and PYPI_API_TOKEN from repo secrets so a future revert cannot silently re-disable provenance.

Verification (next release after merge)

npm view cryptoserve@<v> dist.attestations --json     # must be non-empty, predicateType matches SLSA v1
curl -s https://pypi.org/pypi/cryptoserve/<v>/json | jq '.urls[].has_sig'   # must be true

Out of scope

  • The cryptoserve@0.3.4 npm release that has no provenance because no js-v0.3.4 tag was ever pushed (manual release outside the workflow). Tag-push gate hook will be a separate PR.
  • Bumping actions/checkout and actions/download-artifact to silence the Node 20 deprecation warning. Those actions are scheduled to migrate themselves before June 2026.

Test plan

  • Confirm Trusted Publishing is configured on npm + 4 PyPI packages (admin steps above).
  • After merge, push a real release tag and verify npm notice publish Provenance statement published to transparency log: appears in the npm job log.
  • After merge, verify the PyPI job log no longer prints the attestations input is ignored.
  • Run the verification commands above and confirm both return signed/non-empty.

The npm workflow was already requesting `--provenance` with `id-token:
write` AND a long-lived `NODE_AUTH_TOKEN`. The Python workflow set
`id-token: write` AND `password: PYPI_API_TOKEN`. The PyPI side has
been silently emitting attestations: ignored on every run since
0a31816 (2026-02-07):

  The workflow was run with the 'attestations: true' input, but an
  explicit password was also set, disabling Trusted Publishing. As
  a result, the attestations input is ignored.

Both registries now exclusively use OIDC Trusted Publishing:

publish-npm.yml
- Bump runner Node to 24 (TP requires modern npm).
- Drop `NODE_AUTH_TOKEN: secrets.NPM_TOKEN`. `id-token: write` plus
  `registry-url: https://registry.npmjs.org` is sufficient for
  setup-node@v4 + npm 11+ to handle the OIDC handshake.

publish.yml
- Drop `password: secrets.PYPI_API_TOKEN` from all four publish
  jobs (cryptoserve-core, cryptoserve-client, cryptoserve-auto,
  cryptoserve). `id-token: write` plus `environment: pypi` is what
  pypa/gh-action-pypi-publish needs to enable attestations.
- Harden the GitHub Release step so re-runs against an existing
  tag (which was the actual cause of the v1.4.3 publish-job
  failure) update assets via `gh release upload --clobber` instead
  of crashing the whole job.

Pre-merge checklist (admin actions, then merge):
1. npmjs.com -> cryptoserve -> Settings -> Publishing access ->
   Trusted Publisher: org=ecolibria, repo=cryptoserve,
   workflow=publish-npm.yml, environment blank.
2. For each of cryptoserve, cryptoserve-core, cryptoserve-client,
   cryptoserve-auto on pypi.org -> Settings -> Publishing -> add
   Trusted Publisher: owner=ecolibria, repo=cryptoserve,
   workflow=publish.yml, environment=pypi.
3. After steps 1 + 2 are confirmed: merge this PR. Then delete
   NPM_TOKEN and PYPI_API_TOKEN from repo secrets.
4. Verify on the next release with
   `npm view cryptoserve dist.attestations --json` and
   `curl https://pypi.org/pypi/cryptoserve/<v>/json | jq '.urls[].has_sig'`.

This PR keeps the workflow on `js-v*` and `v*` tag triggers; tag
hygiene and the manual-publish hole that produced npm cryptoserve
0.3.4 without provenance are out of scope here.
@github-actions

github-actions Bot commented May 15, 2026

Copy link
Copy Markdown

Claude finished @thebenignhacker's task —— View job


Security Review Complete ✅

Tasks

  • Read changed workflow files
  • Perform security analysis (12 categories)
  • Trace logic with concrete inputs
  • Post findings

Changes Summary

publish-npm.yml:

  • Removed NODE_AUTH_TOKEN: secrets.NPM_TOKEN from publish step
  • Bumped Node version from 20 to 24
  • Added id-token: write permission (already present)

publish.yml:

  • Removed password: secrets.PYPI_API_TOKEN from all 4 PyPI publish jobs
  • Enhanced GitHub Release step to handle re-runs (--clobber flag)
  • Changed from always creating release to checking if exists first

Security Analysis

I've performed a comprehensive security review across all 12 categories specified:

✅ Categories 1-9: No Issues Found

  1. Injection vulnerabilities: No SQL/command/path traversal issues. The bash script at .github/workflows/publish.yml:211-236 uses safe patterns:

    • TAG=${GITHUB_REF#refs/tags/} - parameter expansion, not command substitution
    • find with literal patterns, piped to while read
    • gh release commands use array expansion "${ASSETS[@]}" which properly quotes arguments
    • No unquoted variable expansion in dangerous contexts
  2. Authentication/authorization bypasses: ✅ SECURITY IMPROVEMENT - This PR actually strengthens security by:

    • Removing static API tokens in favor of OIDC Trusted Publishing
    • Properly scoping id-token: write permission to only the jobs that need it
    • The npm job already had id-token: write permission (line 34)
    • All PyPI jobs correctly declare environment: pypi (lines 122, 141, 160, 179) for environment protection
  3. Insecure deserialization/eval: Not applicable to YAML workflow files

  4. Hardcoded credentials: ✅ SECURITY IMPROVEMENT - Removes references to secrets.NPM_TOKEN and secrets.PYPI_API_TOKEN, reducing secret sprawl

  5. SSRF/open redirect: No HTTP requests with user-controlled URLs

  6. Cryptographic misuse: Not applicable to workflow files

  7. Prototype pollution: Not applicable to YAML workflow files

  8. ReDoS: No user-controlled regex patterns

  9. Command injection: Bash script safely handles variables (see category 1)

✅ Categories 10-12: Logic Verification

  1. Scoring/gating functions: Not applicable - no scoring logic in changed code

  2. Filter predicates: The conditional at .github/workflows/publish.yml:229 is correct:

    if gh release view "$TAG" >/dev/null 2>&1; then
    • Test case 1: Tag exists → gh release view exits 0 → condition true → runs gh release upload --clobber
    • Test case 2: Tag doesn't exist → gh release view exits non-zero → condition false → runs gh release create
    • Test case 3: Empty TAG → command fails safely, doesn't create malformed release ✅
  3. Boolean expressions: All boolean logic is sound:

    • permissions.id-token: write is only set on jobs that publish (not test jobs) ✅
    • needs: dependencies correctly sequence PyPI jobs (core first, then client/auto, then main) ✅

Additional Observations

Positive changes:

  • The --clobber flag addition (.github/workflows/publish.yml:230) is a robustness improvement that prevents workflow failures on re-runs
  • Proper use of bash arrays and quoting prevents expansion issues
  • The skip_existing: true parameter on all PyPI publish jobs provides idempotency

No security regressions identified. The migration from static tokens to OIDC Trusted Publishing is a security best practice.


FINDINGS_COUNT=0


@github-actions

Copy link
Copy Markdown

Manual review required. This PR did not meet auto-merge criteria:

  • Security review found issues (findings: unknown)
  • PR author 'thebenignhacker' is not in the trusted auto-merge list
  • Changes to protected path: .github/workflows/
  • PR is too new (2 min < 5 min minimum)

@thebenignhacker

Copy link
Copy Markdown
Contributor Author

Closing for now per maintainer call. The diagnosis stands (PyPI password: silently disables attestations; npm side needs Node 24 + drop NODE_AUTH_TOKEN; gh release create should fall through to upload --clobber on existing tags) but the registry-side admin steps are deferred. Branch ci/trusted-publishing-rollout is preserved if we revisit; the technical notes are captured in session memory under feedback-trusted-publishing-rules. Reopen or rebase when ready to do the npm + PyPI Trusted Publishing setup.

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