{"record":{"id":"5ce895839648f4eb","repo":"aio-libs/aiohttp","slug":"method-cannot-contain-non-token-characters-method","errorCode":null,"errorMessage":"Method cannot contain non-token characters {method!r} (found at least {match.group()!r})","messagePattern":"Method cannot contain non-token characters (.+?) \\(found at least (.+?)\\)","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"aiohttp/client_reqrep.py","lineNumber":827,"sourceCode":"    _skip_auto_headers: \"CIMultiDict[None] | None\" = None\n\n    # N.B.\n    # Adding __del__ method with self._writer closing doesn't make sense\n    # because _writer is instance method, thus it keeps a reference to self.\n    # Until writer has finished finalizer will not be called.\n\n    def __init__(\n        self,\n        method: str,\n        url: URL,\n        *,\n        headers: CIMultiDict[str],\n        loop: asyncio.AbstractEventLoop,\n        ssl: SSLContext | bool | Fingerprint,\n        trust_env: bool = False,\n    ):\n        if match := _CONTAINS_CONTROL_CHAR_RE.search(method):\n            raise ValueError(\n                f\"Method cannot contain non-token characters {method!r} \"\n                f\"(found at least {match.group()!r})\"\n            )\n        # URL forbids subclasses, so a simple type check is enough.\n        assert type(url) is URL, url\n        self.original_url = url\n        self.url = url.with_fragment(None) if url.raw_fragment else url\n        self.method = method.upper()\n        self.loop = loop\n        self._ssl = ssl\n\n        if loop.get_debug():\n            self._source_traceback = traceback.extract_stack(sys._getframe(1))\n\n        if not url.raw_host:\n            raise InvalidURL(url)\n        self._update_headers(headers)\n        if url.raw_user or url.raw_password:","sourceCodeStart":809,"sourceCodeEnd":845,"githubUrl":"https://github.com/aio-libs/aiohttp/blob/d041d4d0fd48c3f0832084d33be16cf1c4835f85/aiohttp/client_reqrep.py#L809-L845","documentation":"Raised by ClientRequest.__init__ when the HTTP method string contains a character outside the HTTP token set (the regex _CONTAINS_CONTROL_CHAR_RE matches anything not in [-!#$%&'*+.^_`|~0-9a-zA-Z]). This catches control characters, whitespace, and other invalid bytes that would corrupt the request line. It is a ValueError raised at request construction, before any network I/O.","triggerScenarios":"Passing a method like 'GET\\r\\nX-Injected: 1' (CRLF injection attempt), 'GET ' (trailing space), 'POS\\x00T' (null byte), or any method with a non-token character to session.request() / session.get() etc. The regex search in ClientRequest.__init__ matches and raises ValueError.","commonSituations":"User-supplied or config-driven method strings not sanitized; CRLF/header-injection attempts (security-relevant); accidental newline or trailing whitespace in a method constant; constructing methods from concatenated/templated input.","solutions":["Hard-code method names as literals ('GET', 'POST') instead of building from untrusted input.","If accepting user input, validate against an allowlist: {'GET','POST','PUT','PATCH','DELETE','HEAD','OPTIONS'}.","Strip and reject any method containing whitespace or non-printable characters before passing to aiohttp.","Treat this error as a possible injection attempt and log it for security review."],"exampleFix":"# before\nmethod = user_input  # e.g. 'GET\\r\\nX-Evil: 1'\nawait session.request(method, url)  # ValueError\n\n# after — allowlist validation\nALLOWED = {'GET','POST','PUT','PATCH','DELETE','HEAD','OPTIONS'}\nif method.upper() not in ALLOWED:\n    raise ValueError(f'unsupported method: {method!r}')\nawait session.request(method.upper(), url)","handlingStrategy":"type-guard","validationCode":"import re\nTOKEN_RE = re.compile(r\"^[-!#$%&'*+.^_`|~0-9a-zA-Z]+$\")\nALLOWED_METHODS = {'GET','POST','PUT','PATCH','DELETE','HEAD','OPTIONS'}\n\ndef safe_method(m: str) -> str:\n    m = m.upper()\n    if m not in ALLOWED_METHODS or not TOKEN_RE.fullmatch(m):\n        raise ValueError(f'unsafe HTTP method: {m!r}')\n    return m","typeGuard":"import re\n_TOKEN = re.compile(r\"^[-!#$%&'*+.^_`|~0-9a-zA-Z]+$\")\ndef is_valid_method(method: str) -> bool:\n    return isinstance(method, str) and bool(_TOKEN.fullmatch(method))","tryCatchPattern":"try:\n    await session.request(method, url)\nexcept ValueError as e:\n    if 'non-token characters' in str(e):\n        raise ValueError(f'Reject method as possibly injected: {method!r}')\n    raise","preventionTips":["Never build HTTP methods from untrusted input; use an allowlist.","Sanitize any config-driven method string before passing to aiohttp.","Treat method-validation failures as security events and log them."],"tags":["client","request","security","validation","http-method"],"backgroundTag":null,"analyzedSha":"d041d4d0fd48c3f0832084d33be16cf1c4835f85","analyzedAt":"2026-08-11T20:44:15.550Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}