Skip to content

feat(nextjs) Environment Drift Telemetry Event for Keyless Applications #6522

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

heatlikeheatwave
Copy link
Contributor

@heatlikeheatwave heatlikeheatwave commented Aug 11, 2025

Description

  • Add new telemetry event KEYLESS_ENV_DRIFT_DETECTED to detect drift between publishable and secret keys in keyless apps and values in the .env file.
    Example event payload:
{
    "envPublishableKey":"pk_test_[redacted]",
    "envVarsMissing":false,
    "keylessFileHasKeys":true,
    "keylessPublishableKey":"pk_test_[redacted]",
    "publicKeyMatch":false,
    "secretKeyMatch":false
}
  • This event only fires once (controlled with the .clerk/.tmp/keyless-telemetry-fired.json file) to prevent noise

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

  • New Features

    • Added server-side detection and telemetry for configuration drift in keyless setups, helping surface mismatches between environment variables and app configuration.
    • Emits a one-time event per app instance to aid diagnostics without affecting runtime behavior.
  • Chores

    • Introduced supporting telemetry module to improve observability around keyless configuration states, with graceful failure handling and no changes to public APIs.

Copy link

changeset-bot bot commented Aug 11, 2025

⚠️ No Changeset found

Latest commit: d31a57b

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

This PR includes no changesets

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

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

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

Copy link

vercel bot commented Aug 11, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

1 Skipped Deployment
Project Deployment Preview Comments Updated (UTC)
clerk-js-sandbox ⬜️ Skipped Aug 11, 2025 11:15pm

@heatlikeheatwave heatlikeheatwave changed the title Heat/grow 664 investigate 10 drop between application created and dev feat(nextjs) Environment Drift Telemetry Event for Keyless Applications Aug 11, 2025
Copy link

pkg-pr-new bot commented Aug 11, 2025

Open in StackBlitz

@clerk/agent-toolkit

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

@clerk/astro

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

@clerk/backend

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

@clerk/chrome-extension

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

@clerk/clerk-js

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

@clerk/dev-cli

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

@clerk/elements

npm i https://pkg.pr.new/@clerk/elements@6522

@clerk/clerk-expo

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

@clerk/expo-passkeys

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

@clerk/express

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

@clerk/fastify

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

@clerk/localizations

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

@clerk/nextjs

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

@clerk/nuxt

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

@clerk/clerk-react

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

@clerk/react-router

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

@clerk/remix

npm i https://pkg.pr.new/@clerk/remix@6522

@clerk/shared

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

@clerk/tanstack-react-start

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

@clerk/testing

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

@clerk/themes

npm i https://pkg.pr.new/@clerk/themes@6522

@clerk/types

npm i https://pkg.pr.new/@clerk/types@6522

@clerk/upgrade

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

@clerk/vue

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

commit: d31a57b

Copy link
Contributor

coderabbitai bot commented Aug 11, 2025

📝 Walkthrough

Walkthrough

Introduces a new server-only module packages/nextjs/src/server/keyless-telemetry.ts that exports detectKeylessEnvDrift(): Promise. It reads keyless configuration and environment variables, compares values to detect drift, and, if detected, emits a single Clerk telemetry event per app instance using a file-based guard. The function uses dynamic imports, filesystem checks/writes, and wraps operations in error handling. Additionally, packages/nextjs/src/app-router/keyless-actions.ts now invokes detectKeylessEnvDrift() on the non-matching-key path within createOrReadKeylessAction, without changing exported signatures.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes


🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🧹 Nitpick comments (5)
packages/nextjs/src/server/keyless-telemetry.ts (5)

62-70: JSDoc mentions a NOT_DETECTED event that isn’t implemented

Either remove the NOT_DETECTED reference or implement the complementary event to match docs.

Suggested change:

