-
Notifications
You must be signed in to change notification settings - Fork 0
ci: hardened changesets release workflow (OIDC, no stored npm token) #57
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
fdbf0a8
ci: add hardened changesets release workflow (OIDC, no stored npm token)
thecodedrift 1356571
ci: use Node 24 and pin npm for the release workflow
thecodedrift d4c1860
ci: gate publish behind a credential-free check job
thecodedrift 8e387d5
ci: harden publish job — no persisted git creds, --ignore-scripts on npm
thecodedrift File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,143 @@ | ||
| # SPDX-License-Identifier: MIT | ||
| # Automated changesets release. Adapted from the pattern in | ||
| # thecodedrift/firebot-script-music-to-my-ears and hardened for npm publishing. | ||
| # | ||
| # Security model (why it is split into two jobs): | ||
| # | ||
| # `version` reads contributor-authored changesets (UNTRUSTED text) and opens | ||
| # the "Version Packages" PR. It has NO npm credential and NO OIDC | ||
| # identity, so a crafted changeset / PR body has nothing to steal | ||
| # or escape into. The changeset TEXT is fully consumed here (into | ||
| # the CHANGELOG + PR body) and never reaches the credentialed job. | ||
| # | ||
| # `publish` runs only when main's version is not yet on npm — i.e. right | ||
| # after the Version Packages PR merges. By then there are no | ||
| # changesets left, so this job sees no untrusted PR/changeset text; | ||
| # it builds from reviewed, merged source only. It authenticates to | ||
| # npm with a SHORT-LIVED token minted via GitHub OIDC (npm trusted | ||
| # publishing) — there is NO stored NPM_TOKEN anywhere to exfiltrate. | ||
| # | ||
| # Residual perimeter, stated honestly: the publish job builds merged repo code, | ||
| # so "what can merge to main" is the real boundary. That is enforced by branch | ||
| # protection (review required) on main. `--ignore-scripts` keeps dependency | ||
| # lifecycle hooks from running while the OIDC identity is available; only our | ||
| # own build runs. No `pull_request_target` and no `${{ }}` interpolation of | ||
| # untrusted text into any `run:` — the two classic token-exfiltration footguns. | ||
| # | ||
| # Action refs are pinned to commit SHAs (supply-chain hardening); the trailing | ||
| # comment records the human-readable tag. | ||
|
|
||
| name: Release | ||
|
|
||
| on: | ||
| push: | ||
| branches: [main] | ||
|
|
||
| # Serialize releases so two pushes can't race the version PR / publish. | ||
| concurrency: release-${{ github.ref }} | ||
|
|
||
| # No workflow-wide grants; each job requests exactly what it needs. | ||
| permissions: {} | ||
|
|
||
| jobs: | ||
| version: | ||
| name: Version Packages PR | ||
| runs-on: ubuntu-latest | ||
| permissions: | ||
| contents: write # push the changeset-release/main branch | ||
| pull-requests: write # open/update the Version Packages PR | ||
| steps: | ||
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | ||
| with: | ||
| fetch-depth: 0 | ||
| - uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4 | ||
| - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 | ||
| with: | ||
| node-version: 24 | ||
| cache: pnpm | ||
| - run: pnpm install --frozen-lockfile --ignore-scripts | ||
|
|
||
| # `version: pnpm bump` runs `changeset version` AND `sync-skill-versions`, | ||
| # so the bumped version is propagated into skills/recipes in the same PR. | ||
| # No `publish:` input — this job can never publish. | ||
| - uses: changesets/action@a45c4d594aa4e2c509dc14a9f2b3b67ba3780d0d # v1 | ||
| with: | ||
| version: pnpm bump | ||
| commit: "chore: version packages" | ||
| title: "chore: version packages" | ||
| env: | ||
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
|
|
||
| # Decides whether main's version needs publishing — with NO credential and NO | ||
| # OIDC identity. Runs on every push, but the credentialed publish job below | ||
| # only starts when this reports a new version, so ordinary non-release pushes | ||
| # never instantiate an OIDC-capable job or touch the npm-production environment. | ||
| check: | ||
| name: Check for a new version | ||
| runs-on: ubuntu-latest | ||
| permissions: | ||
| contents: read # checkout + read package.json | ||
| outputs: | ||
| publish: ${{ steps.check.outputs.publish }} | ||
| steps: | ||
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | ||
| with: | ||
| persist-credentials: false # no git writes here; don't leave the token in git config | ||
| - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 | ||
| with: | ||
| node-version: 24 | ||
| # Publish only a version npm has never seen. On an ordinary feature merge | ||
| # the version is unchanged (already on npm) so this is false; it flips true | ||
| # only on the merge of the Version Packages PR. Reads package.json (repo | ||
| # source) via `node -p` — no untrusted interpolation into the shell. | ||
| - id: check | ||
| run: | | ||
| name=$(node -p "require('./packages/cli/package.json').name") | ||
| version=$(node -p "require('./packages/cli/package.json').version") | ||
| if npm view "$name@$version" version >/dev/null 2>&1; then | ||
| echo "publish=false" >> "$GITHUB_OUTPUT" | ||
| echo "$name@$version already published — nothing to do." | ||
| else | ||
| echo "publish=true" >> "$GITHUB_OUTPUT" | ||
| echo "Will publish $name@$version." | ||
| fi | ||
|
|
||
| publish: | ||
| name: Publish to npm | ||
| # Gate on the credential-free check: this job — and therefore the OIDC | ||
| # identity + npm-production environment — only exists for an actual release. | ||
| needs: check | ||
| if: needs.check.outputs.publish == 'true' | ||
| runs-on: ubuntu-latest | ||
| # Environment is the scoping/audit boundary for the release and where the | ||
| # npm trusted-publisher is bound. No required reviewers (fully automatic | ||
| # once the Version Packages PR merges), by design. | ||
| environment: npm-production | ||
| permissions: | ||
| contents: read # checkout only | ||
| id-token: write # OIDC → short-lived npm auth + build provenance | ||
|
thecodedrift marked this conversation as resolved.
|
||
| steps: | ||
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | ||
| with: | ||
| persist-credentials: false # publish authenticates via OIDC/npm, not git creds | ||
| - uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4 | ||
| - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 | ||
| with: | ||
| node-version: 24 | ||
| cache: pnpm | ||
| registry-url: https://registry.npmjs.org | ||
|
Copilot marked this conversation as resolved.
|
||
| - run: pnpm install --frozen-lockfile --ignore-scripts | ||
|
|
||
| # OIDC trusted publishing + provenance need npm >= 11.5.1. Pin the version | ||
| # (not @latest) so the release is deterministic and a new npm release | ||
| # can't change publish behavior unreviewed; bump this intentionally. | ||
| # --ignore-scripts: no lifecycle code runs while the OIDC identity exists. | ||
| - run: npm install -g npm@12.0.1 --ignore-scripts | ||
|
|
||
| - run: pnpm --filter @taskless/cli build | ||
|
|
||
| # OIDC handshake happens here (id-token: write + registry-url + a | ||
| # registered trusted publisher). No token in env. `--provenance` attaches | ||
| # a signed build-provenance attestation. | ||
| - run: npm publish --provenance --access public | ||
| working-directory: packages/cli | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.