{"record":{"id":"4aa8df28da01688a","repo":"unslothai/unsloth","slug":"save-directory-may-not-contain-control-characters","errorCode":null,"errorMessage":"save_directory may not contain control characters","messagePattern":"save_directory may not contain control characters","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"studio/backend/models/export.py","lineNumber":22,"sourceCode":"\"\"\"Pydantic schemas for Export API.\"\"\"\n\nfrom pathlib import Path, PureWindowsPath\n\nfrom pydantic import BaseModel, Field, field_validator\nfrom typing import List, Optional, Literal, Dict, Any, Union\n\n\ndef _validate_save_directory(value: str) -> str:\n    \"\"\"Validate save_directory — allows absolute paths (user may want a different drive).\"\"\"\n    if value is None:\n        raise ValueError(\"save_directory is required\")\n    raw = str(value).strip()\n    if not raw:\n        raise ValueError(\"save_directory must not be empty\")\n    if \"\\x00\" in raw:\n        raise ValueError(\"save_directory may not contain null bytes\")\n    if any(ch in raw for ch in (\"\\r\", \"\\n\")):\n        raise ValueError(\"save_directory may not contain control characters\")\n    path = Path(raw).expanduser()\n    path_parts = (*path.parts, *PureWindowsPath(raw).parts, *raw.replace(\"\\\\\", \"/\").split(\"/\"))\n    if any(len(part) > 255 for part in path_parts if part not in (\"\", \".\", \"/\", \"\\\\\")):\n        raise ValueError(\"save_directory path components must be <= 255 characters\")\n    if (\n        \"..\" in path.parts\n        or \"..\" in PureWindowsPath(raw).parts\n        or \"..\" in raw.replace(\"\\\\\", \"/\").split(\"/\")\n    ):\n        raise ValueError(\"save_directory may not contain '..' segments\")\n    return raw\n\n\nclass LoadCheckpointRequest(BaseModel):\n    \"\"\"Request for loading a checkpoint into the export backend.\"\"\"\n\n    checkpoint_path: str = Field(..., description = \"Path to the checkpoint directory\")\n    max_seq_length: int = Field(","sourceCodeStart":4,"sourceCodeEnd":40,"githubUrl":"https://github.com/unslothai/unsloth/blob/203007d19051dcd2ae33876786d117c99f6b0368/studio/backend/models/export.py#L4-L40","documentation":"ValueError from _validate_save_directory when the path contains carriage-return or line-feed characters. Newlines in paths can corrupt logs, manifests, and shell commands that later consume the export destination (CRLF injection), so they are rejected up front.","triggerScenarios":"Sending save_directory with embedded \\r or \\n, e.g. \"/exports\\nrm -rf\" or a value pasted from a spreadsheet/terminal that retained a trailing newline before a closing quote.","commonSituations":"Copy/paste from terminals or docs introducing trailing newlines; malicious CRLF-injection attempts against the export API; CSV/spreadsheet-sourced config values.","solutions":["Trim newlines/CRs from the path value on the client before sending (value.replace(/[\\r\\n]/g, '')).","When pasting paths into config, paste into a single-line field or strip whitespace.","Treat occurrences in production traffic as suspicious and inspect the source client."],"exampleFix":"// before\n{ \"save_directory\": \"/exports/model\\n\" }\n// after\n{ \"save_directory\": \"/exports/model\" }","handlingStrategy":"validation","validationCode":"def save_directory_no_control_chars(payload: dict) -> bool:\n    v = payload.get(\"save_directory\")\n    return isinstance(v, str) and not any(ch in v for ch in (\"\\r\", \"\\n\"))","typeGuard":"def is_single_line_path(v: str) -> bool:\n    return isinstance(v, str) and \"\\r\" not in v and \"\\n\" not in v","tryCatchPattern":null,"preventionTips":["Strip CR/LF from pasted paths client-side (v.replace(/[\\r\\n]/g, '')).","Avoid multi-line inputs for path fields in the UI.","Treat CRLF in path fields as injection attempts when they appear in server traffic."],"tags":["validation","security","crlf","export","http-422"],"backgroundTag":null,"analyzedSha":"203007d19051dcd2ae33876786d117c99f6b0368","analyzedAt":"2026-08-15T02:48:39.846Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}