{"record":{"id":"210a47d9ba06f41d","repo":"mudler/LocalAI","slug":"dataset-source-path-is-outside-the-allowed-directo","errorCode":null,"errorMessage":"Dataset source path is outside the allowed directory","messagePattern":"Dataset source path is outside the allowed directory","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"backend/python/trl/backend.py","lineNumber":315,"sourceCode":"                lora_alpha=lora_alpha,\n                lora_dropout=lora_dropout,\n                target_modules=target_modules or \"all-linear\",\n                bias=\"none\",\n                task_type=\"CAUSAL_LM\",\n            )\n            model = get_peft_model(model, peft_config)\n\n        # Load dataset\n        job.progress_queue.put(backend_pb2.FineTuneProgressUpdate(\n            job_id=job.job_id, status=\"loading_dataset\", message=\"Loading dataset\",\n        ))\n\n        dataset_split = request.dataset_split or \"train\"\n        if os.path.exists(request.dataset_source):\n            _allowed_dir = os.path.realpath(os.path.abspath(os.environ.get(\"LOCALAI_DATASET_DIR\", os.getcwd())))\n            _real_path = os.path.realpath(os.path.abspath(request.dataset_source))\n            if not (_real_path == _allowed_dir or _real_path.startswith(_allowed_dir + os.sep)):\n                raise ValueError(\"Dataset source path is outside the allowed directory\")\n            if request.dataset_source.endswith('.json') or request.dataset_source.endswith('.jsonl'):\n                dataset = load_dataset(\"json\", data_files=request.dataset_source, split=dataset_split)\n            elif request.dataset_source.endswith('.csv'):\n                dataset = load_dataset(\"csv\", data_files=request.dataset_source, split=dataset_split)\n            else:\n                dataset = load_dataset(request.dataset_source, split=dataset_split)\n        else:\n            dataset = load_dataset(request.dataset_source, split=dataset_split)\n\n        # Eval dataset setup\n        eval_dataset = None\n        eval_strategy = extra.get(\"eval_strategy\", \"steps\")\n        eval_steps = int(extra.get(\"eval_steps\", str(request.save_steps if request.save_steps > 0 else 500)))\n\n        if eval_strategy != \"no\":\n            eval_split = extra.get(\"eval_split\")\n            eval_dataset_source = extra.get(\"eval_dataset_source\")\n            if eval_split:","sourceCodeStart":297,"sourceCodeEnd":333,"githubUrl":"https://github.com/mudler/LocalAI/blob/44413a9d06bf5bc52ce088ba8ca74e5a2e8bee26/backend/python/trl/backend.py#L297-L333","documentation":"The TRL fine-tuning backend restricts local dataset files to an allowed directory (LOCALAI_DATASET_DIR env var, default: the backend's cwd). When request.dataset_source is an existing local path, its realpath must equal or live under the allowed dir; symlinked or ../-escaping paths are rejected via the os.sep suffix check, preventing path traversal.","triggerScenarios":"Sending a FineTune request with dataset_source='/etc/data.json' while the allowed dir is '/data'; using a symlink inside the sandbox that resolves outside it; running the backend without LOCALAI_DATASET_DIR set so only its working directory is allowed.","commonSituations":"Operator mounts datasets at a path but forgets to set LOCALAI_DATASET_DIR; client sends host-absolute paths that do not match container paths; symlinked dataset folders.","solutions":["Move/copy the dataset under the allowed directory, or set LOCALAI_DATASET_DIR on the backend to the directory containing the dataset and restart.","Send a dataset_source relative to the allowed dir (e.g. 'mydata.jsonl') so the resolved path falls inside it.","If the path uses a symlink, replace it with a bind mount / real directory under the allowed root."],"exampleFix":"# before\nLOCALAI_DATASET_DIR unset; request.dataset_source = \"/host/datasets/train.jsonl\"\n# after\n# backend env: LOCALAI_DATASET_DIR=/data  (dataset mounted at /data)\nrequest.dataset_source = \"/data/train.jsonl\"","handlingStrategy":"validation","validationCode":"import os\n\ndef dataset_path_allowed(source: str, env_var: str = \"LOCALAI_DATASET_DIR\") -> bool:\n    if not os.path.exists(source):\n        return True  # hub id path, no local check\n    allowed = os.path.realpath(os.path.abspath(os.environ.get(env_var, os.getcwd())))\n    real = os.path.realpath(os.path.abspath(source))\n    return real == allowed or real.startswith(allowed + os.sep)","typeGuard":null,"tryCatchPattern":"try:\n    start_finetune(request)\nexcept ValueError as e:\n    if \"outside the allowed directory\" in str(e):\n        return error_response(str(e), hint=f\"set {env_var} or place files under it\")\n    raise","preventionTips":["Set LOCALAI_DATASET_DIR explicitly and document it next to the volume mount.","Send relative dataset paths from clients.","Avoid symlinks in dataset paths; use bind mounts."],"tags":["trl","fine-tuning","path-traversal","security","configuration","localai"],"backgroundTag":null,"analyzedSha":"44413a9d06bf5bc52ce088ba8ca74e5a2e8bee26","analyzedAt":"2026-08-15T10:13:50.291Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}