Skip to content

feat(js): attach an optional server-configured session token to sign-in - #9299

Open
zourzouvillys wants to merge 4 commits into
mainfrom
theo/js-session-token-clean
Open

feat(js): attach an optional server-configured session token to sign-in#9299
zourzouvillys wants to merge 4 commits into
mainfrom
theo/js-session-token-clean

Conversation

@zourzouvillys

@zourzouvillys zourzouvillys commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Description

Adds an optional, server-configured session token to @clerk/clerk-js, attached to sign-in and sign-up requests.

It is inert unless an instance's loader configuration opts into it. An instance not using it keeps today's behaviour exactly, stores nothing in the browser, and sends no additional parameters.

  • Placeholder interpolation in loader config. A closed set — {cid}, {pid}, {rid}, {instance_id}, {sdkver} — substituted into a loader's attribute values and textContent before the element is appended. An unrecognised {…} is left verbatim, so an older SDK loading the same configuration stays compatible.
  • An opaque, random correlation id, minted with crypto.getRandomValues as lowercase unpadded RFC 4648 base32. Nothing is derived from the device — no fingerprinting input, no clock, no user data.
  • One acquisition per browser session, shared across tabs. Runs under the existing SafeLock primitive (core/auth/safeLock.ts) that SessionCookiePoller already uses, with a re-check inside the lock so concurrent tabs do not each acquire in turn. A wedged leader delays nobody, and a leader tab closing mid-run releases its lock automatically.
  • Prefetched at Clerk.load() rather than at sign-in, so acquisition stays off the latency-critical path. It is bounded by a deadline and can never block or fail a sign-in: when no token is obtained, a status travels in its place and the request proceeds unchanged.
  • Parameters ride in the form-encoded body of sign-in and sign-up POSTs. Not the query string, since a signed credential should not land in access logs along the path; and not a header, since a custom header reintroduces the CORS preflight that breaks cookie dropping in Safari — the same constraint that already forces _method into the query. No request header is added anywhere. The merge is an explicit step in fapiClient.request on a path allowlist, because the onBeforeRequest callbacks fire after the body is stringified.

ProtectLoader gains two optional fields, tokenUrl and tokenTimeoutMs, both inert unless set.

rollout moved out of applyLoader into a module-level helper so load() can decide participation before it starts applying loaders. Same semantics, evaluated once.

Bundle thresholds were bumped on the affected bundles to cover the addition.

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 — the persistent pseudonymous identifier needs a mention in customer-facing privacy docs; tracked separately.

Type of change

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

Mints an opaque, random correlation id and acquires a signed Protect
session token once per browser session, shared across tabs under a lock.
The token, the correlation id and an acquisition status travel in the
form-encoded body of sign-in and sign-up POSTs.

Acquisition never blocks a sign-in: on timeout, script-load failure or a
non-2xx response a structured status travels in the token's place and the
request proceeds. Instances whose loader config does not reference the new
placeholders keep today's behaviour and store nothing in the browser.
@changeset-bot

changeset-bot Bot commented Jul 31, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: ed13b06

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

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

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

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

@vercel

vercel Bot commented Jul 31, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
clerk-js-sandbox Ready Ready Preview Aug 3, 2026 7:06pm
swingset Ready Ready Preview Aug 3, 2026 7:06pm

Request Review

@pkg-pr-new

pkg-pr-new Bot commented Jul 31, 2026

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

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

@clerk/backend

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

@clerk/chrome-extension

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

@clerk/clerk-js

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

@clerk/electron

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

@clerk/electron-passkeys

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

@clerk/eslint-plugin

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

@clerk/expo

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

@clerk/expo-google-signin

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

@clerk/expo-passkeys

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

@clerk/express

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

@clerk/fastify

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

@clerk/hono

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

@clerk/localizations

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

@clerk/nextjs

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

@clerk/nuxt

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

@clerk/react

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

@clerk/react-router

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

@clerk/shared

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

@clerk/tanstack-react-start

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

@clerk/testing

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

@clerk/ui

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

@clerk/upgrade

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

@clerk/vue

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

commit: ed13b06

