MemPalace/mempalace · error · ValueError
{field_name} contains control characters
Error message
{field_name} contains control characters What it means
ValueError raised by _sanitize_routing() when a routing field contains control characters (any code point < 0x20, or DEL 0x7f). Control characters corrupt line-oriented log formats and can enable log injection, so they are rejected outright.
Source
Thrown at mempalace/logstream.py:116
def _sanitize_routing(value, field_name: str, required: bool = True) -> Optional[str]:
"""Validate a short routing field (stream, room, agent, correlation_id).
Streams may contain ``/`` (``project/mempalace``), so this is looser
than ``config.sanitize_name`` — but null bytes, control characters,
and over-length values are still rejected.
"""
if value is None or value == "":
if required:
raise ValueError(f"{field_name} must be a non-empty string")
return None
if not isinstance(value, str) or not value.strip():
raise ValueError(f"{field_name} must be a non-empty string")
value = strip_lone_surrogates(value.strip())
if len(value) > _MAX_ROUTING_LENGTH:
raise ValueError(f"{field_name} exceeds maximum length of {_MAX_ROUTING_LENGTH} characters")
if any(ord(ch) < 0x20 or ch == "\x7f" for ch in value):
raise ValueError(f"{field_name} contains control characters")
return value
def _sanitize_event_type(value) -> str:
if not isinstance(value, str) or not value.strip():
raise ValueError("type must be a non-empty string")
value = value.strip()
if not _EVENT_TYPE_RE.match(value):
raise ValueError(
f"type={value!r} is not a valid event type "
"(lowercase letters, digits, '.', '_', '-'; max 64 chars)"
)
return value
def _sanitize_status(value) -> Optional[str]:
if value is None or value == "":
return NoneView on GitHub (pinned to 06cb6987f0)
Solutions
- Strip or replace control characters before emit: ''.join(ch for ch in v if ord(ch) >= 0x20 and ch != '\x7f')
- Reject user-supplied identifiers containing whitespace beyond spaces at input validation time
- Keep routing fields machine-generated where possible
Example fix
# before events.emit(type="x", stream="a\nb", room="r") # ValueError: stream contains control characters # after stream = "".join(ch for ch in raw if ord(ch) >= 0x20 and ch != "\x7f") events.emit(type="x", stream=stream, room="r")
Defensive patterns
Strategy: validation
Validate before calling
def strip_control(value: str) -> str:
return "".join(ch for ch in value if ord(ch) >= 0x20 and ch != "\x7f")
stream = strip_control(stream) Type guard
def is_control_free(value) -> bool:
return isinstance(value, str) and not any(ord(ch) < 0x20 or ch == "\x7f" for ch in value) Try / catch
try:
events.emit(type=t, stream=s, room=r)
except ValueError as e:
if "contains control characters" in str(e):
s = "".join(ch for ch in s if ord(ch) >= 0x20 and ch != "\x7f")
events.emit(type=t, stream=s, room=r)
else:
raise Prevention
- Sanitize all user-supplied identifiers before they reach the event API
- Treat control characters in names as hostile input (log injection)
- Prefer machine-generated routing values over free-text ones
When it happens
Trigger: Passing a room/stream value containing \n, \t, \r, \x00, or \x7f — e.g. raw clipboard content, values parsed from binary sources, or embedded escape sequences in user input.
Common situations: Multi-line strings pasted into identifiers; values decoded from binary protocols carrying stray bytes; log-injection attempts through user-controlled names; tabs inside supposedly atomic tokens.
Related errors
- {field_name} contains invalid path characters
- {field_name} contains null bytes
- {field_name} must be a non-empty string
- {field_name} exceeds maximum length of {_MAX_ROUTING_LENGTH}
- type must be a non-empty string
AI-assisted analysis of MemPalace/mempalace@06cb6987f0 (2026-08-15).
Data as JSON: /api/errors/1530add6af221503.
Report an issue: GitHub.