Skip to content

fix(frontend): validate redirect query targets#2158

Closed
antaloaalonso wants to merge 4 commits into
Cap-go:mainfrom
antaloaalonso:codex/harden-redirect-targets
Closed

fix(frontend): validate redirect query targets#2158
antaloaalonso wants to merge 4 commits into
Cap-go:mainfrom
antaloaalonso:codex/harden-redirect-targets

Conversation

@antaloaalonso
Copy link
Copy Markdown

@antaloaalonso antaloaalonso commented May 11, 2026

/claim #1667

Summary

  • add a shared safe redirect helper for same-origin app paths
  • apply it to login, SSO callback, resend email, account restore, and onboarding redirect targets
  • cover absolute, protocol-relative, browser-normalized, and loop targets with unit tests

Tests

  • npx --yes bun@latest x vitest run tests/redirects.unit.test.ts tests/sso-enforcement-redirect.unit.test.ts
  • npx --yes bun@latest run lint
  • npx --yes bun@latest run typecheck
  • git diff --check

Summary by CodeRabbit

  • New Features

    • Introduced a centralized redirect-path sanitizer used across auth and onboarding flows.
  • Bug Fixes

    • Enhanced redirect safety to block unsafe or unwanted destinations (including certain path prefixes) after login, SSO, onboarding, and verification.
    • Prevented potential redirect loops and removed inconsistent fallback behaviors.
  • Tests

    • Added unit tests covering redirect validation and fallback behavior.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 11, 2026

Warning

Rate limit exceeded

@antaloaalonso has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 44 minutes and 14 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1563baaf-1efb-47ca-b67d-3b7077ad27ea

📥 Commits

Reviewing files that changed from the base of the PR and between a3abf35 and 407268e.

📒 Files selected for processing (1)
  • tests/redirects.unit.test.ts
📝 Walkthrough

Walkthrough

This PR centralizes redirect-path validation by adding getSafeRedirectPath and applying it across auth, onboarding, and email flows to sanitize redirect targets and block configured path prefixes.

Changes

Centralized Redirect Path Validation

Layer / File(s) Summary
Core Redirect Safety Service
src/services/redirects.ts
Exports isSafeRedirectPath(path) and getSafeRedirectPath(target, fallback, options) to validate same-origin application paths, reject unsafe inputs, and return a safe fallback when needed.
Service Test Coverage
tests/redirects.unit.test.ts
Unit tests cover same-origin acceptance, rejection of absolute/protocol-relative/javascript: URLs and ambiguous inputs, fallback behavior, and blockedPrefixes handling.
Account Restore Redirect
src/pages/accountDisabled.vue
restoreTarget now uses getSafeRedirectPath(route.query.to, '/dashboard', { blockedPrefixes: ['/accountDisabled'] }).
Login and SSO Redirect Integration
src/pages/login.vue
nextLogin() and SSO callback construction now use getSafeRedirectPath (SSO to set only when sanitized target is non-empty).
Onboarding Back Navigation and Query Sync
src/pages/onboarding/organization.vue
goBack() and syncRouteQuery() compute and include sanitized to via getSafeRedirectPath, blocking /onboarding prefix.
Email Verification and OTP Redirect
src/pages/resend_email.vue
Adds rawReturnTo, returnTo, and returnToLabel computed values using getSafeRedirectPath; redirects use sanitized returnTo and banner shows label only for explicit query.
SSO Callback Redirect Consolidation
src/pages/sso-callback.vue
Removes local validateRedirectPath() and replaces redirects with getSafeRedirectPath(route.query.to).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • Cap-go/capgo#1980: Related changes to account restoration and redirect handling in the accountDisabled flow.

Poem

🐰 I hop along the redirect trail,
Sniffing paths both short and frail;
I guard each route with careful art,
So journeys end where they should start. 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly and specifically summarizes the main change: adding validation for redirect query targets across multiple frontend pages.
Description check ✅ Passed The PR description is largely complete with a clear summary, test commands, and steps provided, though the template's checklist items are not addressed.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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 and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
src/services/redirects.ts (1)

47-56: ⚡ Quick win

Apply blocked-path checks to the fallback too.

Right now a blocked target falls back to fallbackPath, but fallback itself is only checked with isSafeRedirectPath(). That means a future caller can still reintroduce the loop the blockedPaths / blockedPrefixes option is meant to prevent.

