{"record":{"id":"a7b16bbc7c0107e4","repo":"unslothai/unsloth","slug":"native-path-grant-contains-invalid-characters","errorCode":null,"errorMessage":"Native path grant contains invalid characters.","messagePattern":"Native path grant contains invalid characters\\.","errorType":"validation","errorClass":"NativePathLeaseError","httpStatus":400,"severity":"error","filePath":"studio/backend/utils/native_path_leases.py","lineNumber":340,"sourceCode":"    if payload[\"operation\"] != operation:\n        raise NativePathLeaseError(\"Native path grant operation is invalid.\")\n    if expected_kind and payload[\"path_kind\"] != expected_kind:\n        raise NativePathLeaseError(\"Native path grant kind is invalid.\")\n    now_ms = int(time.time() * 1000)\n    issued_at_ms = _required_int(payload, \"issued_at_ms\")\n    expires_at_ms = _required_int(payload, \"expires_at_ms\")\n    if issued_at_ms >= expires_at_ms:\n        raise NativePathLeaseError(\"Native path grant timestamps are inconsistent.\")\n    if expires_at_ms <= now_ms:\n        raise NativePathLeaseError(\"Native path grant has expired.\")\n    if issued_at_ms > now_ms + 30_000:\n        raise NativePathLeaseError(\"Native path grant issue time is invalid.\")\n    for key in (\"canonical_path\", \"nonce\", \"token_id_hash\", \"display_label\"):\n        raw = payload.get(key)\n        if raw is None:\n            continue\n        if \"\\x00\" in str(raw):\n            raise NativePathLeaseError(\"Native path grant contains invalid characters.\")\n\n\ndef _validate_current_stat(\n    grant: NativePathGrant, identity_options: tuple[tuple[int, int], ...]\n) -> tuple[int, int] | None:\n    try:\n        st = os.lstat(grant.canonical_path)\n    except OSError as exc:\n        raise NativePathLeaseError(\"Native path is no longer accessible.\") from exc\n    if _stat_module.S_ISLNK(st.st_mode):\n        raise NativePathLeaseError(\"Native path is no longer a regular file.\")\n    if grant.path_type == \"file\":\n        if not _stat_module.S_ISREG(st.st_mode):\n            raise NativePathLeaseError(\"Native path is no longer a regular file.\")\n    elif grant.path_type == \"directory\":\n        if not _stat_module.S_ISDIR(st.st_mode):\n            raise NativePathLeaseError(\"Native path is no longer a directory.\")\n    else:","sourceCodeStart":322,"sourceCodeEnd":358,"githubUrl":"https://github.com/unslothai/unsloth/blob/203007d19051dcd2ae33876786d117c99f6b0368/studio/backend/utils/native_path_leases.py#L322-L358","documentation":"One of the grant's free-text payload fields (canonical_path, nonce, token_id_hash, display_label) contains a NUL byte (\\x00). The backend refuses NUL bytes because they cannot round-trip through C-based filesystem and OS APIs and are a classic payload-injection vector. Any presence means the signed payload itself was malformed at signing time. This check runs on optional fields too (only absent fields are skipped).","triggerScenarios":"verify_native_path_lease() with a payload where any of canonical_path/nonce/token_id_hash/display_label contains \\x00. Produced by a signer bug that embeds raw bytes instead of UTF-8 strings, or by a hand-crafted signed payload used in tests containing embedded NULs.","commonSituations":"Rust signer writing a fixed-size buffer or a length-prefixed value into a JSON string without trimming; test fixtures generated with struct.pack or bytes concatenation; fuzzing pipelines feeding arbitrary bytes into the grant builder.","solutions":["Decode the grant payload and locate the offending field: python -c \"import base64,json;print(json.loads(base64.urlsafe_b64decode(lease.split('.')[0]+'==')))\" and inspect for \\x00.","Fix the signer to strip/reject NUL bytes before building the JSON payload.","Regenerate the grant; the backend cannot sanitize a signed payload (any edit breaks the HMAC).","Add a unit test on the signer asserting no control characters in the four string fields."],"exampleFix":"// before (Rust signer)\nlet nonce = bytes_with_nul.to_vec(); // embedded \\x00 leaks into JSON\n\n// after\nlet nonce = String::from_utf8(bytes)?.replace('\\0', \"\");\nassert!(!nonce.contains('\\0'));","handlingStrategy":"validation","validationCode":"import base64, json\n\ndef grant_strings_clean(lease: str) -> bool:\n    payload = json.loads(base64.urlsafe_b64decode(lease.split('.')[0] + '=='))\n    return all(\n        '\\x00' not in str(payload.get(k, ''))\n        for k in ('canonical_path', 'nonce', 'token_id_hash', 'display_label')\n    )","typeGuard":null,"tryCatchPattern":"try:\n    grant = verify_native_path_lease(lease, operation='read')\nexcept NativePathLeaseError as exc:\n    if 'invalid characters' in str(exc):\n        lease = request_fresh_grant()  # signed payload is corrupt at source\n        grant = verify_native_path_lease(lease, operation='read')\n    else:\n        raise","preventionTips":["Sanitize signer inputs: reject control characters before building the payload.","Build payloads with a real JSON serializer, never string concatenation.","Fuzz-test the signer's path and nonce fields for byte-level cleanliness."],"tags":["native-path-lease","injection","validation","payload"],"backgroundTag":null,"analyzedSha":"203007d19051dcd2ae33876786d117c99f6b0368","analyzedAt":"2026-08-15T02:48:39.846Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}