Skip to content

Commit

Permalink
Ignore component requests outside of the component root
Browse files Browse the repository at this point in the history
  • Loading branch information
kmcgrady committed Jul 27, 2022
1 parent 4a04eef commit 80d9979
Show file tree
Hide file tree
Showing 2 changed files with 58 additions and 1 deletion.
10 changes: 9 additions & 1 deletion lib/streamlit/components/v1/components.py
Expand Up @@ -316,8 +316,16 @@ def get(self, path: str) -> None:
self.set_status(404)
return

# follow symlinks to get an accurate normalized path
component_root = os.path.realpath(component_root)
filename = "/".join(parts[1:])
abspath = os.path.join(component_root, filename)
abspath = os.path.realpath(os.path.join(component_root, filename))

# Do NOT expose anything outside of the component root.
if os.path.commonprefix([component_root, abspath]) != component_root:
self.write("forbidden")
self.set_status(403)
return

LOGGER.debug("ComponentRequestHandler: GET: %s -> %s", path, abspath)

Expand Down
49 changes: 49 additions & 0 deletions lib/tests/streamlit/components_test.py
Expand Up @@ -434,6 +434,55 @@ def test_success_request(self):
self.assertEqual(200, response.code)
self.assertEqual(b"Test Content", response.body)

def test_outside_component_root_request(self):
"""Tests to ensure a path based on the root directory (and therefore
outside of the component root) is disallowed."""

with mock.patch("streamlit.components.v1.components.os.path.isdir"):
# We don't need the return value in this case.
declare_component("test", path=PATH)

response = self._request_component(
"components_test.test//etc/hosts"
)

self.assertEqual(403, response.code)
self.assertEqual(b"forbidden", response.body)

def test_relative_outside_component_root_request(self):
"""Tests to ensure a path relative to the component root directory
(and specifically outside of the component root) is disallowed."""

with mock.patch("streamlit.components.v1.components.os.path.isdir"):
# We don't need the return value in this case.
declare_component("test", path=PATH)

response = self._request_component(
"components_test.test/../foo"
)

self.assertEqual(403, response.code)
self.assertEqual(b"forbidden", response.body)

def test_symlink_outside_component_root_request(self):
"""Tests to ensure a path symlinked to a file outside the component
root directory is disallowed."""

with mock.patch("streamlit.components.v1.components.os.path.isdir"):
# We don't need the return value in this case.
declare_component("test", path=PATH)

with mock.patch(
"streamlit.components.v1.components.os.path.realpath",
side_effect=[PATH, "/etc/hosts"],
):
response = self._request_component(
"components_test.test"
)

self.assertEqual(403, response.code)
self.assertEqual(b"forbidden", response.body)

def test_invalid_component_request(self):
"""Test request failure when invalid component name is provided."""

Expand Down

0 comments on commit 80d9979

Please sign in to comment.