♻️ Suggested change
 export function getSafeRedirectPath(target: unknown, fallback = defaultRedirectPath, options: SafeRedirectOptions = {}): string {
-  const fallbackPath = fallback === '' || isSafeRedirectPath(fallback)
-    ? fallback
-    : defaultRedirectPath
+  const fallbackPath = fallback === ''
+    ? ''
+    : isSafeRedirectPath(fallback) && !isBlockedRedirectPath(fallback, options)
+      ? fallback
+      : defaultRedirectPath
 
   if (typeof target !== 'string')
     return fallbackPath
🤖 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 `@src/services/redirects.ts` around lines 47 - 56, The logic in
getSafeRedirectPath allows a blocked target to fall back to a fallback that
wasn't checked against blocked-path rules; update the function so the computed
fallbackPath is validated by both isSafeRedirectPath and isBlockedRedirectPath
(using the same options) before returning it — i.e., after computing
fallbackPath (allowing ''), if the original target is invalid/blocked then check
if fallbackPath isBlockedRedirectPath and if so return defaultRedirectPath; use
the existing helpers isSafeRedirectPath and isBlockedRedirectPath and the
SafeRedirectOptions parameter to perform this additional check.
tests/redirects.unit.test.ts (1)

3-3: ⚡ Quick win

Use the ~/ alias in this test import.

The relative path works, but it breaks the repo-wide TS import convention and is more brittle if the test file moves.

♻️ Suggested change
-import { getSafeRedirectPath, isSafeRedirectPath } from '../src/services/redirects.ts'
+import { getSafeRedirectPath, isSafeRedirectPath } from '~/services/redirects'

As per coding guidelines, **/*.{ts,tsx}: use path alias ~/ to reference src/ directory.

🤖 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 `@tests/redirects.unit.test.ts` at line 3, Replace the relative import for
getSafeRedirectPath and isSafeRedirectPath with the project path alias; update
the import statement that currently references ../src/services/redirects.ts to
use the '~/services/redirects' alias so the test imports getSafeRedirectPath and
isSafeRedirectPath via the repo-wide TS convention.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/services/redirects.ts`:
- Around line 47-56: The logic in getSafeRedirectPath allows a blocked target to
fall back to a fallback that wasn't checked against blocked-path rules; update
the function so the computed fallbackPath is validated by both
isSafeRedirectPath and isBlockedRedirectPath (using the same options) before
returning it — i.e., after computing fallbackPath (allowing ''), if the original
target is invalid/blocked then check if fallbackPath isBlockedRedirectPath and
if so return defaultRedirectPath; use the existing helpers isSafeRedirectPath
and isBlockedRedirectPath and the SafeRedirectOptions parameter to perform this
additional check.

In `@tests/redirects.unit.test.ts`:
- Line 3: Replace the relative import for getSafeRedirectPath and
isSafeRedirectPath with the project path alias; update the import statement that
currently references ../src/services/redirects.ts to use the
'~/services/redirects' alias so the test imports getSafeRedirectPath and
isSafeRedirectPath via the repo-wide TS convention.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b7107785-d616-47e3-a441-1beefd9fef3a

📥 Commits

Reviewing files that changed from the base of the PR and between 3d184f8 and 37dc4ae.

📒 Files selected for processing (7)
  • src/pages/accountDisabled.vue
  • src/pages/login.vue
  • src/pages/onboarding/organization.vue
  • src/pages/resend_email.vue
  • src/pages/sso-callback.vue
  • src/services/redirects.ts
  • tests/redirects.unit.test.ts

@codspeed-hq
Copy link
Copy Markdown
Contributor

codspeed-hq Bot commented May 11, 2026

Merging this PR will not alter performance

✅ 43 untouched benchmarks
⏩ 2 skipped benchmarks1


Comparing antaloaalonso:codex/harden-redirect-targets (407268e) with main (af3dedf)

Open in CodSpeed

Footnotes

  1. 2 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@sonarqubecloud
Copy link
Copy Markdown

Copy link
Copy Markdown

@jooj211 jooj211 left a comment

Choose a reason for hiding this comment

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

Approved. I verified the centralized redirect helper rejects external, protocol-relative, scheme-like, whitespace/control-character, and backslash-normalized targets, preserves valid same-origin app paths, and applies blocked-prefix checks to fallbacks as well as requested targets.

Checks run locally:

  • bun x vitest run tests/redirects.unit.test.ts tests/sso-enforcement-redirect.unit.test.ts
  • bun x eslint src/services/redirects.ts src/pages/accountDisabled.vue src/pages/login.vue src/pages/onboarding/organization.vue src/pages/resend_email.vue src/pages/sso-callback.vue tests/redirects.unit.test.ts
  • bun -e redirect edge-case smoke test
  • git diff --check HEAD~4..HEAD

@WcaleNieWolny
Copy link
Copy Markdown
Contributor

Closing as AI-generated spam. Part of a 50+ PR wave of duplicate redact logs PRs from disposable accounts. If you're human, open an issue first.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants