Skip to content

🔒 Fix Potential Open Redirect in Auth Router#17

Open
sunnylqm wants to merge 1 commit intomainfrom
fix-open-redirect-auth-router-5167146257682080570
Open

🔒 Fix Potential Open Redirect in Auth Router#17
sunnylqm wants to merge 1 commit intomainfrom
fix-open-redirect-auth-router-5167146257682080570

Conversation

@sunnylqm
Copy link
Collaborator

@sunnylqm sunnylqm commented Mar 9, 2026

🎯 What: Fixed a potential Open Redirect vulnerability in the authentication login flow.
⚠️ Risk: An attacker could construct a malicious link with a loginFrom parameter pointing to an external site. After a successful login, the user would be redirected to the attacker's site, which could be used for phishing attacks.
🛡️ Solution: Implemented an isSafeRedirect utility that validates the loginFrom path. It ensures the path is internal by checking that it starts with a single / and specifically rejects protocol-relative URLs (//) and other common bypasses. If the path is invalid, it defaults to a safe internal route.


PR created automatically by Jules for task 5167146257682080570 started by @sunnylqm

Summary by CodeRabbit

Release Notes

  • Bug Fixes

    • Enhanced login flow security by implementing validation for redirect URLs, ensuring only safe internal paths are used during authentication.
  • Tests

    • Added comprehensive test suite for redirect URL validation, covering edge cases and various URL patterns.

Validate 'loginFrom' parameter to ensure it is a safe internal path
before navigating. This prevents attackers from redirecting users to
malicious external websites after login.

Co-authored-by: sunnylqm <615282+sunnylqm@users.noreply.github.com>
@google-labs-jules
Copy link
Contributor

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

@netlify
Copy link

netlify bot commented Mar 9, 2026

Deploy Preview for pushy ready!

Name Link
🔨 Latest commit 8889a50
🔍 Latest deploy log https://app.netlify.com/projects/pushy/deploys/69ae490ef0fca60008df03c3
😎 Deploy Preview https://deploy-preview-17--pushy.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai
Copy link

coderabbitai bot commented Mar 9, 2026

📝 Walkthrough

Walkthrough

This PR introduces a isSafeRedirect utility function to validate redirect URLs during authentication, preventing open redirect vulnerabilities. The function is integrated into the login flow to guard navigation with the loginFrom parameter, alongside comprehensive test coverage.

Changes

Cohort / File(s) Summary
URL Validation Utility
src/utils/helper.ts
Added new isSafeRedirect function that validates redirect URLs, ensuring they start with a single forward slash and reject double slashes, backslash prefixes, and null/undefined values.
Authentication Integration
src/services/auth.ts
Updated login flow to guard navigation using isSafeRedirect, replacing direct navigation with conditional routing that validates loginFrom before proceeding.
Test Coverage
src/utils/helper.test.ts
Added comprehensive test suite for isSafeRedirect covering internal paths, empty/nullish inputs, external URLs, protocol-relative URLs, and invalid path formats.

Sequence Diagram

sequenceDiagram
    participant User
    participant AuthService as Auth Service
    participant Helper as Helper Utility
    participant Router

    User->>AuthService: Initiate login with loginFrom parameter
    AuthService->>Helper: Call isSafeRedirect(loginFrom)
    Helper->>Helper: Validate URL format<br/>(starts with /, not //, not /\)
    Helper-->>AuthService: Return boolean (safe or unsafe)
    
    alt URL is safe
        AuthService->>Router: Navigate to loginFrom
    else URL is unsafe or undefined
        AuthService->>Router: Navigate to default rootRouterPath.user
    end
    
    Router-->>User: Redirect complete
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐰 A hop toward safety, we bound,
With redirects checked, true and sound,
No slashes double shall deceive,
Just wholesome paths, we do believe!

🚥 Pre-merge checks | ✅ 2 | ❌ 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 (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly addresses the main security fix implemented in the changeset: preventing open redirect vulnerabilities in the authentication router.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix-open-redirect-auth-router-5167146257682080570

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


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

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/utils/helper.test.ts (1)

5-9: Add a search-bearing internal path to the allow-list tests.

loginFrom is populated from pathname + search, so a case like /user/profile?tab=security would lock coverage to the real input shape used by the auth loader.

🧪 Small coverage bump
   test('should return true for internal paths', () => {
     expect(isSafeRedirect('/dashboard')).toBe(true);
     expect(isSafeRedirect('/user/profile')).toBe(true);
+    expect(isSafeRedirect('/user/profile?tab=security')).toBe(true);
     expect(isSafeRedirect('/')).toBe(true);
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utils/helper.test.ts` around lines 5 - 9, Add a test case that includes a
search/query string to verify isSafeRedirect accepts full pathname+search inputs
(e.g., '/user/profile?tab=security'); update the test block in
src/utils/helper.test.ts (the test that currently asserts
isSafeRedirect('/dashboard'), '/user/profile', '/') to include a call like
expect(isSafeRedirect('/user/profile?tab=security')).toBe(true) so the test
covers the actual loginFrom shape used by the auth loader.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/utils/helper.ts`:
- Around line 69-72: Reject inputs containing ASCII control characters before
the prefix checks in isSafeRedirect: add an early validation in the
isSafeRedirect function to scan the decoded url string for any code points in
the ASCII control range (0x00–0x1F) or DEL (0x7F) and return false if any are
found (this will catch tabs/newlines like \t or \n). After this
control-character check, keep the existing prefix logic (startsWith('/') and not
starting with '//' or '/\\') so URLs like "/%09/evil.com" (decoded to a literal
tab) are rejected before browser normalization can turn them into
protocol-relative redirects.

---

Nitpick comments:
In `@src/utils/helper.test.ts`:
- Around line 5-9: Add a test case that includes a search/query string to verify
isSafeRedirect accepts full pathname+search inputs (e.g.,
'/user/profile?tab=security'); update the test block in src/utils/helper.test.ts
(the test that currently asserts isSafeRedirect('/dashboard'), '/user/profile',
'/') to include a call like
expect(isSafeRedirect('/user/profile?tab=security')).toBe(true) so the test
covers the actual loginFrom shape used by the auth loader.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f599d757-4bea-4fc4-a3e4-be1ee52dfb97

📥 Commits

Reviewing files that changed from the base of the PR and between 0e256d1 and 8889a50.

📒 Files selected for processing (3)
  • src/services/auth.ts
  • src/utils/helper.test.ts
  • src/utils/helper.ts

Comment on lines +69 to +72
export const isSafeRedirect = (url: string | null | undefined): boolean => {
if (!url) return false;
// Ensure it starts with / and not // or \
return url.startsWith('/') && !url.startsWith('//') && !url.startsWith('/\\');
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

Do browser URL parsers / the History API strip ASCII tab or newline characters from URL inputs, and can a decoded path like "/%09/evil.com" or "/%0A/evil.com" normalize to a protocol-relative URL?

💡 Result:

Yes—literal ASCII tab/newline characters in a URL input string are stripped by the WHATWG URL parser (used by modern browsers).

  • The URL Standard’s basic URL parser says that if the input contains any ASCII tab or newline, it’s a validation error, and then it “Remove[s] all ASCII tab or newline from input.” (url.spec.whatwg.org)
  • The History API (history.pushState / replaceState) parses the provided url by running “encoding-parsing a URL” relative to the relevant settings object, which goes through URL parsing (and therefore gets the same tab/newline stripping behavior). (html.spec.whatwg.org)

Can "/%09/evil.com" or "/%0A/evil.com" normalize to a protocol-relative URL?

Not by the browser’s URL parser alone.

  • /%09/evil.com and /%0A/evil.com contain the percent sign + hex digits, not literal tab/newline characters, so the parser’s “strip ASCII tab/newline” step does not remove %09/%0A. (url.spec.whatwg.org)
  • A protocol-relative (scheme-relative) URL requires the parsed input to start with // (e.g. //evil.com). Your examples start with /%09/... or /%0A/..., so they remain ordinary paths.

When could it become protocol-relative?

Only if something percent-decodes first (turning %09 into a literal TAB or %0A into a literal LF) and then the browser parses that decoded string. For example, if an application decodes to the literal string "/\t/evil.com", the URL parser will strip the tab, producing "//evil.com", which is protocol-relative. (url.spec.whatwg.org)

Citations:


🏁 Script executed:

# Find where isSafeRedirect is used to understand the actual attack surface
rg -A 5 'isSafeRedirect' --type ts

Repository: reactnativecn/pushy-admin

Length of output: 3259


🏁 Script executed:

# Get the full context around the isSafeRedirect usage in auth.ts
rg -B 10 -A 5 'isSafeRedirect\(loginFrom\)' src/services/auth.ts

Repository: reactnativecn/pushy-admin

Length of output: 548


Reject ASCII control characters before prefix validation to prevent normalization bypass.

loginFrom is decoded by URLSearchParams, so encoded control characters like %09 (tab) and %0A (newline) reach this validator as literal control characters. The current prefix-only checks pass strings like "/%09/evil.com", but when passed to router.navigate(), the browser's URL parser strips the literal tab per the WHATWG specification, normalizing it to "//evil.com" (a protocol-relative URL) and enabling an open redirect.

🔐 Hardening required
 export const isSafeRedirect = (url: string | null | undefined): boolean => {
   if (!url) return false;
-  // Ensure it starts with / and not // or \
-  return url.startsWith('/') && !url.startsWith('//') && !url.startsWith('/\\');
+  // Reject characters that URL parsers strip or normalize.
+  if (/[\u0000-\u001F\u007F]/.test(url)) return false;
+  // Only allow app-internal absolute paths.
+  return /^\/(?![\\/])/.test(url);
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utils/helper.ts` around lines 69 - 72, Reject inputs containing ASCII
control characters before the prefix checks in isSafeRedirect: add an early
validation in the isSafeRedirect function to scan the decoded url string for any
code points in the ASCII control range (0x00–0x1F) or DEL (0x7F) and return
false if any are found (this will catch tabs/newlines like \t or \n). After this
control-character check, keep the existing prefix logic (startsWith('/') and not
starting with '//' or '/\\') so URLs like "/%09/evil.com" (decoded to a literal
tab) are rejected before browser normalization can turn them into
protocol-relative redirects.

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