fix(backend): reject machine JWTs presented as session tokens - #9168

Merged
dominic-clerk merged 1 commit into
mainfrom
dc-machine-jwt
Jul 16, 2026
Merged

fix(backend): reject machine JWTs presented as session tokens#9168
dominic-clerk merged 1 commit into
mainfrom
dc-machine-jwt

Conversation

@dominic-clerk

@dominic-clerkdominic-clerk commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Description

The cookie path (authenticateRequestWithTokenInCookie) lacked the isMachineJwt() guard the header path has, so a same-instance M2M JWT in the __session cookie passed verifyToken() and produced a signed-in state with userId = "mch_...".

  • Guard the cookie path with isMachineJwt(), mirroring the header path
  • verifyToken() now also rejects any JWT tagged with a non-session token category (cat), closing the confusion regardless of transport
  • Add regression tests for both paths

Fixes SDK-107

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:

Summary by CodeRabbit

  • Bug Fixes
    • Machine-to-machine tokens and OAuth JWTs provided through the session cookie are now rejected instead of being treated as signed-in sessions.
    • Invalid session token categories now consistently return a signed-out authentication state.
    • Session token validation rejects unsupported token types earlier, improving authorization safety.

@changeset-bot

changeset-botBot commented Jul 15, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: b66f063

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

This PR includes changesets to release 10 packages
NameType
@clerk/backendPatch
@clerk/astroPatch
@clerk/expressPatch
@clerk/fastifyPatch
@clerk/honoPatch
@clerk/nextjsPatch
@clerk/nuxtPatch
@clerk/react-routerPatch
@clerk/tanstack-react-startPatch
@clerk/testingPatch

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

vercelBot commented Jul 15, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
clerk-js-sandboxReadyReadyPreview, CommentJul 15, 2026 12:29pm
swingsetReadyReadyPreview, CommentJul 15, 2026 12:29pm

Request Review

@coderabbitai

coderabbitaiBot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Machine-token JWTs are rejected when supplied as session cookies or verified as session tokens. Cookie authentication returns a signed-out state, token verification fails before key resolution, and tests plus a patch changeset document the behavior.

Changes

Session token security

Layer / File(s)Summary
Token category verification
packages/backend/src/tokens/verify.ts, packages/backend/src/tokens/__tests__/verify.test.ts
verifyToken rejects M2M-category JWTs before JWK resolution and reports an invalid session token category, with coverage for the early failure.
Cookie authentication rejection
packages/backend/src/tokens/request.ts, packages/backend/src/tokens/__tests__/request.test.ts, .changeset/shiny-words-lay.md
Session cookies containing machine JWTs return signed-out state with TokenTypeMismatch; request tests and release notes record the behavior.

Estimated code review effort: 2 (Simple) | ~10 minutes

Suggested reviewers:alexcarpenter

Sequence Diagram(s)

sequenceDiagram
participant Request
participant CookieAuthentication
participant TokenVerification
Request->>CookieAuthentication: Supply machine JWT in __session
CookieAuthentication->>CookieAuthentication: Detect machine JWT
CookieAuthentication-->>Request: Return signedOut with TokenTypeMismatch
TokenVerification->>TokenVerification: Reject M2M category before key resolution
Loading

Poem

A rabbit found a token in the cookie’s leafy nest,
“Not a session!” it thumped, “This guard will do its best.”
Machine JWTs hop out, signed-out states remain,
Keys are never fetched again.
Patch released with a carrot refrain!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 50.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly matches the main change: rejecting machine JWTs when they are presented as session tokens.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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

The cookie path (authenticateRequestWithTokenInCookie) lacked the
isMachineJwt() guard the header path has, so a same-instance M2M JWT in
the __session cookie passed verifyToken() and produced a signed-in state
with userId = "mch_...".
- Guard the cookie path with isMachineJwt(), mirroring the header path
- verifyToken() now also rejects any JWT tagged with a non-session
token category (cat), closing the confusion regardless of transport
- Add regression tests for both paths
@dominic-clerkdominic-clerk changed the title fix(backend): reject machine JWTs presented as session tokens (AISEC-91)fix(backend): reject machine JWTs presented as session tokensJul 15, 2026
@pkg-pr-new

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

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

@clerk/backend

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

@clerk/chrome-extension

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

@clerk/clerk-js

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

@clerk/electron

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

@clerk/electron-passkeys

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

@clerk/eslint-plugin

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

@clerk/expo

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

@clerk/expo-passkeys

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

@clerk/express

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

@clerk/fastify

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

@clerk/hono

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

@clerk/localizations

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

@clerk/nextjs

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

@clerk/nuxt

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

@clerk/react

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

@clerk/react-router

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

@clerk/shared

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

@clerk/tanstack-react-start

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

@clerk/testing

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

@clerk/ui

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

@clerk/upgrade

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

@clerk/vue

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

commit: b66f063

@coderabbitaicoderabbitaiBot 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

🧹 Nitpick comments (1)
packages/backend/src/tokens/__tests__/request.test.ts (1)

1283-1307: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Cover the OAuth cookie branch as well.

This regression test covers only M2M JWTs, while the production guard handles both OAuth and M2M tokens. Add or confirm a cookie case for an OAuth JWT asserting the same signed-out TokenTypeMismatch result.

As per coding guidelines, unit tests are required for all new functionality and should cover error handling and edge cases.

🤖 Prompt for 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.
In `@packages/backend/src/tokens/__tests__/request.test.ts` around lines 1283 -
1307, The cookieToken regression coverage only exercises the M2M JWT path;
extend the request tests around the existing cookieToken test to present an
OAuth JWT in the __session cookie and assert the same signed-out result with
AuthErrorReason.TokenTypeMismatch, including the existing toAuth() expectation.
Reuse the established OAuth JWT fixture or construction helpers and preserve the
current M2M coverage.

Source: Coding guidelines

