Skip to content

fix(shared): decode JWT v2 permission masks exactly - #9381

Open
jeremy-clerk wants to merge 2 commits into
mainfrom
jw/fix-has-bitmask-check
Open

fix(shared): decode JWT v2 permission masks exactly#9381
jeremy-clerk wants to merge 2 commits into
mainfrom
jw/fix-has-bitmask-check

Conversation

@jeremy-clerk

Copy link
Copy Markdown
Contributor

Description

JWT v2 organization permission masks were parsed through JavaScript numbers, which could lose precision above the safe integer limit and cause auth().has() to incorrectly grant or deny permissions.

Decode permission masks directly from their decimal strings with decimalToBinaryBits. This avoids relying on BigInt, which may be incompatible with ClerkJS's legacy browser bundle, while preserving the existing bit ordering for normal masks. Malformed and negative masks now fail closed.

Type of change

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

@changeset-bot

changeset-bot Bot commented Aug 10, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 9dd9f76

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 23 packages
Name Type
@clerk/shared Patch
@clerk/astro Patch
@clerk/backend Patch
@clerk/chrome-extension Patch
@clerk/clerk-js Patch
@clerk/electron Patch
@clerk/expo-passkeys Patch
@clerk/expo Patch
@clerk/express Patch
@clerk/fastify Patch
@clerk/headless Patch
@clerk/hono Patch
@clerk/localizations Patch
@clerk/msw Patch
@clerk/nextjs Patch
@clerk/nuxt Patch
@clerk/react-router Patch
@clerk/react Patch
@clerk/tanstack-react-start Patch
@clerk/testing Patch
@clerk/ui Patch
@clerk/vue Patch
@clerk/swingset Patch

Not sure what this means? Click here to learn what changesets are.

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

@vercel

vercel Bot commented Aug 10, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
clerk-js-sandbox Ready Ready Preview Aug 10, 2026 8:11pm
swingset Ready Ready Preview Aug 10, 2026 8:11pm

Request Review

@pkg-pr-new

pkg-pr-new Bot commented Aug 10, 2026

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

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

@clerk/backend

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

@clerk/chrome-extension

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

@clerk/clerk-js

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

@clerk/electron

npm i https://pkg.pr.new/@clerk/electron@9381

@clerk/electron-passkeys

npm i https://pkg.pr.new/@clerk/electron-passkeys@9381

@clerk/eslint-plugin

npm i https://pkg.pr.new/@clerk/eslint-plugin@9381

@clerk/expo

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

@clerk/expo-google-signin

npm i https://pkg.pr.new/@clerk/expo-google-signin@9381

@clerk/expo-passkeys

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

@clerk/express

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

@clerk/fastify

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

@clerk/hono

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

@clerk/localizations

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

@clerk/nextjs

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

@clerk/nuxt

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

@clerk/react

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

@clerk/react-router

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

@clerk/shared

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

@clerk/tanstack-react-start

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

@clerk/testing

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

@clerk/ui

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

@clerk/upgrade

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

@clerk/vue

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

commit: 9dd9f76

@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

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

Review profile: CHILL

Plan: Pro Plus

Run ID: 9fe110e6-d198-48b6-9ca7-82430f245265

📥 Commits

Reviewing files that changed from the base of the PR and between c83fe31 and 9dd9f76.

📒 Files selected for processing (2)
  • packages/shared/src/__tests__/jwtPayloadParser.spec.ts
  • packages/shared/src/jwtPayloadParser.ts
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • clerk/clerk_go (manual)
  • clerk/dashboard (manual)
  • clerk/accounts (manual)
  • clerk/backoffice (manual)
  • clerk/clerk (manual)
  • clerk/clerk-docs (manual)
  • clerk/cloudflare-workers (manual)
  • clerk/cli (auto-detected)
  • clerk/clerk-ios (auto-detected)
  • clerk/clerk-android (auto-detected)
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/shared/src/tests/jwtPayloadParser.spec.ts
  • packages/shared/src/jwtPayloadParser.ts

📝 Walkthrough

Walkthrough

The JWT payload parser now converts decimal permission masks through custom decimal division. This avoids precision loss for values beyond JavaScript’s safe integer range. Invalid masks produce empty bit arrays. Tests cover large masks, valid safe masks, out-of-range bits, malformed masks, and fail-closed authorization checks. A patch changeset was added for @clerk/shared.

Estimated code review effort: 3 (Moderate) | ~20 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the exact JWT v2 permission-mask decoding fix.
Description check ✅ Passed The description explains the precision issue, decoding approach, compatibility consideration, and fail-closed behavior.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/shared/src/jwtPayloadParser.ts`:
- Around line 52-54: Update buildOrgPermissions and the featurePermissionMap
handling around decimalToBinaryBits so bit indexes greater than or equal to
permissions.length are discarded before permission names are resolved. Preserve
valid permission mappings and add a regression test covering the 54-item list
with mask 18014398509481984, ensuring no org:feature:undefined permission is
produced.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

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

Review profile: CHILL

Plan: Pro Plus

Run ID: 1868a535-5352-4646-8435-325176767171

📥 Commits

Reviewing files that changed from the base of the PR and between aa86d9f and c83fe31.

📒 Files selected for processing (3)
  • .changeset/exact-jwt-permission-masks.md
  • packages/shared/src/__tests__/jwtPayloadParser.spec.ts
  • packages/shared/src/jwtPayloadParser.ts
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • clerk/clerk_go (manual)
  • clerk/dashboard (manual)
  • clerk/accounts (manual)
  • clerk/backoffice (manual)
  • clerk/clerk (manual)
  • clerk/clerk-docs (manual)
  • clerk/cloudflare-workers (manual)
  • clerk/cli (auto-detected)
  • clerk/clerk-ios (auto-detected)
  • clerk/clerk-android (auto-detected)

Comment thread packages/shared/src/jwtPayloadParser.ts
@github-actions

github-actions Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

API Changes Report

Generated by Break Check on 2026-08-10T20:12:53.862Z

Summary

Metric Count
Packages analyzed 19
Packages with changes 0
🔴 Breaking changes 0
🟡 Non-breaking changes 0
🟢 Additions 0

No API Changes Detected

All packages have stable APIs with no detected changes.


Report generated by Break Check

Last ran on 9dd9f76.

}

for (let permIndex = 0; permIndex < permissionBits.length; permIndex++) {
for (let permIndex = 0; permIndex < permissionBits.length && permIndex < permissions.length; permIndex++) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[MEDIUM] fea/fpm index misalignment attaches a feature's permission mask to the wrong feature when feature targeting is active

buildOrgPermissions assumes featurePermissionMap[i] corresponds to features[i], but the encoder does not emit a mask per feature — pkg/auth/v2.go:223 skips any feature not in featuresInPermissions, so fpm is a compacted list while fea is the full one. The two only stay aligned when the permission-bearing features happen to form a prefix of fea. When params.Plan != nil (v2.go:69-95) featureSet is seeded from plan features and targeted features are appended after, breaking that prefix invariant — a plan feature the member has no permissions on then inherits the next mask in the list, so has({ permission: 'org:<wrong-feature>:manage' }) returns true for a permission the user was never granted.

This predates the diff, but it is in the function this PR rewrites and the PR's stated goal is decoding these masks exactly — the new permIndex < permissions.length clamp bounds the inner loop without fixing the outer index mapping. Suggest having the encoder emit a mask for every entry in fea (zero for features with no permissions), or emitting the feature name alongside each mask so the decoder can key on it rather than on position.

— Comment generated with Claude with @dominic-clerk's supervision

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.

2 participants