{"record":{"id":"638df2d562fa6be5","repo":"unslothai/unsloth","slug":"save-directory-may-not-contain-null-bytes","errorCode":null,"errorMessage":"save_directory may not contain null bytes","messagePattern":"save_directory may not contain null bytes","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"studio/backend/models/export.py","lineNumber":20,"sourceCode":"# Copyright 2026-present the Unsloth AI Inc. team. All rights reserved. See /studio/LICENSE.AGPL-3.0\n\n\"\"\"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","sourceCodeStart":2,"sourceCodeEnd":38,"githubUrl":"https://github.com/unslothai/unsloth/blob/203007d19051dcd2ae33876786d117c99f6b0368/studio/backend/models/export.py#L2-L38","documentation":"ValueError from _validate_save_directory when the path contains a NUL byte (\\x00). NUL cannot appear in a real filesystem path on Linux/macOS and would cause OSError ('embedded null byte') at write time, so it is rejected at the validation boundary — it is also a classic path-injection probe.","triggerScenarios":"Sending save_directory containing a literal \\u0000, e.g. \"/tmp/\\x00evil\" or a value deserialized from truncated binary input; fuzzing or security scanning of the export endpoint.","commonSituations":"Penetration tests / automated scanners probing path handling; corrupted client state or truncated base64 decoding producing control characters.","solutions":["Remove NUL bytes from the path on the client side (they are never legitimate).","If this appears in logs without an obvious client, investigate whether a scanner or proxy is mutating payloads.","Reject or sanitize inputs that fail a printable-ASCII/UTF-8 sanity check before they reach the API."],"exampleFix":"// before\n{ \"save_directory\": \"/tmp/\\u0000export\" }\n// after\n{ \"save_directory\": \"/tmp/export\" }","handlingStrategy":"validation","validationCode":"def save_directory_safe(payload: dict) -> bool:\n    v = payload.get(\"save_directory\")\n    return isinstance(v, str) and \"\\x00\" not in v","typeGuard":"def is_null_free_path(v: str) -> bool:\n    return isinstance(v, str) and \"\\x00\" not in v","tryCatchPattern":null,"preventionTips":["Sanitize user-supplied paths by rejecting non-printable characters before they reach the API.","Treat NUL bytes in requests as malicious — alert on them, don't silently clean them.","Validate decoded base64/URL-encoded payload segments before use."],"tags":["validation","security","path-traversal","export","http-422"],"backgroundTag":null,"analyzedSha":"203007d19051dcd2ae33876786d117c99f6b0368","analyzedAt":"2026-08-15T02:48:39.846Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}