Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 22 additions & 8 deletions src/bentoml/_internal/cloud/deployment.py
Original file line number Diff line number Diff line change
Expand Up @@ -1627,9 +1627,17 @@ def _build_requirements_txt(bento_dir: str, image: Image | None) -> bytes:
config = BentoBuildConfig.from_bento_dir(bento_dir)
filename = config.python.requirements_txt
content = b""
if filename and os.path.exists(fullpath := os.path.join(bento_dir, filename)):
with open(fullpath, "rb") as f:
content = f.read().rstrip(b"\n") + b"\n"
if filename:
fullpath = os.path.join(bento_dir, filename)
if os.path.commonpath(
[os.path.realpath(fullpath), os.path.realpath(bento_dir)]
) != os.path.realpath(bento_dir):
raise BentoMLException(
f"Path traversal detected: requirements.txt path '{filename}' is outside of bento directory '{bento_dir}'"
)
if os.path.exists(fullpath):
with open(fullpath, "rb") as f:
content = f.read().rstrip(b"\n") + b"\n"
elif config.python.packages:
for package in config.python.packages:
content += f"{package}\n".encode()
Expand Down Expand Up @@ -1658,9 +1666,15 @@ def _build_post_setup_script(bento_dir: str, image: Image | None) -> bytes:
config = BentoBuildConfig.from_bento_dir(bento_dir)
if image and image.post_commands:
content += "\n".join(image.post_commands).encode() + b"\n"
if config.docker.setup_script and os.path.exists(
fullpath := os.path.join(bento_dir, config.docker.setup_script)
):
with open(fullpath, "rb") as f:
content += f.read()
if config.docker.setup_script:
fullpath = os.path.join(bento_dir, config.docker.setup_script)
if os.path.commonpath(
[os.path.realpath(fullpath), os.path.realpath(bento_dir)]
) != os.path.realpath(bento_dir):
raise BentoMLException(
f"Path traversal detected: setup_script path '{config.docker.setup_script}' is outside of bento directory '{bento_dir}'"
)
if os.path.exists(fullpath):
with open(fullpath, "rb") as f:
content += f.read()
return content
32 changes: 32 additions & 0 deletions tests/unit/_internal/cloud/test_deployment.py
Original file line number Diff line number Diff line change
Expand Up @@ -537,3 +537,35 @@ def test_update_deployment_distributed(deployment_api: DeploymentAPI):
deployment_strategy="RollingUpdate",
),
}


def test_build_requirements_txt_path_traversal(tmp_path: t.Any):
from pathlib import Path

from bentoml._internal.cloud.deployment import _build_requirements_txt
from bentoml.exceptions import BentoMLException

bento_dir = Path(tmp_path) / "bento"
bento_dir.mkdir()
(bento_dir / "bentofile.yaml").write_text(
"service: service.py\npython:\n requirements_txt: ../../secret.txt\n"
)

with pytest.raises(BentoMLException, match="Path traversal detected"):
_build_requirements_txt(str(bento_dir), None)


def test_build_post_setup_script_path_traversal(tmp_path: t.Any):
from pathlib import Path

from bentoml._internal.cloud.deployment import _build_post_setup_script
from bentoml.exceptions import BentoMLException

bento_dir = Path(tmp_path) / "bento"
bento_dir.mkdir()
(bento_dir / "bentofile.yaml").write_text(
"service: service.py\ndocker:\n setup_script: ../../evil_setup.sh\n"
)

with pytest.raises(BentoMLException, match="Path traversal detected"):
_build_post_setup_script(str(bento_dir), None)