NUL sanitization corrupts escaped text in annotation drafts
Describe the bug
sanitize_null_bytes corrupts valid literal backslash text in draft results. A backslash followed by u0000n becomes a newline; a backslash followed by u0000 raises JSONDecodeError.
The helper is called by AnnotationDraft.save() and draft bulk import. It serializes the payload to JSON and removes NUL-looking escape substrings, including occurrences inside escaped backslashes. This leaves an incomplete escape or changes the remaining characters into a different escape.
To Reproduce
From a checkout, set PYTHONPATH=label_studio and run:
from tasks.result_utils import sanitize_null_bytes
text = 'JSON escape: ' + chr(92) + 'u0000n'
payload = [{
'id': 'r1',
'from_name': 'transcription',
'to_name': 'text',
'type': 'textarea',
'value': {'text': [text]},
}]
cleaned = sanitize_null_bytes(payload)
print(repr(cleaned[0]['value']['text'][0]))
assert cleaned[0]['value']['text'][0] == textActual: the text contains a real newline after JSON escape: and the assertion fails. Changing the text to chr(92) + 'u0000' raises JSONDecodeError: Unterminated string.
Expected behavior
Only actual U+0000 characters should be removed. Literal backslash-u text, including in dictionary keys, should remain unchanged. Actual NUL characters still need sanitizing before PostgreSQL writes.
Environment
- Current
developsource, reporting Label Studio1.24.0.dev0. - Python 3.13.15, macOS 26.6.2 ARM64.
- Reproduced directly through the real helper; no Django server or database was used for this reproduction.
Additional context
The existing tests cover actual NUL removal but not escaped-backslash text. New regressions reproduce both the exception and silent text/key corruption, while retaining controls for actual NUL characters next to backslashes.
Source: HumanSignal/label-studio