🤖 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/backend/src/tokens/request.ts`:
- Around line 634-643: Move the isMachineJwt check in the cookie authentication
flow before the handleMaybeHandshakeStatus branches, so machine JWTs in
__session return the TokenTypeMismatch signedOut result even when __client_uat
is absent. Add a regression case covering a machine JWT without __client_uat and
verify the expected signed-out response.
---
Nitpick comments:
In `@packages/backend/src/tokens/__tests__/request.test.ts`:
- Around line 1283-1307: The cookieToken regression coverage only exercises the
M2M JWT path; extend the request tests around the existing cookieToken test to
present an OAuth JWT in the __session cookie and assert the same signed-out
result with AuthErrorReason.TokenTypeMismatch, including the existing toAuth()
expectation. Reuse the established OAuth JWT fixture or construction helpers and
preserve the current M2M coverage.
🪄 Autofix (Beta)

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), Repository UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 063de93e-c94c-496a-9c69-5ab6fabc5524

📥 Commits

Reviewing files that changed from the base of the PR and between 59774c6 and b66f063.

📒 Files selected for processing (5)
  • .changeset/shiny-words-lay.md
  • packages/backend/src/tokens/__tests__/request.test.ts
  • packages/backend/src/tokens/__tests__/verify.test.ts
  • packages/backend/src/tokens/request.ts
  • packages/backend/src/tokens/verify.ts

Comment on lines +634 to +643
// Machine JWTs pass verifyToken() but must not be accepted as session tokens (mirrors header path).
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
if (isMachineJwt(authenticateContext.sessionTokenInCookie!)) {
return signedOut({
tokenType: TokenType.SessionToken,
authenticateContext,
reason: AuthErrorReason.TokenTypeMismatch,
message: '',
});
}

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.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bashset -euo pipefail
# Map the relevant file and surrounding symbols first.
ast-grep outline packages/backend/src/tokens/request.ts --view expanded ||trueechoecho"---- relevant line ranges ----"
nl -ba packages/backend/src/tokens/request.ts | sed -n '560,700p'echoecho"---- search for handshake helpers / machine jwt checks ----"
rg -n "isMachineJwt|handleMaybeHandshakeStatus|TokenTypeMismatch|__client_uat|sessionTokenInCookie|signedOut" packages/backend/src/tokens/request.ts

Repository: clerk/javascript

Length of output: 1189


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"---- packages/backend/src/tokens/request.ts (lines 600-660) ----"
sed -n '600,660p' packages/backend/src/tokens/request.ts
echoecho"---- surrounding references ----"
rg -n "handleMaybeHandshakeStatus|isMachineJwt|sessionTokenInCookie|__client_uat|TokenTypeMismatch" packages/backend/src/tokens/request.ts

Repository: clerk/javascript

Length of output: 4888


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"---- state derivation around hasActiveClient / client UAT / session token ----"
sed -n '440,625p' packages/backend/src/tokens/request.ts
echoecho"---- machine helpers ----"
sed -n '1,220p' packages/backend/src/tokens/machine.ts

Repository: clerk/javascript

Length of output: 14777


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"---- handleMaybeHandshakeStatus definition ----"
sed -n '300,380p' packages/backend/src/tokens/request.ts
echoecho"---- call sites of handleMaybeHandshakeStatus in request.ts ----"
rg -n "function handleMaybeHandshakeStatus|return handleMaybeHandshakeStatus|handleMaybeHandshakeStatus\(" packages/backend/src/tokens/request.ts

Repository: clerk/javascript

Length of output: 4982


Move the machine-JWT check before the cookie handshake branches. A machine JWT in __session without __client_uat hits handleMaybeHandshakeStatus first, so it never returns the intended TokenTypeMismatch signed-out state. Add a regression case for the no-__client_uat path.

🤖 Prompt for 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.
In `@packages/backend/src/tokens/request.ts` around lines 634 - 643, Move the
isMachineJwt check in the cookie authentication flow before the
handleMaybeHandshakeStatus branches, so machine JWTs in __session return the
TokenTypeMismatch signedOut result even when __client_uat is absent. Add a
regression case covering a machine JWT without __client_uat and verify the
expected signed-out response.

@github-actions

Copy link
Copy Markdown
Contributor

API Changes Report

Generated by Break Check on 2026-07-15T12:33:17.387Z

Summary

MetricCount
Packages analyzed19
Packages with changes0
🔴 Breaking changes0
🟡 Non-breaking changes0
🟢 Additions0

No API Changes Detected

All packages have stable APIs with no detected changes.


Report generated by Break Check

Last ran on b66f063.

@wobsorianowobsoriano left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

ty Dom!

@dominic-clerk
dominic-clerk merged commit e657d99 into mainJul 16, 2026
55 checks passed
@dominic-clerk
dominic-clerk deleted the dc-machine-jwt branch July 16, 2026 06:00
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@dominic-clerk@wobsoriano
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

fix(backend): reject machine JWTs presented as session tokens - #9168

Merged
dominic-clerk merged 1 commit into
mainfrom
dc-machine-jwt
Jul 16, 2026
Merged

fix(backend): reject machine JWTs presented as session tokens#9168
dominic-clerk merged 1 commit into
mainfrom
dc-machine-jwt

Conversation

@dominic-clerk

@dominic-clerkdominic-clerk commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Description

The cookie path (authenticateRequestWithTokenInCookie) lacked the isMachineJwt() guard the header path has, so a same-instance M2M JWT in the __session cookie passed verifyToken() and produced a signed-in state with userId = "mch_...".

  • Guard the cookie path with isMachineJwt(), mirroring the header path
  • verifyToken() now also rejects any JWT tagged with a non-session token category (cat), closing the confusion regardless of transport
  • Add regression tests for both paths

Fixes SDK-107

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:

Summary by CodeRabbit

  • Bug Fixes
    • Machine-to-machine tokens and OAuth JWTs provided through the session cookie are now rejected instead of being treated as signed-in sessions.
    • Invalid session token categories now consistently return a signed-out authentication state.
    • Session token validation rejects unsupported token types earlier, improving authorization safety.

@changeset-bot

changeset-botBot commented Jul 15, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: b66f063

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

This PR includes changesets to release 10 packages
NameType
@clerk/backendPatch
@clerk/astroPatch
@clerk/expressPatch
@clerk/fastifyPatch
@clerk/honoPatch
@clerk/nextjsPatch
@clerk/nuxtPatch
@clerk/react-routerPatch
@clerk/tanstack-react-startPatch
@clerk/testingPatch

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

vercelBot commented Jul 15, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
clerk-js-sandboxReadyReadyPreview, CommentJul 15, 2026 12:29pm
swingsetReadyReadyPreview, CommentJul 15, 2026 12:29pm

Request Review

@coderabbitai

coderabbitaiBot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Machine-token JWTs are rejected when supplied as session cookies or verified as session tokens. Cookie authentication returns a signed-out state, token verification fails before key resolution, and tests plus a patch changeset document the behavior.

Changes

Session token security

Layer / File(s)Summary
Token category verification
packages/backend/src/tokens/verify.ts, packages/backend/src/tokens/__tests__/verify.test.ts
verifyToken rejects M2M-category JWTs before JWK resolution and reports an invalid session token category, with coverage for the early failure.
Cookie authentication rejection
packages/backend/src/tokens/request.ts, packages/backend/src/tokens/__tests__/request.test.ts, .changeset/shiny-words-lay.md
Session cookies containing machine JWTs return signed-out state with TokenTypeMismatch; request tests and release notes record the behavior.

Estimated code review effort: 2 (Simple) | ~10 minutes

Suggested reviewers:alexcarpenter

Sequence Diagram(s)

sequenceDiagram
participant Request
participant CookieAuthentication
participant TokenVerification
Request->>CookieAuthentication: Supply machine JWT in __session
CookieAuthentication->>CookieAuthentication: Detect machine JWT
CookieAuthentication-->>Request: Return signedOut with TokenTypeMismatch
TokenVerification->>TokenVerification: Reject M2M category before key resolution
Loading

Poem

A rabbit found a token in the cookie’s leafy nest,
“Not a session!” it thumped, “This guard will do its best.”
Machine JWTs hop out, signed-out states remain,
Keys are never fetched again.
Patch released with a carrot refrain!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 50.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly matches the main change: rejecting machine JWTs when they are presented as session tokens.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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

The cookie path (authenticateRequestWithTokenInCookie) lacked the
isMachineJwt() guard the header path has, so a same-instance M2M JWT in
the __session cookie passed verifyToken() and produced a signed-in state
with userId = "mch_...".
- Guard the cookie path with isMachineJwt(), mirroring the header path
- verifyToken() now also rejects any JWT tagged with a non-session
token category (cat), closing the confusion regardless of transport
- Add regression tests for both paths
@dominic-clerkdominic-clerk changed the title fix(backend): reject machine JWTs presented as session tokens (AISEC-91)fix(backend): reject machine JWTs presented as session tokensJul 15, 2026
@pkg-pr-new

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

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

@clerk/backend

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

@clerk/chrome-extension

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

@clerk/clerk-js

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

@clerk/electron

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

@clerk/electron-passkeys

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

@clerk/eslint-plugin

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

@clerk/expo

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

@clerk/expo-passkeys

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

@clerk/express

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

@clerk/fastify

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

@clerk/hono

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

@clerk/localizations

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

@clerk/nextjs

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

@clerk/nuxt

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

@clerk/react

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

@clerk/react-router

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

@clerk/shared

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

@clerk/tanstack-react-start

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

@clerk/testing

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

@clerk/ui

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

@clerk/upgrade

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

@clerk/vue

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

commit: b66f063

@coderabbitaicoderabbitaiBot 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

🧹 Nitpick comments (1)
packages/backend/src/tokens/__tests__/request.test.ts (1)

1283-1307: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Cover the OAuth cookie branch as well.

This regression test covers only M2M JWTs, while the production guard handles both OAuth and M2M tokens. Add or confirm a cookie case for an OAuth JWT asserting the same signed-out TokenTypeMismatch result.

As per coding guidelines, unit tests are required for all new functionality and should cover error handling and edge cases.

🤖 Prompt for 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.
In `@packages/backend/src/tokens/__tests__/request.test.ts` around lines 1283 -
1307, The cookieToken regression coverage only exercises the M2M JWT path;
extend the request tests around the existing cookieToken test to present an
OAuth JWT in the __session cookie and assert the same signed-out result with
AuthErrorReason.TokenTypeMismatch, including the existing toAuth() expectation.
Reuse the established OAuth JWT fixture or construction helpers and preserve the
current M2M coverage.

Source: Coding guidelines

🤖 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/backend/src/tokens/request.ts`:
- Around line 634-643: Move the isMachineJwt check in the cookie authentication
flow before the handleMaybeHandshakeStatus branches, so machine JWTs in
__session return the TokenTypeMismatch signedOut result even when __client_uat
is absent. Add a regression case covering a machine JWT without __client_uat and
verify the expected signed-out response.
---
Nitpick comments:
In `@packages/backend/src/tokens/__tests__/request.test.ts`:
- Around line 1283-1307: The cookieToken regression coverage only exercises the
M2M JWT path; extend the request tests around the existing cookieToken test to
present an OAuth JWT in the __session cookie and assert the same signed-out
result with AuthErrorReason.TokenTypeMismatch, including the existing toAuth()
expectation. Reuse the established OAuth JWT fixture or construction helpers and
preserve the current M2M coverage.
🪄 Autofix (Beta)

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), Repository UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 063de93e-c94c-496a-9c69-5ab6fabc5524

📥 Commits

Reviewing files that changed from the base of the PR and between 59774c6 and b66f063.

📒 Files selected for processing (5)
  • .changeset/shiny-words-lay.md
  • packages/backend/src/tokens/__tests__/request.test.ts
  • packages/backend/src/tokens/__tests__/verify.test.ts
  • packages/backend/src/tokens/request.ts
  • packages/backend/src/tokens/verify.ts

Comment on lines +634 to +643
// Machine JWTs pass verifyToken() but must not be accepted as session tokens (mirrors header path).
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
if (isMachineJwt(authenticateContext.sessionTokenInCookie!)) {
return signedOut({
tokenType: TokenType.SessionToken,
authenticateContext,
reason: AuthErrorReason.TokenTypeMismatch,
message: '',
});
}

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.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bashset -euo pipefail
# Map the relevant file and surrounding symbols first.
ast-grep outline packages/backend/src/tokens/request.ts --view expanded ||trueechoecho"---- relevant line ranges ----"
nl -ba packages/backend/src/tokens/request.ts | sed -n '560,700p'echoecho"---- search for handshake helpers / machine jwt checks ----"
rg -n "isMachineJwt|handleMaybeHandshakeStatus|TokenTypeMismatch|__client_uat|sessionTokenInCookie|signedOut" packages/backend/src/tokens/request.ts

Repository: clerk/javascript

Length of output: 1189


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"---- packages/backend/src/tokens/request.ts (lines 600-660) ----"
sed -n '600,660p' packages/backend/src/tokens/request.ts
echoecho"---- surrounding references ----"
rg -n "handleMaybeHandshakeStatus|isMachineJwt|sessionTokenInCookie|__client_uat|TokenTypeMismatch" packages/backend/src/tokens/request.ts

Repository: clerk/javascript

Length of output: 4888


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"---- state derivation around hasActiveClient / client UAT / session token ----"
sed -n '440,625p' packages/backend/src/tokens/request.ts
echoecho"---- machine helpers ----"
sed -n '1,220p' packages/backend/src/tokens/machine.ts

Repository: clerk/javascript

Length of output: 14777


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"---- handleMaybeHandshakeStatus definition ----"
sed -n '300,380p' packages/backend/src/tokens/request.ts
echoecho"---- call sites of handleMaybeHandshakeStatus in request.ts ----"
rg -n "function handleMaybeHandshakeStatus|return handleMaybeHandshakeStatus|handleMaybeHandshakeStatus\(" packages/backend/src/tokens/request.ts

Repository: clerk/javascript

Length of output: 4982


Move the machine-JWT check before the cookie handshake branches. A machine JWT in __session without __client_uat hits handleMaybeHandshakeStatus first, so it never returns the intended TokenTypeMismatch signed-out state. Add a regression case for the no-__client_uat path.

🤖 Prompt for 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.
In `@packages/backend/src/tokens/request.ts` around lines 634 - 643, Move the
isMachineJwt check in the cookie authentication flow before the
handleMaybeHandshakeStatus branches, so machine JWTs in __session return the
TokenTypeMismatch signedOut result even when __client_uat is absent. Add a
regression case covering a machine JWT without __client_uat and verify the
expected signed-out response.

@github-actions

Copy link
Copy Markdown
Contributor

API Changes Report

Generated by Break Check on 2026-07-15T12:33:17.387Z

Summary

MetricCount
Packages analyzed19
Packages with changes0
🔴 Breaking changes0
🟡 Non-breaking changes0
🟢 Additions0

No API Changes Detected

All packages have stable APIs with no detected changes.


Report generated by Break Check

Last ran on b66f063.

@wobsorianowobsoriano left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

ty Dom!

@dominic-clerk
dominic-clerk merged commit e657d99 into mainJul 16, 2026
55 checks passed
@dominic-clerk
dominic-clerk deleted the dc-machine-jwt branch July 16, 2026 06:00
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@dominic-clerk@wobsoriano
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(backend): reject machine JWTs presented as session tokens - #9168

