-
Notifications
You must be signed in to change notification settings - Fork 331
fix(web): show 404 for invalid browse paths #1546
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
brendan-kellam
wants to merge
3
commits into
main
Choose a base branch
from
brendan-kellam/fix-SOU-1585
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| import { getConfiguredLanguageModelsInfo } from "@/features/chat/utils.server"; | ||
| import { notFound } from "next/navigation"; | ||
| import { getBrowseParamsFromPathParam } from "../hooks/utils"; | ||
| import { LayoutClient } from "../layoutClient"; | ||
|
|
||
| interface LayoutProps { | ||
| children: React.ReactNode; | ||
| params: Promise<{ | ||
| path: string[]; | ||
| }>; | ||
| } | ||
|
|
||
| export default async function Layout({ | ||
| children, | ||
| params, | ||
| }: LayoutProps) { | ||
| const { path } = await params; | ||
| const browseParams = getBrowseParamsFromPathParam(path.join('/')); | ||
| if (!browseParams) { | ||
| notFound(); | ||
| } | ||
|
|
||
| const languageModels = await getConfiguredLanguageModelsInfo(); | ||
| return ( | ||
| <LayoutClient | ||
| browseParams={browseParams} | ||
| isSearchAssistSupported={languageModels.length > 0} | ||
| > | ||
| {children} | ||
| </LayoutClient> | ||
| ) | ||
| } | ||
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
23 changes: 9 additions & 14 deletions
23
packages/web/src/app/(app)/browse/hooks/useBrowseParams.ts
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,18 +1,13 @@ | ||
| import { usePathname } from "next/navigation"; | ||
| import { useMemo } from "react"; | ||
| import { getBrowseParamsFromPathParam } from "./utils"; | ||
| import { createContext, useContext } from "react"; | ||
| import type { BrowseProps } from "./utils"; | ||
|
|
||
| export const useBrowseParams = () => { | ||
| const pathname = usePathname(); | ||
| export const BrowseParamsContext = createContext<BrowseProps | null>(null); | ||
|
|
||
| return useMemo(() => { | ||
| const startIndex = pathname.indexOf('/browse/'); | ||
| if (startIndex === -1) { | ||
| throw new Error(`Invalid browse pathname: "${pathname}" - expected to contain "/browse/"`); | ||
| } | ||
| export const useBrowseParams = () => { | ||
| const browseParams = useContext(BrowseParamsContext); | ||
| if (!browseParams) { | ||
| throw new Error('useBrowseParams must be used within a BrowseParamsContext provider'); | ||
| } | ||
|
|
||
| const rawPath = pathname.substring(startIndex + '/browse/'.length); | ||
| return getBrowseParamsFromPathParam(rawPath); | ||
| }, [pathname]); | ||
| return browseParams; | ||
| } | ||
|
|
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
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
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.
Oops, something went wrong.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
Repository: sourcebot-dev/sourcebot
Length of output: 24523
🏁 Script executed:
Repository: sourcebot-dev/sourcebot
Length of output: 12150
🌐 Web query:
Next.js 16.2.11 routeMatcher parameter decodeURIComponent repeated catch-all route parameters source route-matcher💡 Result:
In Next.js, including version 16.2.11, the route matcher process extracts dynamic route parameters from the URL path. These parameters are URL-encoded by nature when they originate from the request URL. To ensure that parameters are usable as expected (e.g., converting
%20back to a space), the framework appliesdecodeURIComponentto these matched segments. When handling catch-all routes (e.g.,[...slug]), the framework processes each individual element of the catch-all array by applying decoding [1]. A known technical challenge in this process is the risk of double-encoding or decoding errors, particularly when a path segment is already decoded or contains characters that might causedecodeURIComponentto throw an error (such as malformed percent-encoded sequences or already decoded path delimiters) [2]. To handle these cases robustly, the standard implementation pattern in Next.js—often observed in its routing and middleware utilities—involves wrapping the decoding process in a try-catch block [3][4]: try { return decodeURIComponent(param) } catch { // If decoding fails (e.g., param is already decoded or contains invalid sequences), // return the parameter as-is to preserve the original value. return param } This defensive approach prevents application crashes due to malformed URI components while ensuring that properly encoded parameters are correctly normalized for use in the application, such as inrouter.queryoruseParamshooks [1]. Ongoing refinements in the Next.js routing layer continue to address edge cases where path delimiters (like slashes or encoded equivalents) interact with this decoding logic, especially regarding middleware behavior and base path rewrites [5][6].Citations:
🌐 Web query:
github vercel next.js 16.2.6 packages next src shared lib router utils route matcher decodeURIComponent params💡 Result:
In Next.js, the
route-matcherutility is located inpackages/next/src/shared/lib/router/utils/route-matcher.ts[1][2]. This utility is responsible for matching request paths against dynamic routes and extracting parameters [3][2]. ThegetRouteMatcherfunction inside this file defines adecodehelper that usesdecodeURIComponentto process path parameters captured by regular expressions [2]. When a dynamic route is matched, the matcher iterates through the identified groups (parameters) and applies thisdecodefunction to each value [2]. If a parameter is marked as a repeatable segment (using rest parameters like[...slug]), it splits the string by/and decodes each part individually [2]. Historically, this area of the codebase has been sensitive to how parameters are encoded, leading to issues such as double-decoding or failure when encountering special characters like%[4][5][6]. The implementation specifically includes atry-catchblock arounddecodeURIComponentto handle scenarios where the input might be invalid, throwing a customDecodeError: failed to decode paramif decoding fails [2]. Next.js version 16.2.6, released in May 2026, was a significant security release that addressed multiple vulnerabilities, including those related to middleware/proxy bypass through dynamic route parameter injection (e.g., GHSA-492v-c6pp-mqqv) [7][8][9]. While version 16.2.6 does not focus solely onroute-matcher, the routing and parameter handling logic remain foundational components of the App Router and Pages Router functionality within thenextpackage [1][3].Citations:
🌐 Web query:
github vercel next.js v16.2.11 routeMatcher.ts decodeURIComponent💡 Result:
In Next.js, particularly within files like routeMatcher.ts, the use of decodeURIComponent is a frequent point of interaction for handling URL path segments [1][2][3]. The handling of decodeURIComponent in the Next.js router has evolved to address several critical issues: 1. Handling Non-ASCII Characters: Earlier updates introduced logic in route-matcher.ts to attempt decoding of pathnames to properly match route definitions, allowing for non-ASCII path names (e.g., Unicode characters) that might otherwise return 404 errors [1]. 2. Robustness Against Invalid Sequences: Next.js has implemented specific error handling for cases where a URL segment contains invalid percent-encoded sequences (e.g., %A0). Instead of allowing a URIError to propagate and cause a 500 server error, the router catches these cases and correctly returns a 404 Not Found status [2]. 3. Preventing Encoding Errors: There has been historical work to ensure that parameters are not double-encoded or improperly decoded. Developers have noted that applying decodeURIComponent directly can fail if the input is already partially decoded or contains characters like %25, leading to recommendations for safe, try-catch based approaches when manually handling these values in user-land code [3][4]. Regarding Next.js version 16.2.11, released on July 21, 2026, the primary focus was on addressing several high-severity security vulnerabilities, including issues related to Server Actions, Middleware, and SSRF [5][6][7]. While the core router files like routeMatcher.ts continue to refine how they handle URI decoding to maintain stability and security, version 16.2.11 is specifically characterized by these security patches rather than a direct, major overhaul of the route matcher's decoding logic [5][8][9].
Citations:
🌐 Web query:
raw.githubusercontent.com vercel/next.js v16.2.11 packages next src shared lib router utils route-matcher.ts decodeURIComponent decode function💡 Result:
In the Next.js source code (such as in v16.2.11), the
getRouteMatcherfunction located inpackages/next/src/shared/lib/router/utils/route-matcher.tsuses thedecodeURIComponentfunction to decode dynamic route parameters [1]. When a path is matched against a route, thegetRouteMatchercreates a helperdecodefunction that wrapsdecodeURIComponentin atry...catchblock [1]. IfdecodeURIComponentfails (for example, due to a malformed percent-encoded sequence), it throws aDecodeError(custom error) with the message "failed to decode param" [1][2]. This mechanism ensures that captured dynamic segments—whether they are single parameters or elements of a catch-all route—are properly URL-decoded before being processed by the application [1][3]. If decoding fails, recent versions of Next.js are designed to handle the error, typically resulting in a 404 response rather than allowing the error to propagate as a 500 server error [2][4].Citations:
🌐 Web query:
raw.githubusercontent.com vercel/next.js v16.2.6 packages next src shared lib router utils route-matcher.ts decodeURIComponent decode function💡 Result:
In Next.js version 16.2.6, the route matcher logic found in
packages/next/src/shared/lib/router/utils/route-matcher.tsusesdecodeURIComponentto process URL path segments when matching dynamic routes [1][2]. The use ofdecodeURIComponentin this utility has historically been a source of errors when URLs contain invalid percent-encoded sequences (e.g.,%A0) or when paths are double-encoded [3][4][5]. WhendecodeURIComponentencounters a malformed URI sequence, it throws aURIError. Within the Next.js router, this error is typically wrapped or handled to ensure the application does not crash with a 500 error [2][6]. Recent improvements and fixes in the Next.js routing infrastructure—specifically around the 16.x release cycle—have focused on making this decoding process more resilient [2][7][6]. Notably, if a segment cannot be decoded, modern versions of the router are designed to catch the resultingDecodeErrorand treat it as a non-match (resulting in a 404 Not Found response) rather than allowing the error to propagate or trigger an incorrect status code [2][6]. This prevents issues where invalid encoding in a URL segment could inadvertently cause server-side exceptions [2][6].Citations:
🌐 Web query:
raw.githubusercontent.com vercel/next.js v16.2.6 packages/next/src/shared/lib/router/utils/route-matcher.ts "for (const segment of restSegments)" decodeURIComponent💡 Result:
In Next.js v16.2.6, the file located at packages/next/src/shared/lib/router/utils/route-matcher.ts is responsible for matching URL path segments against defined routes [1][2]. The use of decodeURIComponent within this module—specifically when iterating over path segments—is a critical part of how Next.js handles dynamic route parameters [2]. Historically, this logic has been a focus for reliability improvements to prevent unhandled errors and handle edge cases in URI decoding [3][4][5]. Key technical context for this implementation includes: 1. Handling Invalid Encoding: When a URL segment contains a percent-encoded sequence that cannot be decoded (e.g., %A0), decodeURIComponent throws a URIError [3]. In modern versions of Next.js, this is often wrapped or handled to ensure the framework returns a 404 Not Found instead of a 500 error, as an un-decodable segment is treated as a non-match for the route [3]. 2. Preventing Double Decoding and Errors: Next.js has evolved to ensure that parameters are decoded safely [6][2]. Because the router might encounter segments that have already been processed, careless use of decodeURIComponent can lead to errors if it attempts to decode an already-decoded string or handles malformed input [6][5]. The implementation typically involves try-catch blocks around decodeURIComponent to allow the router to fall back gracefully if a segment is invalid or already decoded [6]. 3. Support for Special Characters: Using decodeURIComponent allows dynamic route parameters (like [slug]) to correctly capture and provide literal values for encoded characters (e.g., converting %20 to a space or %2F to a slash where appropriate), ensuring consistent behavior across different router configurations [2]. For exact source code visualization, you can view the repository at github.com/vercel/next.js/tree/v16.2.6, though direct access to internal source files via raw.githubusercontent.com URLs is subject to GitHub's raw file serving policies and specific repository branch structures [7][8].
Citations:
🏁 Script executed:
Repository: sourcebot-dev/sourcebot
Length of output: 4620
Preserve URL encoding at the Next.js route boundary.
Next.js decodes each catch-all route segment before returning
params.path, butgetBrowseParamsFromPathParam()decodes the joined string again. A path such as100%.txtcan then faildecodeURIComponent(), and legitimate%2Fpath components are decoded twice. Add an already-decoded parser entry point or encode before joining route segments for this layout/page path.🤖 Prompt for AI Agents
Source: MCP tools