{"record":{"id":"1530add6af221503","repo":"MemPalace/mempalace","slug":"field-name-contains-control-characters","errorCode":null,"errorMessage":"{field_name} contains control characters","messagePattern":"(.+?) contains control characters","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"mempalace/logstream.py","lineNumber":116,"sourceCode":"\ndef _sanitize_routing(value, field_name: str, required: bool = True) -> Optional[str]:\n    \"\"\"Validate a short routing field (stream, room, agent, correlation_id).\n\n    Streams may contain ``/`` (``project/mempalace``), so this is looser\n    than ``config.sanitize_name`` — but null bytes, control characters,\n    and over-length values are still rejected.\n    \"\"\"\n    if value is None or value == \"\":\n        if required:\n            raise ValueError(f\"{field_name} must be a non-empty string\")\n        return None\n    if not isinstance(value, str) or not value.strip():\n        raise ValueError(f\"{field_name} must be a non-empty string\")\n    value = strip_lone_surrogates(value.strip())\n    if len(value) > _MAX_ROUTING_LENGTH:\n        raise ValueError(f\"{field_name} exceeds maximum length of {_MAX_ROUTING_LENGTH} characters\")\n    if any(ord(ch) < 0x20 or ch == \"\\x7f\" for ch in value):\n        raise ValueError(f\"{field_name} contains control characters\")\n    return value\n\n\ndef _sanitize_event_type(value) -> str:\n    if not isinstance(value, str) or not value.strip():\n        raise ValueError(\"type must be a non-empty string\")\n    value = value.strip()\n    if not _EVENT_TYPE_RE.match(value):\n        raise ValueError(\n            f\"type={value!r} is not a valid event type \"\n            \"(lowercase letters, digits, '.', '_', '-'; max 64 chars)\"\n        )\n    return value\n\n\ndef _sanitize_status(value) -> Optional[str]:\n    if value is None or value == \"\":\n        return None","sourceCodeStart":98,"sourceCodeEnd":134,"githubUrl":"https://github.com/MemPalace/mempalace/blob/06cb6987f02610784fefbad4b2bd5d026d164ba6/mempalace/logstream.py#L98-L134","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"# before\nevents.emit(type=\"x\", stream=\"a\\nb\", room=\"r\")  # ValueError: stream contains control characters\n\n# after\nstream = \"\".join(ch for ch in raw if ord(ch) >= 0x20 and ch != \"\\x7f\")\nevents.emit(type=\"x\", stream=stream, room=\"r\")","handlingStrategy":"validation","validationCode":"def strip_control(value: str) -> str:\n    return \"\".join(ch for ch in value if ord(ch) >= 0x20 and ch != \"\\x7f\")\n\nstream = strip_control(stream)","typeGuard":"def is_control_free(value) -> bool:\n    return isinstance(value, str) and not any(ord(ch) < 0x20 or ch == \"\\x7f\" for ch in value)","tryCatchPattern":"try:\n    events.emit(type=t, stream=s, room=r)\nexcept ValueError as e:\n    if \"contains control characters\" in str(e):\n        s = \"\".join(ch for ch in s if ord(ch) >= 0x20 and ch != \"\\x7f\")\n        events.emit(type=t, stream=s, room=r)\n    else:\n        raise","preventionTips":["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"],"tags":["logstream","validation","control-characters","log-injection","security"],"backgroundTag":null,"analyzedSha":"06cb6987f02610784fefbad4b2bd5d026d164ba6","analyzedAt":"2026-08-15T03:03:36.213Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}