Skip to content

fix(e2e): never carry production keys in staging mode without a staging key#8758

Open
jacekradko wants to merge 1 commit into
mainfrom
jacek/staging-e2e-prod-key-fallback
Open

fix(e2e): never carry production keys in staging mode without a staging key#8758
jacekradko wants to merge 1 commit into
mainfrom
jacek/staging-e2e-prod-key-fallback

Conversation

@jacekradko
Copy link
Copy Markdown
Member

@jacekradko jacekradko commented Jun 5, 2026

A latent safety bug surfaced during the staging-e2e investigation. withInstanceKeys sets the production PK/SK first, and in staging mode (E2E_STAGING=1) with no clerkstage- key it only deleted CLERK_API_URL and returned, leaving the production credentials live on the env. Long-running apps are filtered out by isStagingReady, but a test that bypasses that filter by building its own app via app.withEnv would silently drive a production instance from staging CI.

The fix pulls credential resolution into a small side-effect-free instanceKeys module and, in staging mode without a staging key, applies intentionally-invalid placeholder keys (never the production keys) and clears the API URL. The env stays not-staging-ready, so it is still filtered out of long-running apps, and any accidental direct use now fails fast against a non-existent instance instead of hitting production.

// before: staging mode, no staging key -> production keys stay live
env.privateVariables.delete('CLERK_API_URL');
return env; // still holds production PK/SK

This is behavior-preserving for the suite: every env used directly via withEnv either has a staging key or clones one that does (e.g. withDynamicKeys/withEmailCodesQuickstart derive from withEmailCodes), so the placeholder path is only reachable by envs that were already being filtered out. The separate module exists so the "never production in staging" invariant can be unit-tested without constructing every env in envs.ts.

Summary by CodeRabbit

  • New Features

    • Added support for staging environment configuration with automatic fallback handling for instance credentials.
  • Tests

    • Added comprehensive test coverage for instance configuration resolution in staging and production modes.

…ng key

withInstanceKeys set the production PK/SK first, then in staging mode with no
clerkstage- key it only deleted CLERK_API_URL and returned, leaving the
production credentials live. Long-running apps are filtered by isStagingReady,
but a test that bypasses that filter (e.g. one that builds its own app via
app.withEnv) would silently drive a production instance from staging CI.

Extract the credential resolution into a side-effect-free instanceKeys module
and, in staging mode without a staging key, apply intentionally-invalid
placeholder keys (never the production keys) and clear the API URL. The env
stays not-staging-ready (so it is still filtered out of long-running apps),
and any accidental use now fails fast against a non-existent instance instead
of hitting production. No behavioral change to existing staging tests: every
env used directly via withEnv has a staging key or clones one that does.
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Jun 5, 2026

🦋 Changeset detected

Latest commit: 55a0c20

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

This PR includes changesets to release 0 packages

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

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
Copy link
Copy Markdown

vercel Bot commented Jun 5, 2026

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

Project Deployment Actions Updated (UTC)
clerk-js-sandbox Ready Ready Preview, Comment Jun 5, 2026 3:26am

Request Review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 5, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

A new TypeScript module instanceKeys.ts centralizes staging-aware credential resolution for Clerk integration tests. The module exports staging constants, type contracts, and resolveInstanceConfig, which selects production credentials in non-staging mode, staging credentials plus the staging API URL when available, or placeholder keys with a flag to clear inherited API URLs when staging is unavailable. The logic is integrated into envs.ts via a refactored withInstanceKeys helper, and comprehensive tests verify all resolution paths.

Changes

Instance Credentials Staging Resolution

Layer / File(s) Summary
Credential resolution module
integration/presets/instanceKeys.ts
Exports STAGING_API_URL, STAGING_KEY_PREFIX, placeholder keys (STAGING_UNAVAILABLE_PK, STAGING_UNAVAILABLE_SK), type interfaces (InstanceCredentials, ResolvedInstanceConfig), and resolveInstanceConfig function with branching logic: uses production keys when not staging (throwing on missing), uses staging keys plus API URL when staging keys exist, or returns placeholder keys with clearApiUrl: true when staging is unavailable.
Test suite for credential resolution
integration/presets/__tests__/instanceKeys.test.ts
Imports module exports, defines production and staging key fixtures, provides a makeKeys helper for test setup, and implements four test cases verifying production-mode credential selection, staging-mode swap with available keys, fallback to unavailable placeholders, and placeholder key identifier validation.
Integration into environment configuration
integration/presets/envs.ts
Adds import of resolveInstanceConfig and STAGING_API_URL from ./instanceKeys, removes file-local staging constants, and refactors withInstanceKeys to call resolveInstanceConfig for credential and staging URL resolution, then conditionally clears or sets CLERK_API_URL based on resolved config.
Changeset entry
.changeset/staging-e2e-prod-key-fallback.md
Documents the staging credential fallback feature.

🎯 2 (Simple) | ⏱️ ~12 minutes

