Skip to content

Commit 1970410

Browse files
RonaldJENRonaldJEN
authored andcommitted
fix: file download path encoding and host volume validation errors
- Fix file download path encoding: use quote(path, safe='/') to properly encode spaces and non-ASCII characters while preserving path separators in both sync and async filesystem adapters. - Fix host volume directory handling: auto-create missing host bind-mount directories with os.makedirs, return HTTP 500 with HOST_PATH_CREATE_FAILED error code on failure. Improve error message to use generic wording and avoid leaking raw OS error details to clients. - Fix endpoint proxy query string: preserve original query parameters when proxying requests to sandbox endpoints.
1 parent 291841b commit 1970410

5 files changed

Lines changed: 22 additions & 16 deletions

File tree

sdks/sandbox/python/src/opensandbox/adapters/filesystem_adapter.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -476,15 +476,15 @@ def _build_download_request(
476476
Returns:
477477
Dictionary containing URL, parameters, and headers for the request
478478
"""
479-
url = self._get_execd_url(self.FILESYSTEM_DOWNLOAD_PATH)
480-
params = {"path": path}
479+
encoded_path = quote(path, safe="/")
480+
url = f"{self._get_execd_url(self.FILESYSTEM_DOWNLOAD_PATH)}?path={encoded_path}"
481481
headers: dict[str, str] = {}
482482

483483
if range_header:
484484
headers["Range"] = range_header
485485

486486
return {
487487
"url": url,
488-
"params": params,
488+
"params": {},
489489
"headers": headers,
490490
}

sdks/sandbox/python/src/opensandbox/sync/adapters/filesystem_adapter.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,12 +87,12 @@ def _get_execd_url(self, path: str) -> str:
8787
return f"{self.connection_config.protocol}://{self.execd_endpoint.endpoint}{path}"
8888

8989
def _build_download_request(self, path: str, range_header: str | None = None) -> _DownloadRequest:
90-
url = self._get_execd_url(self.FILESYSTEM_DOWNLOAD_PATH)
91-
params = {"path": path}
90+
encoded_path = quote(path, safe="/")
91+
url = f"{self._get_execd_url(self.FILESYSTEM_DOWNLOAD_PATH)}?path={encoded_path}"
9292
headers: dict[str, str] = {}
9393
if range_header:
9494
headers["Range"] = range_header
95-
return {"url": url, "params": params, "headers": headers}
95+
return {"url": url, "params": {}, "headers": headers}
9696

9797
def read_file(
9898
self,

server/src/api/lifecycle.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -431,9 +431,11 @@ async def proxy_sandbox_endpoint_request(request: Request, sandbox_id: str, port
431431

432432
target_host = endpoint.endpoint
433433
query_string = request.url.query
434-
target_url = f"http://{target_host}/{full_path}"
435-
if query_string:
436-
target_url = f"{target_url}?{query_string}"
434+
target_url = (
435+
f"http://{target_host}/{full_path}?{query_string}"
436+
if query_string
437+
else f"http://{target_host}/{full_path}"
438+
)
437439

438440
client: httpx.AsyncClient = request.app.state.http_client
439441

server/src/services/constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ class SandboxErrorCodes:
6969
INVALID_PVC_NAME = "VOLUME::INVALID_PVC_NAME"
7070
UNSUPPORTED_VOLUME_BACKEND = "VOLUME::UNSUPPORTED_BACKEND"
7171
HOST_PATH_NOT_FOUND = "VOLUME::HOST_PATH_NOT_FOUND"
72+
HOST_PATH_CREATE_FAILED = "VOLUME::HOST_PATH_CREATE_FAILED"
7273
PVC_VOLUME_NOT_FOUND = "VOLUME::PVC_NOT_FOUND"
7374
PVC_VOLUME_INSPECT_FAILED = "VOLUME::PVC_INSPECT_FAILED"
7475
PVC_SUBPATH_UNSUPPORTED_DRIVER = "VOLUME::PVC_SUBPATH_UNSUPPORTED_DRIVER"

server/src/services/docker.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -976,14 +976,15 @@ def _validate_host_volume(volume, allowed_prefixes: Optional[list[str]]) -> None
976976
Docker-specific validation for host bind mount volumes.
977977
978978
Validates that the resolved host path (host.path + optional subPath)
979-
exists on the filesystem and remains within allowed prefixes.
979+
remains within allowed prefixes, then ensures the directory exists on
980+
the filesystem — creating it automatically if it does not.
980981
981982
Args:
982983
volume: Volume with host backend.
983984
allowed_prefixes: Optional allowlist of host path prefixes.
984985
985986
Raises:
986-
HTTPException: When the resolved path is invalid or missing.
987+
HTTPException: When the resolved path is invalid or cannot be created.
987988
"""
988989
resolved_path = volume.host.path
989990
if volume.sub_path:
@@ -996,14 +997,16 @@ def _validate_host_volume(volume, allowed_prefixes: Optional[list[str]]) -> None
996997
if allowed_prefixes and resolved_path != volume.host.path:
997998
ensure_valid_host_path(resolved_path, allowed_prefixes)
998999

999-
if not os.path.exists(resolved_path):
1000+
try:
1001+
os.makedirs(resolved_path, exist_ok=True)
1002+
except OSError as e:
10001003
raise HTTPException(
1001-
status_code=status.HTTP_400_BAD_REQUEST,
1004+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
10021005
detail={
1003-
"code": SandboxErrorCodes.HOST_PATH_NOT_FOUND,
1006+
"code": SandboxErrorCodes.HOST_PATH_CREATE_FAILED,
10041007
"message": (
1005-
f"Volume '{volume.name}': resolved host path '{resolved_path}' "
1006-
"does not exist. Host paths must exist before sandbox creation."
1008+
f"Volume '{volume.name}': could not ensure host path "
1009+
f"directory exists at '{resolved_path}': {type(e).__name__}"
10071010
),
10081011
},
10091012
)

0 commit comments

Comments
 (0)