From 4faf8a7e86bfe811ef3d90ac5abdbd41409fafb1 Mon Sep 17 00:00:00 2001 From: Abubakar Abid Date: Thu, 15 Feb 2024 15:48:06 -0800 Subject: [PATCH] Block `/file=` filepaths that could expose credentials on Windows (#7444) * fix for windowss * path * add changeset * changes --------- Co-authored-by: gradio-pr-bot --- .changeset/brown-ties-happen.md | 5 +++++ gradio/routes.py | 6 ++++++ test/test_routes.py | 25 ++++++------------------- 3 files changed, 17 insertions(+), 19 deletions(-) create mode 100644 .changeset/brown-ties-happen.md diff --git a/.changeset/brown-ties-happen.md b/.changeset/brown-ties-happen.md new file mode 100644 index 0000000000000..dcc8f3f856f7c --- /dev/null +++ b/.changeset/brown-ties-happen.md @@ -0,0 +1,5 @@ +--- +"gradio": minor +--- + +feat:Block `/file=` filepaths that could expose credentials on Windows diff --git a/gradio/routes.py b/gradio/routes.py index 2ea7fd49a1f6f..68a91b9ef3c46 100644 --- a/gradio/routes.py +++ b/gradio/routes.py @@ -428,12 +428,18 @@ async def file(path_or_url: str, request: fastapi.Request): return RedirectResponse( url=path_or_url, status_code=status.HTTP_302_FOUND ) + + invalid_prefixes = ["//", "file://", "ftp://", "sftp://", "smb://"] + if any(path_or_url.startswith(prefix) for prefix in invalid_prefixes): + raise HTTPException(403, f"File not allowed: {path_or_url}.") + abs_path = utils.abspath(path_or_url) in_blocklist = any( utils.is_in_or_equal(abs_path, blocked_path) for blocked_path in blocks.blocked_paths ) + is_dir = abs_path.is_dir() if in_blocklist or is_dir: diff --git a/test/test_routes.py b/test/test_routes.py index e7c3ae1ae48b6..c3065b666f780 100644 --- a/test/test_routes.py +++ b/test/test_routes.py @@ -398,11 +398,17 @@ def test_asset_file_missing(self, test_client): def test_cannot_access_files_in_working_directory(self, test_client): response = test_client.get(r"/file=not-here.js") assert response.status_code == 403 + response = test_client.get(r"/file=subdir/.env") + assert response.status_code == 403 def test_cannot_access_directories_in_working_directory(self, test_client): response = test_client.get(r"/file=gradio") assert response.status_code == 403 + def test_block_protocols_that_expose_windows_credentials(self, test_client): + response = test_client.get(r"/file=//11.0.225.200/share") + assert response.status_code == 403 + def test_do_not_expose_existence_of_files_outside_working_directory( self, test_client ): @@ -720,25 +726,6 @@ def test_orjson_serialization(): demo.close() -def test_file_route_does_not_allow_dot_paths(tmp_path): - dot_file = tmp_path / ".env" - dot_file.write_text("secret=1234") - subdir = tmp_path / "subdir" - subdir.mkdir() - sub_dot_file = subdir / ".env" - sub_dot_file.write_text("secret=1234") - secret_sub_dir = tmp_path / ".versioncontrol" - secret_sub_dir.mkdir() - secret_sub_dir_regular_file = secret_sub_dir / "settings" - secret_sub_dir_regular_file.write_text("token = 8") - with closing(gr.Interface(lambda s: s.name, gr.File(), gr.File())) as io: - app, _, _ = io.launch(prevent_thread_lock=True) - client = TestClient(app) - assert client.get("/file=.env").status_code == 403 - assert client.get("/file=subdir/.env").status_code == 403 - assert client.get("/file=.versioncontrol/settings").status_code == 403 - - def test_api_name_set_for_all_events(connect): with gr.Blocks() as demo: i = Textbox()