Merged
dominic-clerk merged 1 commit into
mainfrom
dc-machine-jwt
Jul 16, 2026
Merged

fix(backend): reject machine JWTs presented as session tokens#9168
dominic-clerk merged 1 commit into
mainfrom
dc-machine-jwt

Conversation

@dominic-clerk

@dominic-clerkdominic-clerk commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Description

The cookie path (authenticateRequestWithTokenInCookie) lacked the isMachineJwt() guard the header path has, so a same-instance M2M JWT in the __session cookie passed verifyToken() and produced a signed-in state with userId = "mch_...".

  • Guard the cookie path with isMachineJwt(), mirroring the header path
  • verifyToken() now also rejects any JWT tagged with a non-session token category (cat), closing the confusion regardless of transport
  • Add regression tests for both paths

Fixes SDK-107

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:

Summary by CodeRabbit

  • Bug Fixes
    • Machine-to-machine tokens and OAuth JWTs provided through the session cookie are now rejected instead of being treated as signed-in sessions.
    • Invalid session token categories now consistently return a signed-out authentication state.
    • Session token validation rejects unsupported token types earlier, improving authorization safety.

@changeset-bot

changeset-botBot commented Jul 15, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: b66f063

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

This PR includes changesets to release 10 packages
NameType
@clerk/backendPatch
@clerk/astroPatch
@clerk/expressPatch
@clerk/fastifyPatch
@clerk/honoPatch
@clerk/nextjsPatch
@clerk/nuxtPatch
@clerk/react-routerPatch
@clerk/tanstack-react-startPatch
@clerk/testingPatch

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

vercelBot commented Jul 15, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
clerk-js-sandboxReadyReadyPreview, CommentJul 15, 2026 12:29pm
swingsetReadyReadyPreview, CommentJul 15, 2026 12:29pm

Request Review

@coderabbitai

coderabbitaiBot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Machine-token JWTs are rejected when supplied as session cookies or verified as session tokens. Cookie authentication returns a signed-out state, token verification fails before key resolution, and tests plus a patch changeset document the behavior.

Changes

Session token security

Layer / File(s)Summary
Token category verification
packages/backend/src/tokens/verify.ts, packages/backend/src/tokens/__tests__/verify.test.ts
verifyToken rejects M2M-category JWTs before JWK resolution and reports an invalid session token category, with coverage for the early failure.
Cookie authentication rejection
packages/backend/src/tokens/request.ts, packages/backend/src/tokens/__tests__/request.test.ts, .changeset/shiny-words-lay.md
Session cookies containing machine JWTs return signed-out state with TokenTypeMismatch; request tests and release notes record the behavior.

Estimated code review effort: 2 (Simple) | ~10 minutes

Suggested reviewers:alexcarpenter

Sequence Diagram(s)

sequenceDiagram
participant Request
participant CookieAuthentication
participant TokenVerification
Request->>CookieAuthentication: Supply machine JWT in __session
CookieAuthentication->>CookieAuthentication: Detect machine JWT
CookieAuthentication-->>Request: Return signedOut with TokenTypeMismatch
TokenVerification->>TokenVerification: Reject M2M category before key resolution
Loading

Poem

A rabbit found a token in the cookie’s leafy nest,
“Not a session!” it thumped, “This guard will do its best.”
Machine JWTs hop out, signed-out states remain,
Keys are never fetched again.
Patch released with a carrot refrain!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 50.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly matches the main change: rejecting machine JWTs when they are presented as session tokens.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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

The cookie path (authenticateRequestWithTokenInCookie) lacked the
isMachineJwt() guard the header path has, so a same-instance M2M JWT in
the __session cookie passed verifyToken() and produced a signed-in state
with userId = "mch_...".
- Guard the cookie path with isMachineJwt(), mirroring the header path
- verifyToken() now also rejects any JWT tagged with a non-session
token category (cat), closing the confusion regardless of transport
- Add regression tests for both paths
@dominic-clerkdominic-clerk changed the title fix(backend): reject machine JWTs presented as session tokens (AISEC-91)fix(backend): reject machine JWTs presented as session tokensJul 15, 2026
@pkg-pr-new

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

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

@clerk/backend

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

@clerk/chrome-extension

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

@clerk/clerk-js

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

@clerk/electron

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

@clerk/electron-passkeys

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

@clerk/eslint-plugin

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

@clerk/expo

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

@clerk/expo-passkeys

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

@clerk/express

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

@clerk/fastify

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

@clerk/hono

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

@clerk/localizations

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

@clerk/nextjs

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

@clerk/nuxt

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

@clerk/react

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

@clerk/react-router

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

@clerk/shared

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

@clerk/tanstack-react-start

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

@clerk/testing

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

@clerk/ui

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

@clerk/upgrade

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

@clerk/vue

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

commit: b66f063

@coderabbitaicoderabbitaiBot 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

🧹 Nitpick comments (1)
packages/backend/src/tokens/__tests__/request.test.ts (1)

1283-1307: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Cover the OAuth cookie branch as well.

This regression test covers only M2M JWTs, while the production guard handles both OAuth and M2M tokens. Add or confirm a cookie case for an OAuth JWT asserting the same signed-out TokenTypeMismatch result.

As per coding guidelines, unit tests are required for all new functionality and should cover error handling and edge cases.

🤖 Prompt for 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.
In `@packages/backend/src/tokens/__tests__/request.test.ts` around lines 1283 -
1307, The cookieToken regression coverage only exercises the M2M JWT path;
extend the request tests around the existing cookieToken test to present an
OAuth JWT in the __session cookie and assert the same signed-out result with
AuthErrorReason.TokenTypeMismatch, including the existing toAuth() expectation.
Reuse the established OAuth JWT fixture or construction helpers and preserve the
current M2M coverage.

Source: Coding guidelines

