Skip to content

fix: URL-encode next_path in OAuth sign-in redirects - #4

Merged
zerodarkzone merged 1 commit into
previewfrom
fix/encode-next-path-oauth-redirects
Sep 8, 2026
Merged

zerodarkzone merged 1 commit into
previewfrom
fix/encode-next-path-oauth-redirects

Conversation

@zerodarkzone

Copy link
Copy Markdown

Description

The OAuth sign-in handlers interpolate next_path straight into the auth URL:

window.location.assign(`${API_BASE_URL}/auth/google/${next_path ? `?next_path=${next_path}` : ``}`);

useSearchParams() returns an already-decoded value, so a next_path containing ?, & or # splits into extra query parameters on the way to the endpoint rather than staying a single value.

Wrapped in encodeURIComponent at all eight sites — Google, GitHub, GitLab and Gitea, in both the web and spaces hooks. Django decodes it again before validate_next_path runs, so nothing downstream changes.

Low impact, not an open redirect. The server rejects absolute URLs and requires a leading slash (validate_next_path), so a crafted next_path cannot move the redirect off-origin. The symptom is a malformed URL and stray parameters, not a navigation to an attacker's host.

Raised by Copilot on #1 against the OIDC entry, which is the same pattern copied a fifth time. That PR encodes only its own new line to stay scoped; this brings the four pre-existing providers in line.

On not extracting a helper: the natural fix for a pattern repeated eight times is one shared URL builder, which is what I did for the equivalent duplication in #3. Deliberately not done here — #1 adds a fifth entry to these same arrays, and a refactor would guarantee a conflict with it. Worth doing once both have landed.

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • Feature (non-breaking change which adds functionality)
  • Improvement (change that would cause existing functionality to not work as expected)
  • Code refactoring
  • Performance improvements
  • Documentation update

Screenshots and Media (if applicable)

Test Scenarios

  1. Visit a sign-in URL carrying a plain path, e.g. http://localhost:3000/?next_path=/projects. Sign in with any OAuth provider and confirm you land on /projects as before — encoding must not change the ordinary case.
  2. Repeat with a next_path containing a reserved character, e.g. ?next_path=/projects%3Fa%3D1%26b%3D2. Before this change the & split into a separate parameter on the request to /auth/<provider>/; now it arrives intact as one value.
  3. Confirm in devtools that the outgoing request carries a single next_path parameter.
  4. Repeat for the spaces app on :3002.
  5. Confirm an absolute next_path (?next_path=https://example.com) is still discarded server-side and does not redirect off-origin — behaviour is unchanged, validate_next_path handles it.

Verified: pnpm check:lint 16/16 and pnpm check:format 16/16. (check:types not run here — it carries a ^build dependency in turbo.json that rewrites packages/propel/dist and breaks a running dev server; the change is two template literals with no type surface.)

References

No Plane work item — private fork. Follow-up to a Copilot review comment on #1.

🤖 Generated with Claude Code

`useSearchParams()` returns an already-decoded value, so interpolating
next_path straight into the auth URL lets a value containing ? & or #
split into extra query parameters on the way to the endpoint.

Wraps it in encodeURIComponent at all eight sites — Google, GitHub, GitLab
and Gitea, in both the web and spaces hooks. Django decodes it again before
validate_next_path runs, so nothing downstream changes.

Low impact rather than an open redirect: the server rejects absolute URLs
and requires a leading slash, so a crafted next_path cannot move the
redirect off-origin. The symptom is a malformed URL and stray parameters.

Kept as eight in-place edits rather than extracting a shared URL builder,
which would prevent the pattern drifting again but would collide with the
OIDC branch (#1) that adds a fifth entry to these same arrays. Worth doing
once both have landed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings September 8, 2026 02:36
@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown

React Doctor found no new issues. 🎉

Reviewed by React Doctor for commit 16351c3.

Copilot AI 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.

🟢 Approval recommended

The change is a small, targeted fix that correctly encodes an already-decoded query value and is applied consistently across all affected redirect sites.

Pull request overview

This PR fixes OAuth sign-in redirect URL construction by URL-encoding the next_path query parameter before interpolating it into provider auth endpoints, preventing reserved characters (?, &, #) from being misinterpreted as additional query params.

Changes:

  • Encode next_path via encodeURIComponent(next_path) for Google/GitHub/GitLab/Gitea OAuth redirects in the web app hook.
  • Apply the same encoding fix for the same providers in the spaces app hook.
File summaries
File Description
apps/web/core/hooks/oauth/core.tsx URL-encodes next_path when building OAuth redirect URLs for core web sign-in.
apps/space/hooks/oauth/core.tsx Mirrors the same next_path encoding fix for the spaces sign-in OAuth redirects.
Review details
  • Files reviewed: 2/2 changed files
  • Comments generated: 0
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@zerodarkzone
zerodarkzone merged commit eed775d into preview Sep 8, 2026
12 checks passed
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.

2 participants