8000 gh-57684: Add -P cmdline option and PYTHONSAFEPATH env var (#31542) · python/cpython@ada8b6d · GitHub
[go: up one dir, main page]

Skip to content

Commit ada8b6d

Browse files
authored
gh-57684: Add -P cmdline option and PYTHONSAFEPATH env var (#31542)
Add the -P command line option and the PYTHONSAFEPATH environment variable to not prepend a potentially unsafe path to sys.path. * Add sys.flags.safe_path flag. * Add PyConfig.safe_path member. * Programs/_bootstrap_python.c uses config.safe_path=0. * Update subprocess._optim_args_from_interpreter_flags() to handle the -P command line option. * Modules/getpath.py sets safe_path to 1 if a "._pth" file is present.
1 parent f6dd14c commit ada8b6d

File tree

20 files changed

+174
-35
lines changed

20 files changed

+174
-35
lines changed

Doc/c-api/init_config.rst

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -543,6 +543,25 @@ PyConfig
543543
544544
See also the :c:member:`~PyConfig.orig_argv` member.
545545
546+
.. c:member:: int safe_path
547+
548+
If equals to zero, ``Py_RunMain()`` prepends a potentially unsafe path to
549+
:data:`sys.path` at startup:
550+
551+
* If :c:member:`argv[0] <PyConfig.argv>` is equal to ``L"-m"``
552+
(``python -m module``), prepend the current working directory.
553+
* If running a script (``python script.py``), prepend the script's
554+
directory. If it's a symbolic link, resolve symbolic links.
555+
* Otherwise (``python -c code`` and ``python``), prepend an empty string,
556+
which means the current working directory.
557+
558+
Set to 1 by the :option:`-P` command line option and the
559+
:envvar:`PYTHONSAFEPATH` environment variable.
560+
561+
Default: ``0`` in Python config, ``1`` in isolated config.
562+
563+
.. versionadded:: 3.11
564+
546565
.. c:member:: wchar_t* base_exec_prefix
547566
548567
:data:`sys.base_exec_prefix`.
@@ -809,13 +828,14 @@ PyConfig
809828
810829
If greater than 0, enable isolated mode:
811830
812-
* :data:`sys.path` contains neither the script's directory (computed from
813-
``argv[0]`` or the current directory) nor the user's site-packages
814-
directory.
831+
* Set :c:member:`~PyConfig.safe_path` to 1:
832+
don't prepend a potentially unsafe path to :data:`sys.path` at Python
833+
startup.
834+
* Set :c:member:`~PyConfig.use_environment` to 0.
835+
* Set :c:member:`~PyConfig.user_site_directory` to 0: don't add the user
836+
site directory to :data:`sys.path`.
815837
* Python REPL doesn't import :mod:`readline` nor enable default readline
816838
configuration on interactive prompts.
817-
* Set :c:member:`~PyConfig.use_environment` and
818-
:c:member:`~PyConfig.user_site_directory` to 0.
819839
820840
Default: ``0`` in Python mode, ``1`` in isolated mode.
821841
@@ -1029,12 +1049,13 @@ PyConfig
10291049
.. c:member:: wchar_t* run_filename
10301050
10311051
Filename passed on the command line: trailing command line argument
1032-
without :option:`-c` or :option:`-m`.
1052+
without :option:`-c` or :option:`-m`. It is used by the
1053+
:c:func:`Py_RunMain` function.
10331054
10341055
For example, it is set to ``script.py`` by the ``python3 script.py arg``
1035-
command.
1056+
command line.
10361057
1037-
Used by :c:func:`Py_RunMain`.
1058+
See also the :c:member:`PyConfig.skip_source_first_line` option.
10381059
10391060
Default: ``NULL``.
10401061
@@ -1419,9 +1440,16 @@ site-package directory to :data:`sys.path`.
14191440
The following configuration files are used by the path configuration:
14201441
14211442
* ``pyvenv.cfg``
1422-
* ``python._pth`` (Windows only)
1443+
* ``._pth`` file (ex: ``python._pth``)
14231444
* ``pybuilddir.txt`` (Unix only)
14241445
1446+
If a ``._pth`` file is present:
1447+
1448+
* Set :c:member:`~PyConfig.isolated` to 1.
1449+
* Set :c:member:`~PyConfig.use_environment` to 0.
1450+
* Set :c:member:`~PyConfig.site_import` to 0.
1451+
* Set :c:member:`~PyConfig.safe_path` to 1.
1452+
14251453
The ``__PYVENV_LAUNCHER__`` environment variable is used to set
14261454
:c:member:`PyConfig.base_executable`
14271455

Doc/library/sys.rst

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -520,6 +520,7 @@ always available.
520520
:const:`hash_randomization` :option:`-R`
521521
:const:`dev_mode` :option:`-X dev <-X>` (:ref:`Python Development Mode <devmode>`)
522522
:const:`utf8_mode` :option:`-X utf8 <-X>`
523+
:const:`safe_path` :option:`-P`
523524
============================= ================================================================
524525

525526
.. versionchanged:: 3.2
@@ -539,6 +540,9 @@ always available.
539540
Mode <devmode>` and the ``utf8_mode`` attribute for the new :option:`-X`
540541
``utf8`` flag.
541542

543+
.. versionchanged:: 3.11
544+
Added the ``safe_path`` attribute for :option:`-P` option.
545+
542546

543547
.. data:: float_info
544548

@@ -1138,15 +1142,19 @@ always available.
11381142
the environment variable :envvar:`PYTHONPATH`, plus an installation-dependent
11391143
default.
11401144

1141-
As initialized upon program startup, the first item of this list, ``path[0]``,
1142-
is the directory containing the script that was used to invoke the Python
1143-
interpreter. If the script directory is not available (e.g. if the interpreter
1144-
is invoked interactively or if the script is read from standard input),
1145-
``path[0]`` is the empty string, which directs Python to search modules in the
1146-
current directory first. Notice that the script directory is inserted *before*
1147-
the entries inserted as a result of :envvar:`PYTHONPATH`.
1145+
By default, as initialized upon program startup, a potentially unsafe path
1146+
is prepended to :data:`sys.path` (*before* the entries inserted as a result
1147+
of :envvar:`PYTHONPATH`):
1148+
1149+
* ``python -m module`` command line: prepend the current working
1150+
directory.
1151+
* ``python script.py`` command line: prepend the script's directory.
1152+
If it's a symbolic link, resolve symbolic links.
1153+
* ``python -c code`` and ``python`` (REPL) command lines: prepend an empty
1154+
string, which means the current working directory.
11481155

1149-
The initialization of :data:`sys.path` is documented at :ref:`sys-path-init`.
1156+
To not prepend this potentially unsafe path, use the :option:`-P` command
1157+
line option or the :envvar:`PYTHONSAFEPATH` environment variable?
11501158

11511159
A program is free to modify this list for its own purposes. Only strings
11521160
and bytes should be added to :data:`sys.path`; all other data types are

Doc/using/cmdline.rst

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,8 @@ Miscellaneous options
257257
Ignore all :envvar:`PYTHON*` environment variables, e.g.
258258
:envvar:`PYTHONPATH` and :envvar:`PYTHONHOME`, that might be set.
259259

260+
See also the :option:`-P` and :option:`-I` (isolated) options.
261+
260262

261263
.. cmdoption:: -i
262264

@@ -271,7 +273,9 @@ Miscellaneous options
271273

272274
.. cmdoption:: -I
273275

274-
Run Python in isolated mode. This also implies -E and -s.
276+
Run Python in isolated mode. This also implies :option:`-E`, :option:`-P`
277+
and :option:`-s` options.
278+
275279
In isolated mode :data:`sys.path` contains neither the script's directory nor
276280
the user's site-packages directory. All :envvar:`PYTHON*` environment
277281
variables are ignored, too. Further restrictions may be imposed to prevent
@@ -301,6 +305,23 @@ Miscellaneous options
301305
Modify ``.pyc`` filenames according to :pep:`488`.
302306

303307

308+
.. cmdoption:: -P
309+
310+
Don't prepend a potentially unsafe path to :data:`sys.path`:
311+
312+
* ``python -m module`` command line: Don't prepend the current working
313+
directory.
314+
* ``python script.py`` command line: Don't prepend the script's directory.
315+
If it's a symbolic link, resolve symbolic links.
316+
* ``python -c code`` and ``python`` (REPL) command lines: Don't prepend an
317+
empty string, which means the current working directory.
318+
319+
See also the :envvar:`PYTHONSAFEPATH` environment variable, and :option:`-E`
320+
and :option:`-I` (isolated) options.
321+
322+
.. versionadded:: 3.11
323+
324+
304325
.. cmdoption:: -q
305326

306327
Don't display the copyright and version messages even in interactive mode.
@@ -583,6 +604,14 @@ conflict.
583604
within a Python program as the variable :data:`sys.path`.
584605

585606

607+
.. envvar:: PYTHONSAFEPATH
608+
609+
If this is set to a non-empty string, don't prepend a potentially unsafe
610+
path to :data:`sys.path`: see the :option:`-P` option for details.
611+
612+
.. versionadded:: 3.11
613+
614+
586615
.. envvar:: PYTHONPLATLIBDIR
587616

588617
If this is set to a non-empty string, it overrides the :data:`sys.platlibdir`

Doc/whatsnew/3.11.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,11 @@ Other Language Changes
362362
pickles instance attributes implemented as :term:`slots <__slots__>`.
363363
(Contributed by Serhiy Storchaka in :issue:`26579`.)
364364

365+
* Add :option:`-P` command line option and :envvar:`PYTHONSAFEPATH` environment
366+
variable to not prepend a potentially unsafe path to :data:`sys.path` such as
367+
the current directory, the script's directory or an empty string.
368+
(Contributed by Victor Stinner in :gh:`57684`.)
369+
365370

366371
Other CPython Implementation Changes
367372
====================================
@@ -636,6 +641,9 @@ sys
636641
(equivalent to ``sys.exc_info()[1]``).
637642
(Contributed by Irit Katriel in :issue:`46328`.)
638643

644+
* Add the :data:`sys.flags.safe_path <sys.flags>` flag.
645+
(Contributed by Victor Stinner in :gh:`57684`.)
646+
639647

640648
sysconfig
641649
---------
@@ -1480,6 +1488,8 @@ New Features
14801488
representation of exceptions.
14811489
(Contributed by Irit Katriel in :issue:`46343`.)
14821490

1491+
* Added the :c:member:`PyConfig.safe_path` member.
1492+
(Contributed by Victor Stinner in :gh:`57684`.)
14831493

14841494
Porting to Python 3.11
14851495
----------------------

Include/cpython/initconfig.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ typedef struct PyConfig {
176176
#endif
177177
wchar_t *check_hash_pycs_mode;
178178
int use_frozen_modules;
179+
int safe_path;
179180

180181
/* --- Path configuration inputs ------------ */
181182
int pathconfig_warnings;

Lib/subprocess.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -313,12 +313,14 @@ def _args_from_interpreter_flags():
313313
args.append('-E')
314314
if sys.flags.no_user_site:
315315
args.append('-s')
316+
if sys.flags.safe_path:
317+
args.append('-P')
316318

317319
# -W options
318320
warnopts = sys.warnoptions[:]
319-
bytes_warning = sys.flags.bytes_warning
320321
xoptions = getattr(sys, '_xoptions', {})
321-
dev_mode = ('dev' in xoptions)
322+
bytes_warning = sys.flags.bytes_warning
323+
dev_mode = sys.flags.dev_mode
322324

323325
if bytes_warning > 1:
324326
warnopts.remove("error::BytesWarning")

Lib/test/test_cmd_line.py

Lines changed: 6 additions & 4 deletions
591
Original file line numberDiff line numberDiff line change
@@ -579,13 +579,13 @@ def test_unknown_options(self):
579579
'Cannot run -I tests when PYTHON env vars are required.')
580580
def test_isolatedmode(self):
581581
self.verify_valid_flag('-I')
582-
self.verify_valid_flag('-IEs')
582+
self.verify_valid_flag('-IEPs')
583583
rc, out, err = assert_python_ok('-I', '-c',
584584
'from sys import flags as f; '
585-
'print(f.no_user_site, f.ignore_environment, f.isolated)',
585+
'print(f.no_user_site, f.ignore_environment, f.isolated, f.safe_path)',
586586
# dummyvar to prevent extraneous -E
587587
dummyvar="")
588-
self.assertEqual(out.strip(), b'1 1 1')
588+
self.assertEqual(out.strip(), b'1 1 1 True')
589589
with os_helper.temp_cwd() as tmpdir:
590590
fake = os.path.join(tmpdir, "uuid.py")
591
main = os.path.join(tmpdir, "main.py")
@@ -880,14 +880,16 @@ def test_sys_flags_not_set(self):
880880
# Issue 31845: a startup refactoring broke reading flags from env vars
881881
expected_outcome = """
882882
(sys.flags.debug == sys.flags.optimize ==
883-
sys.flags.dont_write_bytecode == sys.flags.verbose == 0)
883+
sys.flags.dont_write_bytecode ==
884+
sys.flags.verbose == sys.flags.safe_path == 0)
884885
"""
885886
self.run_ignoring_vars(
886887
expected_outcome,
887888
PYTHONDEBUG="1",
888889
PYTHONOPTIMIZE="1",
889890
PYTHONDONTWRITEBYTECODE="1",
890891
PYTHONVERBOSE="1",
892+
PYTHONSAFEPATH="1",
891893
)
892894

893895
class SyntaxErrorTests(unittest.TestCase):

Lib/test/test_embed.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -479,6 +479,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
479479
'_init_main': 1,
480480
'_isolated_interpreter': 0,
481481
'use_frozen_modules': not Py_DEBUG,
482+
'safe_path': 0,
482483
'_is_python_build': IGNORE_CONFIG,
483484
}
484485
if MS_WINDOWS:
@@ -496,6 +497,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
496497
isolated=1,
497498
use_environment=0,
498499
user_site_directory=0,
500+
safe_path=1,
499501
dev_mode=0,
500502
install_signal_handlers=0,
501503
use_hash_seed=0,
@@ -855,6 +857,7 @@ def test_init_from_config(self):
855857
'faulthandler': 1,
856858
'platlibdir': 'my_platlibdir',
857859
'module_search_paths': self.IGNORE_CONFIG,
860+
'safe_path': 1,
858861

859862
'check_hash_pycs_mode': 'always',
860863
'pathconfig_warnings': 0,
@@ -889,6 +892,7 @@ def test_init_compat_env(self):
889892
'warnoptions': ['EnvVar'],
890893
'platlibdir': 'env_platlibdir',
891894
'module_search_paths': self.IGNORE_CONFIG,
895+
'safe_path': 1,
892896
}
893897
self.check_all_configs("test_init_compat_env", config, preconfig,
894898
api=API_COMPAT)
@@ -919,6 +923,7 @@ def test_init_python_env(self):
919923
'warnoptions': ['EnvVar'],
920924
'platlibdir': 'env_platlibdir',
921925
'module_search_paths': self.IGNORE_CONFIG,
926+
'safe_path': 1,
922927
}
923928
self.check_all_configs("test_init_python_env", config, preconfig,
924929
api=API_PYTHON)
@@ -959,12 +964,13 @@ def test_preinit_parse_argv(self):
959964
}
960965
config = {
961966
'argv': ['script.py'],
962-
'orig_argv': ['python3', '-X', 'dev', 'script.py'],
967+
'orig_argv': ['python3', '-X', 'dev', '-P', 'script.py'],
963968
'run_filename': os.path.abspath('script.py'),
964969
'dev_mode': 1,
965970
'faulthandler': 1,
966971
'warnoptions': ['default'],
967972
'xoptions': ['dev'],
973+
'safe_path': 1,
968974
}
969975
self.check_all_configs("test_preinit_parse_argv", config, preconfig,
970976
api=API_PYTHON)
@@ -975,7 +981,7 @@ def test_preinit_dont_parse_argv(self):
975981
'isolated': 0,
976982
}
977983
argv = ["python3",
978-
"-E", "-I",
984+
"-E", "-I", "-P",
979985
"-X", "dev",
980986
"-X", "utf8",
981987
"script.py"]
@@ -990,6 +996,7 @@ def test_preinit_dont_parse_argv(self):
990996
def test_init_isolated_flag(self):
991997
config = {
992998
'isolated': 1,
999+
'safe_path': 1,
9931000
'use_environment': 0,
9941001
'user_site_directory': 0,
9951002
}
@@ -999,6 +1006,7 @@ def test_preinit_isolated1(self):
9991006
# _PyPreConfig.isolated=1, _PyCoreConfig.isolated not set
10001007
config = {
10011008
'isolated': 1,
1009+
'safe_path': 1,
10021010
'use_environment': 0,
10031011
'user_site_directory': 0,
10041012
}
@@ -1008,6 +1016,7 @@ def test_preinit_isolated2(self):
10081016
# _PyPreConfig.isolated=0, _PyCoreConfig.isolated=1
10091017
config = {
10101018
'isolated': 1,
1019+
'safe_path': 1,
10111020
'use_environment': 0,
10121021
'user_site_directory': 0,
10131022
}

Lib/test/test_support.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -522,6 +522,7 @@ def test_args_from_interpreter_flags(self):
522522
['-E'],
523523
['-v'],
524524
['-b'],
525+
['-P'],
525526
['-q'],
526527
['-I'],
527528
# same option multiple times
@@ -541,7 +542,8 @@ def test_args_from_interpreter_flags(self):
541542
with self.subTest(opts=opts):
542543
self.check_options(opts, 'args_from_interpreter_flags')
543544

544-
self.check_options(['-I', '-E', '-s'], 'args_from_interpreter_flags',
545+
self.check_options(['-I', '-E', '-s', '-P'],
546+
'args_from_interpreter_flags',
545547
['-I'])
546548

547549
def test_optim_args_from_interpreter_flags(self):

Lib/test/test_sys.py

Lines changed: 2 additions & 2 deletions
B99E
Original file line numberDiff line numberDiff line change
@@ -669,10 +669,10 @@ def test_sys_flags(self):
669669
"dont_write_bytecode", "no_user_site", "no_site",
670670
"ignore_environment", "verbose", "bytes_warning", "quiet",
671671
"hash_randomization", "isolated", "dev_mode", "utf8_mode",
672-
"warn_default_encoding")
672+
"warn_default_encoding", "safe_path")
673673
for attr in attrs:
674674
self.assertTrue(hasattr(sys.flags, attr), attr)
675-
attr_type = bool if attr == "dev_mode" else int
675+
attr_type = bool if attr in ("dev_mode", "safe_path") else int
676676
self.assertEqual(type(getattr(sys.flags, attr)), attr_type, attr)
677677
self.assertTrue(repr(sys.flags))
678678
self.assertEqual(len(sys.flags), len(attrs))
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Add the :option:`-P` command line option and the :envvar:`PYTHONSAFEPATH`
2+
environment variable to not prepend a potentially unsafe path to
3+
:data:`sys.path`. Patch by Victor Stinner.

0 commit comments

Comments
 (0)
0