🤖 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/backend/src/tokens/request.ts`:
- Around line 634-643: Move the isMachineJwt check in the cookie authentication
flow before the handleMaybeHandshakeStatus branches, so machine JWTs in
__session return the TokenTypeMismatch signedOut result even when __client_uat
is absent. Add a regression case covering a machine JWT without __client_uat and
verify the expected signed-out response.
---
Nitpick comments:
In `@packages/backend/src/tokens/__tests__/request.test.ts`:
- Around line 1283-1307: The cookieToken regression coverage only exercises the
M2M JWT path; extend the request tests around the existing cookieToken test to
present an OAuth JWT in the __session cookie and assert the same signed-out
result with AuthErrorReason.TokenTypeMismatch, including the existing toAuth()
expectation. Reuse the established OAuth JWT fixture or construction helpers and
preserve the current M2M coverage.
🪄 Autofix (Beta)

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), Repository UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 063de93e-c94c-496a-9c69-5ab6fabc5524

📥 Commits

Reviewing files that changed from the base of the PR and between 59774c6 and b66f063.

📒 Files selected for processing (5)
  • .changeset/shiny-words-lay.md
  • packages/backend/src/tokens/__tests__/request.test.ts
  • packages/backend/src/tokens/__tests__/verify.test.ts
  • packages/backend/src/tokens/request.ts
  • packages/backend/src/tokens/verify.ts

Comment on lines +634 to +643
// Machine JWTs pass verifyToken() but must not be accepted as session tokens (mirrors header path).
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
if (isMachineJwt(authenticateContext.sessionTokenInCookie!)) {
return signedOut({
tokenType: TokenType.SessionToken,
authenticateContext,
reason: AuthErrorReason.TokenTypeMismatch,
message: '',
});
}

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.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bashset -euo pipefail
# Map the relevant file and surrounding symbols first.
ast-grep outline packages/backend/src/tokens/request.ts --view expanded ||trueechoecho"---- relevant line ranges ----"
nl -ba packages/backend/src/tokens/request.ts | sed -n '560,700p'echoecho"---- search for handshake helpers / machine jwt checks ----"
rg -n "isMachineJwt|handleMaybeHandshakeStatus|TokenTypeMismatch|__client_uat|sessionTokenInCookie|signedOut" packages/backend/src/tokens/request.ts

Repository: clerk/javascript

Length of output: 1189


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"---- packages/backend/src/tokens/request.ts (lines 600-660) ----"
sed -n '600,660p' packages/backend/src/tokens/request.ts
echoecho"---- surrounding references ----"
rg -n "handleMaybeHandshakeStatus|isMachineJwt|sessionTokenInCookie|__client_uat|TokenTypeMismatch" packages/backend/src/tokens/request.ts

Repository: clerk/javascript

Length of output: 4888


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"---- state derivation around hasActiveClient / client UAT / session token ----"
sed -n '440,625p' packages/backend/src/tokens/request.ts
echoecho"---- machine helpers ----"
sed -n '1,220p' packages/backend/src/tokens/machine.ts

Repository: clerk/javascript

Length of output: 14777


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"---- handleMaybeHandshakeStatus definition ----"
sed -n '300,380p' packages/backend/src/tokens/request.ts
echoecho"---- call sites of handleMaybeHandshakeStatus in request.ts ----"
rg -n "function handleMaybeHandshakeStatus|return handleMaybeHandshakeStatus|handleMaybeHandshakeStatus\(" packages/backend/src/tokens/request.ts

Repository: clerk/javascript

Length of output: 4982


Move the machine-JWT check before the cookie handshake branches. A machine JWT in __session without __client_uat hits handleMaybeHandshakeStatus first, so it never returns the intended TokenTypeMismatch signed-out state. Add a regression case for the no-__client_uat path.

🤖 Prompt for 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.
In `@packages/backend/src/tokens/request.ts` around lines 634 - 643, Move the
isMachineJwt check in the cookie authentication flow before the
handleMaybeHandshakeStatus branches, so machine JWTs in __session return the
TokenTypeMismatch signedOut result even when __client_uat is absent. Add a
regression case covering a machine JWT without __client_uat and verify the
expected signed-out response.

@github-actions

Copy link
Copy Markdown
Contributor

API Changes Report

Generated by Break Check on 2026-07-15T12:33:17.387Z

Summary

MetricCount
Packages analyzed19
Packages with changes0
🔴 Breaking changes0
🟡 Non-breaking changes0
🟢 Additions0

No API Changes Detected

All packages have stable APIs with no detected changes.


Report generated by Break Check

Last ran on b66f063.

@wobsorianowobsoriano left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

ty Dom!

@dominic-clerk
dominic-clerk merged commit e657d99 into mainJul 16, 2026
55 checks passed
@dominic-clerk
dominic-clerk deleted the dc-machine-jwt branch July 16, 2026 06:00
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@dominic-clerk@wobsoriano
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(backend): reject machine JWTs presented as session tokens - #9168

Merged
dominic-clerk merged 1 commit into
mainfrom
dc-machine-jwt
Jul 16, 2026
Merged

fix(backend): reject machine JWTs presented as session tokens#9168
dominic-clerk merged 1 commit into
mainfrom
dc-machine-jwt

Conversation

@dominic-clerk

@dominic-clerkdominic-clerk commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Description

The cookie path (authenticateRequestWithTokenInCookie) lacked the isMachineJwt() guard the header path has, so a same-instance M2M JWT in the __session cookie passed verifyToken() and produced a signed-in state with userId = "mch_...".

  • Guard the cookie path with isMachineJwt(), mirroring the header path
  • verifyToken() now also rejects any JWT tagged with a non-session token category (cat), closing the confusion regardless of transport
  • Add regression tests for both paths

Fixes SDK-107

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:

Summary by CodeRabbit

  • Bug Fixes
    • Machine-to-machine tokens and OAuth JWTs provided through the session cookie are now rejected instead of being treated as signed-in sessions.
    • Invalid session token categories now consistently return a signed-out authentication state.
    • Session token validation rejects unsupported token types earlier, improving authorization safety.

@changeset-bot

changeset-botBot commented Jul 15, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: b66f063

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

This PR includes changesets to release 10 packages
NameType
@clerk/backendPatch
@clerk/astroPatch
@clerk/expressPatch
@clerk/fastifyPatch
@clerk/honoPatch
@clerk/nextjsPatch
@clerk/nuxtPatch
@clerk/react-routerPatch
@clerk/tanstack-react-startPatch
@clerk/testingPatch

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

vercelBot commented Jul 15, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
clerk-js-sandboxReadyReadyPreview, CommentJul 15, 2026 12:29pm
swingsetReadyReadyPreview, CommentJul 15, 2026 12:29pm

Request Review

@coderabbitai

coderabbitaiBot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Machine-token JWTs are rejected when supplied as session cookies or verified as session tokens. Cookie authentication returns a signed-out state, token verification fails before key resolution, and tests plus a patch changeset document the behavior.

Changes

Session token security

Layer / File(s)Summary
Token category verification
packages/backend/src/tokens/verify.ts, packages/backend/src/tokens/__tests__/verify.test.ts
verifyToken rejects M2M-category JWTs before JWK resolution and reports an invalid session token category, with coverage for the early failure.
Cookie authentication rejection
packages/backend/src/tokens/request.ts, packages/backend/src/tokens/__tests__/request.test.ts, .changeset/shiny-words-lay.md
Session cookies containing machine JWTs return signed-out state with TokenTypeMismatch; request tests and release notes record the behavior.

Estimated code review effort: 2 (Simple) | ~10 minutes

Suggested reviewers:alexcarpenter

Sequence Diagram(s)

sequenceDiagram
participant Request
participant CookieAuthentication
participant TokenVerification
Request->>CookieAuthentication: Supply machine JWT in __session
CookieAuthentication->>CookieAuthentication: Detect machine JWT
CookieAuthentication-->>Request: Return signedOut with TokenTypeMismatch
TokenVerification->>TokenVerification: Reject M2M category before key resolution
Loading

Poem

A rabbit found a token in the cookie’s leafy nest,
“Not a session!” it thumped, “This guard will do its best.”
Machine JWTs hop out, signed-out states remain,
Keys are never fetched again.
Patch released with a carrot refrain!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 50.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly matches the main change: rejecting machine JWTs when they are presented as session tokens.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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

The cookie path (authenticateRequestWithTokenInCookie) lacked the
isMachineJwt() guard the header path has, so a same-instance M2M JWT in
the __session cookie passed verifyToken() and produced a signed-in state
with userId = "mch_...".
- Guard the cookie path with isMachineJwt(), mirroring the header path
- verifyToken() now also rejects any JWT tagged with a non-session
token category (cat), closing the confusion regardless of transport
- Add regression tests for both paths
@dominic-clerkdominic-clerk changed the title fix(backend): reject machine JWTs presented as session tokens (AISEC-91)fix(backend): reject machine JWTs presented as session tokensJul 15, 2026
@pkg-pr-new

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

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

@clerk/backend

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

@clerk/chrome-extension

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

@clerk/clerk-js

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

@clerk/electron

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

@clerk/electron-passkeys

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

@clerk/eslint-plugin

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

@clerk/expo

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

@clerk/expo-passkeys

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

@clerk/express

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

@clerk/fastify

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

@clerk/hono

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

@clerk/localizations

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

@clerk/nextjs

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

@clerk/nuxt

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

@clerk/react

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

@clerk/react-router

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

@clerk/shared

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

@clerk/tanstack-react-start

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

@clerk/testing

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

@clerk/ui

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

@clerk/upgrade

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

@clerk/vue

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

commit: b66f063

@coderabbitaicoderabbitaiBot 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

🧹 Nitpick comments (1)
packages/backend/src/tokens/__tests__/request.test.ts (1)

1283-1307: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Cover the OAuth cookie branch as well.

This regression test covers only M2M JWTs, while the production guard handles both OAuth and M2M tokens. Add or confirm a cookie case for an OAuth JWT asserting the same signed-out TokenTypeMismatch result.

As per coding guidelines, unit tests are required for all new functionality and should cover error handling and edge cases.

🤖 Prompt for 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.
In `@packages/backend/src/tokens/__tests__/request.test.ts` around lines 1283 -
1307, The cookieToken regression coverage only exercises the M2M JWT path;
extend the request tests around the existing cookieToken test to present an
OAuth JWT in the __session cookie and assert the same signed-out result with
AuthErrorReason.TokenTypeMismatch, including the existing toAuth() expectation.
Reuse the established OAuth JWT fixture or construction helpers and preserve the
current M2M coverage.

Source: Coding guidelines

🤖 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/backend/src/tokens/request.ts`:
- Around line 634-643: Move the isMachineJwt check in the cookie authentication
flow before the handleMaybeHandshakeStatus branches, so machine JWTs in
__session return the TokenTypeMismatch signedOut result even when __client_uat
is absent. Add a regression case covering a machine JWT without __client_uat and
verify the expected signed-out response.
---
Nitpick comments:
In `@packages/backend/src/tokens/__tests__/request.test.ts`:
- Around line 1283-1307: The cookieToken regression coverage only exercises the
M2M JWT path; extend the request tests around the existing cookieToken test to
present an OAuth JWT in the __session cookie and assert the same signed-out
result with AuthErrorReason.TokenTypeMismatch, including the existing toAuth()
expectation. Reuse the established OAuth JWT fixture or construction helpers and
preserve the current M2M coverage.
🪄 Autofix (Beta)

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), Repository UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 063de93e-c94c-496a-9c69-5ab6fabc5524

📥 Commits

Reviewing files that changed from the base of the PR and between 59774c6 and b66f063.

📒 Files selected for processing (5)
  • .changeset/shiny-words-lay.md
  • packages/backend/src/tokens/__tests__/request.test.ts
  • packages/backend/src/tokens/__tests__/verify.test.ts
  • packages/backend/src/tokens/request.ts
  • packages/backend/src/tokens/verify.ts

Comment on lines +634 to +643
// Machine JWTs pass verifyToken() but must not be accepted as session tokens (mirrors header path).
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
if (isMachineJwt(authenticateContext.sessionTokenInCookie!)) {
return signedOut({
tokenType: TokenType.SessionToken,
authenticateContext,
reason: AuthErrorReason.TokenTypeMismatch,
message: '',
});
}

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.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bashset -euo pipefail
# Map the relevant file and surrounding symbols first.
ast-grep outline packages/backend/src/tokens/request.ts --view expanded ||trueechoecho"---- relevant line ranges ----"
nl -ba packages/backend/src/tokens/request.ts | sed -n '560,700p'echoecho"---- search for handshake helpers / machine jwt checks ----"
rg -n "isMachineJwt|handleMaybeHandshakeStatus|TokenTypeMismatch|__client_uat|sessionTokenInCookie|signedOut" packages/backend/src/tokens/request.ts

Repository: clerk/javascript

Length of output: 1189


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"---- packages/backend/src/tokens/request.ts (lines 600-660) ----"
sed -n '600,660p' packages/backend/src/tokens/request.ts
echoecho"---- surrounding references ----"
rg -n "handleMaybeHandshakeStatus|isMachineJwt|sessionTokenInCookie|__client_uat|TokenTypeMismatch" packages/backend/src/tokens/request.ts

Repository: clerk/javascript

Length of output: 4888


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"---- state derivation around hasActiveClient / client UAT / session token ----"
sed -n '440,625p' packages/backend/src/tokens/request.ts
echoecho"---- machine helpers ----"
sed -n '1,220p' packages/backend/src/tokens/machine.ts

Repository: clerk/javascript

Length of output: 14777


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"---- handleMaybeHandshakeStatus definition ----"
sed -n '300,380p' packages/backend/src/tokens/request.ts
echoecho"---- call sites of handleMaybeHandshakeStatus in request.ts ----"
rg -n "function handleMaybeHandshakeStatus|return handleMaybeHandshakeStatus|handleMaybeHandshakeStatus\(" packages/backend/src/tokens/request.ts

Repository: clerk/javascript

Length of output: 4982


Move the machine-JWT check before the cookie handshake branches. A machine JWT in __session without __client_uat hits handleMaybeHandshakeStatus first, so it never returns the intended TokenTypeMismatch signed-out state. Add a regression case for the no-__client_uat path.

🤖 Prompt for 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.
In `@packages/backend/src/tokens/request.ts` around lines 634 - 643, Move the
isMachineJwt check in the cookie authentication flow before the
handleMaybeHandshakeStatus branches, so machine JWTs in __session return the
TokenTypeMismatch signedOut result even when __client_uat is absent. Add a
regression case covering a machine JWT without __client_uat and verify the
expected signed-out response.

@github-actions

Copy link
Copy Markdown
Contributor

API Changes Report

Generated by Break Check on 2026-07-15T12:33:17.387Z

Summary

MetricCount
Packages analyzed19
Packages with changes0
🔴 Breaking changes0
🟡 Non-breaking changes0
🟢 Additions0

No API Changes Detected

All packages have stable APIs with no detected changes.


Report generated by Break Check

Last ran on b66f063.

@wobsorianowobsoriano left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

ty Dom!

@dominic-clerk
dominic-clerk merged commit e657d99 into mainJul 16, 2026
55 checks passed
@dominic-clerk
dominic-clerk deleted the dc-machine-jwt branch July 16, 2026 06:00
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@dominic-clerk@wobsoriano
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