🐰 New staging keys hop in with grace,
A shared module finds their place,
When staging's not there, we fall back with care,
To placeholders waiting to spare,
Integration tests hop through the air!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main fix: preventing production keys from being used in staging mode without a staging key, which is the core safety improvement introduced by this PR.
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.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Jun 5, 2026

Open in StackBlitz

@clerk/astro

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

@clerk/backend

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

@clerk/chrome-extension

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

@clerk/clerk-js

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

@clerk/expo

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

@clerk/expo-passkeys

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

@clerk/express

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

@clerk/fastify

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

@clerk/hono

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

@clerk/localizations

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

@clerk/nextjs

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

@clerk/nuxt

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

@clerk/react

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

@clerk/react-router

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

@clerk/shared

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

@clerk/tanstack-react-start

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

@clerk/testing

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

@clerk/ui

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

@clerk/upgrade

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

@clerk/vue

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

commit: 55a0c20

Copy link
Copy Markdown
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: 1

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

Inline comments:
In `@integration/presets/__tests__/instanceKeys.test.ts`:
- Around line 17-48: Add a test that asserts resolveInstanceConfig throws when
isStaging=false and the requested instance key is missing: create keys via
makeKeys without the target instance (e.g., makeKeys({} or only unrelated
keys)), call resolveInstanceConfig('missing-instance', keys, false) and expect
it to throw; reference the resolveInstanceConfig function and makeKeys helper
and reuse constants like PROD/STAGING_UNAVAILABLE_* only for assertions about
non-staging behavior, ensuring the test covers the non-staging missing-key error
path.
🪄 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

Run ID: 70a4291a-c052-48c5-a0ae-222427477841

📥 Commits

Reviewing files that changed from the base of the PR and between 83f50f6 and 55a0c20.

📒 Files selected for processing (4)
  • .changeset/staging-e2e-prod-key-fallback.md
  • integration/presets/__tests__/instanceKeys.test.ts
  • integration/presets/envs.ts
  • integration/presets/instanceKeys.ts

Comment on lines +17 to +48
describe('resolveInstanceConfig', () => {
it('uses production keys in non-staging mode and does not touch the API URL', () => {
const keys = makeKeys({ 'with-email-codes': PROD });
const cfg = resolveInstanceConfig('with-email-codes', keys, false);
expect(cfg).toEqual({ pk: PROD.pk, sk: PROD.sk, clearApiUrl: false });
});

it('swaps to staging keys and sets the staging API URL when a staging key exists', () => {
const keys = makeKeys({ 'with-email-codes': PROD, 'clerkstage-with-email-codes': STAGING });
const cfg = resolveInstanceConfig('with-email-codes', keys, true);
expect(cfg).toEqual({ pk: STAGING.pk, sk: STAGING.sk, apiUrl: STAGING_API_URL, clearApiUrl: false });
});

it('NEVER returns production keys in staging mode when the staging key is missing', () => {
const keys = makeKeys({ 'with-no-staging-mirror': PROD });
const cfg = resolveInstanceConfig('with-no-staging-mirror', keys, true);

// The core safety invariant: a staging run must not carry production credentials.
expect(cfg.pk).not.toBe(PROD.pk);
expect(cfg.sk).not.toBe(PROD.sk);
expect(cfg.pk).toBe(STAGING_UNAVAILABLE_PK);
expect(cfg.sk).toBe(STAGING_UNAVAILABLE_SK);
// And it must be marked not-staging-ready by clearing any inherited API URL.
expect(cfg.clearApiUrl).toBe(true);
expect(cfg.apiUrl).toBeUndefined();
});

it('placeholder keys are not real Clerk keys (cannot authenticate anywhere real)', () => {
expect(STAGING_UNAVAILABLE_PK).toContain('staging-key-unavailable');
expect(STAGING_UNAVAILABLE_SK).toContain('staging-key-unavailable');
});
});
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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add coverage for the non-staging missing-key error path.

The suite doesn’t currently assert the throw branch when isStaging=false and the requested key is absent.

Proposed test addition
 describe('resolveInstanceConfig', () => {
+  it('throws in non-staging mode when production keys are missing', () => {
+    const keys = makeKeys({});
+    expect(() => resolveInstanceConfig('missing-instance', keys, false)).toThrow(
+      /Missing instance keys/,
+    );
+  });
+
   it('uses production keys in non-staging mode and does not touch the API URL', () => {

As per coding guidelines: **/*.{test,spec}.{ts,tsx} requires verifying proper error handling and edge cases for new functionality.

🤖 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 `@integration/presets/__tests__/instanceKeys.test.ts` around lines 17 - 48, Add
a test that asserts resolveInstanceConfig throws when isStaging=false and the
requested instance key is missing: create keys via makeKeys without the target
instance (e.g., makeKeys({} or only unrelated keys)), call
resolveInstanceConfig('missing-instance', keys, false) and expect it to throw;
reference the resolveInstanceConfig function and makeKeys helper and reuse
constants like PROD/STAGING_UNAVAILABLE_* only for assertions about non-staging
behavior, ensuring the test covers the non-staging missing-key error path.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant