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 None

View on GitHub (pinned to 06cb6987f0)

Solutions

  1. Strip or replace control characters before emit: ''.join(ch for ch in v if ord(ch) >= 0x20 and ch != '\x7f')
  2. Reject user-supplied identifiers containing whitespace beyond spaces at input validation time
  3. 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

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


AI-assisted analysis of MemPalace/mempalace@06cb6987f0 (2026-08-15). Data as JSON: /api/errors/1530add6af221503. Report an issue: GitHub.