fix(backend): reject machine JWTs presented as session tokens - #9168

Merged
dominic-clerk merged 1 commit into
mainfrom
dc-machine-jwt
Jul 16, 2026
Merged

fix(backend): reject machine JWTs presented as session tokens#9168
dominic-clerk merged 1 commit into
mainfrom
dc-machine-jwt

Conversation

@dominic-clerk

@dominic-clerkdominic-clerk commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Description

The cookie path (authenticateRequestWithTokenInCookie) lacked the isMachineJwt() guard the header path has, so a same-instance M2M JWT in the __session cookie passed verifyToken() and produced a signed-in state with userId = "mch_...".

  • Guard the cookie path with isMachineJwt(), mirroring the header path
  • verifyToken() now also rejects any JWT tagged with a non-session token category (cat), closing the confusion regardless of transport
  • Add regression tests for both paths

Fixes SDK-107

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:

Summary by CodeRabbit

  • Bug Fixes
    • Machine-to-machine tokens and OAuth JWTs provided through the session cookie are now rejected instead of being treated as signed-in sessions.
    • Invalid session token categories now consistently return a signed-out authentication state.
    • Session token validation rejects unsupported token types earlier, improving authorization safety.

@changeset-bot

changeset-botBot commented Jul 15, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: b66f063

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

This PR includes changesets to release 10 packages
NameType
@clerk/backendPatch
@clerk/astroPatch
@clerk/expressPatch
@clerk/fastifyPatch
@clerk/honoPatch
@clerk/nextjsPatch
@clerk/nuxtPatch
@clerk/react-routerPatch
@clerk/tanstack-react-startPatch
@clerk/testingPatch

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

vercelBot commented Jul 15, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
clerk-js-sandboxReadyReadyPreview, CommentJul 15, 2026 12:29pm
swingsetReadyReadyPreview, CommentJul 15, 2026 12:29pm

Request Review

@coderabbitai

coderabbitaiBot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Machine-token JWTs are rejected when supplied as session cookies or verified as session tokens. Cookie authentication returns a signed-out state, token verification fails before key resolution, and tests plus a patch changeset document the behavior.

Changes

Session token security

Layer / File(s)Summary
Token category verification
packages/backend/src/tokens/verify.ts, packages/backend/src/tokens/__tests__/verify.test.ts
verifyToken rejects M2M-category JWTs before JWK resolution and reports an invalid session token category, with coverage for the early failure.
Cookie authentication rejection
packages/backend/src/tokens/request.ts, packages/backend/src/tokens/__tests__/request.test.ts, .changeset/shiny-words-lay.md
Session cookies containing machine JWTs return signed-out state with TokenTypeMismatch; request tests and release notes record the behavior.

Estimated code review effort: 2 (Simple) | ~10 minutes

Suggested reviewers:alexcarpenter

Sequence Diagram(s)

sequenceDiagram
participant Request
participant CookieAuthentication
participant TokenVerification
Request->>CookieAuthentication: Supply machine JWT in __session
CookieAuthentication->>CookieAuthentication: Detect machine JWT
CookieAuthentication-->>Request: Return signedOut with TokenTypeMismatch
TokenVerification->>TokenVerification: Reject M2M category before key resolution
Loading

Poem

A rabbit found a token in the cookie’s leafy nest,
“Not a session!” it thumped, “This guard will do its best.”
Machine JWTs hop out, signed-out states remain,
Keys are never fetched again.
Patch released with a carrot refrain!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 50.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly matches the main change: rejecting machine JWTs when they are presented as session tokens.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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

The cookie path (authenticateRequestWithTokenInCookie) lacked the
isMachineJwt() guard the header path has, so a same-instance M2M JWT in
the __session cookie passed verifyToken() and produced a signed-in state
with userId = "mch_...".
- Guard the cookie path with isMachineJwt(), mirroring the header path
- verifyToken() now also rejects any JWT tagged with a non-session
token category (cat), closing the confusion regardless of transport
- Add regression tests for both paths
@dominic-clerkdominic-clerk changed the title fix(backend): reject machine JWTs presented as session tokens (AISEC-91)fix(backend): reject machine JWTs presented as session tokensJul 15, 2026
@pkg-pr-new

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

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

@clerk/backend

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

@clerk/chrome-extension

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

@clerk/clerk-js

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

@clerk/electron

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

@clerk/electron-passkeys

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

@clerk/eslint-plugin

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

@clerk/expo

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

@clerk/expo-passkeys

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

@clerk/express

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

@clerk/fastify

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

@clerk/hono

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

@clerk/localizations

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

@clerk/nextjs

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

@clerk/nuxt

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

@clerk/react

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

@clerk/react-router

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

@clerk/shared

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

@clerk/tanstack-react-start

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

@clerk/testing

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

@clerk/ui

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

@clerk/upgrade

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

@clerk/vue

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

commit: b66f063

@coderabbitaicoderabbitaiBot 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

🧹 Nitpick comments (1)
packages/backend/src/tokens/__tests__/request.test.ts (1)

1283-1307: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Cover the OAuth cookie branch as well.

This regression test covers only M2M JWTs, while the production guard handles both OAuth and M2M tokens. Add or confirm a cookie case for an OAuth JWT asserting the same signed-out TokenTypeMismatch result.

As per coding guidelines, unit tests are required for all new functionality and should cover error handling and edge cases.

🤖 Prompt for 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.
In `@packages/backend/src/tokens/__tests__/request.test.ts` around lines 1283 -
1307, The cookieToken regression coverage only exercises the M2M JWT path;
extend the request tests around the existing cookieToken test to present an
OAuth JWT in the __session cookie and assert the same signed-out result with
AuthErrorReason.TokenTypeMismatch, including the existing toAuth() expectation.
Reuse the established OAuth JWT fixture or construction helpers and preserve the
current M2M coverage.

Source: Coding guidelines

