{"record":{"id":"a56881ba88aaa22f","repo":"unslothai/unsloth","slug":"dataset-path-may-not-contain-segments-raw-r-a56881","errorCode":null,"errorMessage":"dataset path may not contain '..' segments: {raw!r}","messagePattern":"dataset path may not contain '\\.\\.' segments: (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":400,"severity":"error","filePath":"studio/backend/utils/paths/storage_roots.py","lineNumber":502,"sourceCode":"        strip_prefixes = (\"exports\",),\n    )\n\n\ndef resolve_tensorboard_dir(path_value: str | None = None) -> Path:\n    return resolve_under_root(\n        path_value,\n        root = tensorboard_root(),\n        strip_prefixes = (\"runs\", \"tensorboard\"),\n    )\n\n\ndef resolve_dataset_path(path_value: str) -> Path:\n    raw = str(path_value or \"\").strip()\n    if \"\\x00\" in raw:\n        raise ValueError(\"dataset path may not contain null bytes\")\n    path = Path(raw).expanduser()\n    if \"..\" in path.parts:\n        raise ValueError(f\"dataset path may not contain '..' segments: {raw!r}\")\n    if path.is_absolute():\n        for root_fn in (datasets_root, dataset_uploads_root, recipe_datasets_root):\n            try:\n                _assert_contained(path, root_fn())\n                return path\n            except ValueError:\n                continue\n        raise ValueError(f\"dataset path must be relative or under a dataset root: {raw!r}\")\n\n    parts = [part for part in Path(path_value).parts if part not in (\"\", \".\")]\n    if parts[:2] == [\"assets\", \"datasets\"]:\n        parts = parts[2:]\n    if parts and parts[0] == \"uploads\":\n        cleaned = Path(*parts[1:]) if len(parts) > 1 else Path()\n        return dataset_uploads_root() / cleaned\n    if parts and parts[0] == \"recipes\":\n        cleaned = Path(*parts[1:]) if len(parts) > 1 else Path()\n        return recipe_datasets_root() / cleaned","sourceCodeStart":484,"sourceCodeEnd":520,"githubUrl":"https://github.com/unslothai/unsloth/blob/203007d19051dcd2ae33876786d117c99f6b0368/studio/backend/utils/paths/storage_roots.py#L484-L520","documentation":"Raised by resolve_dataset_path() when the expanded path contains a '..' segment. The resolver deliberately blocks parent-directory segments so a caller cannot escape the managed dataset storage roots; this is a path-traversal guard, not an incidental failure.","triggerScenarios":"Calling resolve_dataset_path() with values like '../secrets.env', 'datasets/../../etc/passwd', or 'a/../b' — note the check runs on Path(raw).expanduser().parts, so a '~' that expands to a path containing '..' also trips it. Both relative and absolute inputs are checked before root containment is evaluated.","commonSituations":"Frontends composing dataset paths from user-typed text, URL parameters carrying relative paths, or migrations importing legacy paths that used '..' for brevity. Crafted payloads attempting directory traversal through a dataset upload/download endpoint.","solutions":["Normalize the path client-side before sending: resolve '..' against a known base so no parent segments remain (e.g. posixpath.normpath on a sandboxed base).","Reject the request with a 400 and surface 'paths may not contain ..' to the user instead of retrying.","If the user genuinely meant a sibling dataset, send the canonical path relative to the dataset root without '..' segments."],"exampleFix":"# before\nresolve_dataset_path(request.args['path'])  # '../../etc/passwd'\n\n# after\nimport posixpath\nraw = request.args['path']\nif '..' in posixpath.normpath(raw).split('/') and raw.startswith('..'):\n    abort(400, 'dataset path may not contain .. segments')\nresolve_dataset_path(raw)","handlingStrategy":"validation","validationCode":"from pathlib import PurePosixPath\n\ndef has_parent_segments(p: str) -> bool:\n    return '..' in PurePosixPath(p.replace('\\\\', '/')).parts\n\nif has_parent_segments(user_path):\n    raise HTTPBadRequest('dataset path may not contain .. segments')","typeGuard":"def is_safe_relative_dataset_path(p: str) -> bool:\n    parts = PurePosixPath(p.replace('\\\\', '/')).parts\n    return bool(p) and '\\x00' not in p and '..' not in parts","tryCatchPattern":"try:\n    path = resolve_dataset_path(raw)\nexcept ValueError as e:\n    if \"'..' segments\" in str(e):\n        return bad_request('invalid dataset path')  # never retry; it is malicious or a client bug\n    raise","preventionTips":["Always send dataset paths relative to a known root; never let clients type free-form paths with '..'.","Mirror the '..' rejection in frontend validation so the error never reaches the backend."],"tags":["path-traversal","security","validation","python"],"backgroundTag":null,"analyzedSha":"203007d19051dcd2ae33876786d117c99f6b0368","analyzedAt":"2026-08-15T02:48:39.846Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}