- * If there's a mismatch, it fires a `KEYLESS_ENV_DRIFT_DETECTED` event.
- * For local testing purposes, it also fires a `KEYLESS_ENV_DRIFT_NOT_DETECTED` event when
- * keys exist and match the environment variables.
+ * If there's a mismatch, it fires a `KEYLESS_ENV_DRIFT_DETECTED` event.

2-3: Use node: scheme for built-ins (optional)

Prefer node:fs and node:path for clarity and tree-shaking friendliness in modern Node/TS setups.

Apply:

-import { promises as fs } from 'fs';
-import { join } from 'path';
+import { promises as fs } from 'node:fs';
+import { join } from 'node:path';

7-9: Narrow constant types

Mark event name and sampling rate as const to keep literal types where helpful.

Apply:

-const EVENT_KEYLESS_ENV_DRIFT_DETECTED = 'KEYLESS_ENV_DRIFT_DETECTED';
-const EVENT_SAMPLING_RATE = 1; // 100% sampling rate
+const EVENT_KEYLESS_ENV_DRIFT_DETECTED = 'KEYLESS_ENV_DRIFT_DETECTED' as const;
+const EVENT_SAMPLING_RATE = 1 as const; // 100% sampling rate

145-148: Use the package’s logger (if available) for consistent diagnostics

Console.warn works, but prefer the internal logger to integrate with existing log levels and silencing rules.


1-150: Tests missing for the new telemetry behavior

Please add tests covering:

  • drift detected -> emits event once (atomic flag ensures single emission),
  • no drift -> no event,
  • env missing + keyless file has keys -> early return,
  • missing keys in keyless file -> safe no-op,
  • masking behavior on keys.

Do you want me to scaffold unit tests (with fs and keyless-node mocks) for this module?

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 29a25f3 and d31a57b.

📒 Files selected for processing (2)
  • packages/nextjs/src/app-router/keyless-actions.ts (2 hunks)
  • packages/nextjs/src/server/keyless-telemetry.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (7)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit Inference Engine (.cursor/rules/development.mdc)

**/*.{js,jsx,ts,tsx}: All code must pass ESLint checks with the project's configuration
Follow established naming conventions (PascalCase for components, camelCase for variables)
Maintain comprehensive JSDoc comments for public APIs
Use dynamic imports for optional features
All public APIs must be documented with JSDoc
Provide meaningful error messages to developers
Include error recovery suggestions where applicable
Log errors appropriately for debugging
Lazy load components and features when possible
Implement proper caching strategies
Use efficient data structures and algorithms
Profile and optimize critical paths
Validate all inputs and sanitize outputs
Implement proper logging with different levels

Files:

  • packages/nextjs/src/app-router/keyless-actions.ts
  • packages/nextjs/src/server/keyless-telemetry.ts
**/*.{js,jsx,ts,tsx,json,css,scss,md,yaml,yml}

📄 CodeRabbit Inference Engine (.cursor/rules/development.mdc)

Use Prettier for consistent code formatting

Files:

  • packages/nextjs/src/app-router/keyless-actions.ts
  • packages/nextjs/src/server/keyless-telemetry.ts
packages/**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (.cursor/rules/development.mdc)

TypeScript is required for all packages

Files:

  • packages/nextjs/src/app-router/keyless-actions.ts
  • packages/nextjs/src/server/keyless-telemetry.ts
packages/**/*.{ts,tsx,d.ts}

📄 CodeRabbit Inference Engine (.cursor/rules/development.mdc)

Packages should export TypeScript types alongside runtime code

Files:

  • packages/nextjs/src/app-router/keyless-actions.ts
  • packages/nextjs/src/server/keyless-telemetry.ts
**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (.cursor/rules/development.mdc)

Use proper TypeScript error types

