{"record":{"id":"df3b4ea75e0f0b47","repo":"unslothai/unsloth","slug":"dataset-path-must-be-relative-or-under-a-dataset-r","errorCode":null,"errorMessage":"dataset path must be relative or under a dataset root: {raw!r}","messagePattern":"dataset path must be relative or under a dataset root: (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"warning","filePath":"studio/backend/hub/utils/paths.py","lineNumber":407,"sourceCode":"\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    # Normalize first so Windows/UNC and backslash paths resolve like the rest of the Hub path\n    # layer, and a backslashed '..' is caught by the traversal guard below.\n    normalized = normalize_path(raw)\n    path = Path(normalized).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 in (datasets_root(), dataset_uploads_root(), recipe_datasets_root()):\n            try:\n                _assert_contained(path, root)\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(normalized).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\n\n    cleaned = Path(*parts) if parts else Path()\n    candidates = [\n        dataset_uploads_root() / cleaned,\n        recipe_datasets_root() / cleaned,\n        datasets_root() / cleaned,\n        dataset_uploads_root() / cleaned.name,\n        recipe_datasets_root() / cleaned.name,","sourceCodeStart":389,"sourceCodeEnd":425,"githubUrl":"https://github.com/unslothai/unsloth/blob/203007d19051dcd2ae33876786d117c99f6b0368/studio/backend/hub/utils/paths.py#L389-L425","documentation":"Raised by resolve_dataset_path when the input is an absolute path that is not contained in any of the accepted roots (datasets_root, dataset_uploads_root, recipe_datasets_root, checked via realpath+commonpath). Absolute paths are only honored when they already live under a managed dataset root; everything else — /etc/passwd, /home/user/data — is refused. Relative paths and the 'uploads/'/'recipes' virtual prefixes are handled by later branches.","triggerScenarios":"Calling a dataset API with '/home/alice/my.csv' (outside all roots) or '/var/data/d.csv'; also fires when a path that looks contained is actually a symlink whose realpath resolves outside every root (commonpath uses realpath).","commonSituations":"Users pasting local absolute paths where a logical dataset-relative path is expected; moved/renamed dataset roots so previously-valid absolute paths no longer resolve inside; symlinked data dirs that point elsewhere.","solutions":["Use a relative path or the logical prefixes: 'mydata.csv', 'uploads/...', or 'recipes/...'.","If an absolute path is required, place the data under one of the dataset roots (or reconfigure the root to cover the location) and ensure symlinks resolve inside it.","Check for symlink escape: `realpath <path>` must start with one of the configured dataset roots."],"exampleFix":"# before\nresolve_dataset_path('/home/alice/my.csv')\n# after\nresolve_dataset_path('my.csv')   # resolved under datasets_root()","handlingStrategy":"validation","validationCode":"from hub.utils.paths import datasets_root, dataset_uploads_root, recipe_datasets_root\n\ndef absolute_dataset_path_ok(path: str) -> bool:\n    p = Path(path).expanduser()\n    if not p.is_absolute():\n        return True\n    pr = Path(os.path.realpath(p))\n    return any(\n        os.path.commonpath([pr, os.path.realpath(str(r))]) == os.path.realpath(str(r))\n        for r in (datasets_root(), dataset_uploads_root(), recipe_datasets_root())\n    )","typeGuard":null,"tryCatchPattern":"try:\n    p = resolve_dataset_path(value)\nexcept ValueError as e:\n    if \"relative or under a dataset root\" in str(e):\n        return bad_request(\"use a relative dataset path like 'uploads/file.csv'\")","preventionTips":["Prefer relative paths and the uploads//recipes/ virtual prefixes over absolute paths.","Remember containment is checked on realpath — symlinks escaping the root are rejected too.","After moving dataset roots, re-issue stored absolute paths as relative ones."],"tags":["validation","security","datasets","path-policy"],"backgroundTag":null,"analyzedSha":"203007d19051dcd2ae33876786d117c99f6b0368","analyzedAt":"2026-08-15T02:48:39.846Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}