Skip to content

Commit 86ddded

Browse files
authored
Fix #2768: Quote template strings in activation scripts (#2771)
1 parent 6bb3f62 commit 86ddded

File tree

17 files changed

+106
-39
lines changed

17 files changed

+106
-39
lines changed

docs/changelog/2768.bugfix.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Properly quote string placeholders in activation script templates to mitigate
2+
potential command injection - by :user:`y5c4l3`.

pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,9 @@ lint.ignore = [
133133
"S404", # Using subprocess is alright
134134
"S603", # subprocess calls are fine
135135
]
136+
lint.per-file-ignores."src/virtualenv/activation/python/activate_this.py" = [
137+
"F821", # ignore undefined template string placeholders
138+
]
136139
lint.per-file-ignores."tests/**/*.py" = [
137140
"D", # don't care about documentation in tests
138141
"FBT", # don't care about booleans as positional arguments in tests

src/virtualenv/activation/bash/activate.sh

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,18 +45,18 @@ deactivate () {
4545
# unset irrelevant variables
4646
deactivate nondestructive
4747

48-
VIRTUAL_ENV='__VIRTUAL_ENV__'
48+
VIRTUAL_ENV=__VIRTUAL_ENV__
4949
if ([ "$OSTYPE" = "cygwin" ] || [ "$OSTYPE" = "msys" ]) && $(command -v cygpath &> /dev/null) ; then
5050
VIRTUAL_ENV=$(cygpath -u "$VIRTUAL_ENV")
5151
fi
5252
export VIRTUAL_ENV
5353

5454
_OLD_VIRTUAL_PATH="$PATH"
55-
PATH="$VIRTUAL_ENV/__BIN_NAME__:$PATH"
55+
PATH="$VIRTUAL_ENV/"__BIN_NAME__":$PATH"
5656
export PATH
5757

58-
if [ "x__VIRTUAL_PROMPT__" != x ] ; then
59-
VIRTUAL_ENV_PROMPT="__VIRTUAL_PROMPT__"
58+
if [ "x"__VIRTUAL_PROMPT__ != x ] ; then
59+
VIRTUAL_ENV_PROMPT=__VIRTUAL_PROMPT__
6060
else
6161
VIRTUAL_ENV_PROMPT=$(basename "$VIRTUAL_ENV")
6262
fi

src/virtualenv/activation/batch/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ def templates(self):
1515
yield "deactivate.bat"
1616
yield "pydoc.bat"
1717

18+
@staticmethod
19+
def quote(string):
20+
return string
21+
1822
def instantiate_template(self, replacements, template, creator):
1923
# ensure the text has all newlines as \r\n - required by batch
2024
base = super().instantiate_template(replacements, template, creator)

src/virtualenv/activation/cshell/activate.csh

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,15 @@ alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PA
1010
# Unset irrelevant variables.
1111
deactivate nondestructive
1212

13-
setenv VIRTUAL_ENV '__VIRTUAL_ENV__'
13+
setenv VIRTUAL_ENV __VIRTUAL_ENV__
1414

1515
set _OLD_VIRTUAL_PATH="$PATH:q"
16-
setenv PATH "$VIRTUAL_ENV:q/__BIN_NAME__:$PATH:q"
16+
setenv PATH "$VIRTUAL_ENV:q/"__BIN_NAME__":$PATH:q"
1717

1818

1919

20-
if ('__VIRTUAL_PROMPT__' != "") then
21-
setenv VIRTUAL_ENV_PROMPT '__VIRTUAL_PROMPT__'
20+
if (__VIRTUAL_PROMPT__ != "") then
21+
setenv VIRTUAL_ENV_PROMPT __VIRTUAL_PROMPT__
2222
else
2323
setenv VIRTUAL_ENV_PROMPT "$VIRTUAL_ENV:t:q"
2424
endif

src/virtualenv/activation/fish/activate.fish

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,20 +58,20 @@ end
5858
# Unset irrelevant variables.
5959
deactivate nondestructive
6060

61-
set -gx VIRTUAL_ENV '__VIRTUAL_ENV__'
61+
set -gx VIRTUAL_ENV __VIRTUAL_ENV__
6262

6363
# https://github.com/fish-shell/fish-shell/issues/436 altered PATH handling
6464
if test (echo $FISH_VERSION | head -c 1) -lt 3
6565
set -gx _OLD_VIRTUAL_PATH (_bashify_path $PATH)
6666
else
6767
set -gx _OLD_VIRTUAL_PATH $PATH
6868
end
69-
set -gx PATH "$VIRTUAL_ENV"'/__BIN_NAME__' $PATH
69+
set -gx PATH "$VIRTUAL_ENV"'/'__BIN_NAME__ $PATH
7070

7171
# Prompt override provided?
7272
# If not, just use the environment name.
73-
if test -n '__VIRTUAL_PROMPT__'
74-
set -gx VIRTUAL_ENV_PROMPT '__VIRTUAL_PROMPT__'
73+
if test -n __VIRTUAL_PROMPT__
74+
set -gx VIRTUAL_ENV_PROMPT __VIRTUAL_PROMPT__
7575
else
7676
set -gx VIRTUAL_ENV_PROMPT (basename "$VIRTUAL_ENV")
7777
end

src/virtualenv/activation/nushell/__init__.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,25 @@ class NushellActivator(ViaTemplateActivator):
77
def templates(self):
88
yield "activate.nu"
99

10+
@staticmethod
11+
def quote(string):
12+
"""
13+
Nushell supports raw strings like: r###'this is a string'###.
14+
15+
This method finds the maximum continuous sharps in the string and then
16+
quote it with an extra sharp.
17+
"""
18+
max_sharps = 0
19+
current_sharps = 0
20+
for char in string:
21+
if char == "#":
22+
current_sharps += 1
23+
max_sharps = max(current_sharps, max_sharps)
24+
else:
25+
current_sharps = 0
26+
wrapping = "#" * (max_sharps + 1)
27+
return f"r{wrapping}'{string}'{wrapping}"
28+
1029
def replacements(self, creator, dest_folder): # noqa: ARG002
1130
return {
1231
"__VIRTUAL_PROMPT__": "" if self.flag_prompt is None else self.flag_prompt,

src/virtualenv/activation/nushell/activate.nu

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ export-env {
3232
}
3333
}
3434

35-
let virtual_env = '__VIRTUAL_ENV__'
36-
let bin = '__BIN_NAME__'
35+
let virtual_env = __VIRTUAL_ENV__
36+
let bin = __BIN_NAME__
3737

3838
let is_windows = ($nu.os-info.family) == 'windows'
3939
let path_name = (if (has-env 'Path') {
@@ -47,10 +47,10 @@ export-env {
4747
let new_path = ($env | get $path_name | prepend $venv_path)
4848

4949
# If there is no default prompt, then use the env name instead
50-
let virtual_env_prompt = (if ('__VIRTUAL_PROMPT__' | is-empty) {
50+
let virtual_env_prompt = (if (__VIRTUAL_PROMPT__ | is-empty) {
5151
($virtual_env | path basename)
5252
} else {
53-
'__VIRTUAL_PROMPT__'
53+
__VIRTUAL_PROMPT__
5454
})
5555

5656
let new_env = {

src/virtualenv/activation/powershell/__init__.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,18 @@ class PowerShellActivator(ViaTemplateActivator):
77
def templates(self):
88
yield "activate.ps1"
99

10+
@staticmethod
11+
def quote(string):
12+
"""
13+
This should satisfy PowerShell quoting rules [1], unless the quoted
14+
string is passed directly to Windows native commands [2].
15+
16+
[1]: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_quoting_rules
17+
[2]: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_parsing#passing-arguments-that-contain-quote-characters
18+
""" # noqa: D205
19+
string = string.replace("'", "''")
20+
return f"'{string}'"
21+
1022

1123
__all__ = [
1224
"PowerShellActivator",

src/virtualenv/activation/powershell/activate.ps1

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,16 +37,16 @@ deactivate -nondestructive
3737
$VIRTUAL_ENV = $BASE_DIR
3838
$env:VIRTUAL_ENV = $VIRTUAL_ENV
3939

40-
if ("__VIRTUAL_PROMPT__" -ne "") {
41-
$env:VIRTUAL_ENV_PROMPT = "__VIRTUAL_PROMPT__"
40+
if (__VIRTUAL_PROMPT__ -ne "") {
41+
$env:VIRTUAL_ENV_PROMPT = __VIRTUAL_PROMPT__
4242
}
4343
else {
4444
$env:VIRTUAL_ENV_PROMPT = $( Split-Path $env:VIRTUAL_ENV -Leaf )
4545
}
4646

4747
New-Variable -Scope global -Name _OLD_VIRTUAL_PATH -Value $env:PATH
4848

49-
$env:PATH = "$env:VIRTUAL_ENV/__BIN_NAME____PATH_SEP__" + $env:PATH
49+
$env:PATH = "$env:VIRTUAL_ENV/" + __BIN_NAME__ + __PATH_SEP__ + $env:PATH
5050
if (!$env:VIRTUAL_ENV_DISABLE_PROMPT) {
5151
function global:_old_virtual_prompt {
5252
""

0 commit comments

Comments
 (0)