diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 1cbbff45705a..1fad6d5d1194 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,7 +5,7 @@ registries: type: docker-registry url: ghcr.io username: PAT - password: ${{secrets.CONTAINER_BUILDER_TOKEN}} + password: ${{secrets.BASE_CONTAINER_IMAGE_READER_DEPENDABOT}} updates: - package-ecosystem: npm diff --git a/Dockerfile b/Dockerfile index d721a58d3e73..d3a34d67428f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,7 +8,7 @@ # --------------------------------------------------------------- # To update the sha: # https://github.com/github/gh-base-image/pkgs/container/gh-base-image%2Fgh-base-noble -FROM ghcr.io/github/gh-base-image/gh-base-noble:20250709-201453-g6a417ef5f AS base +FROM ghcr.io/github/gh-base-image/gh-base-noble:20250711-151843-g9ff1d29c5 AS base # Install curl for Node install and determining the early access branch # Install git for cloning docs-early-access & translations repos diff --git a/content/actions/how-tos/writing-workflows/choosing-what-your-workflow-does/using-environments-for-deployment.md b/content/actions/concepts/workflows-and-actions/deployment-environments.md similarity index 54% rename from content/actions/how-tos/writing-workflows/choosing-what-your-workflow-does/using-environments-for-deployment.md rename to content/actions/concepts/workflows-and-actions/deployment-environments.md index f8151e40784a..70c4bd56667b 100644 --- a/content/actions/how-tos/writing-workflows/choosing-what-your-workflow-does/using-environments-for-deployment.md +++ b/content/actions/concepts/workflows-and-actions/deployment-environments.md @@ -1,27 +1,18 @@ --- -title: Using environments for deployment -shortTitle: Environments -intro: Specify a deployment environment in your workflow. +title: Deployment environments +intro: 'You can create and deploy to different environments.' versions: fpt: '*' ghes: '*' ghec: '*' -redirect_from: - - /actions/using-jobs/using-environments-for-jobs - - /actions/using-jobs/using-environments-for-deployment - - /actions/writing-workflows/choosing-what-your-workflow-does/using-environments-for-deployment +type: overview +topics: + - Actions + - Workflows --- - -{% data reusables.actions.enterprise-github-hosted-runners %} - -## About environments {% data reusables.actions.about-environments %} Each job in a workflow can reference a single environment. Any protection rules configured for the environment must pass before a job referencing the environment is sent to a runner. The job can access the environment's secrets only after the job is sent to a runner. When a workflow references an environment, the environment will appear in the repository's deployments. For more information about viewing current and previous deployments, see [AUTOTITLE](/actions/deployment/managing-your-deployments/viewing-deployment-history). - -## Using an environment in a workflow - -{% data reusables.actions.environment-example %} diff --git a/content/actions/concepts/workflows-and-actions/index.md b/content/actions/concepts/workflows-and-actions/index.md index 22ff6eb4b294..5047b9b11f97 100644 --- a/content/actions/concepts/workflows-and-actions/index.md +++ b/content/actions/concepts/workflows-and-actions/index.md @@ -13,6 +13,7 @@ children: - /about-custom-actions - /contexts - /expressions + - /deployment-environments - /dependency-caching - /about-monitoring-workflows - /notifications-for-workflow-runs diff --git a/content/actions/how-tos/writing-workflows/choosing-what-your-workflow-does/deploying-to-a-specific-environment.md b/content/actions/how-tos/writing-workflows/choosing-what-your-workflow-does/deploying-to-a-specific-environment.md new file mode 100644 index 000000000000..c61b07c0de5f --- /dev/null +++ b/content/actions/how-tos/writing-workflows/choosing-what-your-workflow-does/deploying-to-a-specific-environment.md @@ -0,0 +1,44 @@ +--- +title: Deploying to a specific environment +shortTitle: Deploy to environment +intro: Specify a deployment environment in your workflow. +versions: + fpt: '*' + ghes: '*' + ghec: '*' +redirect_from: + - /actions/using-jobs/using-environments-for-jobs + - /actions/using-jobs/using-environments-for-deployment + - /actions/writing-workflows/choosing-what-your-workflow-does/using-environments-for-deployment + - /actions/how-tos/writing-workflows/choosing-what-your-workflow-does/using-environments-for-deployment +--- + +## Prerequisites + +You need to create an environment before you can use it in a workflow. See [AUTOTITLE](/actions/how-tos/managing-workflow-runs-and-deployments/managing-deployments/managing-environments-for-deployment#creating-an-environment). + +## Using an environment in a workflow + +1. Open the workflow file you want to edit. +1. Use the following syntax to add a [`jobs..environment`](/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idenvironment) key to your workflow: + + ```yaml copy + jobs: + JOB-ID: + environment: ENVIRONMENT-NAME + ``` + + The chosen job will now be subject to any rules configured for the specified environment. +1. Optionally, specify a deployment URL for the environment using the following syntax: + + ```yaml copy + jobs: + JOB-ID: + environment: ENVIRONMENT-NAME + url: URL + ``` + + The specified URL will appear: + * On the deployments page for the repository + * In the visualization graph for the workflow run + * (If a pull request triggers the workflow) As a "View deployment" button in the pull request timeline diff --git a/content/actions/how-tos/writing-workflows/choosing-what-your-workflow-does/index.md b/content/actions/how-tos/writing-workflows/choosing-what-your-workflow-does/index.md index 93bea44eef14..0ef0ad1a2b33 100644 --- a/content/actions/how-tos/writing-workflows/choosing-what-your-workflow-does/index.md +++ b/content/actions/how-tos/writing-workflows/choosing-what-your-workflow-does/index.md @@ -18,7 +18,7 @@ children: - /store-information-in-variables - /passing-information-between-jobs - /setting-a-default-shell-and-working-directory - - /using-environments-for-deployment + - /deploying-to-a-specific-environment - /control-the-concurrency-of-workflows-and-jobs - /running-variations-of-jobs-in-a-workflow - /storing-and-sharing-data-from-a-workflow diff --git a/content/contributing/setting-up-your-environment-to-work-on-github-docs/creating-a-local-environment.md b/content/contributing/setting-up-your-environment-to-work-on-github-docs/creating-a-local-environment.md index 9f0d16386540..178824fd2b6e 100644 --- a/content/contributing/setting-up-your-environment-to-work-on-github-docs/creating-a-local-environment.md +++ b/content/contributing/setting-up-your-environment-to-work-on-github-docs/creating-a-local-environment.md @@ -74,7 +74,7 @@ For example, to enable Japanese and Portuguese in addition to English, you can e > [!NOTE] > Before you commit your changes, you should revert the `package.json` file to its original state. -The supported language codes are defined in [`src/languages/lib/languages.js`](https://github.com/github/docs/blob/main/src/languages/lib/languages.js). +The supported language codes are defined in [`src/languages/lib/languages.ts`](https://github.com/github/docs/blob/main/src/languages/lib/languages.ts). ## Using {% data variables.product.prodname_github_codespaces %} diff --git a/content/contributing/writing-for-github-docs/configuring-redirects.md b/content/contributing/writing-for-github-docs/configuring-redirects.md index 9036cd4f3d4f..03c3810c6a8b 100644 --- a/content/contributing/writing-for-github-docs/configuring-redirects.md +++ b/content/contributing/writing-for-github-docs/configuring-redirects.md @@ -38,7 +38,7 @@ See [`redirect_from`](https://github.com/github/docs/blob/main/content/README.md If a URL for a page is entered without a version (`https://docs.github.com/ARTICLE` instead of `https://docs.github.com/VERSION/ARTICLE`), the site will automatically redirect it to the first available version of the page. -The order of precedence is specified in [`lib/all-versions.js`](https://github.com/github/docs/blob/main/src/versions/lib/all-versions.js). The current order of precedence is: +The order of precedence is specified in [`lib/all-versions.ts`](https://github.com/github/docs/blob/main/src/versions/lib/all-versions.ts). The current order of precedence is: 1. {% data variables.product.prodname_free_team %}, {% data variables.product.prodname_pro %}, or {% data variables.product.prodname_team %} (`fpt`) 1. {% data variables.product.prodname_ghe_cloud %} (`ghec`) diff --git a/content/contributing/writing-for-github-docs/using-yaml-frontmatter.md b/content/contributing/writing-for-github-docs/using-yaml-frontmatter.md index 33edfed2f65f..59fcf89afd41 100644 --- a/content/contributing/writing-for-github-docs/using-yaml-frontmatter.md +++ b/content/contributing/writing-for-github-docs/using-yaml-frontmatter.md @@ -46,7 +46,7 @@ For more information, see [`lib/frontmatter.js`](https://github.com/github/docs/ ### `versions` -* Purpose: Indicates the [versions](https://github.com/github/docs/blob/main/src/versions/lib/all-versions.js) to which a page applies. +* Purpose: Indicates the [versions](https://github.com/github/docs/blob/main/src/versions/lib/all-versions.ts) to which a page applies. For more information about the different types of versioning, see [Versioning documentation](/contributing/syntax-and-versioning-for-github-docs/versioning-documentation). * Type: `Object`. Allowable keys map to product names and can be found in the `versions` object in [`lib/frontmatter.js`](https://github.com/github/docs/blob/main/src/frame/lib/frontmatter.js). * This frontmatter value is currently **required** for all pages. diff --git a/content/copilot/get-started/github-copilot-features.md b/content/copilot/get-started/github-copilot-features.md index 337da2978722..c0caf0037053 100644 --- a/content/copilot/get-started/github-copilot-features.md +++ b/content/copilot/get-started/github-copilot-features.md @@ -57,7 +57,7 @@ AI-generated text completion to help you write pull request descriptions quickly Enhance {% data variables.copilot.copilot_chat_short %} responses by providing contextual details on your preferences, tools, and requirements. See [AUTOTITLE](/copilot/customizing-copilot/about-customizing-github-copilot-chat-responses). -### {% data variables.copilot.copilot_desktop_short %} ({% data variables.release-phases.public_preview %}) +### {% data variables.copilot.copilot_desktop_short %} Automatically generate commit messages and descriptions with {% data variables.copilot.copilot_desktop_short %} based on the changes you make to your project. diff --git a/content/copilot/how-tos/administer/enterprises/managing-policies-and-features-for-copilot-in-your-enterprise.md b/content/copilot/how-tos/administer/enterprises/managing-policies-and-features-for-copilot-in-your-enterprise.md index 854fab8e7ee9..9bd8202e77ba 100644 --- a/content/copilot/how-tos/administer/enterprises/managing-policies-and-features-for-copilot-in-your-enterprise.md +++ b/content/copilot/how-tos/administer/enterprises/managing-policies-and-features-for-copilot-in-your-enterprise.md @@ -30,7 +30,7 @@ You can configure policies for your enterprise. If no policy is chosen at the en | ----------- | ------------------------------------------ | | [{% data variables.product.prodname_copilot_short %} in {% data variables.product.prodname_dotcom_the_website %}](#copilot-in-githubcom) | least restrictive | | [{% data variables.copilot.copilot_cli %}](#github-copilot-in-the-cli) | least restrictive | -| [{% data variables.copilot.copilot_desktop_short %} ({% data variables.release-phases.public_preview %})](#copilot-in-github-desktop-public-preview) | least restrictive | +| [{% data variables.copilot.copilot_desktop_short %}](#copilot-in-github-desktop) | least restrictive | | [{% data variables.copilot.copilot_chat %} in the IDE](#github-copilot-chat-in-the-ide) | least restrictive | | [Editor preview features](#editor-preview-features) | least restrictive | | [{% data variables.copilot.copilot_mobile %}](#github-copilot-chat-in-github-mobile) | least restrictive | @@ -57,7 +57,7 @@ If you enable "{% data variables.product.prodname_copilot_short %} in {% data va {% data variables.copilot.copilot_cli %} is an extension for {% data variables.product.prodname_cli %} which provides a chat-like interface in the terminal. You can ask {% data variables.product.prodname_copilot %} for command suggestions, or for explanations of commands they run. -### {% data variables.copilot.copilot_desktop_short %} ({% data variables.release-phases.public_preview %}) +### {% data variables.copilot.copilot_desktop_short %} You can generate commit messages and descriptions in {% data variables.product.prodname_desktop %} based on the changes you make to your project. diff --git a/content/copilot/how-tos/administer/organizations/managing-policies-for-copilot-in-your-organization.md b/content/copilot/how-tos/administer/organizations/managing-policies-for-copilot-in-your-organization.md index 159544004ff9..7859ae8bb4fb 100644 --- a/content/copilot/how-tos/administer/organizations/managing-policies-for-copilot-in-your-organization.md +++ b/content/copilot/how-tos/administer/organizations/managing-policies-for-copilot-in-your-organization.md @@ -33,11 +33,11 @@ Organization owners can set policies to govern how {% data variables.product.pro * Image support in {% data variables.copilot.copilot_chat_short %} (available in {% data variables.product.prodname_vscode_shortname %} and {% data variables.product.prodname_vs %}) >[!NOTE] This setting only applies to preview features within {% data variables.product.prodname_copilot_short %} and does not control all preview-related settings in {% data variables.product.prodname_vscode_shortname %}. * {% data variables.copilot.copilot_coding_agent %} ({% data variables.release-phases.public_preview %}) -* {% data variables.copilot.copilot_spaces %} (public preview) +* {% data variables.copilot.copilot_spaces %} ({% data variables.release-phases.public_preview %}) * MCP servers on {% data variables.product.prodname_dotcom_the_website %} ({% data variables.release-phases.public_preview %}) * {% data variables.copilot.copilot_mobile_short %} * {% data variables.copilot.copilot_cli_short %} and {% data variables.product.prodname_windows_terminal %} -* {% data variables.copilot.copilot_desktop_short %} ({% data variables.release-phases.public_preview %}) +* {% data variables.copilot.copilot_desktop_short %} * Suggestions matching public code * Access to alternative models for {% data variables.product.prodname_copilot_short %} * Anthropic {% data variables.copilot.copilot_claude %} in {% data variables.product.prodname_copilot_short %} @@ -65,7 +65,7 @@ If an organization member is assigned a seat by multiple organizations with diff For example, to enable or disable suggestion matching, in the "Suggestions matching public code" dropdown, select **Allowed** or **Blocked**. -1. If your organization has a {% data variables.copilot.copilot_business_short %}{% ifversion ghec %} or {% data variables.copilot.copilot_enterprise_short %}{% endif %} plan and you enable "{% data variables.product.prodname_copilot_short %} in {% data variables.product.prodname_dotcom_the_website %}", two additional options are displayed: +1. If your organization has a {% data variables.copilot.copilot_business_short %}{% ifversion ghec %} or {% data variables.copilot.copilot_enterprise_short %}{% endif %} plan and you enable "{% data variables.product.prodname_copilot_short %} in {% data variables.product.prodname_dotcom_the_website %}," two additional options are displayed: {% data reusables.copilot.policies-for-dotcom %} diff --git a/content/copilot/how-tos/custom-instructions/adding-repository-custom-instructions-for-github-copilot.md b/content/copilot/how-tos/custom-instructions/adding-repository-custom-instructions-for-github-copilot.md index 9ce1b511a9de..fd994b5f9ff8 100644 --- a/content/copilot/how-tos/custom-instructions/adding-repository-custom-instructions-for-github-copilot.md +++ b/content/copilot/how-tos/custom-instructions/adding-repository-custom-instructions-for-github-copilot.md @@ -238,7 +238,7 @@ You can create a custom instructions file in your repository via the {% data var Whitespace between instructions is ignored, so the instructions can be written as a single paragraph, each on a new line, or separated by blank lines for legibility. 1. Open the {% data variables.product.prodname_copilot %} for Xcode application. -1. At the top of the application window, click **Advanced**. +1. At the top of the application window, under **Settings**, click **Advanced**. 1. To the right of "Custom Instructions", click **Current Workspace** or **Global** to choose whether the custom instructions apply to the current workspace or all workspaces. {% endxcode %} diff --git a/content/desktop/making-changes-in-a-branch/committing-and-reviewing-changes-to-your-project-in-github-desktop.md b/content/desktop/making-changes-in-a-branch/committing-and-reviewing-changes-to-your-project-in-github-desktop.md index b29ba610d0ba..d4a63b92c839 100644 --- a/content/desktop/making-changes-in-a-branch/committing-and-reviewing-changes-to-your-project-in-github-desktop.md +++ b/content/desktop/making-changes-in-a-branch/committing-and-reviewing-changes-to-your-project-in-github-desktop.md @@ -24,7 +24,7 @@ Repository administrators can enable rulesets for a branch to enforce specific c 1. [Create a new branch](/desktop/making-changes-in-a-branch/managing-branches-in-github-desktop), or select an existing branch by clicking **{% octicon "git-branch" aria-hidden="true" aria-label="git-branch" %} Current Branch** on the toolbar and selecting the branch from the list. - ![Screenshot of the "Current Branch" dropdown view. Under "Recent Branches", a branch, named "my-feature", is highlighted with an orange outline.](/assets/images/help/desktop/select-branch-from-dropdown.png) + ![Screenshot of the "Current Branch" dropdown view. Under "Recent Branches," a branch, named "my-feature," is highlighted with an orange outline.](/assets/images/help/desktop/select-branch-from-dropdown.png) {% data reusables.desktop.make-changes %} ## Choosing how to display diffs @@ -32,7 +32,7 @@ Repository administrators can enable rulesets for a branch to enforce specific c You can change the way diffs are displayed in {% data variables.product.prodname_desktop %} to suit your reviewing needs. To change how you view diffs, in the top-right corner of the diff view, click {% octicon "gear" aria-label="The Gear icon" %}. -* To change how the entire diff is displayed, under "Diff display", select **Unified** or **Split**. The Unified view shows changes linearly, while the Split view shows old content on the left side and new content on the right side. +* To change how the entire diff is displayed, under "Diff display," select **Unified** or **Split**. The Unified view shows changes linearly, while the Split view shows old content on the left side and new content on the right side. * To hide whitespace changes so you can focus on more substantive changes, select **Hide Whitespace Changes**. ![Screenshot of the diff view of a file. A gear icon is outlined in orange and expanded to display "Whitespace" and "Diff display" settings.](/assets/images/help/desktop/diff-selection.png) @@ -43,7 +43,7 @@ If you need to see more of the file than {% data variables.product.prodname_desk * To see the next few lines above or below the highlighted changes, click the arrow above or below the line numbers. * To see the entire file, right-click in the diff view and click **Expand Whole File**. -![Screenshot of the diff view of a "README" file. Over a green "addition" line, in a context menu, the cursor hovers over "Expand Whole File".](/assets/images/help/desktop/expand-diff-view.png) +![Screenshot of the diff view of a "README" file. Over a green "addition" line, in a context menu, the cursor hovers over "Expand Whole File."](/assets/images/help/desktop/expand-diff-view.png) ## Selecting changes to include in a commit @@ -57,7 +57,7 @@ In the "Changes" tab in the left sidebar: * To access stashed changes, click **Stashed Changes**. * {% data reusables.desktop.commit-all-desc %} - ![Screenshot of the "Changes" tab. Above the list of changed files, next to the text "3 changed files", a selected checkbox is outlined in orange.](/assets/images/help/desktop/commit-all.png) + ![Screenshot of the "Changes" tab. Above the list of changed files, next to the text "3 changed files," a selected checkbox is outlined in orange.](/assets/images/help/desktop/commit-all.png) * {% data reusables.desktop.commit-some-desc %} ### Creating a partial commit @@ -79,7 +79,7 @@ Discarded changes are saved in a dated file in the Trash. You can recover discar {% data reusables.desktop.select-discard-files %} {% data reusables.desktop.click-discard-files %} - ![Screenshot of the "Changes" tab. Two selected files are highlighted in blue. In a context menu, the cursor hovers over "Discard 2 Selected Changes".](/assets/images/help/desktop/discard-changes-mac.png) + ![Screenshot of the "Changes" tab. Two selected files are highlighted in blue. In a context menu, the cursor hovers over "Discard 2 Selected Changes."](/assets/images/help/desktop/discard-changes-mac.png) {% data reusables.desktop.confirm-discard-files %} ### Discarding changes in one or more lines @@ -91,7 +91,7 @@ You can discard one or more changed lines that are uncommitted. To discard one added line, in the list of changed lines, right-click the line number of the line you want to discard, then select **Discard Added Line**. -![Screenshot of the diff view of a file. In a context menu, a cursor hovers over "Discard Added Line", highlighted in blue.](/assets/images/help/desktop/discard-single-line.png) +![Screenshot of the diff view of a file. In a context menu, a cursor hovers over "Discard Added Line," highlighted in blue.](/assets/images/help/desktop/discard-single-line.png) To discard a group of changed lines, right-click the vertical bar to the right of the line numbers for the lines you want to discard, then select **Discard added lines**. @@ -105,7 +105,8 @@ Once you're satisfied with the changes you've chosen to include in your commit, > {% data reusables.desktop.tags-push-with-commits %} For more information, see [AUTOTITLE](/desktop/managing-commits/managing-tags-in-github-desktop). 1. At the bottom of the list of changes, next to your profile picture, describe your commit: - * If you have access to {% data variables.product.prodname_copilot %} and you are using [{% data variables.product.prodname_desktop %} beta](https://desktop.github.com/beta/), you can automatically create a commit message and details based on the changes you made. Click {% octicon "copilot" aria-label="Generate commit message with Copilot" %}. + * If you have access to {% data variables.product.prodname_copilot %}, you can automatically create a commit message and details based on the changes you made. Click {% octicon "copilot" aria-label="Generate commit message with Copilot" %}. + * If you want to regenerate a different commit message, click {% octicon "copilot" aria-label="Regenerate commit message with Copilot" %} again to generate a new suggestion. * Alternatively, type your own short, meaningful commit message in the Summary field. You can also add more information about the change in the Description field. ![Screenshot of the "Changes" tab. The "Summary" field and "Generate commit message with Copilot" button are outlined in orange.](/assets/images/help/desktop/create-commit-details.png) @@ -125,7 +126,7 @@ Once you're satisfied with the changes you've chosen to include in your commit, If a pull request has not been created for the current branch, {% data variables.product.prodname_desktop %} will give you the option to preview the changes and create one. For more information, see [AUTOTITLE](/desktop/working-with-your-remote-repository-on-github-or-github-enterprise/creating-an-issue-or-pull-request-from-github-desktop). - ![Screenshot of the "No local changes" view. A button, labeled "Preview Pull Request", is highlighted with an orange outline.](/assets/images/help/desktop/mac-preview-pull-request.png) + ![Screenshot of the "No local changes" view. A button, labeled "Preview Pull Request," is highlighted with an orange outline.](/assets/images/help/desktop/mac-preview-pull-request.png) ## Managing your commit history diff --git a/src/content-render/unified/rewrite-local-links.js b/src/content-render/unified/rewrite-local-links.js index ee297d85db05..795abd0605b8 100644 --- a/src/content-render/unified/rewrite-local-links.js +++ b/src/content-render/unified/rewrite-local-links.js @@ -1,3 +1,6 @@ +// When updating this file to typescript, +// update links in content/contributing as well + import path from 'path' import stripAnsi from 'strip-ansi' diff --git a/src/frame/lib/frontmatter.js b/src/frame/lib/frontmatter.js index bb8dddd9ce61..9d12b7049709 100644 --- a/src/frame/lib/frontmatter.js +++ b/src/frame/lib/frontmatter.js @@ -1,3 +1,6 @@ +// when updating to typescript, +// update links in content/contributing as well + import parse from './read-frontmatter' import { allVersions } from '@/versions/lib/all-versions' import { allTools } from '@/tools/lib/all-tools' diff --git a/src/shielding/middleware/handle-malformed-urls.ts b/src/shielding/middleware/handle-malformed-urls.ts new file mode 100644 index 000000000000..a785864e5069 --- /dev/null +++ b/src/shielding/middleware/handle-malformed-urls.ts @@ -0,0 +1,31 @@ +import type { Response, NextFunction } from 'express' + +import { defaultCacheControl } from '@/frame/middleware/cache-control' +import { ExtendedRequest } from '@/types' + +/** + * Middleware to handle malformed UTF-8 sequences in URLs that cause + * decodeURIComponent to fail. This prevents crashes from malicious + * requests containing invalid URL-encoded sequences like %FF. + */ +export default function handleMalformedUrls( + req: ExtendedRequest, + res: Response, + next: NextFunction, +) { + // Check URL for malformed UTF-8 sequences + // Express/router doesn't catch these during initial parsing - they cause + // crashes later when decodeURIComponent is called at the router level + const url = req.originalUrl || req.url + try { + decodeURIComponent(url) + } catch { + // If any decoding fails, this is a malformed URL + defaultCacheControl(res) + res.setHeader('content-type', 'text/plain') + res.status(400).send('Bad Request: Malformed URL') + return + } + + return next() +} diff --git a/src/shielding/middleware/index.ts b/src/shielding/middleware/index.ts index a22f952c520d..61af52f9c085 100644 --- a/src/shielding/middleware/index.ts +++ b/src/shielding/middleware/index.ts @@ -1,5 +1,6 @@ import express from 'express' +import handleMalformedUrls from './handle-malformed-urls' import handleInvalidQuerystrings from './handle-invalid-query-strings' import handleInvalidPaths from './handle-invalid-paths' import handleOldNextDataPaths from './handle-old-next-data-paths' @@ -9,6 +10,7 @@ import handleInvalidHeaders from './handle-invalid-headers' const router = express.Router() +router.use(handleMalformedUrls) router.use(handleInvalidQuerystrings) router.use(handleInvalidPaths) router.use(handleOldNextDataPaths) diff --git a/src/shielding/tests/malformed-urls.ts b/src/shielding/tests/malformed-urls.ts new file mode 100644 index 000000000000..9262e707f084 --- /dev/null +++ b/src/shielding/tests/malformed-urls.ts @@ -0,0 +1,72 @@ +import { describe, expect, test } from 'vitest' +import { get } from '@/tests/helpers/e2etest' + +describe('malformed URLs', () => { + test('blocks URLs with %FF sequences', async () => { + const res = await get('/en/site-policy/other-site-policies/github-account-%FFqrlkuciqll-policy') + + expect(res.statusCode).toBe(400) + expect(res.headers['content-type']).toMatch('text/plain') + expect(res.body).toBe('Bad Request: Malformed URL') + }) + + test('blocks URLs with %FE sequences', async () => { + const res = await get('/en/some-page-%FE-test') + expect(res.statusCode).toBe(400) + expect(res.headers['content-type']).toMatch('text/plain') + expect(res.body).toBe('Bad Request: Malformed URL') + }) + + test('blocks URLs with overlong encoding %C0%80', async () => { + const res = await get('/en/test-%C0%80-page') + expect(res.statusCode).toBe(400) + expect(res.headers['content-type']).toMatch('text/plain') + expect(res.body).toBe('Bad Request: Malformed URL') + }) + + test('blocks URLs with invalid UTF-8 continuation sequences', async () => { + const res = await get('/en/test-%80%80-page') + expect(res.statusCode).toBe(400) + expect(res.headers['content-type']).toMatch('text/plain') + expect(res.body).toBe('Bad Request: Malformed URL') + }) + + test('allows URLs with control characters (valid UTF-8)', async () => { + const res = await get('/en/test-%01-page') + expect(res.statusCode).toBe(404) // Should be 404 since page doesn't exist, not 400 + // Control characters like %01 are valid UTF-8 and don't cause decoding errors + }) + + test('allows valid URLs with proper encoding', async () => { + const res = await get('/en/get-started') + expect(res.statusCode).not.toBe(400) + // Should not be blocked by malformed URL middleware + }) + + test('allows valid URLs with proper percent encoding', async () => { + const res = await get('/en/search?q=test%20query') + expect(res.statusCode).not.toBe(400) + // Should not be blocked by malformed URL middleware + }) + + test('blocks malformed query parameters', async () => { + // This is caught by checking originalUrl which contains the raw, unparsed URL + const res = await get('/en/search?q=test%FF') + expect(res.statusCode).toBe(400) + expect(res.headers['content-type']).toMatch('text/plain') + expect(res.body).toBe('Bad Request: Malformed URL') + }) + + test('properly caches malformed URL responses', async () => { + const res = await get('/en/malformed-%FF-url') + expect(res.statusCode).toBe(400) + expect(res.headers['cache-control']).toBeDefined() + }) + + test('handles multiple malformed sequences', async () => { + const res = await get('/en/test-%FF%FE%80-page') + expect(res.statusCode).toBe(400) + expect(res.headers['content-type']).toMatch('text/plain') + expect(res.body).toBe('Bad Request: Malformed URL') + }) +})