🤖 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/backend/src/tokens/request.ts`:
- Around line 634-643: Move the isMachineJwt check in the cookie authentication
flow before the handleMaybeHandshakeStatus branches, so machine JWTs in
__session return the TokenTypeMismatch signedOut result even when __client_uat
is absent. Add a regression case covering a machine JWT without __client_uat and
verify the expected signed-out response.
---
Nitpick comments:
In `@packages/backend/src/tokens/__tests__/request.test.ts`:
- Around line 1283-1307: The cookieToken regression coverage only exercises the
M2M JWT path; extend the request tests around the existing cookieToken test to
present an OAuth JWT in the __session cookie and assert the same signed-out
result with AuthErrorReason.TokenTypeMismatch, including the existing toAuth()
expectation. Reuse the established OAuth JWT fixture or construction helpers and
preserve the current M2M coverage.
🪄 Autofix (Beta)

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), Repository UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 063de93e-c94c-496a-9c69-5ab6fabc5524

📥 Commits

Reviewing files that changed from the base of the PR and between 59774c6 and b66f063.

📒 Files selected for processing (5)
  • .changeset/shiny-words-lay.md
  • packages/backend/src/tokens/__tests__/request.test.ts
  • packages/backend/src/tokens/__tests__/verify.test.ts
  • packages/backend/src/tokens/request.ts
  • packages/backend/src/tokens/verify.ts

Comment on lines +634 to +643
// Machine JWTs pass verifyToken() but must not be accepted as session tokens (mirrors header path).
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
if (isMachineJwt(authenticateContext.sessionTokenInCookie!)) {
return signedOut({
tokenType: TokenType.SessionToken,
authenticateContext,
reason: AuthErrorReason.TokenTypeMismatch,
message: '',
});
}

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.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bashset -euo pipefail
# Map the relevant file and surrounding symbols first.
ast-grep outline packages/backend/src/tokens/request.ts --view expanded ||trueechoecho"---- relevant line ranges ----"
nl -ba packages/backend/src/tokens/request.ts | sed -n '560,700p'echoecho"---- search for handshake helpers / machine jwt checks ----"
rg -n "isMachineJwt|handleMaybeHandshakeStatus|TokenTypeMismatch|__client_uat|sessionTokenInCookie|signedOut" packages/backend/src/tokens/request.ts

Repository: clerk/javascript

Length of output: 1189


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"---- packages/backend/src/tokens/request.ts (lines 600-660) ----"
sed -n '600,660p' packages/backend/src/tokens/request.ts
echoecho"---- surrounding references ----"
rg -n "handleMaybeHandshakeStatus|isMachineJwt|sessionTokenInCookie|__client_uat|TokenTypeMismatch" packages/backend/src/tokens/request.ts

Repository: clerk/javascript

Length of output: 4888


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"---- state derivation around hasActiveClient / client UAT / session token ----"
sed -n '440,625p' packages/backend/src/tokens/request.ts
echoecho"---- machine helpers ----"
sed -n '1,220p' packages/backend/src/tokens/machine.ts

Repository: clerk/javascript

Length of output: 14777


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"---- handleMaybeHandshakeStatus definition ----"
sed -n '300,380p' packages/backend/src/tokens/request.ts
echoecho"---- call sites of handleMaybeHandshakeStatus in request.ts ----"
rg -n "function handleMaybeHandshakeStatus|return handleMaybeHandshakeStatus|handleMaybeHandshakeStatus\(" packages/backend/src/tokens/request.ts

Repository: clerk/javascript

Length of output: 4982


Move the machine-JWT check before the cookie handshake branches. A machine JWT in __session without __client_uat hits handleMaybeHandshakeStatus first, so it never returns the intended TokenTypeMismatch signed-out state. Add a regression case for the no-__client_uat path.

🤖 Prompt for 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.
In `@packages/backend/src/tokens/request.ts` around lines 634 - 643, Move the
isMachineJwt check in the cookie authentication flow before the
handleMaybeHandshakeStatus branches, so machine JWTs in __session return the
TokenTypeMismatch signedOut result even when __client_uat is absent. Add a
regression case covering a machine JWT without __client_uat and verify the
expected signed-out response.

@github-actions

Copy link
Copy Markdown
Contributor

API Changes Report

Generated by Break Check on 2026-07-15T12:33:17.387Z

Summary

MetricCount
Packages analyzed19
Packages with changes0
🔴 Breaking changes0
🟡 Non-breaking changes0
🟢 Additions0

No API Changes Detected

All packages have stable APIs with no detected changes.


Report generated by Break Check

Last ran on b66f063.

@wobsorianowobsoriano left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

ty Dom!

@dominic-clerk
dominic-clerk merged commit e657d99 into mainJul 16, 2026
55 checks passed
@dominic-clerk
dominic-clerk deleted the dc-machine-jwt branch July 16, 2026 06:00
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@dominic-clerk@wobsoriano
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(backend): reject machine JWTs presented as session tokens - #9168

Merged
dominic-clerk merged 1 commit into
mainfrom
dc-machine-jwt
Jul 16, 2026
Merged

fix(backend): reject machine JWTs presented as session tokens#9168
dominic-clerk merged 1 commit into
mainfrom
dc-machine-jwt

Conversation

@dominic-clerk

@dominic-clerkdominic-clerk commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Description

The cookie path (authenticateRequestWithTokenInCookie) lacked the isMachineJwt() guard the header path has, so a same-instance M2M JWT in the __session cookie passed verifyToken() and produced a signed-in state with userId = "mch_...".

  • Guard the cookie path with isMachineJwt(), mirroring the header path
  • verifyToken() now also rejects any JWT tagged with a non-session token category (cat), closing the confusion regardless of transport
  • Add regression tests for both paths

Fixes SDK-107

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:

Summary by CodeRabbit

  • Bug Fixes
    • Machine-to-machine tokens and OAuth JWTs provided through the session cookie are now rejected instead of being treated as signed-in sessions.
    • Invalid session token categories now consistently return a signed-out authentication state.
    • Session token validation rejects unsupported token types earlier, improving authorization safety.

@changeset-bot

changeset-botBot commented Jul 15, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: b66f063

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

This PR includes changesets to release 10 packages
NameType
@clerk/backendPatch
@clerk/astroPatch
@clerk/expressPatch
@clerk/fastifyPatch
@clerk/honoPatch
@clerk/nextjsPatch
@clerk/nuxtPatch
@clerk/react-routerPatch
@clerk/tanstack-react-startPatch
@clerk/testingPatch

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

vercelBot commented Jul 15, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
clerk-js-sandboxReadyReadyPreview, CommentJul 15, 2026 12:29pm
swingsetReadyReadyPreview, CommentJul 15, 2026 12:29pm

Request Review

@coderabbitai

coderabbitaiBot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Machine-token JWTs are rejected when supplied as session cookies or verified as session tokens. Cookie authentication returns a signed-out state, token verification fails before key resolution, and tests plus a patch changeset document the behavior.

Changes

Session token security

Layer / File(s)Summary
Token category verification
packages/backend/src/tokens/verify.ts, packages/backend/src/tokens/__tests__/verify.test.ts
verifyToken rejects M2M-category JWTs before JWK resolution and reports an invalid session token category, with coverage for the early failure.
Cookie authentication rejection
packages/backend/src/tokens/request.ts, packages/backend/src/tokens/__tests__/request.test.ts, .changeset/shiny-words-lay.md
Session cookies containing machine JWTs return signed-out state with TokenTypeMismatch; request tests and release notes record the behavior.

Estimated code review effort: 2 (Simple) | ~10 minutes

Suggested reviewers:alexcarpenter

Sequence Diagram(s)

sequenceDiagram
participant Request
participant CookieAuthentication
participant TokenVerification
Request->>CookieAuthentication: Supply machine JWT in __session
CookieAuthentication->>CookieAuthentication: Detect machine JWT
CookieAuthentication-->>Request: Return signedOut with TokenTypeMismatch
TokenVerification->>TokenVerification: Reject M2M category before key resolution
Loading

Poem

A rabbit found a token in the cookie’s leafy nest,
“Not a session!” it thumped, “This guard will do its best.”
Machine JWTs hop out, signed-out states remain,
Keys are never fetched again.
Patch released with a carrot refrain!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 50.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly matches the main change: rejecting machine JWTs when they are presented as session tokens.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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

The cookie path (authenticateRequestWithTokenInCookie) lacked the
isMachineJwt() guard the header path has, so a same-instance M2M JWT in
the __session cookie passed verifyToken() and produced a signed-in state
with userId = "mch_...".
- Guard the cookie path with isMachineJwt(), mirroring the header path
- verifyToken() now also rejects any JWT tagged with a non-session
token category (cat), closing the confusion regardless of transport
- Add regression tests for both paths
@dominic-clerkdominic-clerk changed the title fix(backend): reject machine JWTs presented as session tokens (AISEC-91)fix(backend): reject machine JWTs presented as session tokensJul 15, 2026
@pkg-pr-new

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

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

@clerk/backend

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

@clerk/chrome-extension

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

@clerk/clerk-js

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

@clerk/electron

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

@clerk/electron-passkeys

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

@clerk/eslint-plugin

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

@clerk/expo

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

@clerk/expo-passkeys

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

@clerk/express

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

@clerk/fastify

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

@clerk/hono

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

@clerk/localizations

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

@clerk/nextjs

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

@clerk/nuxt

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

@clerk/react

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

@clerk/react-router

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

@clerk/shared

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

@clerk/tanstack-react-start

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

@clerk/testing

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

@clerk/ui

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

@clerk/upgrade

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

@clerk/vue

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

commit: b66f063

@coderabbitaicoderabbitaiBot 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

🧹 Nitpick comments (1)
packages/backend/src/tokens/__tests__/request.test.ts (1)

1283-1307: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Cover the OAuth cookie branch as well.

This regression test covers only M2M JWTs, while the production guard handles both OAuth and M2M tokens. Add or confirm a cookie case for an OAuth JWT asserting the same signed-out TokenTypeMismatch result.

As per coding guidelines, unit tests are required for all new functionality and should cover error handling and edge cases.

🤖 Prompt for 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.
In `@packages/backend/src/tokens/__tests__/request.test.ts` around lines 1283 -
1307, The cookieToken regression coverage only exercises the M2M JWT path;
extend the request tests around the existing cookieToken test to present an
OAuth JWT in the __session cookie and assert the same signed-out result with
AuthErrorReason.TokenTypeMismatch, including the existing toAuth() expectation.
Reuse the established OAuth JWT fixture or construction helpers and preserve the
current M2M coverage.

Source: Coding guidelines

🤖 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/backend/src/tokens/request.ts`:
- Around line 634-643: Move the isMachineJwt check in the cookie authentication
flow before the handleMaybeHandshakeStatus branches, so machine JWTs in
__session return the TokenTypeMismatch signedOut result even when __client_uat
is absent. Add a regression case covering a machine JWT without __client_uat and
verify the expected signed-out response.
---
Nitpick comments:
In `@packages/backend/src/tokens/__tests__/request.test.ts`:
- Around line 1283-1307: The cookieToken regression coverage only exercises the
M2M JWT path; extend the request tests around the existing cookieToken test to
present an OAuth JWT in the __session cookie and assert the same signed-out
result with AuthErrorReason.TokenTypeMismatch, including the existing toAuth()
expectation. Reuse the established OAuth JWT fixture or construction helpers and
preserve the current M2M coverage.
🪄 Autofix (Beta)

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), Repository UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 063de93e-c94c-496a-9c69-5ab6fabc5524

📥 Commits

Reviewing files that changed from the base of the PR and between 59774c6 and b66f063.

📒 Files selected for processing (5)
  • .changeset/shiny-words-lay.md
  • packages/backend/src/tokens/__tests__/request.test.ts
  • packages/backend/src/tokens/__tests__/verify.test.ts
  • packages/backend/src/tokens/request.ts
  • packages/backend/src/tokens/verify.ts

Comment on lines +634 to +643
// Machine JWTs pass verifyToken() but must not be accepted as session tokens (mirrors header path).
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
if (isMachineJwt(authenticateContext.sessionTokenInCookie!)) {
return signedOut({
tokenType: TokenType.SessionToken,
authenticateContext,
reason: AuthErrorReason.TokenTypeMismatch,
message: '',
});
}

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.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bashset -euo pipefail
# Map the relevant file and surrounding symbols first.
ast-grep outline packages/backend/src/tokens/request.ts --view expanded ||trueechoecho"---- relevant line ranges ----"
nl -ba packages/backend/src/tokens/request.ts | sed -n '560,700p'echoecho"---- search for handshake helpers / machine jwt checks ----"
rg -n "isMachineJwt|handleMaybeHandshakeStatus|TokenTypeMismatch|__client_uat|sessionTokenInCookie|signedOut" packages/backend/src/tokens/request.ts

Repository: clerk/javascript

Length of output: 1189


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"---- packages/backend/src/tokens/request.ts (lines 600-660) ----"
sed -n '600,660p' packages/backend/src/tokens/request.ts
echoecho"---- surrounding references ----"
rg -n "handleMaybeHandshakeStatus|isMachineJwt|sessionTokenInCookie|__client_uat|TokenTypeMismatch" packages/backend/src/tokens/request.ts

Repository: clerk/javascript

Length of output: 4888


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"---- state derivation around hasActiveClient / client UAT / session token ----"
sed -n '440,625p' packages/backend/src/tokens/request.ts
echoecho"---- machine helpers ----"
sed -n '1,220p' packages/backend/src/tokens/machine.ts

Repository: clerk/javascript

Length of output: 14777


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"---- handleMaybeHandshakeStatus definition ----"
sed -n '300,380p' packages/backend/src/tokens/request.ts
echoecho"---- call sites of handleMaybeHandshakeStatus in request.ts ----"
rg -n "function handleMaybeHandshakeStatus|return handleMaybeHandshakeStatus|handleMaybeHandshakeStatus\(" packages/backend/src/tokens/request.ts

Repository: clerk/javascript

Length of output: 4982


Move the machine-JWT check before the cookie handshake branches. A machine JWT in __session without __client_uat hits handleMaybeHandshakeStatus first, so it never returns the intended TokenTypeMismatch signed-out state. Add a regression case for the no-__client_uat path.

🤖 Prompt for 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.
In `@packages/backend/src/tokens/request.ts` around lines 634 - 643, Move the
isMachineJwt check in the cookie authentication flow before the
handleMaybeHandshakeStatus branches, so machine JWTs in __session return the
TokenTypeMismatch signedOut result even when __client_uat is absent. Add a
regression case covering a machine JWT without __client_uat and verify the
expected signed-out response.

@github-actions

Copy link
Copy Markdown
Contributor

API Changes Report

Generated by Break Check on 2026-07-15T12:33:17.387Z

Summary

MetricCount
Packages analyzed19
Packages with changes0
🔴 Breaking changes0
🟡 Non-breaking changes0
🟢 Additions0

No API Changes Detected

All packages have stable APIs with no detected changes.


Report generated by Break Check

Last ran on b66f063.

@wobsorianowobsoriano left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

ty Dom!

@dominic-clerk
dominic-clerk merged commit e657d99 into mainJul 16, 2026
55 checks passed
@dominic-clerk
dominic-clerk deleted the dc-machine-jwt branch July 16, 2026 06:00
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@dominic-clerk@wobsoriano
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(backend): reject machine JWTs presented as session tokens - #9168