**/*.{ts,tsx}: Always define explicit return types for functions, especially public APIs
Use proper type annotations for variables and parameters where inference isn't clear
Avoid any type - prefer unknown when type is uncertain, then narrow with type guards
Use interface for object shapes that might be extended
Use type for unions, primitives, and computed types
Prefer readonly properties for immutable data structures
Use private for internal implementation details
Use protected for inheritance hierarchies
Use public explicitly for clarity in public APIs
Prefer readonly for properties that shouldn't change after construction
Prefer composition and interfaces over deep inheritance chains
Use mixins for shared behavior across unrelated classes
Implement dependency injection for loose coupling
Let TypeScript infer when types are obvious
Use const assertions for literal types: as const
Use satisfies operator for type checking without widening
Use mapped types for transforming object types
Use conditional types for type-level logic
Leverage template literal types for string manipulation
Use ES6 imports/exports consistently
Use default exports sparingly, prefer named exports
Use type-only imports: import type { ... } from ...
No any types without justification
Proper error handling with typed errors
Consistent use of readonly for immutable data
Proper generic constraints
No unused type parameters
Proper use of utility types instead of manual type construction
Type-only imports where possible
Proper tree-shaking friendly exports
No circular dependencies
Efficient type computations (avoid deep recursion)

Files:

  • packages/nextjs/src/app-router/keyless-actions.ts
  • packages/nextjs/src/server/keyless-telemetry.ts
**/*.{js,ts,tsx,jsx}

📄 CodeRabbit Inference Engine (.cursor/rules/monorepo.mdc)

Support multiple Clerk environment variables (CLERK_, NEXT_PUBLIC_CLERK_, etc.) for configuration.

Files:

  • packages/nextjs/src/app-router/keyless-actions.ts
  • packages/nextjs/src/server/keyless-telemetry.ts
