fix(web): show 404 for invalid browse paths - #1546
Conversation
Fixes WEB-8
This comment has been minimized.
This comment has been minimized.
WalkthroughInvalid browse paths now return the 404 page instead of a service error. The parser returns ChangesBrowse path validation
Estimated code review effort: 3 (Moderate) | ~20 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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.
Inline comments:
In `@packages/web/src/app/`(app)/browse/[...path]/layout.tsx:
- Around line 17-18: Update the route handling around
getBrowseParamsFromPathParam so Next.js-decoded catch-all segments are not
decoded a second time. Use an entry point that accepts already-decoded path
data, or re-encode segments before joining them, while preserving literal
percent characters and encoded slash components.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 31c17ca9-177d-451e-88e2-6d76b210749a
📒 Files selected for processing (4)
packages/web/src/app/(app)/browse/[...path]/layout.tsxpackages/web/src/app/(app)/browse/hooks/useBrowseParams.tspackages/web/src/app/(app)/browse/layout.tsxpackages/web/src/app/(app)/browse/layoutClient.tsx
💤 Files with no reviewable changes (1)
- packages/web/src/app/(app)/browse/layout.tsx
| const { path } = await params; | ||
| const browseParams = getBrowseParamsFromPathParam(path.join('/')); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Repo files matching layout/params:"
git ls-files | rg '(^|/)layout\.tsx$|getBrowseParamsFromPathParam|browseParams|routeMatchers|route-matcher' || true
echo
echo "Target file excerpt:"
if [ -f packages/web/src/app/'(app)'/browse/[...path]/layout.tsx ]; then
cat -n 'packages/web/src/app/(app)/browse/[...path]/layout.tsx'
else
fd -a 'layout.tsx' packages/web/src/app | sed -n '1,80p'
fi
echo
echo "Find getBrowseParamsFromPathParam definitions/usages:"
rg -n "getBrowseParamsFromPathParam|BrowseParams|decodeURIComponent|encodeURIComponent|notFound\(" packages/web/src -S || true
echo
echo "Next.js version declarations:"
for f in package.json packages/web/package.json pnpm-lock.yaml package-lock.json yarn.lock bun.lockb; do
[ -f "$f" ] && { echo "--- $f"; sed -n '1,180p' "$f" | rg -n '"next"|"`@next/`"|next@|next:' || true; }
doneRepository: sourcebot-dev/sourcebot
Length of output: 24523
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Source hook utils:"
cat -n 'packages/web/src/app/(app)/browse/hooks/utils.ts' | sed -n '1,120p'
echo
echo "Tests for decoding-related cases:"
cat -n 'packages/web/src/app/(app)/browse/hooks/utils.test.ts' | sed -n '1,130p'
echo
echo "Inspect Next.js route matcher from declared lockfile / source if available:"
if [ -f node_modules/next/package.json ]; then
node - <<'JS'
const p = require('/app/node_modules/next/package.json');
console.log(JSON.stringify({next: p.version}, null, 2));
JS
rg -n "decodeURIComponent|route-matcher|routeMatcher" node_modules/next -S -m 20 || true
else
echo "node_modules/next/package.json not present"
fi
echo
echo "Try NPM registry source bundle for declared next patch version:"
tmp="$(mktemp -d)"
cd "$tmp"
set +e
npm pack next@16.2.6 --json 2>/dev/null | jq -r '.[0].filename' 2>/dev/null || true
tarball="$(npm pack next@16.2.6 2>/dev/null | tail -n 1)"
if [ -n "$tarball" ] && [ -f "$tarball" ]; then
tar -xzf "$tarball"
echo "--- package files matching route matcher/decode:"
find package -type f | rg 'path/dist/shared/lib/router/utils/route-matcher|route-matcher|decodeURIComponent' || true
echo "--- source contains decodeURIComponent:"
rg -n "decodeURIComponent|decode =? function|decodeURIComponentOnce|decodeURI" package -S -m 40 || true
fiRepository: 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 %20 back to a space), the framework applies decodeURIComponent to 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 cause decodeURIComponent to 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 in router.query or useParams hooks [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:
- 1: fix(routing): decode matched params with decodeURIComponent cloudflare/vinext#1049
- 2: avoid double encoding path params vercel/next.js#87003
- 3: https://github.com/vercel/next.js/blob/canary/packages/next/src/server/next-server.ts
- 4: https://github.com/vercel/next.js/blob/1b45163bce3dab30d7a79dff95238b84016b54c2/packages/next/src/server/next-server.ts
- 5: vercel/next.js@v16.2.6...v16.2.11
- 6: vercel/next.js@v16.1.6...v16.2.11
🌐 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-matcher utility is located in packages/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]. The getRouteMatcher function inside this file defines a decode helper that uses decodeURIComponent to process path parameters captured by regular expressions [2]. When a dynamic route is matched, the matcher iterates through the identified groups (parameters) and applies this decode function 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 a try-catch block around decodeURIComponent to handle scenarios where the input might be invalid, throwing a custom DecodeError: failed to decode param if 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 on route-matcher, the routing and parameter handling logic remain foundational components of the App Router and Pages Router functionality within the next package [1][3].
Citations:
- 1: https://github.com/vercel/next.js/blob/canary/packages/next/src/shared/lib/router/router.ts
- 2: https://forge.makearmy.io/makearmy/makearmy-app/raw/commit/6162722c875d0c07813fdc62dc1fb3023fb65dc4/node_modules/next/dist/esm/shared/lib/router/utils/route-matcher.js.map
- 3: https://github.com/vercel/next.js/blob/1b45163bce3dab30d7a79dff95238b84016b54c2/packages/next/src/server/next-server.ts
- 4: avoid double encoding path params vercel/next.js#87003
- 5: Special characters are not decoded in dynamic routes using app directory vercel/next.js#48058
- 6: Symbols in dynamic routes cause "Error: failed to decode param" vercel/next.js#14212
- 7: https://github.com/vercel/next.js/releases/tag/v16.2.6
- 8: https://vercel.com/changelog/next-js-may-2026-security-release
- 9: https://newreleases.io/project/yarn/next/release/16.2.6
🌐 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:
- 1: fix: non-ASCII page names returning 404 under SSR vercel/next.js#89572
- 2: fix(routing): return 404 for dynamic segments with un-decodable percent-encoded sequences vercel/next.js#92557
- 3: avoid double encoding path params vercel/next.js#87003
- 4: Change dynamic route decode from "%20" to hypen "-" vercel/next.js#54096
- 5: https://github.com/vercel/next.js/releases/tag/v16.2.11
- 6: https://nextjs.org/blog/july-2026-security-release
- 7: https://www.changes.watch/updates/nextjs-2026-07-21-v16-2-11
- 8: vercel/next.js@v16.2.10...v16.2.11
- 9: vercel/next.js@v16.2.9...v16.2.11
🌐 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 getRouteMatcher function located in packages/next/src/shared/lib/router/utils/route-matcher.ts uses the decodeURIComponent function to decode dynamic route parameters [1]. When a path is matched against a route, the getRouteMatcher creates a helper decode function that wraps decodeURIComponent in a try...catch block [1]. If decodeURIComponent fails (for example, due to a malformed percent-encoded sequence), it throws a DecodeError (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:
- 1: https://forge.makearmy.io/makearmy/makearmy-app/src/commit/4e2fd5d8132f6bb1e94cf6ff1a9ba57447445f8c/node_modules/next/dist/esm/shared/lib/router/utils/route-matcher.js.map
- 2: fix(routing): return 404 for dynamic segments with un-decodable percent-encoded sequences vercel/next.js#92557
- 3: fix(routing): decode matched params with decodeURIComponent cloudflare/vinext#1049
- 4: https://www.stepcodex.com/en/issue/dynamic-segments-with-un-decodable-entities
🌐 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.ts uses decodeURIComponent to process URL path segments when matching dynamic routes [1][2]. The use of decodeURIComponent in 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]. When decodeURIComponent encounters a malformed URI sequence, it throws a URIError. 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 resulting DecodeError and 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:
- 1: https://app.unpkg.com/next@16.2.12/files/dist/server/route-matchers
- 2: fix(routing): return 404 for dynamic segments with un-decodable percent-encoded sequences vercel/next.js#92557
- 3: Symbols in dynamic routes cause "Error: failed to decode param" vercel/next.js#14212
- 4: % in url parameter causes "Uncaught URIError: URI malformed" vercel/next.js#12145
- 5: avoid double encoding path params vercel/next.js#87003
- 6: https://www.stepcodex.com/en/issue/dynamic-segments-with-un-decodable-entities
- 7: fix: non-ASCII page names returning 404 under SSR vercel/next.js#89572
🌐 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:
- 1: https://app.unpkg.com/next@16.2.12/files/dist/server/route-matchers
- 2: fix(routing): decode matched params with decodeURIComponent cloudflare/vinext#1049
- 3: fix(routing): return 404 for dynamic segments with un-decodable percent-encoded sequences vercel/next.js#92557
- 4: fix: non-ASCII page names returning 404 under SSR vercel/next.js#89572
- 5: Symbols in dynamic routes cause "Error: failed to decode param" vercel/next.js#14212
- 6: avoid double encoding path params vercel/next.js#87003
- 7: https://github.com/vercel/next.js/releases/tag/v16.2.6
- 8: vercel/next.js@v16.2.4...v16.2.6
🏁 Script executed:
#!/bin/bash
set -euo pipefail
tmp="$(mktemp -d || true)"
if [ -n "${tmp:-}" ]; then
echo "Checking next@16.2.6 package metadata/source fetch:"
(set +e; npm view next@16.2.6 version dist.tarball dist.integrity 2>&1)
tarball="$(npm pack next@16.2.6 2>/dev/null | tail -n 1)"
if [ -n "${tarball:-}" ] && [ -f "$tarball" ]; then
tar -xzf "$tarball"
echo "--- route-matcher matches:"
grep -RIn "decodeURIComponent|restSegments|for \\(const segment of restSegments\\)" package/dist package/src 2>/dev/null | head -80 || true
echo "--- inspect candidate source:"
for f in package/src/shared/lib/router/utils/route-matcher.ts package/dist/server/route-matchers/next.js package/dist/esm/shared/lib/router/utils/route-matcher.js; do
if [ -f "$f" ]; then
echo "### $f"
sed -n '1,180p' "$f"
fi
done
echo "--- inspect compiled route-matchers if present:"
f="package/dist/server/route-matchers/route-matcher.js"
if [ -f "$f" ]; then
sed -n '1,220p' "$f"
fi
fi
fi
echo
echo "Programmatic probe of getBrowseParamsFromPathParam decoding behavior for route-param decoded shapes:"
node - <<'JS'
function decodeBrowsePathPart(pathPart) {
try {
return decodeURIComponent(pathPart);
} catch {
return null;
}
}
function getDecodeShapeFor(urlOrEncoded) {
// Simulate Next.js catch-all repeated segments decoded before rejoining plus slash normalization.
// This is not full Next router matching; it only shows the decoded string the parser receives
// for the stated examples in the review.
if (urlOrEncoded.includes('/-/')) return urlOrEncoded;
const match = urlOrEncoded.match(/^([^/]+)\/([^/]+)\/-(\/[a-zA-Z0-9-_\/]+)$/);
if (!match) return null;
const repoAndRevisionPart = decodeURIComponent(match[1]) + '/' + decodeURIComponent(match[2]) || '';
const pathPart = '/' + decodeURIComponent(match[3]).replace(/\/+/g, '/').replace(/^\/+/, '');
return repoAndRevisionPart + '/-/tree' + pathPart;
}
const tests = [
'100%.txt',
'/-/tree/100%.txt',
'github.com/repo@HEAD/-/tree/100%.txt',
'github.com/repo@HEAD/-/blob/%25hello%25%2Fworld.c',
];
for (const input of tests) {
const shape = getDecodeShapeFor(input);
console.log(JSON.stringify({ input, shape, parserParsed: getBrowseParamsFromPathParam(shape ? shape : input) })
// Since getBrowseParamsFromPathParam is not exported to Node, just log parsing decision point inputs instead.
);
}
JSRepository: 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, but getBrowseParamsFromPathParam() decodes the joined string again. A path such as 100%.txt can then fail decodeURIComponent(), and legitimate %2F path 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
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/web/src/app/`(app)/browse/[...path]/layout.tsx around lines 17 - 18,
Update the route handling around getBrowseParamsFromPathParam so Next.js-decoded
catch-all segments are not decoded a second time. Use an entry point that
accepts already-decoded path data, or re-encode segments before joining them,
while preserving literal percent characters and encoded slash components.
Source: MCP tools
Fixes SOU-1585
Fixes WEB-8
Summary
Testing
Note
Low Risk
Browse URL parsing and Next.js layout routing only; no auth, data, or API contract changes beyond returning 404 for bad paths.
Overview
Invalid browse URLs now hit the normal 404 flow instead of surfacing as server errors.
Parser behavior:
getBrowseParamsFromPathParamreturnsnullfor malformed paths (missing/-/, empty blob path, missing commit SHA, bad URL encoding, partial type prefixes liketreehouse) instead of throwing. Decoding is wrapped so invalid%sequences are treated as invalid paths.Routing/layout: Browse chrome is tied to
[...path]/layout.tsx, which parses the path on the server and callsnotFound()when parsing fails. The browse page does the same guard. Parsed params are passed intoLayoutClientand exposed viaBrowseParamsContext, so client hooks no longer re-parse the pathname (which could throw during invalid navigations).Tests and changelog were updated for the new null-based invalid-path handling and commit/history URL cases.
Reviewed by Cursor Bugbot for commit a8d54d0. Bugbot is set up for automated code reviews on this repo. Configure here.
Summary by CodeRabbit
Bug Fixes
Tests
Documentation