fix(frontend): guard prod builds against staging deploy-config leak - #309

Merged
AndresL230 merged 1 commit into
mainfrom
fix/frontend-prod-deploy-guard
Jul 3, 2026
Merged

fix(frontend): guard prod builds against staging deploy-config leak#309
AndresL230 merged 1 commit into
mainfrom
fix/frontend-prod-deploy-guard

Conversation

@AndresL230

@AndresL230AndresL230 commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

Problem

The prod frontend Cloudflare Workers Build's deploy step was npx wrangler deploy --env staging. That applied wrangler.toml's [env.staging.vars] — staging API URLs + a .staging.saplinglearn.com cookie domain — to the prod worker (wrangler even logged that it overrode the worker name frontend-stagingfrontend). Sign-in on saplinglearn.com broke: the middleware routed auth to the staging backend and the sapling_session cookie was scoped to .staging, so it never stuck.

These values bake at build time (NEXT_PUBLIC_API_URL is inlined; the /api rewrite uses BACKEND_URL), so runtime wrangler.toml [vars] couldn't correct a build that already shipped the wrong values.

Fix

Add checkFrontendDeployEnv() (src/lib/deployGuard.ts), called from next.config.ts on production builds (alongside the existing BACKEND_URL guard):

  • Consistency (always on, no config): fail if NEXT_PUBLIC_API_URL / BACKEND_URL / COOKIE_DOMAIN describe more than one environment (e.g. prod API URL + .staging cookie domain).
  • Env-lock (opt-in via DEPLOY_ENV): when set, fail if any value doesn't match that environment — this catches an all-staging build shipped to the prod worker (the exact bug), which the consistency check alone can't detect.

Operator follow-up (not code — do in the Cloudflare dashboard)

  1. Prod frontend Workers Build → Deploy command: npx wrangler deploy --env stagingnpx wrangler deploy.
  2. Set DEPLOY_ENV=production (prod build var) and DEPLOY_ENV=staging (staging build var) to activate the exact-match check.

Tests

src/lib/deployGuard.test.ts — 9 cases: clean prod/staging, matching DEPLOY_ENV, split-brain mix, all-staging-on-prod, bad DEPLOY_ENV, unset/no-op (local dev), non-canonical preview URLs, whitespace/case tolerance. tsc --noEmit + eslint clean.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added stronger production build checks for frontend deployments.
    • Deployment settings are now validated together to catch mismatched staging/prod configurations earlier.
  • Bug Fixes
    • Builds now fail with clear guidance when deployment variables don’t align.
    • Improved handling of unknown or preview values so they don’t trigger false failures.
  • Tests
    • Added coverage for matching, mismatched, and malformed deployment environments.

The prod `frontend` Cloudflare Workers Build deployed with `--env staging`,
baking staging API URLs + a `.staging` cookie domain onto saplinglearn.com,
which broke sign-in (middleware routed auth to the staging backend; the session
cookie was scoped to `.staging` and never stuck).
Add checkFrontendDeployEnv() (src/lib/deployGuard.ts), called from next.config.ts
on production builds: fail the build if NEXT_PUBLIC_API_URL / BACKEND_URL /
COOKIE_DOMAIN mix environments, or — when DEPLOY_ENV is set — don't match the
intended one. Runtime wrangler.toml [vars] can't fix a build that already baked
the wrong values, so this catches it at build time.
Set DEPLOY_ENV=production on the prod Workers Build and DEPLOY_ENV=staging on the
staging one to activate the exact-match check.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@cloudflare-workers-and-pages

cloudflare-workers-and-pagesBot commented Jul 3, 2026

Copy link
Copy Markdown

Deploying with Cloudflare Workers Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

StatusNameLatest CommitPreview URLUpdated (UTC)
✅ Deployment successful!
View logs
frontend-stagingdc93218Commit Preview URL

Branch Preview URL
Jul 03 2026, 02:29 AM

@coderabbitai

coderabbitaiBot commented Jul 3, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a new deployGuard.ts module exporting checkFrontendDeployEnv to detect mismatched/mixed staging and production deployment environment variables, wires this check into next.config.ts's production build guard to fail builds on misconfiguration, and adds a corresponding Vitest test suite.

Changes

Frontend deploy environment guard

Layer / File(s)Summary
Canonical env definitions and classifier
frontend/src/lib/deployGuard.ts
Defines FRONTEND_ENVS canonical apiUrl/cookieDomain values, FrontendEnv type, EnvSource type, and a classify() helper that normalizes and matches env values to canonical environments.
Consistency and DEPLOY_ENV lock check
frontend/src/lib/deployGuard.ts
Implements exported checkFrontendDeployEnv(env) which classifies NEXT_PUBLIC_API_URL, BACKEND_URL, COOKIE_DOMAIN, checks for mixed environments, validates against an explicit DEPLOY_ENV, and returns a list of problem messages.
Build wiring and tests
frontend/next.config.ts, frontend/src/lib/deployGuard.test.ts
Imports and invokes checkFrontendDeployEnv during production builds, throwing a detailed error when problems are found; adds tests covering pass/fail cases, mismatch detection, and normalization tolerance.

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

Possibly related PRs

  • SaplingLearn/Sapling#281: Both PRs modify frontend/next.config.ts to fail production builds on missing/misconfigured deployment env vars, with this PR generalizing the earlier BACKEND_URL-only check into the new checkFrontendDeployEnv guard.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Title check✅ PassedThe title is concise and accurately summarizes the main change: preventing prod builds from using staging deploy config.
Description check✅ PassedThe description covers the problem, fix, follow-up actions, and testing, with only minor template fields like related issues and screenshots omitted.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/frontend-prod-deploy-guard

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

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)
frontend/src/lib/deployGuard.test.ts (1)

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

Add coverage for missing/malformed vars under an explicit DEPLOY_ENV lock.

