new file mode 100644
@@ -0,0 +1,422 @@
+From 8675348c70a2d0c4938f0b31c4aa2aba46c00b32 Mon Sep 17 00:00:00 2001
+From: Y5 <124019959+y5c4l3@users.noreply.github.com>
+Date: Fri, 27 Sep 2024 16:16:08 +0000
+Subject: [PATCH] Fix #2768: Quote template strings in activation scripts
+ (#2771)
+
+CVE: CVE-2024-53899
+Upstream-Status: Backport [https://github.com/pypa/virtualenv/commit/86dddeda7c991f8529e1995bbff280fb7b761972]
+Signed-off-by: Ankur Tyagi <ankur.tyagi85@gmail.com>
+---
+ src/virtualenv/activation/bash/activate.sh | 8 +++----
+ src/virtualenv/activation/batch/__init__.py | 4 ++++
+ src/virtualenv/activation/cshell/activate.csh | 8 +++----
+ src/virtualenv/activation/fish/activate.fish | 8 +++----
+ src/virtualenv/activation/nushell/__init__.py | 19 +++++++++++++++++
+ src/virtualenv/activation/nushell/activate.nu | 8 +++----
+ .../activation/powershell/__init__.py | 12 +++++++++++
+ .../activation/powershell/activate.ps1 | 6 +++---
+ src/virtualenv/activation/python/__init__.py | 6 +++++-
+ .../activation/python/activate_this.py | 8 +++----
+ src/virtualenv/activation/via_template.py | 13 +++++++++++-
+ tests/conftest.py | 6 +++++-
+ tests/unit/activation/conftest.py | 3 +--
+ tests/unit/activation/test_batch.py | 10 ++++-----
+ tests/unit/activation/test_powershell.py | 21 +++++++++++++------
+ 16 files changed, 104 insertions(+), 39 deletions(-)
+
+diff --git a/src/virtualenv/activation/bash/activate.sh b/src/virtualenv/activation/bash/activate.sh
+index b06e3fd3..e412509b 100644
+--- a/src/virtualenv/activation/bash/activate.sh
++++ b/src/virtualenv/activation/bash/activate.sh
+@@ -45,18 +45,18 @@ deactivate () {
+ # unset irrelevant variables
+ deactivate nondestructive
+
+-VIRTUAL_ENV='__VIRTUAL_ENV__'
++VIRTUAL_ENV=__VIRTUAL_ENV__
+ if ([ "$OSTYPE" = "cygwin" ] || [ "$OSTYPE" = "msys" ]) && $(command -v cygpath &> /dev/null) ; then
+ VIRTUAL_ENV=$(cygpath -u "$VIRTUAL_ENV")
+ fi
+ export VIRTUAL_ENV
+
+ _OLD_VIRTUAL_PATH="$PATH"
+-PATH="$VIRTUAL_ENV/__BIN_NAME__:$PATH"
++PATH="$VIRTUAL_ENV/"__BIN_NAME__":$PATH"
+ export PATH
+
+-if [ "x__VIRTUAL_PROMPT__" != x ] ; then
+- VIRTUAL_ENV_PROMPT="__VIRTUAL_PROMPT__"
++if [ "x"__VIRTUAL_PROMPT__ != x ] ; then
++ VIRTUAL_ENV_PROMPT=__VIRTUAL_PROMPT__
+ else
+ VIRTUAL_ENV_PROMPT=$(basename "$VIRTUAL_ENV")
+ fi
+diff --git a/src/virtualenv/activation/batch/__init__.py b/src/virtualenv/activation/batch/__init__.py
+index a6d58ebb..3d74ba83 100644
+--- a/src/virtualenv/activation/batch/__init__.py
++++ b/src/virtualenv/activation/batch/__init__.py
+@@ -15,6 +15,10 @@ class BatchActivator(ViaTemplateActivator):
+ yield "deactivate.bat"
+ yield "pydoc.bat"
+
++ @staticmethod
++ def quote(string):
++ return string
++
+ def instantiate_template(self, replacements, template, creator):
+ # ensure the text has all newlines as \r\n - required by batch
+ base = super().instantiate_template(replacements, template, creator)
+diff --git a/src/virtualenv/activation/cshell/activate.csh b/src/virtualenv/activation/cshell/activate.csh
+index f0c9cca9..24de5508 100644
+--- a/src/virtualenv/activation/cshell/activate.csh
++++ b/src/virtualenv/activation/cshell/activate.csh
+@@ -10,15 +10,15 @@ alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PA
+ # Unset irrelevant variables.
+ deactivate nondestructive
+
+-setenv VIRTUAL_ENV '__VIRTUAL_ENV__'
++setenv VIRTUAL_ENV __VIRTUAL_ENV__
+
+ set _OLD_VIRTUAL_PATH="$PATH:q"
+-setenv PATH "$VIRTUAL_ENV:q/__BIN_NAME__:$PATH:q"
++setenv PATH "$VIRTUAL_ENV:q/"__BIN_NAME__":$PATH:q"
+
+
+
+-if ('__VIRTUAL_PROMPT__' != "") then
+- setenv VIRTUAL_ENV_PROMPT '__VIRTUAL_PROMPT__'
++if (__VIRTUAL_PROMPT__ != "") then
++ setenv VIRTUAL_ENV_PROMPT __VIRTUAL_PROMPT__
+ else
+ setenv VIRTUAL_ENV_PROMPT "$VIRTUAL_ENV:t:q"
+ endif
+diff --git a/src/virtualenv/activation/fish/activate.fish b/src/virtualenv/activation/fish/activate.fish
+index c453caf9..f3cd1f2a 100644
+--- a/src/virtualenv/activation/fish/activate.fish
++++ b/src/virtualenv/activation/fish/activate.fish
+@@ -58,7 +58,7 @@ end
+ # Unset irrelevant variables.
+ deactivate nondestructive
+
+-set -gx VIRTUAL_ENV '__VIRTUAL_ENV__'
++set -gx VIRTUAL_ENV __VIRTUAL_ENV__
+
+ # https://github.com/fish-shell/fish-shell/issues/436 altered PATH handling
+ if test (echo $FISH_VERSION | head -c 1) -lt 3
+@@ -66,12 +66,12 @@ if test (echo $FISH_VERSION | head -c 1) -lt 3
+ else
+ set -gx _OLD_VIRTUAL_PATH $PATH
+ end
+-set -gx PATH "$VIRTUAL_ENV"'/__BIN_NAME__' $PATH
++set -gx PATH "$VIRTUAL_ENV"'/'__BIN_NAME__ $PATH
+
+ # Prompt override provided?
+ # If not, just use the environment name.
+-if test -n '__VIRTUAL_PROMPT__'
+- set -gx VIRTUAL_ENV_PROMPT '__VIRTUAL_PROMPT__'
++if test -n __VIRTUAL_PROMPT__
++ set -gx VIRTUAL_ENV_PROMPT __VIRTUAL_PROMPT__
+ else
+ set -gx VIRTUAL_ENV_PROMPT (basename "$VIRTUAL_ENV")
+ end
+diff --git a/src/virtualenv/activation/nushell/__init__.py b/src/virtualenv/activation/nushell/__init__.py
+index 68cd4a3b..ef7a79a9 100644
+--- a/src/virtualenv/activation/nushell/__init__.py
++++ b/src/virtualenv/activation/nushell/__init__.py
+@@ -7,6 +7,25 @@ class NushellActivator(ViaTemplateActivator):
+ def templates(self):
+ yield "activate.nu"
+
++ @staticmethod
++ def quote(string):
++ """
++ Nushell supports raw strings like: r###'this is a string'###.
++
++ This method finds the maximum continuous sharps in the string and then
++ quote it with an extra sharp.
++ """
++ max_sharps = 0
++ current_sharps = 0
++ for char in string:
++ if char == "#":
++ current_sharps += 1
++ max_sharps = max(current_sharps, max_sharps)
++ else:
++ current_sharps = 0
++ wrapping = "#" * (max_sharps + 1)
++ return f"r{wrapping}'{string}'{wrapping}"
++
+ def replacements(self, creator, dest_folder): # noqa: ARG002
+ return {
+ "__VIRTUAL_PROMPT__": "" if self.flag_prompt is None else self.flag_prompt,
+diff --git a/src/virtualenv/activation/nushell/activate.nu b/src/virtualenv/activation/nushell/activate.nu
+index 19d4fa1d..00a41e0e 100644
+--- a/src/virtualenv/activation/nushell/activate.nu
++++ b/src/virtualenv/activation/nushell/activate.nu
+@@ -32,8 +32,8 @@ export-env {
+ }
+ }
+
+- let virtual_env = '__VIRTUAL_ENV__'
+- let bin = '__BIN_NAME__'
++ let virtual_env = __VIRTUAL_ENV__
++ let bin = __BIN_NAME__
+
+ let is_windows = ($nu.os-info.family) == 'windows'
+ let path_name = (if (has-env 'Path') {
+@@ -47,10 +47,10 @@ export-env {
+ let new_path = ($env | get $path_name | prepend $venv_path)
+
+ # If there is no default prompt, then use the env name instead
+- let virtual_env_prompt = (if ('__VIRTUAL_PROMPT__' | is-empty) {
++ let virtual_env_prompt = (if (__VIRTUAL_PROMPT__ | is-empty) {
+ ($virtual_env | path basename)
+ } else {
+- '__VIRTUAL_PROMPT__'
++ __VIRTUAL_PROMPT__
+ })
+
+ let new_env = {
+diff --git a/src/virtualenv/activation/powershell/__init__.py b/src/virtualenv/activation/powershell/__init__.py
+index 1f6d0f4e..8489656c 100644
+--- a/src/virtualenv/activation/powershell/__init__.py
++++ b/src/virtualenv/activation/powershell/__init__.py
+@@ -7,6 +7,18 @@ class PowerShellActivator(ViaTemplateActivator):
+ def templates(self):
+ yield "activate.ps1"
+
++ @staticmethod
++ def quote(string):
++ """
++ This should satisfy PowerShell quoting rules [1], unless the quoted
++ string is passed directly to Windows native commands [2].
++
++ [1]: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_quoting_rules
++ [2]: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_parsing#passing-arguments-that-contain-quote-characters
++ """ # noqa: D205
++ string = string.replace("'", "''")
++ return f"'{string}'"
++
+
+ __all__ = [
+ "PowerShellActivator",
+diff --git a/src/virtualenv/activation/powershell/activate.ps1 b/src/virtualenv/activation/powershell/activate.ps1
+index 5ccfe120..bd30e2ee 100644
+--- a/src/virtualenv/activation/powershell/activate.ps1
++++ b/src/virtualenv/activation/powershell/activate.ps1
+@@ -37,8 +37,8 @@ deactivate -nondestructive
+ $VIRTUAL_ENV = $BASE_DIR
+ $env:VIRTUAL_ENV = $VIRTUAL_ENV
+
+-if ("__VIRTUAL_PROMPT__" -ne "") {
+- $env:VIRTUAL_ENV_PROMPT = "__VIRTUAL_PROMPT__"
++if (__VIRTUAL_PROMPT__ -ne "") {
++ $env:VIRTUAL_ENV_PROMPT = __VIRTUAL_PROMPT__
+ }
+ else {
+ $env:VIRTUAL_ENV_PROMPT = $( Split-Path $env:VIRTUAL_ENV -Leaf )
+@@ -46,7 +46,7 @@ else {
+
+ New-Variable -Scope global -Name _OLD_VIRTUAL_PATH -Value $env:PATH
+
+-$env:PATH = "$env:VIRTUAL_ENV/__BIN_NAME____PATH_SEP__" + $env:PATH
++$env:PATH = "$env:VIRTUAL_ENV/" + __BIN_NAME__ + __PATH_SEP__ + $env:PATH
+ if (!$env:VIRTUAL_ENV_DISABLE_PROMPT) {
+ function global:_old_virtual_prompt {
+ ""
+diff --git a/src/virtualenv/activation/python/__init__.py b/src/virtualenv/activation/python/__init__.py
+index 3126a39f..e900f7ec 100644
+--- a/src/virtualenv/activation/python/__init__.py
++++ b/src/virtualenv/activation/python/__init__.py
+@@ -10,10 +10,14 @@ class PythonActivator(ViaTemplateActivator):
+ def templates(self):
+ yield "activate_this.py"
+
++ @staticmethod
++ def quote(string):
++ return repr(string)
++
+ def replacements(self, creator, dest_folder):
+ replacements = super().replacements(creator, dest_folder)
+ lib_folders = OrderedDict((os.path.relpath(str(i), str(dest_folder)), None) for i in creator.libs)
+- lib_folders = os.pathsep.join(lib_folders.keys()).replace("\\", "\\\\") # escape Windows path characters
++ lib_folders = os.pathsep.join(lib_folders.keys())
+ replacements.update(
+ {
+ "__LIB_FOLDERS__": lib_folders,
+diff --git a/src/virtualenv/activation/python/activate_this.py b/src/virtualenv/activation/python/activate_this.py
+index befe8f40..f297cae3 100644
+--- a/src/virtualenv/activation/python/activate_this.py
++++ b/src/virtualenv/activation/python/activate_this.py
+@@ -19,18 +19,18 @@ except NameError as exc:
+ raise AssertionError(msg) from exc
+
+ bin_dir = os.path.dirname(abs_file)
+-base = bin_dir[: -len("__BIN_NAME__") - 1] # strip away the bin part from the __file__, plus the path separator
++base = bin_dir[: -len(__BIN_NAME__) - 1] # strip away the bin part from the __file__, plus the path separator
+
+ # prepend bin to PATH (this file is inside the bin directory)
+ os.environ["PATH"] = os.pathsep.join([bin_dir, *os.environ.get("PATH", "").split(os.pathsep)])
+ os.environ["VIRTUAL_ENV"] = base # virtual env is right above bin directory
+-os.environ["VIRTUAL_ENV_PROMPT"] = "__VIRTUAL_PROMPT__" or os.path.basename(base) # noqa: SIM222
++os.environ["VIRTUAL_ENV_PROMPT"] = __VIRTUAL_PROMPT__ or os.path.basename(base)
+
+ # add the virtual environments libraries to the host python import mechanism
+ prev_length = len(sys.path)
+-for lib in "__LIB_FOLDERS__".split(os.pathsep):
++for lib in __LIB_FOLDERS__.split(os.pathsep):
+ path = os.path.realpath(os.path.join(bin_dir, lib))
+- site.addsitedir(path.decode("utf-8") if "__DECODE_PATH__" else path)
++ site.addsitedir(path.decode("utf-8") if __DECODE_PATH__ else path)
+ sys.path[:] = sys.path[prev_length:] + sys.path[0:prev_length]
+
+ sys.real_prefix = sys.prefix
+diff --git a/src/virtualenv/activation/via_template.py b/src/virtualenv/activation/via_template.py
+index 373316cf..1f532213 100644
+--- a/src/virtualenv/activation/via_template.py
++++ b/src/virtualenv/activation/via_template.py
+@@ -1,6 +1,7 @@
+ from __future__ import annotations
+
+ import os
++import shlex
+ import sys
+ from abc import ABC, abstractmethod
+
+@@ -21,6 +22,16 @@ class ViaTemplateActivator(Activator, ABC):
+ def templates(self):
+ raise NotImplementedError
+
++ @staticmethod
++ def quote(string):
++ """
++ Quote strings in the activation script.
++
++ :param string: the string to quote
++ :return: quoted string that works in the activation script
++ """
++ return shlex.quote(string)
++
+ def generate(self, creator):
+ dest_folder = creator.bin_dir
+ replacements = self.replacements(creator, dest_folder)
+@@ -63,7 +74,7 @@ class ViaTemplateActivator(Activator, ABC):
+ text = binary.decode("utf-8", errors="strict")
+ for key, value in replacements.items():
+ value_uni = self._repr_unicode(creator, value)
+- text = text.replace(key, value_uni)
++ text = text.replace(key, self.quote(value_uni))
+ return text
+
+ @staticmethod
+diff --git a/tests/conftest.py b/tests/conftest.py
+index 03f808fa..b67c2956 100644
+--- a/tests/conftest.py
++++ b/tests/conftest.py
+@@ -275,7 +275,11 @@ def is_inside_ci():
+
+ @pytest.fixture(scope="session")
+ def special_char_name():
+- base = "e-$ èрт