Merged
dominic-clerk merged 1 commit into
mainfrom
dc-machine-jwt
Jul 16, 2026
Merged

fix(backend): reject machine JWTs presented as session tokens#9168
dominic-clerk merged 1 commit into
mainfrom
dc-machine-jwt

Conversation

@dominic-clerk

@dominic-clerkdominic-clerk commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Description

The cookie path (authenticateRequestWithTokenInCookie) lacked the isMachineJwt() guard the header path has, so a same-instance M2M JWT in the __session cookie passed verifyToken() and produced a signed-in state with userId = "mch_...".

  • Guard the cookie path with isMachineJwt(), mirroring the header path
  • verifyToken() now also rejects any JWT tagged with a non-session token category (cat), closing the confusion regardless of transport
  • Add regression tests for both paths

Fixes SDK-107

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:

Summary by CodeRabbit

  • Bug Fixes
    • Machine-to-machine tokens and OAuth JWTs provided through the session cookie are now rejected instead of being treated as signed-in sessions.
    • Invalid session token categories now consistently return a signed-out authentication state.
    • Session token validation rejects unsupported token types earlier, improving authorization safety.

@changeset-bot

changeset-botBot commented Jul 15, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: b66f063

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

This PR includes changesets to release 10 packages
NameType
@clerk/backendPatch
@clerk/astroPatch
@clerk/expressPatch
@clerk/fastifyPatch
@clerk/honoPatch
@clerk/nextjsPatch
@clerk/nuxtPatch
@clerk/react-routerPatch
@clerk/tanstack-react-startPatch
@clerk/testingPatch

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

vercelBot commented Jul 15, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
clerk-js-sandboxReadyReadyPreview, CommentJul 15, 2026 12:29pm
swingsetReadyReadyPreview, CommentJul 15, 2026 12:29pm

Request Review

@coderabbitai

coderabbitaiBot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Machine-token JWTs are rejected when supplied as session cookies or verified as session tokens. Cookie authentication returns a signed-out state, token verification fails before key resolution, and tests plus a patch changeset document the behavior.

Changes

Session token security

Layer / File(s)Summary
Token category verification
packages/backend/src/tokens/verify.ts, packages/backend/src/tokens/__tests__/verify.test.ts
verifyToken rejects M2M-category JWTs before JWK resolution and reports an invalid session token category, with coverage for the early failure.
Cookie authentication rejection
packages/backend/src/tokens/request.ts, packages/backend/src/tokens/__tests__/request.test.ts, .changeset/shiny-words-lay.md
Session cookies containing machine JWTs return signed-out state with TokenTypeMismatch; request tests and release notes record the behavior.

Estimated code review effort: 2 (Simple) | ~10 minutes

Suggested reviewers:alexcarpenter

Sequence Diagram(s)

sequenceDiagram
participant Request
participant CookieAuthentication
participant TokenVerification
Request->>CookieAuthentication: Supply machine JWT in __session
CookieAuthentication->>CookieAuthentication: Detect machine JWT
CookieAuthentication-->>Request: Return signedOut with TokenTypeMismatch
TokenVerification->>TokenVerification: Reject M2M category before key resolution
Loading

Poem

A rabbit found a token in the cookie’s leafy nest,
“Not a session!” it thumped, “This guard will do its best.”
Machine JWTs hop out, signed-out states remain,
Keys are never fetched again.
Patch released with a carrot refrain!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 50.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly matches the main change: rejecting machine JWTs when they are presented as session tokens.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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

The cookie path (authenticateRequestWithTokenInCookie) lacked the
isMachineJwt() guard the header path has, so a same-instance M2M JWT in
the __session cookie passed verifyToken() and produced a signed-in state
with userId = "mch_...".
- Guard the cookie path with isMachineJwt(), mirroring the header path
- verifyToken() now also rejects any JWT tagged with a non-session
token category (cat), closing the confusion regardless of transport
- Add regression tests for both paths
@dominic-clerkdominic-clerk changed the title fix(backend): reject machine JWTs presented as session tokens (AISEC-91)fix(backend): reject machine JWTs presented as session tokensJul 15, 2026
@pkg-pr-new

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

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

@clerk/backend

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

@clerk/chrome-extension

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

@clerk/clerk-js

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

@clerk/electron

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

@clerk/electron-passkeys

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

@clerk/eslint-plugin

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

@clerk/expo

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

@clerk/expo-passkeys

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

@clerk/express

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

@clerk/fastify

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

@clerk/hono

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

@clerk/localizations

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

@clerk/nextjs

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

@clerk/nuxt

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

@clerk/react

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

@clerk/react-router

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

@clerk/shared

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

@clerk/tanstack-react-start

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

@clerk/testing

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

@clerk/ui

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

@clerk/upgrade

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

@clerk/vue

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

commit: b66f063

@coderabbitaicoderabbitaiBot 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

🧹 Nitpick comments (1)
packages/backend/src/tokens/__tests__/request.test.ts (1)

1283-1307: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Cover the OAuth cookie branch as well.

This regression test covers only M2M JWTs, while the production guard handles both OAuth and M2M tokens. Add or confirm a cookie case for an OAuth JWT asserting the same signed-out TokenTypeMismatch result.

As per coding guidelines, unit tests are required for all new functionality and should cover error handling and edge cases.

🤖 Prompt for 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.
In `@packages/backend/src/tokens/__tests__/request.test.ts` around lines 1283 -
1307, The cookieToken regression coverage only exercises the M2M JWT path;
extend the request tests around the existing cookieToken test to present an
OAuth JWT in the __session cookie and assert the same signed-out result with
AuthErrorReason.TokenTypeMismatch, including the existing toAuth() expectation.
Reuse the established OAuth JWT fixture or construction helpers and preserve the
current M2M coverage.

Source: Coding guidelines