Current cases only exercise wrong-but-canonical values (all-staging on prod) and unknown DEPLOY_ENV. Consider adding a case where DEPLOY_ENV=production but NEXT_PUBLIC_API_URL is unset or non-canonical (e.g., a typo'd URL) — this currently passes silently per the gap noted in deployGuard.ts, and a test would make that limitation explicit or catch a fix regression.

🤖 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 `@frontend/src/lib/deployGuard.test.ts` around lines 33 - 41, Add a test in
deployGuard.test.ts that exercises the explicit DEPLOY_ENV lock in
checkFrontendDeployEnv with DEPLOY_ENV set to production while
NEXT_PUBLIC_API_URL is missing or malformed, so the gap in deployGuard.ts is
covered. Use the existing checkFrontendDeployEnv helper and the STAGING/PROD
fixtures as the starting point, then assert that the returned problems include
the expected missing/invalid URL validation instead of passing silently.
🤖 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 `@frontend/src/lib/deployGuard.ts`:
- Around line 86-104: The DEPLOY_ENV check in deployGuard.ts only flags entries
already classified by classify(), so missing, blank, or malformed values can
slip through unnoticed. Update the DEPLOY_ENV branch in deployGuard to require
NEXT_PUBLIC_API_URL, BACKEND_URL, and COOKIE_DOMAIN to each classify to the
active deployEnv, and treat any null/omitted classification as a problem. Use
the existing FRONTEND_ENVS, classify, and mismatched/deployEnv logic to report
which of the three keys are absent or not matching the expected environment.
---
Nitpick comments:
In `@frontend/src/lib/deployGuard.test.ts`:
- Around line 33-41: Add a test in deployGuard.test.ts that exercises the
explicit DEPLOY_ENV lock in checkFrontendDeployEnv with DEPLOY_ENV set to
production while NEXT_PUBLIC_API_URL is missing or malformed, so the gap in
deployGuard.ts is covered. Use the existing checkFrontendDeployEnv helper and
the STAGING/PROD fixtures as the starting point, then assert that the returned
problems include the expected missing/invalid URL validation instead of passing
silently.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ca709c59-9fd9-49be-b01a-e4c99fcc4e10

📥 Commits

Reviewing files that changed from the base of the PR and between f472300 and dc93218.

📒 Files selected for processing (3)
  • frontend/next.config.ts
  • frontend/src/lib/deployGuard.test.ts
  • frontend/src/lib/deployGuard.ts

Comment on lines +86 to +104
const deployEnv = (env.DEPLOY_ENV ?? '').trim().toLowerCase();
if (deployEnv) {
if (!(deployEnv in FRONTEND_ENVS)) {
problems.push(
`DEPLOY_ENV must be one of ${Object.keys(FRONTEND_ENVS).join(' | ')}, ` +
`got ${JSON.stringify(env.DEPLOY_ENV)}`,
);
} else {
const want = FRONTEND_ENVS[deployEnv as FrontendEnv];
const mismatched = Object.entries(classified).filter(([, v]) => v !== deployEnv);
if (mismatched.length) {
const detail = mismatched.map(([k, v]) => `${k}→${v}`).join(', ');
problems.push(
`DEPLOY_ENV=${deployEnv} but ${detail} point elsewhere; expected ` +
`NEXT_PUBLIC_API_URL/BACKEND_URL=${want.apiUrl}, COOKIE_DOMAIN=${want.cookieDomain}`,
);
}
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

DEPLOY_ENV lock silently passes when a var is missing or malformed, not just wrong-environment.

The mismatch check on Line 95 only inspects classified entries — i.e., values that exactly matched a canonical FRONTEND_ENVS string. If NEXT_PUBLIC_API_URL/BACKEND_URL/COOKIE_DOMAIN is unset, blank, or subtly malformed (trailing slash, wrong subdomain, http vs https), classify() returns null and the key is simply omitted from classified, so it never appears in mismatched and is never flagged. With DEPLOY_ENV=production set, a build where the API URL is accidentally empty or wrong-but-non-canonical will report zero problems — exactly the "silent misconfiguration reaches prod" failure mode this guard exists to prevent, just via a different vector (missing/malformed value) than the original bug (correct-but-wrong-environment value).

Consider requiring all three keys to be classified (matching the intended deployEnv) when DEPLOY_ENV is set, rather than only checking values that already classified successfully:

💡 Proposed fix
 } else {
const want = FRONTEND_ENVS[deployEnv as FrontendEnv];
- const mismatched = Object.entries(classified).filter(([, v]) => v !== deployEnv);+ const keys = ['NEXT_PUBLIC_API_URL', 'BACKEND_URL', 'COOKIE_DOMAIN'] as const;+ const mismatched = keys+ .filter((k) => env[k] !== undefined && env[k] !== '')+ .map((k) => [k, classified[k] ?? 'unrecognized'] as const)+ .filter(([, v]) => v !== deployEnv);
if (mismatched.length) {
🤖 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 `@frontend/src/lib/deployGuard.ts` around lines 86 - 104, The DEPLOY_ENV check
in deployGuard.ts only flags entries already classified by classify(), so
missing, blank, or malformed values can slip through unnoticed. Update the
DEPLOY_ENV branch in deployGuard to require NEXT_PUBLIC_API_URL, BACKEND_URL,
and COOKIE_DOMAIN to each classify to the active deployEnv, and treat any
null/omitted classification as a problem. Use the existing FRONTEND_ENVS,
classify, and mismatched/deployEnv logic to report which of the three keys are
absent or not matching the expected environment.

@AndresL230
AndresL230 merged commit c515314 into mainJul 3, 2026
6 checks passed
@AndresL230
AndresL230 deleted the fix/frontend-prod-deploy-guard branch August 2, 2026 18:30
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@AndresL230
, '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(frontend): guard prod builds against staging deploy-config leak - #309

Merged
AndresL230 merged 1 commit into
mainfrom
fix/frontend-prod-deploy-guard
Jul 3, 2026
Merged

fix(frontend): guard prod builds against staging deploy-config leak#309
AndresL230 merged 1 commit into
mainfrom
fix/frontend-prod-deploy-guard

Conversation

@AndresL230

@AndresL230AndresL230 commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

Problem

The prod frontend Cloudflare Workers Build's deploy step was npx wrangler deploy --env staging. That applied wrangler.toml's [env.staging.vars] — staging API URLs + a .staging.saplinglearn.com cookie domain — to the prod worker (wrangler even logged that it overrode the worker name frontend-stagingfrontend). Sign-in on saplinglearn.com broke: the middleware routed auth to the staging backend and the sapling_session cookie was scoped to .staging, so it never stuck.

These values bake at build time (NEXT_PUBLIC_API_URL is inlined; the /api rewrite uses BACKEND_URL), so runtime wrangler.toml [vars] couldn't correct a build that already shipped the wrong values.

Fix

Add checkFrontendDeployEnv() (src/lib/deployGuard.ts), called from next.config.ts on production builds (alongside the existing BACKEND_URL guard):

  • Consistency (always on, no config): fail if NEXT_PUBLIC_API_URL / BACKEND_URL / COOKIE_DOMAIN describe more than one environment (e.g. prod API URL + .staging cookie domain).
  • Env-lock (opt-in via DEPLOY_ENV): when set, fail if any value doesn't match that environment — this catches an all-staging build shipped to the prod worker (the exact bug), which the consistency check alone can't detect.

Operator follow-up (not code — do in the Cloudflare dashboard)

  1. Prod frontend Workers Build → Deploy command: npx wrangler deploy --env stagingnpx wrangler deploy.
  2. Set DEPLOY_ENV=production (prod build var) and DEPLOY_ENV=staging (staging build var) to activate the exact-match check.

Tests

src/lib/deployGuard.test.ts — 9 cases: clean prod/staging, matching DEPLOY_ENV, split-brain mix, all-staging-on-prod, bad DEPLOY_ENV, unset/no-op (local dev), non-canonical preview URLs, whitespace/case tolerance. tsc --noEmit + eslint clean.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added stronger production build checks for frontend deployments.
    • Deployment settings are now validated together to catch mismatched staging/prod configurations earlier.
  • Bug Fixes
    • Builds now fail with clear guidance when deployment variables don’t align.
    • Improved handling of unknown or preview values so they don’t trigger false failures.
  • Tests
    • Added coverage for matching, mismatched, and malformed deployment environments.

The prod `frontend` Cloudflare Workers Build deployed with `--env staging`,
baking staging API URLs + a `.staging` cookie domain onto saplinglearn.com,
which broke sign-in (middleware routed auth to the staging backend; the session
cookie was scoped to `.staging` and never stuck).
Add checkFrontendDeployEnv() (src/lib/deployGuard.ts), called from next.config.ts
on production builds: fail the build if NEXT_PUBLIC_API_URL / BACKEND_URL /
COOKIE_DOMAIN mix environments, or — when DEPLOY_ENV is set — don't match the
intended one. Runtime wrangler.toml [vars] can't fix a build that already baked
the wrong values, so this catches it at build time.
Set DEPLOY_ENV=production on the prod Workers Build and DEPLOY_ENV=staging on the
staging one to activate the exact-match check.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@cloudflare-workers-and-pages

cloudflare-workers-and-pagesBot commented Jul 3, 2026

Copy link
Copy Markdown

Deploying with Cloudflare Workers Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

StatusNameLatest CommitPreview URLUpdated (UTC)
✅ Deployment successful!
View logs
frontend-stagingdc93218Commit Preview URL

Branch Preview URL
Jul 03 2026, 02:29 AM

@coderabbitai

coderabbitaiBot commented Jul 3, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a new deployGuard.ts module exporting checkFrontendDeployEnv to detect mismatched/mixed staging and production deployment environment variables, wires this check into next.config.ts's production build guard to fail builds on misconfiguration, and adds a corresponding Vitest test suite.

Changes

Frontend deploy environment guard

Layer / File(s)Summary
Canonical env definitions and classifier
frontend/src/lib/deployGuard.ts
Defines FRONTEND_ENVS canonical apiUrl/cookieDomain values, FrontendEnv type, EnvSource type, and a classify() helper that normalizes and matches env values to canonical environments.
Consistency and DEPLOY_ENV lock check
frontend/src/lib/deployGuard.ts
Implements exported checkFrontendDeployEnv(env) which classifies NEXT_PUBLIC_API_URL, BACKEND_URL, COOKIE_DOMAIN, checks for mixed environments, validates against an explicit DEPLOY_ENV, and returns a list of problem messages.
Build wiring and tests
frontend/next.config.ts, frontend/src/lib/deployGuard.test.ts
Imports and invokes checkFrontendDeployEnv during production builds, throwing a detailed error when problems are found; adds tests covering pass/fail cases, mismatch detection, and normalization tolerance.

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

Possibly related PRs

  • SaplingLearn/Sapling#281: Both PRs modify frontend/next.config.ts to fail production builds on missing/misconfigured deployment env vars, with this PR generalizing the earlier BACKEND_URL-only check into the new checkFrontendDeployEnv guard.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Title check✅ PassedThe title is concise and accurately summarizes the main change: preventing prod builds from using staging deploy config.
Description check✅ PassedThe description covers the problem, fix, follow-up actions, and testing, with only minor template fields like related issues and screenshots omitted.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/frontend-prod-deploy-guard

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

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)
frontend/src/lib/deployGuard.test.ts (1)

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

Add coverage for missing/malformed vars under an explicit DEPLOY_ENV lock.

Current cases only exercise wrong-but-canonical values (all-staging on prod) and unknown DEPLOY_ENV. Consider adding a case where DEPLOY_ENV=production but NEXT_PUBLIC_API_URL is unset or non-canonical (e.g., a typo'd URL) — this currently passes silently per the gap noted in deployGuard.ts, and a test would make that limitation explicit or catch a fix regression.

🤖 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 `@frontend/src/lib/deployGuard.test.ts` around lines 33 - 41, Add a test in
deployGuard.test.ts that exercises the explicit DEPLOY_ENV lock in
checkFrontendDeployEnv with DEPLOY_ENV set to production while
NEXT_PUBLIC_API_URL is missing or malformed, so the gap in deployGuard.ts is
covered. Use the existing checkFrontendDeployEnv helper and the STAGING/PROD
fixtures as the starting point, then assert that the returned problems include
the expected missing/invalid URL validation instead of passing silently.
🤖 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 `@frontend/src/lib/deployGuard.ts`:
- Around line 86-104: The DEPLOY_ENV check in deployGuard.ts only flags entries
already classified by classify(), so missing, blank, or malformed values can
slip through unnoticed. Update the DEPLOY_ENV branch in deployGuard to require
NEXT_PUBLIC_API_URL, BACKEND_URL, and COOKIE_DOMAIN to each classify to the
active deployEnv, and treat any null/omitted classification as a problem. Use
the existing FRONTEND_ENVS, classify, and mismatched/deployEnv logic to report
which of the three keys are absent or not matching the expected environment.
---
Nitpick comments:
In `@frontend/src/lib/deployGuard.test.ts`:
- Around line 33-41: Add a test in deployGuard.test.ts that exercises the
explicit DEPLOY_ENV lock in checkFrontendDeployEnv with DEPLOY_ENV set to
production while NEXT_PUBLIC_API_URL is missing or malformed, so the gap in
deployGuard.ts is covered. Use the existing checkFrontendDeployEnv helper and
the STAGING/PROD fixtures as the starting point, then assert that the returned
problems include the expected missing/invalid URL validation instead of passing
silently.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ca709c59-9fd9-49be-b01a-e4c99fcc4e10

📥 Commits

Reviewing files that changed from the base of the PR and between f472300 and dc93218.

📒 Files selected for processing (3)
  • frontend/next.config.ts
  • frontend/src/lib/deployGuard.test.ts
  • frontend/src/lib/deployGuard.ts

Comment on lines +86 to +104
const deployEnv = (env.DEPLOY_ENV ?? '').trim().toLowerCase();
if (deployEnv) {
if (!(deployEnv in FRONTEND_ENVS)) {
problems.push(
`DEPLOY_ENV must be one of ${Object.keys(FRONTEND_ENVS).join(' | ')}, ` +
`got ${JSON.stringify(env.DEPLOY_ENV)}`,
);
} else {
const want = FRONTEND_ENVS[deployEnv as FrontendEnv];
const mismatched = Object.entries(classified).filter(([, v]) => v !== deployEnv);
if (mismatched.length) {
const detail = mismatched.map(([k, v]) => `${k}→${v}`).join(', ');
problems.push(
`DEPLOY_ENV=${deployEnv} but ${detail} point elsewhere; expected ` +
`NEXT_PUBLIC_API_URL/BACKEND_URL=${want.apiUrl}, COOKIE_DOMAIN=${want.cookieDomain}`,
);
}
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

DEPLOY_ENV lock silently passes when a var is missing or malformed, not just wrong-environment.

The mismatch check on Line 95 only inspects classified entries — i.e., values that exactly matched a canonical FRONTEND_ENVS string. If NEXT_PUBLIC_API_URL/BACKEND_URL/COOKIE_DOMAIN is unset, blank, or subtly malformed (trailing slash, wrong subdomain, http vs https), classify() returns null and the key is simply omitted from classified, so it never appears in mismatched and is never flagged. With DEPLOY_ENV=production set, a build where the API URL is accidentally empty or wrong-but-non-canonical will report zero problems — exactly the "silent misconfiguration reaches prod" failure mode this guard exists to prevent, just via a different vector (missing/malformed value) than the original bug (correct-but-wrong-environment value).

Consider requiring all three keys to be classified (matching the intended deployEnv) when DEPLOY_ENV is set, rather than only checking values that already classified successfully:

💡 Proposed fix
 } else {
const want = FRONTEND_ENVS[deployEnv as FrontendEnv];
- const mismatched = Object.entries(classified).filter(([, v]) => v !== deployEnv);+ const keys = ['NEXT_PUBLIC_API_URL', 'BACKEND_URL', 'COOKIE_DOMAIN'] as const;+ const mismatched = keys+ .filter((k) => env[k] !== undefined && env[k] !== '')+ .map((k) => [k, classified[k] ?? 'unrecognized'] as const)+ .filter(([, v]) => v !== deployEnv);
if (mismatched.length) {
🤖 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 `@frontend/src/lib/deployGuard.ts` around lines 86 - 104, The DEPLOY_ENV check
in deployGuard.ts only flags entries already classified by classify(), so
missing, blank, or malformed values can slip through unnoticed. Update the
DEPLOY_ENV branch in deployGuard to require NEXT_PUBLIC_API_URL, BACKEND_URL,
and COOKIE_DOMAIN to each classify to the active deployEnv, and treat any
null/omitted classification as a problem. Use the existing FRONTEND_ENVS,
classify, and mismatched/deployEnv logic to report which of the three keys are
absent or not matching the expected environment.

@AndresL230
AndresL230 merged commit c515314 into mainJul 3, 2026
6 checks passed
@AndresL230
AndresL230 deleted the fix/frontend-prod-deploy-guard branch August 2, 2026 18:30
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@AndresL230
, '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(frontend): guard prod builds against staging deploy-config leak - #309

Merged
AndresL230 merged 1 commit into
mainfrom
fix/frontend-prod-deploy-guard
Jul 3, 2026
Merged

fix(frontend): guard prod builds against staging deploy-config leak#309
AndresL230 merged 1 commit into
mainfrom
fix/frontend-prod-deploy-guard

Conversation

@AndresL230

@AndresL230AndresL230 commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

Problem

The prod frontend Cloudflare Workers Build's deploy step was npx wrangler deploy --env staging. That applied wrangler.toml's [env.staging.vars] — staging API URLs + a .staging.saplinglearn.com cookie domain — to the prod worker (wrangler even logged that it overrode the worker name frontend-stagingfrontend). Sign-in on saplinglearn.com broke: the middleware routed auth to the staging backend and the sapling_session cookie was scoped to .staging, so it never stuck.

These values bake at build time (NEXT_PUBLIC_API_URL is inlined; the /api rewrite uses BACKEND_URL), so runtime wrangler.toml [vars] couldn't correct a build that already shipped the wrong values.

Fix

Add checkFrontendDeployEnv() (src/lib/deployGuard.ts), called from next.config.ts on production builds (alongside the existing BACKEND_URL guard):

  • Consistency (always on, no config): fail if NEXT_PUBLIC_API_URL / BACKEND_URL / COOKIE_DOMAIN describe more than one environment (e.g. prod API URL + .staging cookie domain).
  • Env-lock (opt-in via DEPLOY_ENV): when set, fail if any value doesn't match that environment — this catches an all-staging build shipped to the prod worker (the exact bug), which the consistency check alone can't detect.

Operator follow-up (not code — do in the Cloudflare dashboard)

  1. Prod frontend Workers Build → Deploy command: npx wrangler deploy --env stagingnpx wrangler deploy.
  2. Set DEPLOY_ENV=production (prod build var) and DEPLOY_ENV=staging (staging build var) to activate the exact-match check.

Tests

src/lib/deployGuard.test.ts — 9 cases: clean prod/staging, matching DEPLOY_ENV, split-brain mix, all-staging-on-prod, bad DEPLOY_ENV, unset/no-op (local dev), non-canonical preview URLs, whitespace/case tolerance. tsc --noEmit + eslint clean.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added stronger production build checks for frontend deployments.
    • Deployment settings are now validated together to catch mismatched staging/prod configurations earlier.
  • Bug Fixes
    • Builds now fail with clear guidance when deployment variables don’t align.
    • Improved handling of unknown or preview values so they don’t trigger false failures.
  • Tests
    • Added coverage for matching, mismatched, and malformed deployment environments.

The prod `frontend` Cloudflare Workers Build deployed with `--env staging`,
baking staging API URLs + a `.staging` cookie domain onto saplinglearn.com,
which broke sign-in (middleware routed auth to the staging backend; the session
cookie was scoped to `.staging` and never stuck).
Add checkFrontendDeployEnv() (src/lib/deployGuard.ts), called from next.config.ts
on production builds: fail the build if NEXT_PUBLIC_API_URL / BACKEND_URL /
COOKIE_DOMAIN mix environments, or — when DEPLOY_ENV is set — don't match the
intended one. Runtime wrangler.toml [vars] can't fix a build that already baked
the wrong values, so this catches it at build time.
Set DEPLOY_ENV=production on the prod Workers Build and DEPLOY_ENV=staging on the
staging one to activate the exact-match check.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@cloudflare-workers-and-pages

cloudflare-workers-and-pagesBot commented Jul 3, 2026

Copy link
Copy Markdown

Deploying with Cloudflare Workers Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

StatusNameLatest CommitPreview URLUpdated (UTC)
✅ Deployment successful!
View logs
frontend-stagingdc93218Commit Preview URL

Branch Preview URL
Jul 03 2026, 02:29 AM

@coderabbitai

coderabbitaiBot commented Jul 3, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a new deployGuard.ts module exporting checkFrontendDeployEnv to detect mismatched/mixed staging and production deployment environment variables, wires this check into next.config.ts's production build guard to fail builds on misconfiguration, and adds a corresponding Vitest test suite.

Changes

Frontend deploy environment guard

Layer / File(s)Summary
Canonical env definitions and classifier
frontend/src/lib/deployGuard.ts
Defines FRONTEND_ENVS canonical apiUrl/cookieDomain values, FrontendEnv type, EnvSource type, and a classify() helper that normalizes and matches env values to canonical environments.
Consistency and DEPLOY_ENV lock check
frontend/src/lib/deployGuard.ts
Implements exported checkFrontendDeployEnv(env) which classifies NEXT_PUBLIC_API_URL, BACKEND_URL, COOKIE_DOMAIN, checks for mixed environments, validates against an explicit DEPLOY_ENV, and returns a list of problem messages.
Build wiring and tests
frontend/next.config.ts, frontend/src/lib/deployGuard.test.ts
Imports and invokes checkFrontendDeployEnv during production builds, throwing a detailed error when problems are found; adds tests covering pass/fail cases, mismatch detection, and normalization tolerance.

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

Possibly related PRs

  • SaplingLearn/Sapling#281: Both PRs modify frontend/next.config.ts to fail production builds on missing/misconfigured deployment env vars, with this PR generalizing the earlier BACKEND_URL-only check into the new checkFrontendDeployEnv guard.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Title check✅ PassedThe title is concise and accurately summarizes the main change: preventing prod builds from using staging deploy config.
Description check✅ PassedThe description covers the problem, fix, follow-up actions, and testing, with only minor template fields like related issues and screenshots omitted.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/frontend-prod-deploy-guard

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

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)
frontend/src/lib/deployGuard.test.ts (1)

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

Add coverage for missing/malformed vars under an explicit DEPLOY_ENV lock.

Current cases only exercise wrong-but-canonical values (all-staging on prod) and unknown DEPLOY_ENV. Consider adding a case where DEPLOY_ENV=production but NEXT_PUBLIC_API_URL is unset or non-canonical (e.g., a typo'd URL) — this currently passes silently per the gap noted in deployGuard.ts, and a test would make that limitation explicit or catch a fix regression.

🤖 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 `@frontend/src/lib/deployGuard.test.ts` around lines 33 - 41, Add a test in
deployGuard.test.ts that exercises the explicit DEPLOY_ENV lock in
checkFrontendDeployEnv with DEPLOY_ENV set to production while
NEXT_PUBLIC_API_URL is missing or malformed, so the gap in deployGuard.ts is
covered. Use the existing checkFrontendDeployEnv helper and the STAGING/PROD
fixtures as the starting point, then assert that the returned problems include
the expected missing/invalid URL validation instead of passing silently.
🤖 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 `@frontend/src/lib/deployGuard.ts`:
- Around line 86-104: The DEPLOY_ENV check in deployGuard.ts only flags entries
already classified by classify(), so missing, blank, or malformed values can
slip through unnoticed. Update the DEPLOY_ENV branch in deployGuard to require
NEXT_PUBLIC_API_URL, BACKEND_URL, and COOKIE_DOMAIN to each classify to the
active deployEnv, and treat any null/omitted classification as a problem. Use
the existing FRONTEND_ENVS, classify, and mismatched/deployEnv logic to report
which of the three keys are absent or not matching the expected environment.
---
Nitpick comments:
In `@frontend/src/lib/deployGuard.test.ts`:
- Around line 33-41: Add a test in deployGuard.test.ts that exercises the
explicit DEPLOY_ENV lock in checkFrontendDeployEnv with DEPLOY_ENV set to
production while NEXT_PUBLIC_API_URL is missing or malformed, so the gap in
deployGuard.ts is covered. Use the existing checkFrontendDeployEnv helper and
the STAGING/PROD fixtures as the starting point, then assert that the returned
problems include the expected missing/invalid URL validation instead of passing
silently.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ca709c59-9fd9-49be-b01a-e4c99fcc4e10

📥 Commits

Reviewing files that changed from the base of the PR and between f472300 and dc93218.

📒 Files selected for processing (3)
  • frontend/next.config.ts
  • frontend/src/lib/deployGuard.test.ts
  • frontend/src/lib/deployGuard.ts

Comment on lines +86 to +104
const deployEnv = (env.DEPLOY_ENV ?? '').trim().toLowerCase();
if (deployEnv) {
if (!(deployEnv in FRONTEND_ENVS)) {
problems.push(
`DEPLOY_ENV must be one of ${Object.keys(FRONTEND_ENVS).join(' | ')}, ` +
`got ${JSON.stringify(env.DEPLOY_ENV)}`,
);
} else {
const want = FRONTEND_ENVS[deployEnv as FrontendEnv];
const mismatched = Object.entries(classified).filter(([, v]) => v !== deployEnv);
if (mismatched.length) {
const detail = mismatched.map(([k, v]) => `${k}→${v}`).join(', ');
problems.push(
`DEPLOY_ENV=${deployEnv} but ${detail} point elsewhere; expected ` +
`NEXT_PUBLIC_API_URL/BACKEND_URL=${want.apiUrl}, COOKIE_DOMAIN=${want.cookieDomain}`,
);
}
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

DEPLOY_ENV lock silently passes when a var is missing or malformed, not just wrong-environment.

The mismatch check on Line 95 only inspects classified entries — i.e., values that exactly matched a canonical FRONTEND_ENVS string. If NEXT_PUBLIC_API_URL/BACKEND_URL/COOKIE_DOMAIN is unset, blank, or subtly malformed (trailing slash, wrong subdomain, http vs https), classify() returns null and the key is simply omitted from classified, so it never appears in mismatched and is never flagged. With DEPLOY_ENV=production set, a build where the API URL is accidentally empty or wrong-but-non-canonical will report zero problems — exactly the "silent misconfiguration reaches prod" failure mode this guard exists to prevent, just via a different vector (missing/malformed value) than the original bug (correct-but-wrong-environment value).

Consider requiring all three keys to be classified (matching the intended deployEnv) when DEPLOY_ENV is set, rather than only checking values that already classified successfully:

💡 Proposed fix
 } else {
const want = FRONTEND_ENVS[deployEnv as FrontendEnv];
- const mismatched = Object.entries(classified).filter(([, v]) => v !== deployEnv);+ const keys = ['NEXT_PUBLIC_API_URL', 'BACKEND_URL', 'COOKIE_DOMAIN'] as const;+ const mismatched = keys+ .filter((k) => env[k] !== undefined && env[k] !== '')+ .map((k) => [k, classified[k] ?? 'unrecognized'] as const)+ .filter(([, v]) => v !== deployEnv);
if (mismatched.length) {
🤖 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 `@frontend/src/lib/deployGuard.ts` around lines 86 - 104, The DEPLOY_ENV check
in deployGuard.ts only flags entries already classified by classify(), so
missing, blank, or malformed values can slip through unnoticed. Update the
DEPLOY_ENV branch in deployGuard to require NEXT_PUBLIC_API_URL, BACKEND_URL,
and COOKIE_DOMAIN to each classify to the active deployEnv, and treat any
null/omitted classification as a problem. Use the existing FRONTEND_ENVS,
classify, and mismatched/deployEnv logic to report which of the three keys are
absent or not matching the expected environment.

@AndresL230
AndresL230 merged commit c515314 into mainJul 3, 2026
6 checks passed
@AndresL230
AndresL230 deleted the fix/frontend-prod-deploy-guard branch August 2, 2026 18:30
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@AndresL230
, '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(frontend): guard prod builds against staging deploy-config leak - #309

Merged
AndresL230 merged 1 commit into
mainfrom
fix/frontend-prod-deploy-guard
Jul 3, 2026
Merged

fix(frontend): guard prod builds against staging deploy-config leak#309
AndresL230 merged 1 commit into
mainfrom
fix/frontend-prod-deploy-guard

Conversation

@AndresL230

@AndresL230AndresL230 commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

Problem

The prod frontend Cloudflare Workers Build's deploy step was npx wrangler deploy --env staging. That applied wrangler.toml's [env.staging.vars] — staging API URLs + a .staging.saplinglearn.com cookie domain — to the prod worker (wrangler even logged that it overrode the worker name frontend-stagingfrontend). Sign-in on saplinglearn.com broke: the middleware routed auth to the staging backend and the sapling_session cookie was scoped to .staging, so it never stuck.

These values bake at build time (NEXT_PUBLIC_API_URL is inlined; the /api rewrite uses BACKEND_URL), so runtime wrangler.toml [vars] couldn't correct a build that already shipped the wrong values.

Fix

Add checkFrontendDeployEnv() (src/lib/deployGuard.ts), called from next.config.ts on production builds (alongside the existing BACKEND_URL guard):

  • Consistency (always on, no config): fail if NEXT_PUBLIC_API_URL / BACKEND_URL / COOKIE_DOMAIN describe more than one environment (e.g. prod API URL + .staging cookie domain).
  • Env-lock (opt-in via DEPLOY_ENV): when set, fail if any value doesn't match that environment — this catches an all-staging build shipped to the prod worker (the exact bug), which the consistency check alone can't detect.

Operator follow-up (not code — do in the Cloudflare dashboard)

  1. Prod frontend Workers Build → Deploy command: npx wrangler deploy --env stagingnpx wrangler deploy.
  2. Set DEPLOY_ENV=production (prod build var) and DEPLOY_ENV=staging (staging build var) to activate the exact-match check.

Tests

src/lib/deployGuard.test.ts — 9 cases: clean prod/staging, matching DEPLOY_ENV, split-brain mix, all-staging-on-prod, bad DEPLOY_ENV, unset/no-op (local dev), non-canonical preview URLs, whitespace/case tolerance. tsc --noEmit + eslint clean.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added stronger production build checks for frontend deployments.
    • Deployment settings are now validated together to catch mismatched staging/prod configurations earlier.
  • Bug Fixes
    • Builds now fail with clear guidance when deployment variables don’t align.
    • Improved handling of unknown or preview values so they don’t trigger false failures.
  • Tests
    • Added coverage for matching, mismatched, and malformed deployment environments.

The prod `frontend` Cloudflare Workers Build deployed with `--env staging`,
baking staging API URLs + a `.staging` cookie domain onto saplinglearn.com,
which broke sign-in (middleware routed auth to the staging backend; the session
cookie was scoped to `.staging` and never stuck).
Add checkFrontendDeployEnv() (src/lib/deployGuard.ts), called from next.config.ts
on production builds: fail the build if NEXT_PUBLIC_API_URL / BACKEND_URL /
COOKIE_DOMAIN mix environments, or — when DEPLOY_ENV is set — don't match the
intended one. Runtime wrangler.toml [vars] can't fix a build that already baked
the wrong values, so this catches it at build time.
Set DEPLOY_ENV=production on the prod Workers Build and DEPLOY_ENV=staging on the
staging one to activate the exact-match check.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@cloudflare-workers-and-pages

cloudflare-workers-and-pagesBot commented Jul 3, 2026

Copy link
Copy Markdown

Deploying with Cloudflare Workers Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

StatusNameLatest CommitPreview URLUpdated (UTC)
✅ Deployment successful!
View logs
frontend-stagingdc93218Commit Preview URL

Branch Preview URL
Jul 03 2026, 02:29 AM

@coderabbitai

coderabbitaiBot commented Jul 3, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a new deployGuard.ts module exporting checkFrontendDeployEnv to detect mismatched/mixed staging and production deployment environment variables, wires this check into next.config.ts's production build guard to fail builds on misconfiguration, and adds a corresponding Vitest test suite.

Changes

Frontend deploy environment guard

Layer / File(s)Summary
Canonical env definitions and classifier
frontend/src/lib/deployGuard.ts
Defines FRONTEND_ENVS canonical apiUrl/cookieDomain values, FrontendEnv type, EnvSource type, and a classify() helper that normalizes and matches env values to canonical environments.
Consistency and DEPLOY_ENV lock check
frontend/src/lib/deployGuard.ts
Implements exported checkFrontendDeployEnv(env) which classifies NEXT_PUBLIC_API_URL, BACKEND_URL, COOKIE_DOMAIN, checks for mixed environments, validates against an explicit DEPLOY_ENV, and returns a list of problem messages.
Build wiring and tests
frontend/next.config.ts, frontend/src/lib/deployGuard.test.ts
Imports and invokes checkFrontendDeployEnv during production builds, throwing a detailed error when problems are found; adds tests covering pass/fail cases, mismatch detection, and normalization tolerance.

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

Possibly related PRs

  • SaplingLearn/Sapling#281: Both PRs modify frontend/next.config.ts to fail production builds on missing/misconfigured deployment env vars, with this PR generalizing the earlier BACKEND_URL-only check into the new checkFrontendDeployEnv guard.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Title check✅ PassedThe title is concise and accurately summarizes the main change: preventing prod builds from using staging deploy config.
Description check✅ PassedThe description covers the problem, fix, follow-up actions, and testing, with only minor template fields like related issues and screenshots omitted.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/frontend-prod-deploy-guard

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

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)
frontend/src/lib/deployGuard.test.ts (1)

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

Add coverage for missing/malformed vars under an explicit DEPLOY_ENV lock.

Current cases only exercise wrong-but-canonical values (all-staging on prod) and unknown DEPLOY_ENV. Consider adding a case where DEPLOY_ENV=production but NEXT_PUBLIC_API_URL is unset or non-canonical (e.g., a typo'd URL) — this currently passes silently per the gap noted in deployGuard.ts, and a test would make that limitation explicit or catch a fix regression.

🤖 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 `@frontend/src/lib/deployGuard.test.ts` around lines 33 - 41, Add a test in
deployGuard.test.ts that exercises the explicit DEPLOY_ENV lock in
checkFrontendDeployEnv with DEPLOY_ENV set to production while
NEXT_PUBLIC_API_URL is missing or malformed, so the gap in deployGuard.ts is
covered. Use the existing checkFrontendDeployEnv helper and the STAGING/PROD
fixtures as the starting point, then assert that the returned problems include
the expected missing/invalid URL validation instead of passing silently.
🤖 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 `@frontend/src/lib/deployGuard.ts`:
- Around line 86-104: The DEPLOY_ENV check in deployGuard.ts only flags entries
already classified by classify(), so missing, blank, or malformed values can
slip through unnoticed. Update the DEPLOY_ENV branch in deployGuard to require
NEXT_PUBLIC_API_URL, BACKEND_URL, and COOKIE_DOMAIN to each classify to the
active deployEnv, and treat any null/omitted classification as a problem. Use
the existing FRONTEND_ENVS, classify, and mismatched/deployEnv logic to report
which of the three keys are absent or not matching the expected environment.
---
Nitpick comments:
In `@frontend/src/lib/deployGuard.test.ts`:
- Around line 33-41: Add a test in deployGuard.test.ts that exercises the
explicit DEPLOY_ENV lock in checkFrontendDeployEnv with DEPLOY_ENV set to
production while NEXT_PUBLIC_API_URL is missing or malformed, so the gap in
deployGuard.ts is covered. Use the existing checkFrontendDeployEnv helper and
the STAGING/PROD fixtures as the starting point, then assert that the returned
problems include the expected missing/invalid URL validation instead of passing
silently.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ca709c59-9fd9-49be-b01a-e4c99fcc4e10

📥 Commits

Reviewing files that changed from the base of the PR and between f472300 and dc93218.

📒 Files selected for processing (3)
  • frontend/next.config.ts
  • frontend/src/lib/deployGuard.test.ts
  • frontend/src/lib/deployGuard.ts

Comment on lines +86 to +104
const deployEnv = (env.DEPLOY_ENV ?? '').trim().toLowerCase();
if (deployEnv) {
if (!(deployEnv in FRONTEND_ENVS)) {
problems.push(
`DEPLOY_ENV must be one of ${Object.keys(FRONTEND_ENVS).join(' | ')}, ` +
`got ${JSON.stringify(env.DEPLOY_ENV)}`,
);
} else {
const want = FRONTEND_ENVS[deployEnv as FrontendEnv];
const mismatched = Object.entries(classified).filter(([, v]) => v !== deployEnv);
if (mismatched.length) {
const detail = mismatched.map(([k, v]) => `${k}→${v}`).join(', ');
problems.push(
`DEPLOY_ENV=${deployEnv} but ${detail} point elsewhere; expected ` +
`NEXT_PUBLIC_API_URL/BACKEND_URL=${want.apiUrl}, COOKIE_DOMAIN=${want.cookieDomain}`,
);
}
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

DEPLOY_ENV lock silently passes when a var is missing or malformed, not just wrong-environment.

The mismatch check on Line 95 only inspects classified entries — i.e., values that exactly matched a canonical FRONTEND_ENVS string. If NEXT_PUBLIC_API_URL/BACKEND_URL/COOKIE_DOMAIN is unset, blank, or subtly malformed (trailing slash, wrong subdomain, http vs https), classify() returns null and the key is simply omitted from classified, so it never appears in mismatched and is never flagged. With DEPLOY_ENV=production set, a build where the API URL is accidentally empty or wrong-but-non-canonical will report zero problems — exactly the "silent misconfiguration reaches prod" failure mode this guard exists to prevent, just via a different vector (missing/malformed value) than the original bug (correct-but-wrong-environment value).

Consider requiring all three keys to be classified (matching the intended deployEnv) when DEPLOY_ENV is set, rather than only checking values that already classified successfully:

💡 Proposed fix
 } else {
const want = FRONTEND_ENVS[deployEnv as FrontendEnv];
- const mismatched = Object.entries(classified).filter(([, v]) => v !== deployEnv);+ const keys = ['NEXT_PUBLIC_API_URL', 'BACKEND_URL', 'COOKIE_DOMAIN'] as const;+ const mismatched = keys+ .filter((k) => env[k] !== undefined && env[k] !== '')+ .map((k) => [k, classified[k] ?? 'unrecognized'] as const)+ .filter(([, v]) => v !== deployEnv);
if (mismatched.length) {
🤖 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 `@frontend/src/lib/deployGuard.ts` around lines 86 - 104, The DEPLOY_ENV check
in deployGuard.ts only flags entries already classified by classify(), so
missing, blank, or malformed values can slip through unnoticed. Update the
DEPLOY_ENV branch in deployGuard to require NEXT_PUBLIC_API_URL, BACKEND_URL,
and COOKIE_DOMAIN to each classify to the active deployEnv, and treat any
null/omitted classification as a problem. Use the existing FRONTEND_ENVS,
classify, and mismatched/deployEnv logic to report which of the three keys are
absent or not matching the expected environment.

@AndresL230
AndresL230 merged commit c515314 into mainJul 3, 2026
6 checks passed
@AndresL230
AndresL230 deleted the fix/frontend-prod-deploy-guard branch August 2, 2026 18:30
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@AndresL230
, '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(frontend): guard prod builds against staging deploy-config leak - #309

Merged
AndresL230 merged 1 commit into
mainfrom
fix/frontend-prod-deploy-guard
Jul 3, 2026
Merged

fix(frontend): guard prod builds against staging deploy-config leak#309
AndresL230 merged 1 commit into
mainfrom
fix/frontend-prod-deploy-guard

Conversation

@AndresL230

@AndresL230AndresL230 commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

Problem

The prod frontend Cloudflare Workers Build's deploy step was npx wrangler deploy --env staging. That applied wrangler.toml's [env.staging.vars] — staging API URLs + a .staging.saplinglearn.com cookie domain — to the prod worker (wrangler even logged that it overrode the worker name frontend-stagingfrontend). Sign-in on saplinglearn.com broke: the middleware routed auth to the staging backend and the sapling_session cookie was scoped to .staging, so it never stuck.

These values bake at build time (NEXT_PUBLIC_API_URL is inlined; the /api rewrite uses BACKEND_URL), so runtime wrangler.toml [vars] couldn't correct a build that already shipped the wrong values.

Fix

Add checkFrontendDeployEnv() (src/lib/deployGuard.ts), called from next.config.ts on production builds (alongside the existing BACKEND_URL guard):

  • Consistency (always on, no config): fail if NEXT_PUBLIC_API_URL / BACKEND_URL / COOKIE_DOMAIN describe more than one environment (e.g. prod API URL + .staging cookie domain).
  • Env-lock (opt-in via DEPLOY_ENV): when set, fail if any value doesn't match that environment — this catches an all-staging build shipped to the prod worker (the exact bug), which the consistency check alone can't detect.

Operator follow-up (not code — do in the Cloudflare dashboard)

  1. Prod frontend Workers Build → Deploy command: npx wrangler deploy --env stagingnpx wrangler deploy.
  2. Set DEPLOY_ENV=production (prod build var) and DEPLOY_ENV=staging (staging build var) to activate the exact-match check.

Tests

src/lib/deployGuard.test.ts — 9 cases: clean prod/staging, matching DEPLOY_ENV, split-brain mix, all-staging-on-prod, bad DEPLOY_ENV, unset/no-op (local dev), non-canonical preview URLs, whitespace/case tolerance. tsc --noEmit + eslint clean.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added stronger production build checks for frontend deployments.
    • Deployment settings are now validated together to catch mismatched staging/prod configurations earlier.
  • Bug Fixes
    • Builds now fail with clear guidance when deployment variables don’t align.
    • Improved handling of unknown or preview values so they don’t trigger false failures.
  • Tests
    • Added coverage for matching, mismatched, and malformed deployment environments.

The prod `frontend` Cloudflare Workers Build deployed with `--env staging`,
baking staging API URLs + a `.staging` cookie domain onto saplinglearn.com,
which broke sign-in (middleware routed auth to the staging backend; the session
cookie was scoped to `.staging` and never stuck).
Add checkFrontendDeployEnv() (src/lib/deployGuard.ts), called from next.config.ts
on production builds: fail the build if NEXT_PUBLIC_API_URL / BACKEND_URL /
COOKIE_DOMAIN mix environments, or — when DEPLOY_ENV is set — don't match the
intended one. Runtime wrangler.toml [vars] can't fix a build that already baked
the wrong values, so this catches it at build time.
Set DEPLOY_ENV=production on the prod Workers Build and DEPLOY_ENV=staging on the
staging one to activate the exact-match check.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@cloudflare-workers-and-pages

cloudflare-workers-and-pagesBot commented Jul 3, 2026

Copy link
Copy Markdown

Deploying with Cloudflare Workers Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

StatusNameLatest CommitPreview URLUpdated (UTC)
✅ Deployment successful!
View logs
frontend-stagingdc93218Commit Preview URL

Branch Preview URL
Jul 03 2026, 02:29 AM

@coderabbitai

coderabbitaiBot commented Jul 3, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a new deployGuard.ts module exporting checkFrontendDeployEnv to detect mismatched/mixed staging and production deployment environment variables, wires this check into next.config.ts's production build guard to fail builds on misconfiguration, and adds a corresponding Vitest test suite.

Changes

Frontend deploy environment guard

Layer / File(s)Summary
Canonical env definitions and classifier
frontend/src/lib/deployGuard.ts
Defines FRONTEND_ENVS canonical apiUrl/cookieDomain values, FrontendEnv type, EnvSource type, and a classify() helper that normalizes and matches env values to canonical environments.
Consistency and DEPLOY_ENV lock check
frontend/src/lib/deployGuard.ts
Implements exported checkFrontendDeployEnv(env) which classifies NEXT_PUBLIC_API_URL, BACKEND_URL, COOKIE_DOMAIN, checks for mixed environments, validates against an explicit DEPLOY_ENV, and returns a list of problem messages.
Build wiring and tests
frontend/next.config.ts, frontend/src/lib/deployGuard.test.ts
Imports and invokes checkFrontendDeployEnv during production builds, throwing a detailed error when problems are found; adds tests covering pass/fail cases, mismatch detection, and normalization tolerance.

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

Possibly related PRs

  • SaplingLearn/Sapling#281: Both PRs modify frontend/next.config.ts to fail production builds on missing/misconfigured deployment env vars, with this PR generalizing the earlier BACKEND_URL-only check into the new checkFrontendDeployEnv guard.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Title check✅ PassedThe title is concise and accurately summarizes the main change: preventing prod builds from using staging deploy config.
Description check✅ PassedThe description covers the problem, fix, follow-up actions, and testing, with only minor template fields like related issues and screenshots omitted.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/frontend-prod-deploy-guard

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

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)
frontend/src/lib/deployGuard.test.ts (1)

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

Add coverage for missing/malformed vars under an explicit DEPLOY_ENV lock.

Current cases only exercise wrong-but-canonical values (all-staging on prod) and unknown DEPLOY_ENV. Consider adding a case where DEPLOY_ENV=production but NEXT_PUBLIC_API_URL is unset or non-canonical (e.g., a typo'd URL) — this currently passes silently per the gap noted in deployGuard.ts, and a test would make that limitation explicit or catch a fix regression.

🤖 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 `@frontend/src/lib/deployGuard.test.ts` around lines 33 - 41, Add a test in
deployGuard.test.ts that exercises the explicit DEPLOY_ENV lock in
checkFrontendDeployEnv with DEPLOY_ENV set to production while
NEXT_PUBLIC_API_URL is missing or malformed, so the gap in deployGuard.ts is
covered. Use the existing checkFrontendDeployEnv helper and the STAGING/PROD
fixtures as the starting point, then assert that the returned problems include
the expected missing/invalid URL validation instead of passing silently.
🤖 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 `@frontend/src/lib/deployGuard.ts`:
- Around line 86-104: The DEPLOY_ENV check in deployGuard.ts only flags entries
already classified by classify(), so missing, blank, or malformed values can
slip through unnoticed. Update the DEPLOY_ENV branch in deployGuard to require
NEXT_PUBLIC_API_URL, BACKEND_URL, and COOKIE_DOMAIN to each classify to the
active deployEnv, and treat any null/omitted classification as a problem. Use
the existing FRONTEND_ENVS, classify, and mismatched/deployEnv logic to report
which of the three keys are absent or not matching the expected environment.
---
Nitpick comments:
In `@frontend/src/lib/deployGuard.test.ts`:
- Around line 33-41: Add a test in deployGuard.test.ts that exercises the
explicit DEPLOY_ENV lock in checkFrontendDeployEnv with DEPLOY_ENV set to
production while NEXT_PUBLIC_API_URL is missing or malformed, so the gap in
deployGuard.ts is covered. Use the existing checkFrontendDeployEnv helper and
the STAGING/PROD fixtures as the starting point, then assert that the returned
problems include the expected missing/invalid URL validation instead of passing
silently.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ca709c59-9fd9-49be-b01a-e4c99fcc4e10

📥 Commits

Reviewing files that changed from the base of the PR and between f472300 and dc93218.

📒 Files selected for processing (3)
  • frontend/next.config.ts
  • frontend/src/lib/deployGuard.test.ts
  • frontend/src/lib/deployGuard.ts

Comment on lines +86 to +104
const deployEnv = (env.DEPLOY_ENV ?? '').trim().toLowerCase();
if (deployEnv) {
if (!(deployEnv in FRONTEND_ENVS)) {
problems.push(
`DEPLOY_ENV must be one of ${Object.keys(FRONTEND_ENVS).join(' | ')}, ` +
`got ${JSON.stringify(env.DEPLOY_ENV)}`,
);
} else {
const want = FRONTEND_ENVS[deployEnv as FrontendEnv];
const mismatched = Object.entries(classified).filter(([, v]) => v !== deployEnv);
if (mismatched.length) {
const detail = mismatched.map(([k, v]) => `${k}→${v}`).join(', ');
problems.push(
`DEPLOY_ENV=${deployEnv} but ${detail} point elsewhere; expected ` +
`NEXT_PUBLIC_API_URL/BACKEND_URL=${want.apiUrl}, COOKIE_DOMAIN=${want.cookieDomain}`,
);
}
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

DEPLOY_ENV lock silently passes when a var is missing or malformed, not just wrong-environment.

The mismatch check on Line 95 only inspects classified entries — i.e., values that exactly matched a canonical FRONTEND_ENVS string. If NEXT_PUBLIC_API_URL/BACKEND_URL/COOKIE_DOMAIN is unset, blank, or subtly malformed (trailing slash, wrong subdomain, http vs https), classify() returns null and the key is simply omitted from classified, so it never appears in mismatched and is never flagged. With DEPLOY_ENV=production set, a build where the API URL is accidentally empty or wrong-but-non-canonical will report zero problems — exactly the "silent misconfiguration reaches prod" failure mode this guard exists to prevent, just via a different vector (missing/malformed value) than the original bug (correct-but-wrong-environment value).

Consider requiring all three keys to be classified (matching the intended deployEnv) when DEPLOY_ENV is set, rather than only checking values that already classified successfully:

💡 Proposed fix
 } else {
const want = FRONTEND_ENVS[deployEnv as FrontendEnv];
- const mismatched = Object.entries(classified).filter(([, v]) => v !== deployEnv);+ const keys = ['NEXT_PUBLIC_API_URL', 'BACKEND_URL', 'COOKIE_DOMAIN'] as const;+ const mismatched = keys+ .filter((k) => env[k] !== undefined && env[k] !== '')+ .map((k) => [k, classified[k] ?? 'unrecognized'] as const)+ .filter(([, v]) => v !== deployEnv);
if (mismatched.length) {
🤖 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 `@frontend/src/lib/deployGuard.ts` around lines 86 - 104, The DEPLOY_ENV check
in deployGuard.ts only flags entries already classified by classify(), so
missing, blank, or malformed values can slip through unnoticed. Update the
DEPLOY_ENV branch in deployGuard to require NEXT_PUBLIC_API_URL, BACKEND_URL,
and COOKIE_DOMAIN to each classify to the active deployEnv, and treat any
null/omitted classification as a problem. Use the existing FRONTEND_ENVS,
classify, and mismatched/deployEnv logic to report which of the three keys are
absent or not matching the expected environment.

@AndresL230
AndresL230 merged commit c515314 into mainJul 3, 2026
6 checks passed
@AndresL230
AndresL230 deleted the fix/frontend-prod-deploy-guard branch August 2, 2026 18:30
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@AndresL230
, '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(frontend): guard prod builds against staging deploy-config leak - #309

Merged
AndresL230 merged 1 commit into
mainfrom
fix/frontend-prod-deploy-guard
Jul 3, 2026
Merged

fix(frontend): guard prod builds against staging deploy-config leak#309
AndresL230 merged 1 commit into
mainfrom
fix/frontend-prod-deploy-guard

Conversation

@AndresL230

@AndresL230AndresL230 commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

Problem

The prod frontend Cloudflare Workers Build's deploy step was npx wrangler deploy --env staging. That applied wrangler.toml's [env.staging.vars] — staging API URLs + a .staging.saplinglearn.com cookie domain — to the prod worker (wrangler even logged that it overrode the worker name frontend-stagingfrontend). Sign-in on saplinglearn.com broke: the middleware routed auth to the staging backend and the sapling_session cookie was scoped to .staging, so it never stuck.

These values bake at build time (NEXT_PUBLIC_API_URL is inlined; the /api rewrite uses BACKEND_URL), so runtime wrangler.toml [vars] couldn't correct a build that already shipped the wrong values.

Fix

Add checkFrontendDeployEnv() (src/lib/deployGuard.ts), called from next.config.ts on production builds (alongside the existing BACKEND_URL guard):

  • Consistency (always on, no config): fail if NEXT_PUBLIC_API_URL / BACKEND_URL / COOKIE_DOMAIN describe more than one environment (e.g. prod API URL + .staging cookie domain).
  • Env-lock (opt-in via DEPLOY_ENV): when set, fail if any value doesn't match that environment — this catches an all-staging build shipped to the prod worker (the exact bug), which the consistency check alone can't detect.

Operator follow-up (not code — do in the Cloudflare dashboard)

  1. Prod frontend Workers Build → Deploy command: npx wrangler deploy --env stagingnpx wrangler deploy.
  2. Set DEPLOY_ENV=production (prod build var) and DEPLOY_ENV=staging (staging build var) to activate the exact-match check.

Tests

src/lib/deployGuard.test.ts — 9 cases: clean prod/staging, matching DEPLOY_ENV, split-brain mix, all-staging-on-prod, bad DEPLOY_ENV, unset/no-op (local dev), non-canonical preview URLs, whitespace/case tolerance. tsc --noEmit + eslint clean.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added stronger production build checks for frontend deployments.
    • Deployment settings are now validated together to catch mismatched staging/prod configurations earlier.
  • Bug Fixes
    • Builds now fail with clear guidance when deployment variables don’t align.
    • Improved handling of unknown or preview values so they don’t trigger false failures.
  • Tests
    • Added coverage for matching, mismatched, and malformed deployment environments.

The prod `frontend` Cloudflare Workers Build deployed with `--env staging`,
baking staging API URLs + a `.staging` cookie domain onto saplinglearn.com,
which broke sign-in (middleware routed auth to the staging backend; the session
cookie was scoped to `.staging` and never stuck).
Add checkFrontendDeployEnv() (src/lib/deployGuard.ts), called from next.config.ts
on production builds: fail the build if NEXT_PUBLIC_API_URL / BACKEND_URL /
COOKIE_DOMAIN mix environments, or — when DEPLOY_ENV is set — don't match the
intended one. Runtime wrangler.toml [vars] can't fix a build that already baked
the wrong values, so this catches it at build time.
Set DEPLOY_ENV=production on the prod Workers Build and DEPLOY_ENV=staging on the
staging one to activate the exact-match check.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@cloudflare-workers-and-pages

cloudflare-workers-and-pagesBot commented Jul 3, 2026

Copy link
Copy Markdown

Deploying with Cloudflare Workers Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

StatusNameLatest CommitPreview URLUpdated (UTC)
✅ Deployment successful!
View logs
frontend-stagingdc93218Commit Preview URL

Branch Preview URL
Jul 03 2026, 02:29 AM

@coderabbitai

coderabbitaiBot commented Jul 3, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a new deployGuard.ts module exporting checkFrontendDeployEnv to detect mismatched/mixed staging and production deployment environment variables, wires this check into next.config.ts's production build guard to fail builds on misconfiguration, and adds a corresponding Vitest test suite.

Changes

Frontend deploy environment guard

Layer / File(s)Summary
Canonical env definitions and classifier
frontend/src/lib/deployGuard.ts
Defines FRONTEND_ENVS canonical apiUrl/cookieDomain values, FrontendEnv type, EnvSource type, and a classify() helper that normalizes and matches env values to canonical environments.
Consistency and DEPLOY_ENV lock check
frontend/src/lib/deployGuard.ts
Implements exported checkFrontendDeployEnv(env) which classifies NEXT_PUBLIC_API_URL, BACKEND_URL, COOKIE_DOMAIN, checks for mixed environments, validates against an explicit DEPLOY_ENV, and returns a list of problem messages.
Build wiring and tests
frontend/next.config.ts, frontend/src/lib/deployGuard.test.ts
Imports and invokes checkFrontendDeployEnv during production builds, throwing a detailed error when problems are found; adds tests covering pass/fail cases, mismatch detection, and normalization tolerance.

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

Possibly related PRs

  • SaplingLearn/Sapling#281: Both PRs modify frontend/next.config.ts to fail production builds on missing/misconfigured deployment env vars, with this PR generalizing the earlier BACKEND_URL-only check into the new checkFrontendDeployEnv guard.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Title check✅ PassedThe title is concise and accurately summarizes the main change: preventing prod builds from using staging deploy config.
Description check✅ PassedThe description covers the problem, fix, follow-up actions, and testing, with only minor template fields like related issues and screenshots omitted.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/frontend-prod-deploy-guard

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

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)
frontend/src/lib/deployGuard.test.ts (1)

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

Add coverage for missing/malformed vars under an explicit DEPLOY_ENV lock.

Current cases only exercise wrong-but-canonical values (all-staging on prod) and unknown DEPLOY_ENV. Consider adding a case where DEPLOY_ENV=production but NEXT_PUBLIC_API_URL is unset or non-canonical (e.g., a typo'd URL) — this currently passes silently per the gap noted in deployGuard.ts, and a test would make that limitation explicit or catch a fix regression.

🤖 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 `@frontend/src/lib/deployGuard.test.ts` around lines 33 - 41, Add a test in
deployGuard.test.ts that exercises the explicit DEPLOY_ENV lock in
checkFrontendDeployEnv with DEPLOY_ENV set to production while
NEXT_PUBLIC_API_URL is missing or malformed, so the gap in deployGuard.ts is
covered. Use the existing checkFrontendDeployEnv helper and the STAGING/PROD
fixtures as the starting point, then assert that the returned problems include
the expected missing/invalid URL validation instead of passing silently.
🤖 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 `@frontend/src/lib/deployGuard.ts`:
- Around line 86-104: The DEPLOY_ENV check in deployGuard.ts only flags entries
already classified by classify(), so missing, blank, or malformed values can
slip through unnoticed. Update the DEPLOY_ENV branch in deployGuard to require
NEXT_PUBLIC_API_URL, BACKEND_URL, and COOKIE_DOMAIN to each classify to the
active deployEnv, and treat any null/omitted classification as a problem. Use
the existing FRONTEND_ENVS, classify, and mismatched/deployEnv logic to report
which of the three keys are absent or not matching the expected environment.
---
Nitpick comments:
In `@frontend/src/lib/deployGuard.test.ts`:
- Around line 33-41: Add a test in deployGuard.test.ts that exercises the
explicit DEPLOY_ENV lock in checkFrontendDeployEnv with DEPLOY_ENV set to
production while NEXT_PUBLIC_API_URL is missing or malformed, so the gap in
deployGuard.ts is covered. Use the existing checkFrontendDeployEnv helper and
the STAGING/PROD fixtures as the starting point, then assert that the returned
problems include the expected missing/invalid URL validation instead of passing
silently.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ca709c59-9fd9-49be-b01a-e4c99fcc4e10

📥 Commits

Reviewing files that changed from the base of the PR and between f472300 and dc93218.

📒 Files selected for processing (3)
  • frontend/next.config.ts
  • frontend/src/lib/deployGuard.test.ts
  • frontend/src/lib/deployGuard.ts

Comment on lines +86 to +104
const deployEnv = (env.DEPLOY_ENV ?? '').trim().toLowerCase();
if (deployEnv) {
if (!(deployEnv in FRONTEND_ENVS)) {
problems.push(
`DEPLOY_ENV must be one of ${Object.keys(FRONTEND_ENVS).join(' | ')}, ` +
`got ${JSON.stringify(env.DEPLOY_ENV)}`,
);
} else {
const want = FRONTEND_ENVS[deployEnv as FrontendEnv];
const mismatched = Object.entries(classified).filter(([, v]) => v !== deployEnv);
if (mismatched.length) {
const detail = mismatched.map(([k, v]) => `${k}→${v}`).join(', ');
problems.push(
`DEPLOY_ENV=${deployEnv} but ${detail} point elsewhere; expected ` +
`NEXT_PUBLIC_API_URL/BACKEND_URL=${want.apiUrl}, COOKIE_DOMAIN=${want.cookieDomain}`,
);
}
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

DEPLOY_ENV lock silently passes when a var is missing or malformed, not just wrong-environment.

The mismatch check on Line 95 only inspects classified entries — i.e., values that exactly matched a canonical FRONTEND_ENVS string. If NEXT_PUBLIC_API_URL/BACKEND_URL/COOKIE_DOMAIN is unset, blank, or subtly malformed (trailing slash, wrong subdomain, http vs https), classify() returns null and the key is simply omitted from classified, so it never appears in mismatched and is never flagged. With DEPLOY_ENV=production set, a build where the API URL is accidentally empty or wrong-but-non-canonical will report zero problems — exactly the "silent misconfiguration reaches prod" failure mode this guard exists to prevent, just via a different vector (missing/malformed value) than the original bug (correct-but-wrong-environment value).

Consider requiring all three keys to be classified (matching the intended deployEnv) when DEPLOY_ENV is set, rather than only checking values that already classified successfully:

💡 Proposed fix
 } else {
const want = FRONTEND_ENVS[deployEnv as FrontendEnv];
- const mismatched = Object.entries(classified).filter(([, v]) => v !== deployEnv);+ const keys = ['NEXT_PUBLIC_API_URL', 'BACKEND_URL', 'COOKIE_DOMAIN'] as const;+ const mismatched = keys+ .filter((k) => env[k] !== undefined && env[k] !== '')+ .map((k) => [k, classified[k] ?? 'unrecognized'] as const)+ .filter(([, v]) => v !== deployEnv);
if (mismatched.length) {
🤖 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 `@frontend/src/lib/deployGuard.ts` around lines 86 - 104, The DEPLOY_ENV check
in deployGuard.ts only flags entries already classified by classify(), so
missing, blank, or malformed values can slip through unnoticed. Update the
DEPLOY_ENV branch in deployGuard to require NEXT_PUBLIC_API_URL, BACKEND_URL,
and COOKIE_DOMAIN to each classify to the active deployEnv, and treat any
null/omitted classification as a problem. Use the existing FRONTEND_ENVS,
classify, and mismatched/deployEnv logic to report which of the three keys are
absent or not matching the expected environment.

@AndresL230
AndresL230 merged commit c515314 into mainJul 3, 2026
6 checks passed
@AndresL230
AndresL230 deleted the fix/frontend-prod-deploy-guard branch August 2, 2026 18:30
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@AndresL230
, '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(frontend): guard prod builds against staging deploy-config leak - #309

Merged
AndresL230 merged 1 commit into
mainfrom
fix/frontend-prod-deploy-guard
Jul 3, 2026
Merged

fix(frontend): guard prod builds against staging deploy-config leak#309
AndresL230 merged 1 commit into
mainfrom
fix/frontend-prod-deploy-guard

Conversation

@AndresL230

@AndresL230AndresL230 commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

Problem

The prod frontend Cloudflare Workers Build's deploy step was npx wrangler deploy --env staging. That applied wrangler.toml's [env.staging.vars] — staging API URLs + a .staging.saplinglearn.com cookie domain — to the prod worker (wrangler even logged that it overrode the worker name frontend-stagingfrontend). Sign-in on saplinglearn.com broke: the middleware routed auth to the staging backend and the sapling_session cookie was scoped to .staging, so it never stuck.

These values bake at build time (NEXT_PUBLIC_API_URL is inlined; the /api rewrite uses BACKEND_URL), so runtime wrangler.toml [vars] couldn't correct a build that already shipped the wrong values.

Fix

Add checkFrontendDeployEnv() (src/lib/deployGuard.ts), called from next.config.ts on production builds (alongside the existing BACKEND_URL guard):

  • Consistency (always on, no config): fail if NEXT_PUBLIC_API_URL / BACKEND_URL / COOKIE_DOMAIN describe more than one environment (e.g. prod API URL + .staging cookie domain).
  • Env-lock (opt-in via DEPLOY_ENV): when set, fail if any value doesn't match that environment — this catches an all-staging build shipped to the prod worker (the exact bug), which the consistency check alone can't detect.

Operator follow-up (not code — do in the Cloudflare dashboard)

  1. Prod frontend Workers Build → Deploy command: npx wrangler deploy --env stagingnpx wrangler deploy.
  2. Set DEPLOY_ENV=production (prod build var) and DEPLOY_ENV=staging (staging build var) to activate the exact-match check.

Tests

src/lib/deployGuard.test.ts — 9 cases: clean prod/staging, matching DEPLOY_ENV, split-brain mix, all-staging-on-prod, bad DEPLOY_ENV, unset/no-op (local dev), non-canonical preview URLs, whitespace/case tolerance. tsc --noEmit + eslint clean.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added stronger production build checks for frontend deployments.
    • Deployment settings are now validated together to catch mismatched staging/prod configurations earlier.
  • Bug Fixes
    • Builds now fail with clear guidance when deployment variables don’t align.
    • Improved handling of unknown or preview values so they don’t trigger false failures.
  • Tests
    • Added coverage for matching, mismatched, and malformed deployment environments.

The prod `frontend` Cloudflare Workers Build deployed with `--env staging`,
baking staging API URLs + a `.staging` cookie domain onto saplinglearn.com,
which broke sign-in (middleware routed auth to the staging backend; the session
cookie was scoped to `.staging` and never stuck).
Add checkFrontendDeployEnv() (src/lib/deployGuard.ts), called from next.config.ts
on production builds: fail the build if NEXT_PUBLIC_API_URL / BACKEND_URL /
COOKIE_DOMAIN mix environments, or — when DEPLOY_ENV is set — don't match the
intended one. Runtime wrangler.toml [vars] can't fix a build that already baked
the wrong values, so this catches it at build time.
Set DEPLOY_ENV=production on the prod Workers Build and DEPLOY_ENV=staging on the
staging one to activate the exact-match check.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@cloudflare-workers-and-pages

cloudflare-workers-and-pagesBot commented Jul 3, 2026

Copy link
Copy Markdown

Deploying with Cloudflare Workers Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

StatusNameLatest CommitPreview URLUpdated (UTC)
✅ Deployment successful!
View logs
frontend-stagingdc93218Commit Preview URL

Branch Preview URL
Jul 03 2026, 02:29 AM

@coderabbitai

coderabbitaiBot commented Jul 3, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a new deployGuard.ts module exporting checkFrontendDeployEnv to detect mismatched/mixed staging and production deployment environment variables, wires this check into next.config.ts's production build guard to fail builds on misconfiguration, and adds a corresponding Vitest test suite.

Changes

Frontend deploy environment guard

Layer / File(s)Summary
Canonical env definitions and classifier
frontend/src/lib/deployGuard.ts
Defines FRONTEND_ENVS canonical apiUrl/cookieDomain values, FrontendEnv type, EnvSource type, and a classify() helper that normalizes and matches env values to canonical environments.
Consistency and DEPLOY_ENV lock check
frontend/src/lib/deployGuard.ts
Implements exported checkFrontendDeployEnv(env) which classifies NEXT_PUBLIC_API_URL, BACKEND_URL, COOKIE_DOMAIN, checks for mixed environments, validates against an explicit DEPLOY_ENV, and returns a list of problem messages.
Build wiring and tests
frontend/next.config.ts, frontend/src/lib/deployGuard.test.ts
Imports and invokes checkFrontendDeployEnv during production builds, throwing a detailed error when problems are found; adds tests covering pass/fail cases, mismatch detection, and normalization tolerance.

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

Possibly related PRs

  • SaplingLearn/Sapling#281: Both PRs modify frontend/next.config.ts to fail production builds on missing/misconfigured deployment env vars, with this PR generalizing the earlier BACKEND_URL-only check into the new checkFrontendDeployEnv guard.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Title check✅ PassedThe title is concise and accurately summarizes the main change: preventing prod builds from using staging deploy config.
Description check✅ PassedThe description covers the problem, fix, follow-up actions, and testing, with only minor template fields like related issues and screenshots omitted.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/frontend-prod-deploy-guard

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

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)
frontend/src/lib/deployGuard.test.ts (1)

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

Add coverage for missing/malformed vars under an explicit DEPLOY_ENV lock.

Current cases only exercise wrong-but-canonical values (all-staging on prod) and unknown DEPLOY_ENV. Consider adding a case where DEPLOY_ENV=production but NEXT_PUBLIC_API_URL is unset or non-canonical (e.g., a typo'd URL) — this currently passes silently per the gap noted in deployGuard.ts, and a test would make that limitation explicit or catch a fix regression.

🤖 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 `@frontend/src/lib/deployGuard.test.ts` around lines 33 - 41, Add a test in
deployGuard.test.ts that exercises the explicit DEPLOY_ENV lock in
checkFrontendDeployEnv with DEPLOY_ENV set to production while
NEXT_PUBLIC_API_URL is missing or malformed, so the gap in deployGuard.ts is
covered. Use the existing checkFrontendDeployEnv helper and the STAGING/PROD
fixtures as the starting point, then assert that the returned problems include
the expected missing/invalid URL validation instead of passing silently.
🤖 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 `@frontend/src/lib/deployGuard.ts`:
- Around line 86-104: The DEPLOY_ENV check in deployGuard.ts only flags entries
already classified by classify(), so missing, blank, or malformed values can
slip through unnoticed. Update the DEPLOY_ENV branch in deployGuard to require
NEXT_PUBLIC_API_URL, BACKEND_URL, and COOKIE_DOMAIN to each classify to the
active deployEnv, and treat any null/omitted classification as a problem. Use
the existing FRONTEND_ENVS, classify, and mismatched/deployEnv logic to report
which of the three keys are absent or not matching the expected environment.
---
Nitpick comments:
In `@frontend/src/lib/deployGuard.test.ts`:
- Around line 33-41: Add a test in deployGuard.test.ts that exercises the
explicit DEPLOY_ENV lock in checkFrontendDeployEnv with DEPLOY_ENV set to
production while NEXT_PUBLIC_API_URL is missing or malformed, so the gap in
deployGuard.ts is covered. Use the existing checkFrontendDeployEnv helper and
the STAGING/PROD fixtures as the starting point, then assert that the returned
problems include the expected missing/invalid URL validation instead of passing
silently.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ca709c59-9fd9-49be-b01a-e4c99fcc4e10

📥 Commits

Reviewing files that changed from the base of the PR and between f472300 and dc93218.

📒 Files selected for processing (3)
  • frontend/next.config.ts
  • frontend/src/lib/deployGuard.test.ts
  • frontend/src/lib/deployGuard.ts

Comment on lines +86 to +104
const deployEnv = (env.DEPLOY_ENV ?? '').trim().toLowerCase();
if (deployEnv) {
if (!(deployEnv in FRONTEND_ENVS)) {
problems.push(
`DEPLOY_ENV must be one of ${Object.keys(FRONTEND_ENVS).join(' | ')}, ` +
`got ${JSON.stringify(env.DEPLOY_ENV)}`,
);
} else {
const want = FRONTEND_ENVS[deployEnv as FrontendEnv];
const mismatched = Object.entries(classified).filter(([, v]) => v !== deployEnv);
if (mismatched.length) {
const detail = mismatched.map(([k, v]) => `${k}→${v}`).join(', ');
problems.push(
`DEPLOY_ENV=${deployEnv} but ${detail} point elsewhere; expected ` +
`NEXT_PUBLIC_API_URL/BACKEND_URL=${want.apiUrl}, COOKIE_DOMAIN=${want.cookieDomain}`,
);
}
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

DEPLOY_ENV lock silently passes when a var is missing or malformed, not just wrong-environment.

The mismatch check on Line 95 only inspects classified entries — i.e., values that exactly matched a canonical FRONTEND_ENVS string. If NEXT_PUBLIC_API_URL/BACKEND_URL/COOKIE_DOMAIN is unset, blank, or subtly malformed (trailing slash, wrong subdomain, http vs https), classify() returns null and the key is simply omitted from classified, so it never appears in mismatched and is never flagged. With DEPLOY_ENV=production set, a build where the API URL is accidentally empty or wrong-but-non-canonical will report zero problems — exactly the "silent misconfiguration reaches prod" failure mode this guard exists to prevent, just via a different vector (missing/malformed value) than the original bug (correct-but-wrong-environment value).

Consider requiring all three keys to be classified (matching the intended deployEnv) when DEPLOY_ENV is set, rather than only checking values that already classified successfully:

💡 Proposed fix
 } else {
const want = FRONTEND_ENVS[deployEnv as FrontendEnv];
- const mismatched = Object.entries(classified).filter(([, v]) => v !== deployEnv);+ const keys = ['NEXT_PUBLIC_API_URL', 'BACKEND_URL', 'COOKIE_DOMAIN'] as const;+ const mismatched = keys+ .filter((k) => env[k] !== undefined && env[k] !== '')+ .map((k) => [k, classified[k] ?? 'unrecognized'] as const)+ .filter(([, v]) => v !== deployEnv);
if (mismatched.length) {
🤖 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 `@frontend/src/lib/deployGuard.ts` around lines 86 - 104, The DEPLOY_ENV check
in deployGuard.ts only flags entries already classified by classify(), so
missing, blank, or malformed values can slip through unnoticed. Update the
DEPLOY_ENV branch in deployGuard to require NEXT_PUBLIC_API_URL, BACKEND_URL,
and COOKIE_DOMAIN to each classify to the active deployEnv, and treat any
null/omitted classification as a problem. Use the existing FRONTEND_ENVS,
classify, and mismatched/deployEnv logic to report which of the three keys are
absent or not matching the expected environment.

@AndresL230
AndresL230 merged commit c515314 into mainJul 3, 2026
6 checks passed
@AndresL230
AndresL230 deleted the fix/frontend-prod-deploy-guard branch August 2, 2026 18:30
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@AndresL230
, '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(frontend): guard prod builds against staging deploy-config leak - #309

Merged
AndresL230 merged 1 commit into
mainfrom
fix/frontend-prod-deploy-guard
Jul 3, 2026
Merged

fix(frontend): guard prod builds against staging deploy-config leak#309
AndresL230 merged 1 commit into
mainfrom
fix/frontend-prod-deploy-guard

Conversation

@AndresL230

@AndresL230AndresL230 commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

Problem

The prod frontend Cloudflare Workers Build's deploy step was npx wrangler deploy --env staging. That applied wrangler.toml's [env.staging.vars] — staging API URLs + a .staging.saplinglearn.com cookie domain — to the prod worker (wrangler even logged that it overrode the worker name frontend-stagingfrontend). Sign-in on saplinglearn.com broke: the middleware routed auth to the staging backend and the sapling_session cookie was scoped to .staging, so it never stuck.

These values bake at build time (NEXT_PUBLIC_API_URL is inlined; the /api rewrite uses BACKEND_URL), so runtime wrangler.toml [vars] couldn't correct a build that already shipped the wrong values.

Fix

Add checkFrontendDeployEnv() (src/lib/deployGuard.ts), called from next.config.ts on production builds (alongside the existing BACKEND_URL guard):

  • Consistency (always on, no config): fail if NEXT_PUBLIC_API_URL / BACKEND_URL / COOKIE_DOMAIN describe more than one environment (e.g. prod API URL + .staging cookie domain).
  • Env-lock (opt-in via DEPLOY_ENV): when set, fail if any value doesn't match that environment — this catches an all-staging build shipped to the prod worker (the exact bug), which the consistency check alone can't detect.

Operator follow-up (not code — do in the Cloudflare dashboard)

  1. Prod frontend Workers Build → Deploy command: npx wrangler deploy --env stagingnpx wrangler deploy.
  2. Set DEPLOY_ENV=production (prod build var) and DEPLOY_ENV=staging (staging build var) to activate the exact-match check.

Tests

src/lib/deployGuard.test.ts — 9 cases: clean prod/staging, matching DEPLOY_ENV, split-brain mix, all-staging-on-prod, bad DEPLOY_ENV, unset/no-op (local dev), non-canonical preview URLs, whitespace/case tolerance. tsc --noEmit + eslint clean.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added stronger production build checks for frontend deployments.
    • Deployment settings are now validated together to catch mismatched staging/prod configurations earlier.
  • Bug Fixes
    • Builds now fail with clear guidance when deployment variables don’t align.
    • Improved handling of unknown or preview values so they don’t trigger false failures.
  • Tests
    • Added coverage for matching, mismatched, and malformed deployment environments.

The prod `frontend` Cloudflare Workers Build deployed with `--env staging`,
baking staging API URLs + a `.staging` cookie domain onto saplinglearn.com,
which broke sign-in (middleware routed auth to the staging backend; the session
cookie was scoped to `.staging` and never stuck).
Add checkFrontendDeployEnv() (src/lib/deployGuard.ts), called from next.config.ts
on production builds: fail the build if NEXT_PUBLIC_API_URL / BACKEND_URL /
COOKIE_DOMAIN mix environments, or — when DEPLOY_ENV is set — don't match the
intended one. Runtime wrangler.toml [vars] can't fix a build that already baked
the wrong values, so this catches it at build time.
Set DEPLOY_ENV=production on the prod Workers Build and DEPLOY_ENV=staging on the
staging one to activate the exact-match check.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@cloudflare-workers-and-pages

cloudflare-workers-and-pagesBot commented Jul 3, 2026

Copy link
Copy Markdown

Deploying with Cloudflare Workers Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

StatusNameLatest CommitPreview URLUpdated (UTC)
✅ Deployment successful!
View logs
frontend-stagingdc93218Commit Preview URL

Branch Preview URL
Jul 03 2026, 02:29 AM

@coderabbitai

coderabbitaiBot commented Jul 3, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a new deployGuard.ts module exporting checkFrontendDeployEnv to detect mismatched/mixed staging and production deployment environment variables, wires this check into next.config.ts's production build guard to fail builds on misconfiguration, and adds a corresponding Vitest test suite.

Changes

Frontend deploy environment guard

Layer / File(s)Summary
Canonical env definitions and classifier
frontend/src/lib/deployGuard.ts
Defines FRONTEND_ENVS canonical apiUrl/cookieDomain values, FrontendEnv type, EnvSource type, and a classify() helper that normalizes and matches env values to canonical environments.
Consistency and DEPLOY_ENV lock check
frontend/src/lib/deployGuard.ts
Implements exported checkFrontendDeployEnv(env) which classifies NEXT_PUBLIC_API_URL, BACKEND_URL, COOKIE_DOMAIN, checks for mixed environments, validates against an explicit DEPLOY_ENV, and returns a list of problem messages.
Build wiring and tests
frontend/next.config.ts, frontend/src/lib/deployGuard.test.ts
Imports and invokes checkFrontendDeployEnv during production builds, throwing a detailed error when problems are found; adds tests covering pass/fail cases, mismatch detection, and normalization tolerance.

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

Possibly related PRs

  • SaplingLearn/Sapling#281: Both PRs modify frontend/next.config.ts to fail production builds on missing/misconfigured deployment env vars, with this PR generalizing the earlier BACKEND_URL-only check into the new checkFrontendDeployEnv guard.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Title check✅ PassedThe title is concise and accurately summarizes the main change: preventing prod builds from using staging deploy config.
Description check✅ PassedThe description covers the problem, fix, follow-up actions, and testing, with only minor template fields like related issues and screenshots omitted.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/frontend-prod-deploy-guard

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

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)
frontend/src/lib/deployGuard.test.ts (1)

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

Add coverage for missing/malformed vars under an explicit DEPLOY_ENV lock.

Current cases only exercise wrong-but-canonical values (all-staging on prod) and unknown DEPLOY_ENV. Consider adding a case where DEPLOY_ENV=production but NEXT_PUBLIC_API_URL is unset or non-canonical (e.g., a typo'd URL) — this currently passes silently per the gap noted in deployGuard.ts, and a test would make that limitation explicit or catch a fix regression.

🤖 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 `@frontend/src/lib/deployGuard.test.ts` around lines 33 - 41, Add a test in
deployGuard.test.ts that exercises the explicit DEPLOY_ENV lock in
checkFrontendDeployEnv with DEPLOY_ENV set to production while
NEXT_PUBLIC_API_URL is missing or malformed, so the gap in deployGuard.ts is
covered. Use the existing checkFrontendDeployEnv helper and the STAGING/PROD
fixtures as the starting point, then assert that the returned problems include
the expected missing/invalid URL validation instead of passing silently.
🤖 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 `@frontend/src/lib/deployGuard.ts`:
- Around line 86-104: The DEPLOY_ENV check in deployGuard.ts only flags entries
already classified by classify(), so missing, blank, or malformed values can
slip through unnoticed. Update the DEPLOY_ENV branch in deployGuard to require
NEXT_PUBLIC_API_URL, BACKEND_URL, and COOKIE_DOMAIN to each classify to the
active deployEnv, and treat any null/omitted classification as a problem. Use
the existing FRONTEND_ENVS, classify, and mismatched/deployEnv logic to report
which of the three keys are absent or not matching the expected environment.
---
Nitpick comments:
In `@frontend/src/lib/deployGuard.test.ts`:
- Around line 33-41: Add a test in deployGuard.test.ts that exercises the
explicit DEPLOY_ENV lock in checkFrontendDeployEnv with DEPLOY_ENV set to
production while NEXT_PUBLIC_API_URL is missing or malformed, so the gap in
deployGuard.ts is covered. Use the existing checkFrontendDeployEnv helper and
the STAGING/PROD fixtures as the starting point, then assert that the returned
problems include the expected missing/invalid URL validation instead of passing
silently.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ca709c59-9fd9-49be-b01a-e4c99fcc4e10

📥 Commits

Reviewing files that changed from the base of the PR and between f472300 and dc93218.

📒 Files selected for processing (3)
  • frontend/next.config.ts
  • frontend/src/lib/deployGuard.test.ts
  • frontend/src/lib/deployGuard.ts

Comment on lines +86 to +104
const deployEnv = (env.DEPLOY_ENV ?? '').trim().toLowerCase();
if (deployEnv) {
if (!(deployEnv in FRONTEND_ENVS)) {
problems.push(
`DEPLOY_ENV must be one of ${Object.keys(FRONTEND_ENVS).join(' | ')}, ` +
`got ${JSON.stringify(env.DEPLOY_ENV)}`,
);
} else {
const want = FRONTEND_ENVS[deployEnv as FrontendEnv];
const mismatched = Object.entries(classified).filter(([, v]) => v !== deployEnv);
if (mismatched.length) {
const detail = mismatched.map(([k, v]) => `${k}→${v}`).join(', ');
problems.push(
`DEPLOY_ENV=${deployEnv} but ${detail} point elsewhere; expected ` +
`NEXT_PUBLIC_API_URL/BACKEND_URL=${want.apiUrl}, COOKIE_DOMAIN=${want.cookieDomain}`,
);
}
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

DEPLOY_ENV lock silently passes when a var is missing or malformed, not just wrong-environment.

The mismatch check on Line 95 only inspects classified entries — i.e., values that exactly matched a canonical FRONTEND_ENVS string. If NEXT_PUBLIC_API_URL/BACKEND_URL/COOKIE_DOMAIN is unset, blank, or subtly malformed (trailing slash, wrong subdomain, http vs https), classify() returns null and the key is simply omitted from classified, so it never appears in mismatched and is never flagged. With DEPLOY_ENV=production set, a build where the API URL is accidentally empty or wrong-but-non-canonical will report zero problems — exactly the "silent misconfiguration reaches prod" failure mode this guard exists to prevent, just via a different vector (missing/malformed value) than the original bug (correct-but-wrong-environment value).

Consider requiring all three keys to be classified (matching the intended deployEnv) when DEPLOY_ENV is set, rather than only checking values that already classified successfully:

💡 Proposed fix
 } else {
const want = FRONTEND_ENVS[deployEnv as FrontendEnv];
- const mismatched = Object.entries(classified).filter(([, v]) => v !== deployEnv);+ const keys = ['NEXT_PUBLIC_API_URL', 'BACKEND_URL', 'COOKIE_DOMAIN'] as const;+ const mismatched = keys+ .filter((k) => env[k] !== undefined && env[k] !== '')+ .map((k) => [k, classified[k] ?? 'unrecognized'] as const)+ .filter(([, v]) => v !== deployEnv);
if (mismatched.length) {
🤖 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 `@frontend/src/lib/deployGuard.ts` around lines 86 - 104, The DEPLOY_ENV check
in deployGuard.ts only flags entries already classified by classify(), so
missing, blank, or malformed values can slip through unnoticed. Update the
DEPLOY_ENV branch in deployGuard to require NEXT_PUBLIC_API_URL, BACKEND_URL,
and COOKIE_DOMAIN to each classify to the active deployEnv, and treat any
null/omitted classification as a problem. Use the existing FRONTEND_ENVS,
classify, and mismatched/deployEnv logic to report which of the three keys are
absent or not matching the expected environment.

@AndresL230
AndresL230 merged commit c515314 into mainJul 3, 2026
6 checks passed
@AndresL230
AndresL230 deleted the fix/frontend-prod-deploy-guard branch August 2, 2026 18:30
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@AndresL230