https://github.com/python/cpython/commit/957f9fe162398fceeaa9ddba8b40046b8a03176d
commit: 957f9fe162398fceeaa9ddba8b40046b8a03176d
branch: main
author: Seth Michael Larson <[email protected]>
committer: hugovk <[email protected]>
date: 2026-02-05T22:37:05+02:00
summary:

gh-74453: Deprecate os.path.commonprefix (#144436)

Co-authored-by: Bénédikt Tran <[email protected]>
Co-authored-by: Hugo van Kemenade <[email protected]>

files:
A Misc/NEWS.d/next/Library/2026-02-02-12-09-38.gh-issue-74453.19h4Z5.rst
M Doc/deprecations/pending-removal-in-future.rst
M Doc/library/os.path.rst
M Lib/genericpath.py
M Lib/posixpath.py
M Lib/test/test_genericpath.py
M Lib/test/test_ntpath.py
M Lib/unittest/util.py

diff --git a/Doc/deprecations/pending-removal-in-future.rst 
b/Doc/deprecations/pending-removal-in-future.rst
index 301867416701ea..a54f98d6866e9f 100644
--- a/Doc/deprecations/pending-removal-in-future.rst
+++ b/Doc/deprecations/pending-removal-in-future.rst
@@ -78,6 +78,14 @@ although there is currently no date scheduled for their 
removal.
 
 * :mod:`os`: Calling :func:`os.register_at_fork` in a multi-threaded process.
 
+* :mod:`os.path`: :func:`os.path.commonprefix` is deprecated, use
+  :func:`os.path.commonpath` for path prefixes. The 
:func:`os.path.commonprefix`
+  function is being deprecated due to having a misleading name and module.
+  The function is not safe to use for path prefixes despite being included in a
+  module about path manipulation, meaning it is easy to accidentally
+  introduce path traversal vulnerabilities into Python programs by using this
+  function.
+
 * :class:`!pydoc.ErrorDuringImport`: A tuple value for *exc_info* parameter is
   deprecated, use an exception instance.
 
diff --git a/Doc/library/os.path.rst b/Doc/library/os.path.rst
index bfd59fc5a82049..409fcf4adb754b 100644
--- a/Doc/library/os.path.rst
+++ b/Doc/library/os.path.rst
@@ -120,6 +120,14 @@ the :mod:`glob` module.)
    .. versionchanged:: 3.6
       Accepts a :term:`path-like object`.
 
+   .. deprecated:: next
+      Deprecated in favor of :func:`os.path.commonpath` for path prefixes.
+      The :func:`os.path.commonprefix` function is being deprecated due to
+      having a misleading name and module. The function is not safe to use for
+      path prefixes despite being included in a module about path manipulation,
+      meaning it is easy to accidentally introduce path traversal
+      vulnerabilities into Python programs by using this function.
+
 
 .. function:: dirname(path, /)
 
diff --git a/Lib/genericpath.py b/Lib/genericpath.py
index 7588fe5e8020f9..71ae19190839ae 100644
--- a/Lib/genericpath.py
+++ b/Lib/genericpath.py
@@ -105,6 +105,15 @@ def getctime(filename, /):
 # Return the longest prefix of all list elements.
 def commonprefix(m, /):
     "Given a list of pathnames, returns the longest common leading component"
+    import warnings
+    warnings.warn('os.path.commonprefix() is deprecated. Use '
+                  'os.path.commonpath() for longest path prefix.',
+                  category=DeprecationWarning,
+                  stacklevel=2)
+    return _commonprefix(m)
+
+def _commonprefix(m, /):
+    "Internal implementation of commonprefix()"
     if not m: return ''
     # Some people pass in a list of pathname parts to operate in an OS-agnostic
     # fashion; don't try to translate in that case as that's an abuse of the
diff --git a/Lib/posixpath.py b/Lib/posixpath.py
index 1ee27de3206c7f..8025b063397a03 100644
--- a/Lib/posixpath.py
+++ b/Lib/posixpath.py
@@ -542,7 +542,7 @@ def relpath(path, start=None):
         start_list = start_tail.split(sep) if start_tail else []
         path_list = path_tail.split(sep) if path_tail else []
         # Work out how much of the filepath is shared by start and path.
-        i = len(commonprefix([start_list, path_list]))
+        i = len(genericpath._commonprefix([start_list, path_list]))
 
         rel_list = [pardir] * (len(start_list)-i) + path_list[i:]
         if not rel_list:
