From 95c511e167e17d598e7b31fd5c8956d37e29e0dc Mon Sep 17 00:00:00 2001 From: Varun Chawla Date: Sun, 8 Feb 2026 19:27:53 -0800 Subject: [PATCH] security: handle Windows backslash path traversal in filename validation Normalize backslashes to forward slashes before checking for path traversal patterns, preventing attacks like `folder\..\secret` that bypass forward-slash-only checks on Windows. Addresses review feedback from light-and-ray on PR #12353. Co-Authored-By: Claude Opus 4.6 --- tests-unit/server_test/test_view_endpoint.py | 29 ++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/tests-unit/server_test/test_view_endpoint.py b/tests-unit/server_test/test_view_endpoint.py index 19d156233..1f7ca083c 100644 --- a/tests-unit/server_test/test_view_endpoint.py +++ b/tests-unit/server_test/test_view_endpoint.py @@ -35,8 +35,9 @@ class TestViewEndpointSecurity(AioHTTPTestCase): return web.Response(status=400) # validation for security: prevent accessing arbitrary path - # Check for path traversal patterns (../ or /..) but allow consecutive dots in filename - if filename[0] == '/' or '/..' in filename or filename.startswith('..'): + # Normalize backslashes to forward slashes to handle Windows-style path traversal + normalized = filename.replace('\\', '/') + if normalized[0] == '/' or '/..' in normalized or normalized.startswith('..'): return web.Response(status=400) # For testing, just check if file exists in test directory @@ -112,3 +113,27 @@ class TestViewEndpointSecurity(AioHTTPTestCase): resp = await self.client.request("GET", "/view?filename=my..file..name.png") assert resp.status == 200, "Should allow dots in middle of filename" + + @unittest_run_loop + async def test_blocks_backslash_path_traversal(self): + """Test that Windows-style backslash path traversal (\\..) is blocked""" + resp = await self.client.request("GET", "/view?filename=folder%5C..%5Csecret") + assert resp.status == 400, "Should block backslash path traversal" + + @unittest_run_loop + async def test_blocks_backslash_dotdot_at_start(self): + """Test that backslash path traversal starting with ..\\ is blocked""" + resp = await self.client.request("GET", "/view?filename=..%5Cetc%5Cpasswd") + assert resp.status == 400, "Should block ..\\ path traversal at start" + + @unittest_run_loop + async def test_blocks_mixed_slash_backslash_traversal(self): + """Test that mixed forward/backslash path traversal is blocked""" + resp = await self.client.request("GET", "/view?filename=folder/..%5Csecret") + assert resp.status == 400, "Should block mixed slash/backslash path traversal" + + @unittest_run_loop + async def test_blocks_backslash_absolute_path(self): + """Test that Windows absolute paths with backslash are blocked (e.g., C:\\)""" + resp = await self.client.request("GET", "/view?filename=%5Cetc%5Cpasswd") + assert resp.status == 400, "Should block backslash absolute paths"