{"id":"0c065654bab18b2f","repo":"aio-libs/aiohttp","slug":"reason-cannot-contain-r-or-n-0c0656","errorCode":null,"errorMessage":"Reason cannot contain \\r or \\n","messagePattern":"Reason cannot contain \\\\r or \\\\n","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"warning","filePath":"aiohttp/web_response.py","lineNumber":160,"sourceCode":"    def reason(self) -> str:\n        return self._reason\n\n    def set_status(\n        self,\n        status: int,\n        reason: str | None = None,\n    ) -> None:\n        assert (\n            not self.prepared\n        ), \"Cannot change the response status code after the headers have been sent\"\n        self._set_status(status, reason)\n\n    def _set_status(self, status: int, reason: str | None) -> None:\n        self._status = status\n        if reason is None:\n            reason = REASON_PHRASES.get(self._status, \"\")\n        elif \"\\r\" in reason or \"\\n\" in reason:\n            raise ValueError(\"Reason cannot contain \\\\r or \\\\n\")\n        self._reason = reason\n\n    @property\n    def keep_alive(self) -> bool | None:\n        return self._keep_alive\n\n    def force_close(self) -> None:\n        self._keep_alive = False\n\n    @property\n    def body_length(self) -> int:\n        return self._body_length\n\n    def enable_chunked_encoding(self) -> None:\n        \"\"\"Enables automatic chunked transfer encoding.\"\"\"\n        if hdrs.CONTENT_LENGTH in self._headers:\n            raise RuntimeError(\n                \"You can't enable chunked encoding when a content length is set\"","sourceCodeStart":142,"sourceCodeEnd":178,"githubUrl":"https://github.com/aio-libs/aiohttp/blob/c0ef574e29109210e96e652771ae4e7b88615fa4/aiohttp/web_response.py#L142-L178","documentation":"Raised as ValueError by StreamResponse._set_status (aiohttp/web_response.py:160) when set_status(status, reason) is called with a reason containing '\\r' or '\\n'. Identical in purpose to the same check on HTTPException: aiohttp forbids CR/LF in the reason phrase because it goes into the HTTP status line and would allow response splitting. Triggered from StreamResponse (and subclasses Response, FileResponse) when changing the status after construction.","triggerScenarios":"Calling resp.set_status(200, reason=user_text) where user_text contains a CR or LF. Often combined with dynamic reason phrases built from error messages, log lines, or template output.","commonSituations":"Handlers that pass exception messages or request-derived strings as reason; logging frameworks that interleave '\\n' in formatted strings; tests that copy multi-line expectations into set_status.","solutions":["Strip CR/LF from any dynamic reason before calling set_status: reason.replace('\\r', ' ').replace('\\n', ' ').","Use a short static reason; put dynamic detail in the body or a custom header.","Add an assertion / lint rule that reason phrases are single-line ASCII."],"exampleFix":"// before\nresp = web.Response()\nresp.set_status(200, reason=f\"OK: {dynamic_msg}\")  # ValueError if msg has \\n\n\n# after\nsafe = dynamic_msg.replace(\"\\r\", \" \").replace(\"\\n\", \" \")\nresp.set_status(200, reason=f\"OK: {safe}\")","handlingStrategy":"validation","validationCode":"def safe_reason(s: str, max_len: int = 200) -> str:\n    return s.replace(\"\\r\", \" \").replace(\"\\n\", \" \")[:max_len]\n\nresp.set_status(200, reason=safe_reason(dynamic_msg))","typeGuard":"def is_safe_reason(s: str) -> bool:\n    return \"\\r\" not in s and \"\\n\" not in s","tryCatchPattern":"try:\n    resp.set_status(200, reason=dynamic_msg)\nexcept ValueError:\n    resp.set_status(200, reason=safe_reason(dynamic_msg))","preventionTips":["Strip CR/LF from any dynamic reason phrase.","Keep reason short and static; move detail into body or headers.","Lint that reason phrases are single-line ASCII."],"tags":["http","security","response-splitting","validation"],"analyzedSha":"c0ef574e29109210e96e652771ae4e7b88615fa4","analyzedAt":"2026-08-04T19:51:05.467Z","schemaVersion":2}