🤖 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/backend/src/tokens/request.ts`:
- Around line 634-643: Move the isMachineJwt check in the cookie authentication
flow before the handleMaybeHandshakeStatus branches, so machine JWTs in
__session return the TokenTypeMismatch signedOut result even when __client_uat
is absent. Add a regression case covering a machine JWT without __client_uat and
verify the expected signed-out response.
---
Nitpick comments:
In `@packages/backend/src/tokens/__tests__/request.test.ts`:
- Around line 1283-1307: The cookieToken regression coverage only exercises the
M2M JWT path; extend the request tests around the existing cookieToken test to
present an OAuth JWT in the __session cookie and assert the same signed-out
result with AuthErrorReason.TokenTypeMismatch, including the existing toAuth()
expectation. Reuse the established OAuth JWT fixture or construction helpers and
preserve the current M2M coverage.
🪄 Autofix (Beta)

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), Repository UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 063de93e-c94c-496a-9c69-5ab6fabc5524

📥 Commits

Reviewing files that changed from the base of the PR and between 59774c6 and b66f063.

📒 Files selected for processing (5)
  • .changeset/shiny-words-lay.md
  • packages/backend/src/tokens/__tests__/request.test.ts
  • packages/backend/src/tokens/__tests__/verify.test.ts
  • packages/backend/src/tokens/request.ts
  • packages/backend/src/tokens/verify.ts

Comment on lines +634 to +643
// Machine JWTs pass verifyToken() but must not be accepted as session tokens (mirrors header path).
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
if (isMachineJwt(authenticateContext.sessionTokenInCookie!)) {
return signedOut({
tokenType: TokenType.SessionToken,
authenticateContext,
reason: AuthErrorReason.TokenTypeMismatch,
message: '',
});
}

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.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bashset -euo pipefail
# Map the relevant file and surrounding symbols first.
ast-grep outline packages/backend/src/tokens/request.ts --view expanded ||trueechoecho"---- relevant line ranges ----"
nl -ba packages/backend/src/tokens/request.ts | sed -n '560,700p'echoecho"---- search for handshake helpers / machine jwt checks ----"
rg -n "isMachineJwt|handleMaybeHandshakeStatus|TokenTypeMismatch|__client_uat|sessionTokenInCookie|signedOut" packages/backend/src/tokens/request.ts

Repository: clerk/javascript

Length of output: 1189


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"---- packages/backend/src/tokens/request.ts (lines 600-660) ----"
sed -n '600,660p' packages/backend/src/tokens/request.ts
echoecho"---- surrounding references ----"
rg -n "handleMaybeHandshakeStatus|isMachineJwt|sessionTokenInCookie|__client_uat|TokenTypeMismatch" packages/backend/src/tokens/request.ts

Repository: clerk/javascript

Length of output: 4888


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"---- state derivation around hasActiveClient / client UAT / session token ----"
sed -n '440,625p' packages/backend/src/tokens/request.ts
echoecho"---- machine helpers ----"
sed -n '1,220p' packages/backend/src/tokens/machine.ts

Repository: clerk/javascript

Length of output: 14777


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"---- handleMaybeHandshakeStatus definition ----"
sed -n '300,380p' packages/backend/src/tokens/request.ts
echoecho"---- call sites of handleMaybeHandshakeStatus in request.ts ----"
rg -n "function handleMaybeHandshakeStatus|return handleMaybeHandshakeStatus|handleMaybeHandshakeStatus\(" packages/backend/src/tokens/request.ts

Repository: clerk/javascript

Length of output: 4982


Move the machine-JWT check before the cookie handshake branches. A machine JWT in __session without __client_uat hits handleMaybeHandshakeStatus first, so it never returns the intended TokenTypeMismatch signed-out state. Add a regression case for the no-__client_uat path.

🤖 Prompt for 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.
In `@packages/backend/src/tokens/request.ts` around lines 634 - 643, Move the
isMachineJwt check in the cookie authentication flow before the
handleMaybeHandshakeStatus branches, so machine JWTs in __session return the
TokenTypeMismatch signedOut result even when __client_uat is absent. Add a
regression case covering a machine JWT without __client_uat and verify the
expected signed-out response.

@github-actions

Copy link
Copy Markdown
Contributor

API Changes Report

Generated by Break Check on 2026-07-15T12:33:17.387Z

Summary

MetricCount
Packages analyzed19
Packages with changes0
🔴 Breaking changes0
🟡 Non-breaking changes0
🟢 Additions0

No API Changes Detected

All packages have stable APIs with no detected changes.


Report generated by Break Check

Last ran on b66f063.

@wobsorianowobsoriano left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

ty Dom!

@dominic-clerk
dominic-clerk merged commit e657d99 into mainJul 16, 2026
55 checks passed
@dominic-clerk
dominic-clerk deleted the dc-machine-jwt branch July 16, 2026 06:00
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@dominic-clerk@wobsoriano
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

fix(backend): reject machine JWTs presented as session tokens - #9168

Merged
dominic-clerk merged 1 commit into
mainfrom
dc-machine-jwt
Jul 16, 2026
Merged

fix(backend): reject machine JWTs presented as session tokens#9168
dominic-clerk merged 1 commit into
mainfrom
dc-machine-jwt

Conversation

@dominic-clerk

@dominic-clerkdominic-clerk commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Description

The cookie path (authenticateRequestWithTokenInCookie) lacked the isMachineJwt() guard the header path has, so a same-instance M2M JWT in the __session cookie passed verifyToken() and produced a signed-in state with userId = "mch_...".

  • Guard the cookie path with isMachineJwt(), mirroring the header path
  • verifyToken() now also rejects any JWT tagged with a non-session token category (cat), closing the confusion regardless of transport
  • Add regression tests for both paths

Fixes SDK-107

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:

Summary by CodeRabbit

  • Bug Fixes
    • Machine-to-machine tokens and OAuth JWTs provided through the session cookie are now rejected instead of being treated as signed-in sessions.
    • Invalid session token categories now consistently return a signed-out authentication state.
    • Session token validation rejects unsupported token types earlier, improving authorization safety.

@changeset-bot

changeset-botBot commented Jul 15, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: b66f063

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

This PR includes changesets to release 10 packages
NameType
@clerk/backendPatch
@clerk/astroPatch
@clerk/expressPatch
@clerk/fastifyPatch
@clerk/honoPatch
@clerk/nextjsPatch
@clerk/nuxtPatch
@clerk/react-routerPatch
@clerk/tanstack-react-startPatch
@clerk/testingPatch

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

vercelBot commented Jul 15, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
clerk-js-sandboxReadyReadyPreview, CommentJul 15, 2026 12:29pm
swingsetReadyReadyPreview, CommentJul 15, 2026 12:29pm

Request Review

@coderabbitai

coderabbitaiBot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Machine-token JWTs are rejected when supplied as session cookies or verified as session tokens. Cookie authentication returns a signed-out state, token verification fails before key resolution, and tests plus a patch changeset document the behavior.

Changes

Session token security

Layer / File(s)Summary
Token category verification
packages/backend/src/tokens/verify.ts, packages/backend/src/tokens/__tests__/verify.test.ts
verifyToken rejects M2M-category JWTs before JWK resolution and reports an invalid session token category, with coverage for the early failure.
Cookie authentication rejection
packages/backend/src/tokens/request.ts, packages/backend/src/tokens/__tests__/request.test.ts, .changeset/shiny-words-lay.md
Session cookies containing machine JWTs return signed-out state with TokenTypeMismatch; request tests and release notes record the behavior.

Estimated code review effort: 2 (Simple) | ~10 minutes

Suggested reviewers:alexcarpenter

Sequence Diagram(s)

sequenceDiagram
participant Request
participant CookieAuthentication
participant TokenVerification
Request->>CookieAuthentication: Supply machine JWT in __session
CookieAuthentication->>CookieAuthentication: Detect machine JWT
CookieAuthentication-->>Request: Return signedOut with TokenTypeMismatch
TokenVerification->>TokenVerification: Reject M2M category before key resolution
Loading

Poem

A rabbit found a token in the cookie’s leafy nest,
“Not a session!” it thumped, “This guard will do its best.”
Machine JWTs hop out, signed-out states remain,
Keys are never fetched again.
Patch released with a carrot refrain!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 50.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly matches the main change: rejecting machine JWTs when they are presented as session tokens.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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

The cookie path (authenticateRequestWithTokenInCookie) lacked the
isMachineJwt() guard the header path has, so a same-instance M2M JWT in
the __session cookie passed verifyToken() and produced a signed-in state
with userId = "mch_...".
- Guard the cookie path with isMachineJwt(), mirroring the header path
- verifyToken() now also rejects any JWT tagged with a non-session
token category (cat), closing the confusion regardless of transport
- Add regression tests for both paths
@dominic-clerkdominic-clerk changed the title fix(backend): reject machine JWTs presented as session tokens (AISEC-91)fix(backend): reject machine JWTs presented as session tokensJul 15, 2026
@pkg-pr-new

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

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

@clerk/backend

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

@clerk/chrome-extension

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

@clerk/clerk-js

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

@clerk/electron

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

@clerk/electron-passkeys

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

@clerk/eslint-plugin

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

@clerk/expo

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

@clerk/expo-passkeys

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

@clerk/express

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

@clerk/fastify

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

@clerk/hono

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

@clerk/localizations

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

@clerk/nextjs

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

@clerk/nuxt

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

@clerk/react

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

@clerk/react-router

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

@clerk/shared

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

@clerk/tanstack-react-start

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

@clerk/testing

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

@clerk/ui

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

@clerk/upgrade

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

@clerk/vue

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

commit: b66f063

@coderabbitaicoderabbitaiBot 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

🧹 Nitpick comments (1)
packages/backend/src/tokens/__tests__/request.test.ts (1)

1283-1307: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Cover the OAuth cookie branch as well.

This regression test covers only M2M JWTs, while the production guard handles both OAuth and M2M tokens. Add or confirm a cookie case for an OAuth JWT asserting the same signed-out TokenTypeMismatch result.

As per coding guidelines, unit tests are required for all new functionality and should cover error handling and edge cases.

🤖 Prompt for 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.
In `@packages/backend/src/tokens/__tests__/request.test.ts` around lines 1283 -
1307, The cookieToken regression coverage only exercises the M2M JWT path;
extend the request tests around the existing cookieToken test to present an
OAuth JWT in the __session cookie and assert the same signed-out result with
AuthErrorReason.TokenTypeMismatch, including the existing toAuth() expectation.
Reuse the established OAuth JWT fixture or construction helpers and preserve the
current M2M coverage.

Source: Coding guidelines

🤖 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/backend/src/tokens/request.ts`:
- Around line 634-643: Move the isMachineJwt check in the cookie authentication
flow before the handleMaybeHandshakeStatus branches, so machine JWTs in
__session return the TokenTypeMismatch signedOut result even when __client_uat
is absent. Add a regression case covering a machine JWT without __client_uat and
verify the expected signed-out response.
---
Nitpick comments:
In `@packages/backend/src/tokens/__tests__/request.test.ts`:
- Around line 1283-1307: The cookieToken regression coverage only exercises the
M2M JWT path; extend the request tests around the existing cookieToken test to
present an OAuth JWT in the __session cookie and assert the same signed-out
result with AuthErrorReason.TokenTypeMismatch, including the existing toAuth()
expectation. Reuse the established OAuth JWT fixture or construction helpers and
preserve the current M2M coverage.
🪄 Autofix (Beta)

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), Repository UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 063de93e-c94c-496a-9c69-5ab6fabc5524

📥 Commits

Reviewing files that changed from the base of the PR and between 59774c6 and b66f063.

📒 Files selected for processing (5)
  • .changeset/shiny-words-lay.md
  • packages/backend/src/tokens/__tests__/request.test.ts
  • packages/backend/src/tokens/__tests__/verify.test.ts
  • packages/backend/src/tokens/request.ts
  • packages/backend/src/tokens/verify.ts

Comment on lines +634 to +643
// Machine JWTs pass verifyToken() but must not be accepted as session tokens (mirrors header path).
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
if (isMachineJwt(authenticateContext.sessionTokenInCookie!)) {
return signedOut({
tokenType: TokenType.SessionToken,
authenticateContext,
reason: AuthErrorReason.TokenTypeMismatch,
message: '',
});
}

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.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bashset -euo pipefail
# Map the relevant file and surrounding symbols first.
ast-grep outline packages/backend/src/tokens/request.ts --view expanded ||trueechoecho"---- relevant line ranges ----"
nl -ba packages/backend/src/tokens/request.ts | sed -n '560,700p'echoecho"---- search for handshake helpers / machine jwt checks ----"
rg -n "isMachineJwt|handleMaybeHandshakeStatus|TokenTypeMismatch|__client_uat|sessionTokenInCookie|signedOut" packages/backend/src/tokens/request.ts

Repository: clerk/javascript

Length of output: 1189


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"---- packages/backend/src/tokens/request.ts (lines 600-660) ----"
sed -n '600,660p' packages/backend/src/tokens/request.ts
echoecho"---- surrounding references ----"
rg -n "handleMaybeHandshakeStatus|isMachineJwt|sessionTokenInCookie|__client_uat|TokenTypeMismatch" packages/backend/src/tokens/request.ts

Repository: clerk/javascript

Length of output: 4888


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"---- state derivation around hasActiveClient / client UAT / session token ----"
sed -n '440,625p' packages/backend/src/tokens/request.ts
echoecho"---- machine helpers ----"
sed -n '1,220p' packages/backend/src/tokens/machine.ts

Repository: clerk/javascript

Length of output: 14777


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"---- handleMaybeHandshakeStatus definition ----"
sed -n '300,380p' packages/backend/src/tokens/request.ts
echoecho"---- call sites of handleMaybeHandshakeStatus in request.ts ----"
rg -n "function handleMaybeHandshakeStatus|return handleMaybeHandshakeStatus|handleMaybeHandshakeStatus\(" packages/backend/src/tokens/request.ts

Repository: clerk/javascript

Length of output: 4982


Move the machine-JWT check before the cookie handshake branches. A machine JWT in __session without __client_uat hits handleMaybeHandshakeStatus first, so it never returns the intended TokenTypeMismatch signed-out state. Add a regression case for the no-__client_uat path.

🤖 Prompt for 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.
In `@packages/backend/src/tokens/request.ts` around lines 634 - 643, Move the
isMachineJwt check in the cookie authentication flow before the
handleMaybeHandshakeStatus branches, so machine JWTs in __session return the
TokenTypeMismatch signedOut result even when __client_uat is absent. Add a
regression case covering a machine JWT without __client_uat and verify the
expected signed-out response.

@github-actions

Copy link
Copy Markdown
Contributor

API Changes Report

Generated by Break Check on 2026-07-15T12:33:17.387Z

Summary

MetricCount
Packages analyzed19
Packages with changes0
🔴 Breaking changes0
🟡 Non-breaking changes0
🟢 Additions0

No API Changes Detected

All packages have stable APIs with no detected changes.


Report generated by Break Check

Last ran on b66f063.

@wobsorianowobsoriano left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

ty Dom!

@dominic-clerk
dominic-clerk merged commit e657d99 into mainJul 16, 2026
55 checks passed
@dominic-clerk
dominic-clerk deleted the dc-machine-jwt branch July 16, 2026 06:00
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@dominic-clerk@wobsoriano