diff --git a/Lib/test/test_genericpath.py b/Lib/test/test_genericpath.py
index dfc0817da45fa2..10d3f409d883c5 100644
--- a/Lib/test/test_genericpath.py
+++ b/Lib/test/test_genericpath.py
@@ -34,6 +34,10 @@ def test_no_argument(self):
                                 .format(self.pathmodule.__name__, attr))
 
     def test_commonprefix(self):
+        with warnings_helper.check_warnings((".*commonpath().*", 
DeprecationWarning)):
+            self.do_test_commonprefix()
+
+    def do_test_commonprefix(self):
         commonprefix = self.pathmodule.commonprefix
         self.assertEqual(
             commonprefix([]),
@@ -606,8 +610,9 @@ def test_path_isdir(self):
         self.assertPathEqual(os.path.isdir)
 
     def test_path_commonprefix(self):
-        self.assertEqual(os.path.commonprefix([self.file_path, 
self.file_name]),
-                         self.file_name)
+        with warnings_helper.check_warnings((".*commonpath().*", 
DeprecationWarning)):
+            self.assertEqual(os.path.commonprefix([self.file_path, 
self.file_name]),
+                             self.file_name)
 
     def test_path_getsize(self):
         self.assertPathEqual(os.path.getsize)
diff --git a/Lib/test/test_ntpath.py b/Lib/test/test_ntpath.py
index 3a3c60dea1345f..a3728b58335e63 100644
--- a/Lib/test/test_ntpath.py
+++ b/Lib/test/test_ntpath.py
@@ -10,6 +10,7 @@
 from ntpath import ALL_BUT_LAST, ALLOW_MISSING
 from test import support
 from test.support import os_helper
+from test.support import warnings_helper
 from test.support.os_helper import FakePath
 from test import test_genericpath
 from tempfile import TemporaryFile
@@ -298,6 +299,10 @@ def test_isabs(self):
         tester('ntpath.isabs("\\\\.\\C:")', 1)
 
     def test_commonprefix(self):
+        with warnings_helper.check_warnings((".*commonpath().*", 
DeprecationWarning)):
+            self.do_test_commonprefix()
+
+    def do_test_commonprefix(self):
         tester('ntpath.commonprefix(["/home/swenson/spam", 
"/home/swen/spam"])',
                "/home/swen")
         tester('ntpath.commonprefix(["\\home\\swen\\spam", 
"\\home\\swen\\eggs"])',
diff --git a/Lib/unittest/util.py b/Lib/unittest/util.py
index 050eaed0b3f58f..c7e6b941978cd5 100644
--- a/Lib/unittest/util.py
+++ b/Lib/unittest/util.py
@@ -1,7 +1,6 @@
 """Various utility functions."""
 
 from collections import namedtuple, Counter
-from os.path import commonprefix
 
 __unittest = True
 
@@ -21,13 +20,23 @@ def _shorten(s, prefixlen, suffixlen):
         s = '%s[%d chars]%s' % (s[:prefixlen], skip, s[len(s) - suffixlen:])
     return s
 
+def _common_prefix(m):
+    if not m:
+        return ""
+    s1 = min(m)
+    s2 = max(m)
+    for i, c in enumerate(s1):
+        if c != s2[i]:
+            return s1[:i]
+    return s1
+
 def _common_shorten_repr(*args):
     args = tuple(map(safe_repr, args))
     maxlen = max(map(len, args))
     if maxlen <= _MAX_LENGTH:
         return args
 
-    prefix = commonprefix(args)
+    prefix = _common_prefix(args)
     prefixlen = len(prefix)
 
     common_len = _MAX_LENGTH - \
diff --git 
a/Misc/NEWS.d/next/Library/2026-02-02-12-09-38.gh-issue-74453.19h4Z5.rst 
b/Misc/NEWS.d/next/Library/2026-02-02-12-09-38.gh-issue-74453.19h4Z5.rst
new file mode 100644
index 00000000000000..8629c834e5b0cd
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-02-02-12-09-38.gh-issue-74453.19h4Z5.rst
@@ -0,0 +1,8 @@
+Deprecate :func:`os.path.commonprefix` in favor of
+:func:`os.path.commonpath` for path segment prefixes.
+
+The :func:`os.path.commonprefix` function is being deprecated due to
+having a misleading name and module. The function is not safe to use for
+path prefixes despite being included in a module about path manipulation,
+meaning it is easy to accidentally introduce path traversal
+vulnerabilities into Python programs by using this function.

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to