fix: URL-encode next_path in OAuth sign-in redirects - #4
Merged
Merged
Conversation
`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>
|
React Doctor found no new issues. 🎉 Reviewed by React Doctor for commit |
There was a problem hiding this comment.
🟢 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_pathviaencodeURIComponent(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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
The OAuth sign-in handlers interpolate
next_pathstraight into the auth URL:useSearchParams()returns an already-decoded value, so anext_pathcontaining?,&or#splits into extra query parameters on the way to the endpoint rather than staying a single value.Wrapped in
encodeURIComponentat all eight sites — Google, GitHub, GitLab and Gitea, in both the web and spaces hooks. Django decodes it again beforevalidate_next_pathruns, 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 craftednext_pathcannot 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
Screenshots and Media (if applicable)
Test Scenarios
http://localhost:3000/?next_path=/projects. Sign in with any OAuth provider and confirm you land on/projectsas before — encoding must not change the ordinary case.next_pathcontaining 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.next_pathparameter.:3002.next_path(?next_path=https://example.com) is still discarded server-side and does not redirect off-origin — behaviour is unchanged,validate_next_pathhandles it.Verified:
pnpm check:lint16/16 andpnpm check:format16/16. (check:typesnot run here — it carries a^builddependency inturbo.jsonthat rewritespackages/propel/distand 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