Skip to content

Commit bc4c738

Browse files
authored
Merge pull request #684 from evansd/use-commonpath
Use `os.path.commonpath()` to identify child paths
2 parents b6d8ed4 + 505ed8d commit bc4c738

3 files changed

Lines changed: 30 additions & 1 deletion

File tree

docs/changelog.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ Changelog
33
=========
44

55
* Drop Python 3.9 support.
6+
* Fix potential unauthorised file access vulnerability in "autorefesh" mode. See `PR #684 <https://github.com/evansd/whitenoise/pull/684>`__ for details, and a reminder that autorefresh mode has always been documented as unsuitable for production use. Thanks Seth Larson for reporting.
67

78
6.11.0 (2025-09-18)
89
-------------------

src/whitenoise/base.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ def candidate_paths_for_url(self, url):
147147
for root, prefix in self.directories:
148148
if url.startswith(prefix):
149149
path = os.path.join(root, url[len(prefix) :])
150-
if os.path.commonprefix((root, path)) == root:
150+
if self.path_is_child_of(path, root):
151151
yield path
152152

153153
def find_file_at_path(self, path, url):
@@ -184,6 +184,15 @@ def url_is_canonical(url):
184184
normalised += "/"
185185
return normalised == url
186186

187+
@staticmethod
188+
def path_is_child_of(path, root):
189+
try:
190+
return os.path.commonpath((path, root)) + os.path.sep == root
191+
except ValueError:
192+
# We get a ValueError if `path` and `root` are on different Windows drives:
193+
# https://docs.python.org/3/library/os.path.html#os.path.commonpath
194+
return False
195+
187196
@staticmethod
188197
def is_compressed_variant(path, stat_cache=None):
189198
if path[-3:] in (".gz", ".br"):

tests/test_whitenoise.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,3 +380,22 @@ def test_chunked_file_size_matches_range_with_range_header():
380380
while response.file.read(1):
381381
file_size += 1
382382
assert file_size == 14
383+
384+
385+
@pytest.mark.parametrize(
386+
"root,path,expected",
387+
[
388+
("/one/two/", "/one/two/three", True),
389+
("/one/two/", "/one_two/three", False),
390+
# Having different drive letters triggers an exception in `commonpath()` on
391+
# Windows which we should handle gracefully
392+
("A:/some/path", "B:/another/path", False),
393+
# Relative paths also trigger exceptions (it shouldn't be possible to supply
394+
# these but better to handle all cases)
395+
("/one/two/", "two/three", False),
396+
],
397+
)
398+
def test_path_is_child_of(root, path, expected):
399+
root = root.replace("/", os.path.sep)
400+
path = path.replace("/", os.path.sep)
401+
assert WhiteNoise.path_is_child_of(path, root) == expected

0 commit comments

Comments
 (0)