From 04728b62c78f155c2e78d6d001c898891a3a48f1 Mon Sep 17 00:00:00 2001 From: Rachael Sewell Date: Fri, 28 Feb 2025 13:50:06 -0800 Subject: [PATCH 1/4] Update invalid query middleware (#54610) --- src/frame/middleware/handle-next-data-path.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/frame/middleware/handle-next-data-path.ts b/src/frame/middleware/handle-next-data-path.ts index be4f459f827a..4cc968514e3f 100644 --- a/src/frame/middleware/handle-next-data-path.ts +++ b/src/frame/middleware/handle-next-data-path.ts @@ -1,7 +1,10 @@ import type { Response, NextFunction } from 'express' +import statsd from '@/observability/lib/statsd.js' import type { ExtendedRequest } from '@/types.js' +const STATSD_KEY = 'middleware.handle_next_data_path' + export default function handleNextDataPath( req: ExtendedRequest, res: Response, @@ -12,7 +15,16 @@ export default function handleNextDataPath( // this is triggered via client-side route transitions // example path: // /_next/data/development/en/free-pro-team%40latest/github/setting-up-and-managing-your-github-user-account.json - const decodedPath = decodeURIComponent(req.path) + let decodedPath = '' + try { + decodedPath = decodeURIComponent(req.path) + } catch { + res.status(400).send(`Bad request`) + const tags = ['response:400', `path:${req.path}`] + statsd.increment(STATSD_KEY, 1, tags) + return + } + const parts = decodedPath.split('/').slice(4) // free-pro-team@latest should not be included in the page path if (parts[1] === 'free-pro-team@latest') { From a61dedb09a9a8521a7fce7aa6e137cbbad3edcfe Mon Sep 17 00:00:00 2001 From: Evan Bonsignori Date: Fri, 28 Feb 2025 14:44:16 -0800 Subject: [PATCH 2/4] fix where we turn on variation context for an experiment (#54629) --- src/events/components/experiments/experiment.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/events/components/experiments/experiment.ts b/src/events/components/experiments/experiment.ts index 41a0e67f8383..399bff7c8f20 100644 --- a/src/events/components/experiments/experiment.ts +++ b/src/events/components/experiments/experiment.ts @@ -54,7 +54,6 @@ export function shouldShowExperiment( ? routerQuery.feature.toLowerCase() === experiment.turnOnWithURLParam.toLowerCase() : false ) { - controlGroupOverride[experimentKey] = TREATMENT_VARIATION return true } } @@ -115,6 +114,15 @@ export function getExperimentVariationForContext(locale: string, version: string const experiments = getActiveExperiments(locale, version) for (const experiment of experiments) { if (experiment.includeVariationInContext) { + // If the user is using the URL param to view the experiment, include the variation in the context + if ( + experiment.turnOnWithURLParam && + window.location?.search + ?.toLowerCase() + .includes(`feature=${experiment.turnOnWithURLParam.toLowerCase()}`) + ) { + return TREATMENT_VARIATION + } return getExperimentControlGroupFromSession( experiment.key, experiment.percentOfUsersToGetExperiment, From 5c0ff0c716f1c785d14836e4d06712bfa8a0b0f7 Mon Sep 17 00:00:00 2001 From: Evan Bonsignori Date: Fri, 28 Feb 2025 14:47:20 -0800 Subject: [PATCH 3/4] prevent malicious __nextFallback query in prod (#54523) --- src/shielding/middleware/handle-invalid-nextjs-paths.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/shielding/middleware/handle-invalid-nextjs-paths.ts b/src/shielding/middleware/handle-invalid-nextjs-paths.ts index c109dedf0758..d1003d163b2a 100644 --- a/src/shielding/middleware/handle-invalid-nextjs-paths.ts +++ b/src/shielding/middleware/handle-invalid-nextjs-paths.ts @@ -18,8 +18,8 @@ export default function handleInvalidNextPaths( // In local dev, we don't get these penetration-testing looking requests. if ( process.env.NODE_ENV !== 'development' && - req.path.startsWith('/_next/') && - !req.path.startsWith('/_next/data') + ((req.path.startsWith('/_next/') && !req.path.startsWith('/_next/data')) || + req.query?.['__nextFallback']) ) { defaultCacheControl(res) From 348b46bf0d17df991e2a84da4b7cc7ab97377d94 Mon Sep 17 00:00:00 2001 From: Evan Bonsignori Date: Fri, 28 Feb 2025 14:54:34 -0800 Subject: [PATCH 4/4] AI Search: fix padding & backspace issue (#54628) --- src/search/components/input/AskAIResults.tsx | 1 + .../input/SearchBarButton.module.scss | 4 +- .../components/input/SearchBarButton.tsx | 10 +++- src/search/components/input/SearchOverlay.tsx | 46 +++++++++++-------- src/search/components/input/variables.scss | 2 +- .../handle-invalid-query-strings.ts | 1 + 6 files changed, 39 insertions(+), 25 deletions(-) diff --git a/src/search/components/input/AskAIResults.tsx b/src/search/components/input/AskAIResults.tsx index 16e153cd52e8..7ba230c0e46b 100644 --- a/src/search/components/input/AskAIResults.tsx +++ b/src/search/components/input/AskAIResults.tsx @@ -320,6 +320,7 @@ export function AskAIResults({ - - {urlSearchInputQuery ? urlSearchInputQuery : t('search.input.placeholder')} + {urlSearchInputQuery ? ( + urlSearchInputQuery + ) : ( + <> + {t('search.input.placeholder')} + + + )} diff --git a/src/search/components/input/SearchOverlay.tsx b/src/search/components/input/SearchOverlay.tsx index 865842f8e00e..852b6ad11e5a 100644 --- a/src/search/components/input/SearchOverlay.tsx +++ b/src/search/components/input/SearchOverlay.tsx @@ -240,13 +240,10 @@ export function SearchOverlay({ event.preventDefault() const newQuery = event.target.value setSelectedIndex(-1) // Reset selected index when query changes - // We don't need to fetch autocomplete results when asking the AI - if (!isAskAIState || aiSearchError || aiCouldNotAnswer) { - setSearchLoading(true) - updateAutocompleteResults(newQuery) - } - // If the query empties while we are in the AI state, we should switch back to the search state - if (isAskAIState && newQuery.trim() === '') { + // Whenever the query changes, we want to leave the Ask AI state + setSearchLoading(true) + updateAutocompleteResults(newQuery) + if (isAskAIState) { updateParams({ 'search-overlay-ask-ai': '', 'search-overlay-input': newQuery, @@ -296,6 +293,7 @@ export function SearchOverlay({ 'search-overlay-ask-ai': 'true', 'search-overlay-input': selectedOption.term, }) + setSearchLoading(true) setAIQuery(selectedOption.term) inputRef.current?.focus() } @@ -464,7 +462,6 @@ export function SearchOverlay({ @@ -526,22 +523,12 @@ export function SearchOverlay({ selectedIndex, listElementsRef, askAIState, + searchLoading, + previousSuggestionsListHeight, )} ) - } else if (searchLoading) { - OverlayContents = ( - - - - ) } else { OverlayContents = ( ) @@ -701,6 +690,8 @@ function renderSearchGroups( aiCouldNotAnswer: boolean setAICouldNotAnswer: (value: boolean) => void }, + searchLoading: boolean, + previousSuggestionsListHeight: number | string, ) { const groups = [] @@ -744,6 +735,21 @@ function renderSearchGroups( groups.push() } + if (searchLoading) { + groups.push( + + + , + ) + return groups + } + // We want to show general search suggestions underneath the AI Response section if the AI Could no answer if ((generalSearchOptions.length && !isInAskAIState) || isInAskAIStateButNoAnswer) { const items = [] diff --git a/src/search/components/input/variables.scss b/src/search/components/input/variables.scss index 35fd606713ad..e8f10f994c2a 100644 --- a/src/search/components/input/variables.scss +++ b/src/search/components/input/variables.scss @@ -1,7 +1,7 @@ // Widths of the search bar button at different breakpoints $smHeaderSearchInputWidth: 100%; // Technically we don't show the search bar at this breakpoint $mdHeaderSearchInputWidth: 100%; // Technically we don't show the search bar at this breakpoint -$lgHeaderSearchInputWidth: 30rem; +$lgHeaderSearchInputWidth: 25rem; $xlHeaderSearchInputWidth: 40rem; // Widths of the search overlay popup at different breakpoints diff --git a/src/shielding/middleware/handle-invalid-query-strings.ts b/src/shielding/middleware/handle-invalid-query-strings.ts index 9ef5639c99b8..97246b3c3502 100644 --- a/src/shielding/middleware/handle-invalid-query-strings.ts +++ b/src/shielding/middleware/handle-invalid-query-strings.ts @@ -13,6 +13,7 @@ export const MAX_UNFAMILIAR_KEYS_REDIRECT = 3 const RECOGNIZED_KEYS_BY_PREFIX = { '/_next/data/': ['versionId', 'productId', 'restPage', 'apiVersion', 'category', 'subcategory'], '/api/search': ['query', 'language', 'version', 'page', 'product', 'autocomplete', 'limit'], + '/api/combined-search': ['query', 'version', 'size', 'debug'], '/api/anchor-redirect': ['hash', 'path'], '/api/webhooks': ['category', 'version'], '/api/pageinfo': ['pathname'],