HumanSignal / HumanSignal/label-studio
NUL sanitization corrupts escaped text in annotation drafts
- Dominant language
- TypeScript
- Stars
- 28.3k
- Forks
- 3.7k
- Avg merge
- 14h
- Merged PRs (30d)
- 15
Description
**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:
```python
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] == text
```
Actual: 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 `develop` source, reporting Label Studio `1.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.
Contributor guide
Research direction
Start with tasks.result_utils.sanitize_null_bytes and run the direct reproduction from the issue. Trace its callers in AnnotationDraft.save() and draft bulk import, then check the existing sanitization tests and the described regression cases. Done means actual NUL characters are removed while literal backslash-u text, including dictionary keys, remains unchanged.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- postgresql, python
- Domain
- backend, databases
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 78/100