ci: hardening per TanStack supply-chain postmortem + action updates#750
Merged
Conversation
Mitigates supply-chain risk per the TanStack npm compromise postmortem (2026-05-11). A namespace takeover on complexdatacollective, chromaui, or oven-sh cannot publish a malicious update to a moving tag we trust. Also replaces chromaui/action@latest (the highest-risk reference in the repo) with a specific commit on v16.10.0. netlify-deploy-preview.yml is intentionally untouched here; pinning its third-party actions is bundled with a breaking-change migration in a separate commit so the file is never in a broken state.
- actions/github-script v7 -> v9 (chromatic) - actions/checkout v4 -> v6 (docker-publish) - docker/setup-buildx-action v3 -> v4 (docker-publish) - docker/login-action v3 -> v4 (docker-publish) - docker/setup-qemu-action v3 -> v4 (docker-publish) - docker/build-push-action v6 -> v7 (docker-publish) All first-party / Docker-official actions, kept at major-version refs rather than pinned to SHAs: these namespaces are verified-publisher and out of scope for the third-party supply-chain hardening in the companion commits. Docker v4/v7 and actions/checkout@v6 require Node 24 / Actions Runner v2.327.1+, both present on ubuntu-latest. Verified this repo does not use DOCKER_BUILD_NO_SUMMARY or DOCKER_BUILD_EXPORT_RETENTION_DAYS (removed in build-push v7).
Mitigates supply-chain risk per the TanStack npm compromise postmortem (2026-05-11). A namespace takeover on neondatabase, complexdatacollective, or thollander cannot publish a malicious update to a moving tag we trust. Bundled changes (one commit so the file is never in a broken state): - Pin neondatabase/create-branch-action to 6.3.1 SHA - Pin complexdatacollective/github-actions/setup-pnpm to v1 SHA - Pin thollander/actions-comment-pull-request to v3.0.1 SHA, which is also a major-version bump. v3 renames inputs from snake_case to kebab-case (comment_tag -> comment-tag) and changes mode: delete semantics. The 'Remove failure comment on success' step uses mode: delete-on-completion to preserve the v2 end-of-job-delete behavior the workflow originally relied on. - Remove tj-actions/branch-names@v9 entirely. The tj-actions namespace was compromised in the March 2024 changed-files incident, and the only output we used (current_branch) is github.head_ref on pull_request events.
Closes a privileged-trigger surface flagged by the TanStack npm compromise postmortem (2026-05-11). Two orthogonal mitigations so no single regression reopens it: - author_association gate: only OWNER/MEMBER/COLLABORATOR comments can fire the /chromatic trigger. Fork-PR authors commenting on their own PRs no longer qualify. - Removed the dead Setup-step 'ref:' input (the YAML 'with:' line, not the 'core.setOutput(ref, ...)' value that lives in the github-script step further up -- that value is consumed downstream by 'Post links to PR' so it stays). setup-pnpm@v1 silently dropped the 'ref:' input (no such input declared), so the workflow already checked out the default branch -- but a future 'fix' would silently introduce the untrusted-code path. Leaving no booby trap. Also isolated the Chromatic pnpm-store cache via cache-key-prefix so this workflow cannot poison the cache that build.yml restores on push to main. Residual risk: a maintainer can still be socially engineered into commenting /chromatic on a malicious PR. Treat /chromatic like an approval -- only invoke after reviewing the diff.
Second batch of first-party action bumps on this branch (the first batch covered chromatic + docker-publish). All actions kept at major- version refs rather than SHA-pinned, consistent with the project scope decision treating verified-publisher first-party namespaces as out of scope for the third-party supply-chain hardening. - actions/checkout v4 -> v6 (e2e x2, cleanup x1) - actions/upload-artifact v4 -> v7 (e2e) - actions/download-artifact v4 -> v8 (e2e) - actions/upload-pages-artifact v3 -> v5 (e2e, cleanup) - actions/deploy-pages v4 -> v5 (e2e, cleanup) - actions/github-script v7 -> v9 (e2e) Breaking-change deltas verified safe for the actual usage: - upload-artifact v7's opt-in direct uploads (archive: false) aren't used here; default zip-archive path is unchanged. - download-artifact v8's hash-mismatch-errors-by-default is safe for this single-job upload/download flow. - upload-pages-artifact v4+'s hidden-file exclusion: artifacts are playwright reports + index.html, no dotfiles. - deploy-pages v5 and v6 of every other action require Node 24 / Runner v2.327.1+, present on ubuntu-latest.
Defense-in-depth follow-up to the supply-chain hardening commits on this branch. The TanStack npm compromise postmortem (2026-05-11) demonstrated that id-token: write inherited by a job running untrusted code allows the attacker to mint an OIDC token from process memory. In e2e.yml the e2e job runs untrusted PR code via scripts/run-e2e.sh and inherited id-token: write from the workflow level despite never needing it -- only deploy-report consumes OIDC (for actions/deploy- pages). Moving id-token: write to deploy-report's job-level permissions removes the inheritance to e2e. In cleanup-e2e-reports.yml the workflow has a single job so the move is structural rather than functional, but explicit job-level permissions are self-documenting and consistent with the e2e.yml treatment. The OIDC token in both files is audience-bound to the github-pages environment, so the residual blast radius is limited to Pages deployment (no path to npm / ghcr / secrets). This commit is defense-in-depth, not closing an active vulnerability.
|
|
✅ E2E tests — success |
Contributor
There was a problem hiding this comment.
Pull request overview
CI/CD hardening in response to the TanStack npm supply-chain compromise postmortem, focused on reducing workflow supply-chain and token-exposure risk without touching application code.
Changes:
- SHA-pin third-party GitHub Actions (e.g., Chromatic, Neon, Thollander, Bun setup) and remove
tj-actions/branch-namesusage in favor of nativegithub.head_ref. - Harden
/chromaticcomment-triggered runs by gating onauthor_associationand isolating pnpm-store caching via a dedicated cache key prefix. - Reduce token exposure by scoping elevated
pages/id-token/pull-requestspermissions to only the jobs that require them, and bump first-party actions to newer majors.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
.github/workflows/netlify-deploy-preview.yml |
Removes tj-actions/branch-names, SHA-pins third-party actions, and migrates actions-comment-pull-request v2→v3 inputs/semantics. |
.github/workflows/netlify-cleanup-preview.yml |
SHA-pins oven-sh/setup-bun for cleanup workflow determinism/supply-chain safety. |
.github/workflows/e2e.yml |
Scopes elevated permissions to the Pages deploy job and bumps first-party actions to newer major versions. |
.github/workflows/docker-publish.yml |
Bumps Docker official actions and actions/checkout to newer majors for the release publishing flow. |
.github/workflows/cleanup-e2e-reports.yml |
Moves Pages/OIDC permissions to job-level and bumps Pages-related actions/checkout majors. |
.github/workflows/chromatic.yml |
Adds maintainer-only /chromatic gating, SHA-pins Chromatic + setup action, isolates cache keyspace, and bumps github-script. |
.github/workflows/build.yml |
SHA-pins the shared setup-pnpm composite action. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Summary
Response to the TanStack npm supply-chain compromise (2026-05-11). Six commits, all CI/CD-only — no application code touched.
What changed
chromaui,thollander,neondatabase,oven-sh, orcomplexdatacollectivecan no longer publish a malicious update to a moving tag we trust. Also replaceschromaui/action@latest(the highest-risk reference in the repo before this PR) with a specific commit on v16.10.0.chromatic.yml'sissue_commenttrigger. Added anauthor_associationgate so only OWNER/MEMBER/COLLABORATOR comments can fire/chromatic; removed a deadref:input from the Setup step that would have re-introduced an untrusted-code path if someone later "fixed" it; isolated the Chromatic pnpm-store cache viacache-key-prefixso a Chromatic-triggered run cannot poison the cache thatbuild.ymlrestores on push to main.thollander/actions-comment-pull-requestv2 → v3 innetlify-deploy-preview.yml, including its breaking parameter renames (comment_tag→comment-tag) andmode: deletesemantic change (nowdelete-on-completionto preserve v2's end-of-job-delete behaviour).tj-actions/branch-namesentirely — that namespace was the subject of the March 2024changed-filescompromise. We only used the action'scurrent_branchoutput, which is${{ github.head_ref }}natively onpull_requestevents.id-token: writeine2e.ymlandcleanup-e2e-reports.ymlfrom workflow-level to job-level on only the jobs that consume OIDC. Thee2ejob, which runs untrusted PR code via./scripts/run-e2e.sh, no longer inherits the permission. Defense-in-depth — the OIDC token here is audience-bound to GitHub Pages, so the blast radius was already limited to Pages deployment.Explicitly NOT in this PR
@tanstack/*dependencies were touched — the repo only uses@tanstack/react-table, which is in the confirmed-clean@tanstack/tablefamily per the postmortem.actions/*/docker/*actions./chromaticfeature bug where the workflow runs against main rather than the PR head — fixing it requires a separate product decision about whether the comment-trigger UX is worth the residual risk, and this PR removes the deadref:input that would otherwise silently re-introduce the vulnerability if anyone "fixes" the bug.Plan & audit trail
docs/superpowers/plans/2026-05-12-ci-hardening.md(local-only; thedocs/superpowers/path is gitignored).Test plan
build.yml— passes automatically on this PR push.netlify-deploy-preview.yml— automatic on PR open. Verify:comment-tag:rename).mode: delete-on-completion)./chromaticfrom a maintainer — comment/chromaticon this PR from a maintainer account. Confirm the workflow fires, Chromatic builds, the link comment appears, and the commit status is posted./chromaticfrom a non-maintainer — high-value but optional. Have a non-collaborator account comment/chromatic(or open a second test PR from a fork). Confirm the workflow does not fire — Actions tab shows no run.e2e.yml— automatic on PR open. Verify the run completes throughdeploy-reportand posts the PR comment with the newactions/github-script@v9.docker-publish.yml— triggers only on release. Either wait for the next release or push a test tag in a scratch repo to exercise the Docker v4/v7 + checkout v6 bumps.GitHub-UI checks not automatable
Open Settings → Actions → General and confirm:
If any of these are permissive, the chromatic hardening here is necessary but insufficient.
Follow-ups (not blocking this PR)
complexdatacollective/github-actionsto bump the inner actions used by thesetup-pnpmcomposite (actions/checkout@v4→@v6,actions/setup-node@v4→@v6,pnpm/action-setup@v4→@v6,actions/cache@v4→@v5).