Follows the EDR-0020 amendment (clerk/protect#408): the session token now
ships inline on the loader's own global rather than behind a second request,
so the gate costs no extra round trip on the auth critical path. `tokenUrl`
becomes an explicit opt-in for the upgrade mint, and is the only path that
can report `fetch_error` or an HTTP status.

That removes the derive-the-endpoint-from-an-attribute fallback, which
resolved against `document.baseURI` (pointing polling at the app's own
origin) and stripped a trailing `{cid}` via relative resolution.

The session now owns the token loader: it injects it under the acquisition
lock and reads the token when the element fires `load`. Every other loader
is applied on every page load, rather than all loaders being suppressed
whenever a token happened to be cached.

Also fixed:

- A malformed loader entry no longer escapes `Protect.load()` and fails
  `Clerk.load()` for every visitor.
- `readStored` falls back to the in-memory store, so a token written there
  under an exhausted quota is no longer read back as absent.
- `getRequestParams` is bounded by the acquisition deadline, and a
  server-supplied `tokenTimeoutMs` is capped, so a sign-in cannot be stalled
  before dispatch. It can no longer reject into the request either.
- A settled, tokenless acquisition re-arms after a cooldown instead of being
  replayed for the life of the tab.
- The token store and lock are namespaced per instance, so two instances on
  one origin no longer share a token.
- A stored token is validated against a maximum lifetime and length, so a
  planted entry cannot suppress acquisition indefinitely.
- The upgrade mint retries a transient 408/429/5xx within its deadline.
- `{instance_id}` alone no longer mints and persists a client id.
- `textContent` placeholders are detected and substituted.
- `isMergeableBody` admits only plain objects, so a Blob or array body is no
  longer spread away.
Aligns with clerk/protect: the loader global carries `ready`, a promise
resolving to `{token, exp}` or `{status: "no_token"}`, rather than the
token directly. It is the loader's completion signal and the seam where
page-side probes and the EDR-0020 proof-of-possession challenge will
live, so the token is awaited (raced against the acquisition deadline)
instead of read synchronously.

`{sdkver}` joins the placeholder set, substituted with the build version.
Its presence is what tells the server this build interpolates at all, and
so can be served the current shape; `/v1/environment` is cached and
cannot vary by SDK version, so the negotiation has to happen per request.
A build that leaves it verbatim is served the base `{v, id}` shape.

`no_token` joins the status set, matching Warden's allowlist. It is what
the base shape, a load with no correlation id, and a failed mint all
report. Reporting `timeout` for those would mark every load between this
shipping and the server half deploying as a failure, making a normal
rollout look like an outage.

A `ready` that rejects is treated as nothing served rather than
propagated; the contract says it never rejects, but an unhandled
rejection on every page load is not worth taking on trust.
@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

API Changes Report

Generated by Break Check on 2026-08-03T19:07:16.885Z

Summary

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

@clerk/shared

Current version: 4.25.10
Recommended bump: MINOR → 4.26.0

Subpath ./types

🟢 Additions (2)

Added: ProtectLoader.tokenTimeoutMs
+ tokenTimeoutMs?: number;

Added property ProtectLoader.tokenTimeoutMs

Added: ProtectLoader.tokenUrl
+ tokenUrl?: string;

Added property ProtectLoader.tokenUrl


Report generated by Break Check

Last ran on ed13b06.

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Added ProtectSession for correlation IDs, token-loader and upgrade-endpoint acquisition, shared storage, locking, retries, deadlines, cooldowns, and status reporting. Integrated session placeholders into Protect loaders and exposed request parameters. Added FAPI injection for eligible sign-in and sign-up requests. Added ProtectLoader token settings, tests, a changeset, and updated bundle limits.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

  • clerk/javascript#9256: Both changes increase Bundlewatch limits in packages/clerk-js/bundlewatch.config.json.
  • clerk/javascript#9313: Both changes add Protect parameter resolution and injection for eligible sign-in and sign-up requests.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 54.55% 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 name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: attaching an optional server-configured session token to sign-in requests.
Description check ✅ Passed The description directly explains the optional session-token feature, its configuration, request integration, and fallback behavior.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (11)
packages/clerk-js/bundlewatch.config.json (1)

3-7: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Reduce the Bundlewatch ceilings. Set them to 550KB, 77KB, 119KB, 317KB, and 77KB for the five bundles. The current values add unnecessary headroom.

🤖 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/clerk-js/bundlewatch.config.json` around lines 3 - 7, Update the
Bundlewatch maxSize values for the five bundle entries in order: clerk.js to
550KB, clerk.browser.js to 77KB, clerk.legacy.browser.js to 119KB,
clerk.no-rhc.js to 317KB, and clerk.native.js to 77KB.
packages/shared/src/types/protectConfig.ts (1)

23-27: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Document the upper bound for tokenTimeoutMs.

clampTimeout in packages/clerk-js/src/core/protectSession.ts limits this value to 10000 ms and falls back to 5000 for non-positive or non-finite values. The doc comment states only the default. State the ceiling so instance operators know that a larger configured value has no effect.

📝 Proposed doc update
   /**
    * How long to wait for the token before giving up and reporting a status instead. Defaults to
-   * 5000.
+   * 5000, and is capped at 10000 by the SDK.
    */
   tokenTimeoutMs?: number;
🤖 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/shared/src/types/protectConfig.ts` around lines 23 - 27, Update the
documentation for tokenTimeoutMs in the protect configuration type to state that
values are capped at 10000 ms, while retaining the existing 5000 ms default
description.
packages/clerk-js/src/core/protectSession.ts (2)

419-425: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add an explicit return type to ProtectSession.create.

create is a public static factory on an exported class. Its return type is inferred as ProtectSession | undefined. Declare it so the public surface stays stable if the body changes.

🔧 Proposed fix
-  static create(loaders: ProtectLoader[], instanceId: string | undefined, applyLoader: ApplyLoader) {
+  static create(
+    loaders: ProtectLoader[],
+    instanceId: string | undefined,
+    applyLoader: ApplyLoader,
+  ): ProtectSession | undefined {

As per coding guidelines: "Always define explicit return types for functions, especially public APIs". Based on learnings: enforce explicit return type annotations for exported functions and public APIs.

🤖 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/clerk-js/src/core/protectSession.ts` around lines 419 - 425, Update
the public static factory method ProtectSession.create with an explicit return
type annotation of ProtectSession | undefined, preserving its existing undefined
result for empty templated loaders and ProtectSession result otherwise.

Sources: Coding guidelines, Learnings


5-20: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Reduce the volume of narrative comments.

The file carries many multi-line explanatory blocks. Several restate what the code already shows, for example lines 214-218 and 601-604. Keep the design rationale that is not obvious from the code, such as why no_token is distinct from timeout, and shorten the rest to one line each. Consider moving the long module-level narrative to a design document.

As per coding guidelines: "Keep code comments minimal. Add comments only when critical to explain why a non-obvious change was made; never restate code behavior, and keep warranted comments to one terse line rather than a verbose multi-line block."

Also applies to: 103-108, 153-156, 214-218, 245-248, 290-299, 506-509, 518-521, 601-604, 687-691

🤖 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/clerk-js/src/core/protectSession.ts` around lines 5 - 20, Reduce
comments throughout protectSession.ts, especially the module-level block and the
listed ranges, to terse single-line comments or remove them when they merely
restate the surrounding code. Preserve only non-obvious design rationale,
including why no_token differs from timeout, and retain concise comments only
where they explain that rationale or another critical implementation decision.

Source: Coding guidelines

packages/clerk-js/src/core/fapiClient.ts (2)

236-244: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Bound the getProtectParams await inside fapiClient.

This await sits on the sign-in and sign-up critical path. The current producer, Protect.getRequestParams, bounds itself with an acquisition deadline of at most 10 s and never rejects, so the request is not blocked indefinitely today. getProtectParams is a public option on FapiClientOptions, so any other supplier can hang the request forever with no recovery.

Add a local deadline so fapiClient does not depend on the caller for that guarantee.

🛡️ Proposed defensive bound
+const PROTECT_PARAMS_TIMEOUT_MS = 10_000;
+
+function withTimeout<T>(promise: Promise<T>, ms: number): Promise<T | undefined> {
+  return new Promise(resolve => {
+    const timer = setTimeout(() => resolve(undefined), ms);
+    const settle = (value: T | undefined) => {
+      clearTimeout(timer);
+      resolve(value);
+    };
+    promise.then(settle, () => settle(undefined));
+  });
+}
-      const protectParams = await options.getProtectParams().catch(() => undefined);
+      const protectParams = await withTimeout(options.getProtectParams(), PROTECT_PARAMS_TIMEOUT_MS);
🤖 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/clerk-js/src/core/fapiClient.ts` around lines 236 - 244, Bound the
getProtectParams() await in the fapiClient request flow with a local timeout so
a hanging public option cannot block sign-in or sign-up indefinitely. Preserve
the existing fallback behavior by treating timeout or supplier rejection as
undefined and continuing without protect parameters.

240-243: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove the redundant assignment and cast.

isMergeableBody narrows body to Record<string, unknown> | undefined inside this block, so the as Record<string, unknown> cast adds nothing. Line 242 is also redundant, because lines 246-248 reassign requestInit.body for every plain-object body.

♻️ Proposed simplification
       const protectParams = await options.getProtectParams().catch(() => undefined);
       if (protectParams) {
-        body = { ...((body ?? {}) as Record<string, unknown>), ...protectParams } as unknown as BodyInit;
-        requestInit.body = body;
+        body = { ...(body ?? {}), ...protectParams } as unknown as BodyInit;
       }
🤖 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/clerk-js/src/core/fapiClient.ts` around lines 240 - 243, In the
protectParams handling near isMergeableBody, remove the redundant Record<string,
unknown> cast and the immediate requestInit.body assignment; retain only the
merged body assignment, since the later plain-object body flow already updates
requestInit.body.
packages/clerk-js/src/core/protect.ts (1)

38-41: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Type #apply input as unknown[].

config.loaders comes from the server response and is not validated before this call. The tests pass null, 'nope' and 42 in that array. The parameter type ProtectLoader[] states a guarantee that does not hold, and isLoader is the guard that establishes it. Declare the input as unknown[] so the narrowing performed by isLoader is visible in the types.

🔧 Proposed change
-  `#apply`(configured: ProtectLoader[], instanceId?: string): void {
+  `#apply`(configured: unknown[], instanceId?: string): void {
     // Rollout is decided before anything else, because the session is only meaningful for the
     // loaders we are actually going to apply.
     const loaders = configured.filter(loader => isLoader(loader) && inRollout(loader));

isLoader already returns loader is ProtectLoader, so loaders still narrows to ProtectLoader[].

🤖 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/clerk-js/src/core/protect.ts` around lines 38 - 41, Update the
`#apply` method parameter from ProtectLoader[] to unknown[] to reflect that
server-provided loader values are unvalidated. Preserve the existing
configured.filter call so isLoader narrows valid entries to ProtectLoader[]
before rollout processing.
packages/clerk-js/src/core/__tests__/protectSession.test.ts (3)

370-384: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add a case for a stored token that exceeds MAX_TOKEN_LENGTH.

validateToken rejects a token longer than 4096 characters. The suite covers the expiry bound and the lifetime bound, but not the length bound. Add a planted entry with an over-length token and assert that hasFreshToken() returns false.

As per coding guidelines: "Verify proper 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/clerk-js/src/core/__tests__/protectSession.test.ts` around lines 370
- 384, Add a test case alongside the existing planted-token coverage that stores
a token longer than MAX_TOKEN_LENGTH in localStorage, then initializes a session
and asserts created?.hasFreshToken() is false. Reuse the existing session setup
and token-storage key used in protectSession.test.ts, focusing only on the
over-length validation path.

Source: Coding guidelines


245-255: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Simplify the foreign correlation id.

'z'.repeat(26).replace(/z/g, 'a') produces 'a'.repeat(26). Use the direct form so the intent stays readable.

♻️ Proposed change
-    serveInline(await injected(), { cid: buildCid('z'.repeat(26).replace(/z/g, 'a'), 'b'.repeat(26)) });
+    serveInline(await injected(), { cid: buildCid('a'.repeat(26), 'b'.repeat(26)) });
🤖 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/clerk-js/src/core/__tests__/protectSession.test.ts` around lines 245
- 255, In the test case “ignores a token minted for someone else’s run,”
simplify the foreign correlation ID passed to buildCid by replacing the
redundant 'z'.repeat(26).replace(/z/g, 'a') expression with the direct
equivalent 'a'.repeat(26); leave the test behavior unchanged.

298-305: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

These deadline tests depend on real wall-clock time.

tokenTimeoutMs values of 40, 50 and 60 ms are shorter than a slow CI tick. A stalled event loop can let the loader path settle after the deadline, or let an unrelated status win. The assertion at line 537 also measures elapsed real time.

Raise the deadlines to a value that tolerates scheduler jitter, or drive them with vi.useFakeTimers(). The test at lines 540-556 already mocks Date.now, so fake timers fit the existing style.

Also applies to: 328-334, 531-538

🤖 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/clerk-js/src/core/__tests__/protectSession.test.ts` around lines 298
- 305, Stabilize the deadline tests in the protectSession suite by replacing the
40, 50, and 60 ms real-time tokenTimeoutMs values and elapsed-time assertion
with scheduler-independent timing. Prefer vi.useFakeTimers() while preserving
the existing mocked-Date.now style, and update the tests around the timeout
cases and the assertion near the elapsed-time check so deadlines are advanced
explicitly and timeout remains the winning status.
packages/clerk-js/src/core/__tests__/protect.test.ts (1)

172-179: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add coverage for a token loader whose target does not resolve.

applyLoader now returns undefined when the #id target element is missing, and ProtectSession.#runTokenLoader maps that to script_error. No test drives that branch. Add a case with target: '#missing' on the token loader and assert __clerk_protect_status: 'script_error'.

As per coding guidelines: "Verify proper 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/clerk-js/src/core/__tests__/protect.test.ts` around lines 172 - 179,
Add a test alongside the existing loader error case that configures the token
loader with target: '`#missing`', invokes the Protect request flow, and asserts it
resolves with __clerk_protect_status set to 'script_error'. Exercise the
missing-target path in ProtectSession.#runTokenLoader rather than dispatching a
script element error.

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 @.changeset/lucky-pandas-observe.md:
- Line 12: Update the release note describing ProtectLoader’s tokenUrl field so
it states that the loader token remains inline and tokenUrl is used only for
optional upgrade minting; retain tokenTimeoutMs and the existing optional-field
context.

---

Nitpick comments:
In `@packages/clerk-js/bundlewatch.config.json`:
- Around line 3-7: Update the Bundlewatch maxSize values for the five bundle
entries in order: clerk.js to 550KB, clerk.browser.js to 77KB,
clerk.legacy.browser.js to 119KB, clerk.no-rhc.js to 317KB, and clerk.native.js
to 77KB.

In `@packages/clerk-js/src/core/__tests__/protect.test.ts`:
- Around line 172-179: Add a test alongside the existing loader error case that
configures the token loader with target: '`#missing`', invokes the Protect request
flow, and asserts it resolves with __clerk_protect_status set to 'script_error'.
Exercise the missing-target path in ProtectSession.#runTokenLoader rather than
dispatching a script element error.

In `@packages/clerk-js/src/core/__tests__/protectSession.test.ts`:
- Around line 370-384: Add a test case alongside the existing planted-token
coverage that stores a token longer than MAX_TOKEN_LENGTH in localStorage, then
initializes a session and asserts created?.hasFreshToken() is false. Reuse the
existing session setup and token-storage key used in protectSession.test.ts,
focusing only on the over-length validation path.
- Around line 245-255: In the test case “ignores a token minted for someone
else’s run,” simplify the foreign correlation ID passed to buildCid by replacing
the redundant 'z'.repeat(26).replace(/z/g, 'a') expression with the direct
equivalent 'a'.repeat(26); leave the test behavior unchanged.
- Around line 298-305: Stabilize the deadline tests in the protectSession suite
by replacing the 40, 50, and 60 ms real-time tokenTimeoutMs values and
elapsed-time assertion with scheduler-independent timing. Prefer
vi.useFakeTimers() while preserving the existing mocked-Date.now style, and
update the tests around the timeout cases and the assertion near the
elapsed-time check so deadlines are advanced explicitly and timeout remains the
winning status.

In `@packages/clerk-js/src/core/fapiClient.ts`:
- Around line 236-244: Bound the getProtectParams() await in the fapiClient
request flow with a local timeout so a hanging public option cannot block
sign-in or sign-up indefinitely. Preserve the existing fallback behavior by
treating timeout or supplier rejection as undefined and continuing without
protect parameters.
- Around line 240-243: In the protectParams handling near isMergeableBody,
remove the redundant Record<string, unknown> cast and the immediate
requestInit.body assignment; retain only the merged body assignment, since the
later plain-object body flow already updates requestInit.body.

In `@packages/clerk-js/src/core/protect.ts`:
- Around line 38-41: Update the `#apply` method parameter from ProtectLoader[] to
unknown[] to reflect that server-provided loader values are unvalidated.
Preserve the existing configured.filter call so isLoader narrows valid entries
to ProtectLoader[] before rollout processing.

In `@packages/clerk-js/src/core/protectSession.ts`:
- Around line 419-425: Update the public static factory method
ProtectSession.create with an explicit return type annotation of ProtectSession
| undefined, preserving its existing undefined result for empty templated
loaders and ProtectSession result otherwise.
- Around line 5-20: Reduce comments throughout protectSession.ts, especially the
module-level block and the listed ranges, to terse single-line comments or
remove them when they merely restate the surrounding code. Preserve only
non-obvious design rationale, including why no_token differs from timeout, and
retain concise comments only where they explain that rationale or another
critical implementation decision.

In `@packages/shared/src/types/protectConfig.ts`:
- Around line 23-27: Update the documentation for tokenTimeoutMs in the protect
configuration type to state that values are capped at 10000 ms, while retaining
the existing 5000 ms default description.
🪄 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), Organization UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 5137fbb9-ce5a-4323-ab93-b45f8ad4be06

📥 Commits

Reviewing files that changed from the base of the PR and between 0ba9dbe and ed13b06.

📒 Files selected for processing (10)
  • .changeset/lucky-pandas-observe.md
  • packages/clerk-js/bundlewatch.config.json
  • packages/clerk-js/src/core/__tests__/fapiClient.test.ts
  • packages/clerk-js/src/core/__tests__/protect.test.ts
  • packages/clerk-js/src/core/__tests__/protectSession.test.ts
  • packages/clerk-js/src/core/clerk.ts
  • packages/clerk-js/src/core/fapiClient.ts
  • packages/clerk-js/src/core/protect.ts
  • packages/clerk-js/src/core/protectSession.ts
  • packages/shared/src/types/protectConfig.ts
🔗 Linked repositories identified

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

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

Comment thread .changeset/lucky-pandas-observe.md

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
packages/clerk-js/src/core/__tests__/protectSession.test.ts (1)

336-384: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Add regression coverage for malformed stored token entries. Test invalid JSON, missing or malformed rid, and non-string token; readStoredToken must reject each and start a fresh acquisition without constructing a malformed __clerk_protect_cid.

🤖 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/clerk-js/src/core/__tests__/protectSession.test.ts` around lines 336
- 384, Extend the protectSession tests around stored-token reuse to cover
invalid JSON, missing or malformed rid, and non-string token entries. Verify
readStoredToken rejects each case, hasFreshToken() is false, and the session
starts fresh acquisition through the loader without producing a malformed
__clerk_protect_cid; reuse the existing session, loader, and request-parameter
setup.

Source: Linters/SAST tools

🤖 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.

Nitpick comments:
In `@packages/clerk-js/src/core/__tests__/protectSession.test.ts`:
- Around line 336-384: Extend the protectSession tests around stored-token reuse
to cover invalid JSON, missing or malformed rid, and non-string token entries.
Verify readStoredToken rejects each case, hasFreshToken() is false, and the
session starts fresh acquisition through the loader without producing a
malformed __clerk_protect_cid; reuse the existing session, loader, and
request-parameter setup.

ℹ️ Review info
⚙️ Run configuration

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

Review profile: CHILL

Plan: Pro Plus

Run ID: 681154a7-1772-4ec0-9732-fd243673889d

📥 Commits

Reviewing files that changed from the base of the PR and between 0ba9dbe and ed13b06.

📒 Files selected for processing (10)
  • .changeset/lucky-pandas-observe.md
  • packages/clerk-js/bundlewatch.config.json
  • packages/clerk-js/src/core/__tests__/fapiClient.test.ts
  • packages/clerk-js/src/core/__tests__/protect.test.ts
  • packages/clerk-js/src/core/__tests__/protectSession.test.ts
  • packages/clerk-js/src/core/clerk.ts
  • packages/clerk-js/src/core/fapiClient.ts
  • packages/clerk-js/src/core/protect.ts
  • packages/clerk-js/src/core/protectSession.ts
  • packages/shared/src/types/protectConfig.ts
🔗 Linked repositories identified

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

  • clerk/clerk_go (manual)
  • clerk/dashboard (manual)
  • clerk/accounts (manual)
  • clerk/backoffice (manual)
  • clerk/clerk (manual)
  • clerk/clerk-docs (manual)
  • clerk/cloudflare-workers (manual)
  • clerk/clerk-ios (auto-detected)
  • clerk/clerk-android (auto-detected)
  • clerk/cli (auto-detected)
🚧 Files skipped from review as they are similar to previous changes (8)
  • packages/clerk-js/src/core/clerk.ts
  • packages/clerk-js/src/core/fapiClient.ts
  • packages/clerk-js/bundlewatch.config.json
  • .changeset/lucky-pandas-observe.md
  • packages/shared/src/types/protectConfig.ts
  • packages/clerk-js/src/core/tests/fapiClient.test.ts
  • packages/clerk-js/src/core/protect.ts
  • packages/clerk-js/src/core/protectSession.ts

Sign up for free to 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.

1 participant