3434from pydantic import ValidationError
3535from typing_extensions import override
3636
37- from . import artifact_util
3837from ..errors .input_validation_error import InputValidationError
3938from .base_artifact_service import ArtifactVersion
4039from .base_artifact_service import BaseArtifactService
@@ -143,14 +142,39 @@ def _is_user_scoped(session_id: Optional[str], filename: str) -> bool:
143142 return session_id is None or _file_has_user_namespace (filename )
144143
145144
145+ def _validate_path_segment (value : str , field_name : str ) -> None :
146+ """Rejects values that could alter the constructed filesystem path.
147+
148+ Args:
149+ value: The caller-supplied identifier (e.g. user_id or session_id).
150+ field_name: Human-readable name used in the error message.
151+
152+ Raises:
153+ InputValidationError: If the value contains path separators, traversal
154+ segments, or null bytes.
155+ """
156+ if not value :
157+ raise InputValidationError (f"{ field_name } must not be empty." )
158+ if "\x00 " in value :
159+ raise InputValidationError (f"{ field_name } must not contain null bytes." )
160+ if "/" in value or "\\ " in value :
161+ raise InputValidationError (
162+ f"{ field_name } { value !r} must not contain path separators."
163+ )
164+ if value in ("." , ".." ) or ".." in value .split ("/" ):
165+ raise InputValidationError (
166+ f"{ field_name } { value !r} must not contain traversal segments."
167+ )
168+
169+
146170def _user_artifacts_dir (base_root : Path ) -> Path :
147171 """Returns the path that stores user-scoped artifacts."""
148172 return base_root / "artifacts"
149173
150174
151175def _session_artifacts_dir (base_root : Path , session_id : str ) -> Path :
152176 """Returns the path that stores session-scoped artifacts."""
153- artifact_util . validate_path_segment (session_id , "session_id" )
177+ _validate_path_segment (session_id , "session_id" )
154178 return base_root / "sessions" / session_id / "artifacts"
155179
156180
@@ -232,7 +256,7 @@ def __init__(self, root_dir: Path | str):
232256
233257 def _base_root (self , user_id : str , / ) -> Path :
234258 """Returns the artifacts root directory for a user."""
235- artifact_util . validate_path_segment (user_id , "user_id" )
259+ _validate_path_segment (user_id , "user_id" )
236260 return self .root_dir / "users" / user_id
237261
238262 def _scope_root (
@@ -245,7 +269,7 @@ def _scope_root(
245269 base = self ._base_root (user_id )
246270 if _is_user_scoped (session_id , filename ):
247271 return _user_artifacts_dir (base )
248- if session_id is None :
272+ if not session_id :
249273 raise InputValidationError (
250274 "Session ID must be provided for session-scoped artifacts."
251275 )
@@ -519,7 +543,7 @@ def _list_artifact_keys_sync(
519543
520544 base_root = self ._base_root (user_id )
521545
522- if session_id is not None :
546+ if session_id :
523547 session_root = _session_artifacts_dir (base_root , session_id )
524548 for artifact_dir in _iter_artifact_dirs (session_root ):
525549 metadata = self ._latest_metadata (artifact_dir )
0 commit comments