**/*

⚙️ CodeRabbit Configuration File

If there are no tests added or modified as part of the PR, please suggest that tests be added to cover the changes.

Files:

  • packages/nextjs/src/app-router/keyless-actions.ts
  • packages/nextjs/src/server/keyless-telemetry.ts
🧬 Code Graph Analysis (2)
packages/nextjs/src/app-router/keyless-actions.ts (1)
packages/nextjs/src/server/keyless-telemetry.ts (1)
  • detectKeylessEnvDrift (73-149)
packages/nextjs/src/server/keyless-telemetry.ts (3)
packages/nextjs/src/server/keyless-node.ts (1)
  • safeParseClerkFile (52-66)
packages/nextjs/src/server/createClerkClient.ts (1)
  • createClerkClientWithOptions (34-35)
packages/types/src/telemetry.ts (1)
  • TelemetryEventRaw (40-44)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Formatting | Dedupe | Changeset
  • GitHub Check: semgrep/ci
  • GitHub Check: semgrep-cloud-platform/scan
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (2)
packages/nextjs/src/app-router/keyless-actions.ts (1)

65-67: Mismatch with summary: this runs unconditionally after result exists, not only on a “non-matching key” path

The AI summary states the call happens only on the “non-matching-key path,” but here it executes for every successful create/read. Confirm this is intended.

packages/nextjs/src/server/keyless-telemetry.ts (1)

1-150: No action required: Event name and env-var usage are compatible

  • TelemetryEvent.event is defined as a string, so KEYLESS_ENV_DRIFT_DETECTED is accepted by the telemetry system.
  • Across Next.js packages, environment variables are consistently read via process.env.* (e.g. in mergeNextClerkPropsWithEnv.ts and server/constants.ts); there is no shared utility to replace.

All checks pass—no changes needed.

Comment on lines +30 to +37
async function hasTelemetryEventBeenFired(): Promise<boolean> {
try {
await fs.access(getTelemetryFlagFilePath());
return true;
} catch {
return false;
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

⚠️ Potential issue

Race condition: two-phase “check then mark” can emit duplicate events under concurrency

Between hasTelemetryEventBeenFired and markTelemetryEventAsFired, concurrent calls can both emit before the flag is written. Use an atomic “create-if-not-exists” guard and emit only if you successfully create the flag.

Minimal restructure:

  • Replace the two helpers with a single atomic one that attempts to create the flag file using { flag: 'wx' }.
  • In detectKeylessEnvDrift, after hasDrift is computed, attempt to acquire the flag atomically; only the winner emits.

Apply:

-async function hasTelemetryEventBeenFired(): Promise<boolean> {
-  try {
-    await fs.access(getTelemetryFlagFilePath());
-    return true;
-  } catch {
-    return false;
-  }
-}
+async function tryMarkTelemetryEventAsFired(): Promise<boolean> {
+  try {
+    const dir = join(process.cwd(), '.clerk/.tmp');
+    await fs.mkdir(dir, { recursive: true });
+    const flagData = {
+      firedAt: new Date().toISOString(),
+      event: EVENT_KEYLESS_ENV_DRIFT_DETECTED,
+    };
+    await fs.writeFile(getTelemetryFlagFilePath(), JSON.stringify(flagData, null, 2), { flag: 'wx' });
+    return true; // we created the flag; we are the single emitter
+  } catch (error: unknown) {
+    // EEXIST -> someone else won the race; silently ignore
+    return false;
+  }
+}

And:

-    // Check if telemetry event has already been fired
-    if (await hasTelemetryEventBeenFired()) {
-      return;
-    }
+    // No-op here; atomic flag is taken right before emitting to avoid races

And:

-    if (hasDrift) {
-      // Fire drift detected event
+    if (hasDrift) {
+      // Acquire single-emission lock (atomic file create). Only the winner should emit.
+      const shouldEmit = await tryMarkTelemetryEventAsFired();
+      if (!shouldEmit) {
+        return;
+      }
+      // Fire drift detected event
       const driftDetectedEvent: TelemetryEventRaw<EventKeylessEnvDriftPayload> = {
         event: EVENT_KEYLESS_ENV_DRIFT_DETECTED,
         eventSamplingRate: EVENT_SAMPLING_RATE,
         payload,
       };
-
-      clerkClient.telemetry?.record(driftDetectedEvent);
-
-      // Mark the telemetry event as fired to prevent future executions
-      await markTelemetryEventAsFired();
+      clerkClient.telemetry?.record(driftDetectedEvent);
     }

Also applies to: 42-59, 132-145

🤖 Prompt for AI Agents
In packages/nextjs/src/server/keyless-telemetry.ts around lines 30-37 (and also
apply same pattern to 42-59 and 132-145), replace the current two-phase
hasTelemetryEventBeenFired + markTelemetryEventAsFired pattern with a single
atomic helper that tries to create the telemetry flag file using
fs.writeFile(getTelemetryFlagFilePath(), '', { flag: 'wx' }) (or the equivalent
atomic create API); return true only if the write succeeded (meaning this
process won the race) and false if it failed due to EEXIST; then update
detectKeylessEnvDrift to compute hasDrift and immediately call the new atomic
helper and emit the telemetry event only when the helper indicates it
successfully created the flag (winner), ensuring no duplicate emits under
concurrency.

Comment on lines +96 to +103
const envPublishableKey = process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY;
const envSecretKey = process.env.CLERK_SECRET_KEY;

// Check if environment variables are missing, and keys exist in keyless file
const envVarsMissing = !envPublishableKey && !envSecretKey;
const keylessFileHasKeys = Boolean(keylessFile?.publishableKey && keylessFile?.secretKey);

if (envVarsMissing && keylessFileHasKeys) {
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

❓ Verification inconclusive

Support all recognized Clerk env var names for publishable key

Per guidelines, support multiple env var names. At minimum, also check CLERK_PUBLISHABLE_KEY.

Apply:

-const envPublishableKey = process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY;
+const envPublishableKey =
+  process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY ?? process.env.CLERK_PUBLISHABLE_KEY;

Optionally, centralize via an existing env util if the repo has one.


Add support for CLERK_PUBLISHABLE_KEY alongside NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY

To ensure we recognize both official env var names for the publishable key, update the assignment in packages/nextjs/src/server/keyless-telemetry.ts (lines 96–103):

- const envPublishableKey = process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY;
+ const envPublishableKey =
+   process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY ??
+   process.env.CLERK_PUBLISHABLE_KEY;

• File: packages/nextjs/src/server/keyless-telemetry.ts
• Lines: 96–103

There isn’t an existing environment‐variable helper in this package, so inlining the fallback here is fine. If you’d prefer to centralize, you could introduce an env utility (e.g. packages/nextjs/src/server/env.ts) to handle all Clerk-related vars.

📝 Committable suggestion

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

Suggested change
const envPublishableKey = process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY;
const envSecretKey = process.env.CLERK_SECRET_KEY;
// Check if environment variables are missing, and keys exist in keyless file
const envVarsMissing = !envPublishableKey && !envSecretKey;
const keylessFileHasKeys = Boolean(keylessFile?.publishableKey && keylessFile?.secretKey);
if (envVarsMissing && keylessFileHasKeys) {
const envPublishableKey =
process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY ??
process.env.CLERK_PUBLISHABLE_KEY;
const envSecretKey = process.env.CLERK_SECRET_KEY;
// Check if environment variables are missing, and keys exist in keyless file
const envVarsMissing = !envPublishableKey && !envSecretKey;
const keylessFileHasKeys = Boolean(keylessFile?.publishableKey && keylessFile?.secretKey);
if (envVarsMissing && keylessFileHasKeys) {
🤖 Prompt for AI Agents
In packages/nextjs/src/server/keyless-telemetry.ts around lines 96–103, the code
only reads NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY for the publishable key; change the
assignment to accept either NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY or
CLERK_PUBLISHABLE_KEY (i.e. use the latter as a fallback), keeping the existing
secret key lookup and subsequent envVarsMissing/keylessFileHasKeys logic
unchanged; inlining this fallback is fine since there is no shared env helper in
this package.

Comment on lines +117 to +124
const payload: EventKeylessEnvDriftPayload = {
publicKeyMatch,
secretKeyMatch,
envVarsMissing,
keylessFileHasKeys,
keylessPublishableKey: keylessFile.publishableKey,
envPublishableKey: envPublishableKey as string,
};
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Avoid casting undefined to string; consider masking publishable key before sending

envPublishableKey can be undefined. Casting hides a real mismatch and violates the declared payload type. Also, avoid sending full keys in telemetry; include a masked version.

Apply:

-type EventKeylessEnvDriftPayload = {
+type EventKeylessEnvDriftPayload = {
   publicKeyMatch: boolean;
   secretKeyMatch: boolean;
   envVarsMissing: boolean;
   keylessFileHasKeys: boolean;
-  keylessPublishableKey: string;
-  envPublishableKey: string;
+  keylessPublishableKeyMasked?: string;
+  envPublishableKeyMasked?: string;
 };

And:

-const payload: EventKeylessEnvDriftPayload = {
+const payload: EventKeylessEnvDriftPayload = {
   publicKeyMatch,
   secretKeyMatch,
   envVarsMissing,
   keylessFileHasKeys,
-  keylessPublishableKey: keylessFile.publishableKey,
-  envPublishableKey: envPublishableKey as string,
+  keylessPublishableKeyMasked: maskKeyForTelemetry(keylessFile.publishableKey),
+  envPublishableKeyMasked: maskKeyForTelemetry(envPublishableKey),
 };

Add helper (outside the ranges):

function maskKeyForTelemetry(key?: string): string | undefined {
  if (!key) return undefined;
  // Keep a small prefix/suffix for correlation; adjust length as needed
  const head = key.slice(0, 8);
  const tail = key.slice(-4);
  return `${head}${tail}`;
}

🏁 Script executed:

#!/bin/bash
set -e

# Locate the type definition for EventKeylessEnvDriftPayload
rg -C3 "type EventKeylessEnvDriftPayload" -n packages/nextjs/src/server/keyless-telemetry.ts

# Show the payload instantiation to verify casting
rg -C3 "const payload: EventKeylessEnvDriftPayload" -n packages/nextjs/src/server/keyless-telemetry.ts

# Check if a masking helper already exists
rg "maskKeyForTelemetry" -n packages/nextjs/src/server/keyless-telemetry.ts || echo "maskKeyForTelemetry helper not found"

Length of output: 910


Mask publishable keys and handle undefined in Telemetry payload

Casting envPublishableKey to string can hide real mismatches and violates the declared payload type. We also shouldn’t send full publishable keys in telemetry. Please:

• Update the EventKeylessEnvDriftPayload type in packages/nextjs/src/server/keyless-telemetry.ts
– Replace
ts keylessPublishableKey: string; envPublishableKey: string;
with
ts keylessPublishableKeyMasked?: string; envPublishableKeyMasked?: string;

• Change the payload instantiation (around line 117) to use a masking helper:

const payload: EventKeylessEnvDriftPayload = {
  publicKeyMatch,
  secretKeyMatch,
  envVarsMissing,
  keylessFileHasKeys,
- keylessPublishableKey: keylessFile.publishableKey,
- envPublishableKey: envPublishableKey as string,
+ keylessPublishableKeyMasked: maskKeyForTelemetry(keylessFile.publishableKey),
+ envPublishableKeyMasked: maskKeyForTelemetry(envPublishableKey),
};

• Add a helper function (e.g. at the bottom of the file) to mask keys:

function maskKeyForTelemetry(key?: string): string | undefined {
  if (!key) return undefined;
  const head = key.slice(0, 8);
  const tail = key.slice(-4);
  return `${head}${tail}`;
}

This ensures we don’t send raw keys and correctly represent missing values.

🤖 Prompt for AI Agents
In packages/nextjs/src/server/keyless-telemetry.ts around lines 117 to 124, the
telemetry payload currently casts envPublishableKey to string and sends raw
publishable keys; update the EventKeylessEnvDriftPayload type to replace
keylessPublishableKey and envPublishableKey string fields with optional
keylessPublishableKeyMasked?: string and envPublishableKeyMasked?: string,
change the payload instantiation to stop casting and instead assign masked
values using a masking helper (e.g., keylessPublishableKeyMasked:
maskKeyForTelemetry(keylessFile.publishableKey), envPublishableKeyMasked:
maskKeyForTelemetry(envPublishableKey)), and add the helper function at the
bottom of the file that returns undefined for falsy input and otherwise returns
the first 8 chars + ellipsis + last 4 chars; ensure no raw keys are logged or
sent and types match the updated payload.

Comment on lines +127 to +131
const clerkClient = createClerkClientWithOptions({
publishableKey: keylessFile.publishableKey,
secretKey: keylessFile.secretKey,
});

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Guard against missing keys in keyless file before creating the client

safeParseClerkFile can parse objects without both keys. Avoid passing undefined to the client.

Apply:

-// Create a clerk client to access telemetry
-const clerkClient = createClerkClientWithOptions({
-  publishableKey: keylessFile.publishableKey,
-  secretKey: keylessFile.secretKey,
-});
+if (!keylessFile.publishableKey || !keylessFile.secretKey) {
+  return;
+}
+// Create a clerk client to access telemetry
+const clerkClient = createClerkClientWithOptions({
+  publishableKey: keylessFile.publishableKey,
+  secretKey: keylessFile.secretKey,
+});
📝 Committable suggestion

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

Suggested change
const clerkClient = createClerkClientWithOptions({
publishableKey: keylessFile.publishableKey,
secretKey: keylessFile.secretKey,
});
if (!keylessFile.publishableKey || !keylessFile.secretKey) {
return;
}
// Create a clerk client to access telemetry
const clerkClient = createClerkClientWithOptions({
publishableKey: keylessFile.publishableKey,
secretKey: keylessFile.secretKey,
});

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.

2 participants