{"record":{"id":"616b9498a11a777a","repo":"unslothai/unsloth","slug":"unsloth-mcp-bearer-token-must-contain-ascii-charac","errorCode":null,"errorMessage":"Unsloth MCP bearer token must contain ASCII characters only","messagePattern":"Unsloth MCP bearer token must contain ASCII characters only","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"critical","filePath":"studio/backend/mcp_server.py","lineNumber":28,"sourceCode":"\nfrom __future__ import annotations\n\nimport hmac\nimport asyncio\nfrom typing import Any\n\nfrom fastmcp import FastMCP\n\n\nclass BearerTokenMiddleware:\n    \"\"\"Require an exact bearer token when Unsloth MCP is exposed remotely.\"\"\"\n\n    def __init__(self, app: Any, token: str) -> None:\n        if not token or not token.strip():\n            raise ValueError(\"Unsloth MCP bearer token must be a non-empty value\")\n        if not token.isascii():\n            # A non-ASCII token cannot be sent in an HTTP header; reject it here.\n            raise ValueError(\"Unsloth MCP bearer token must contain ASCII characters only\")\n        self.app = app\n        # Compare on raw header bytes: str hmac.compare_digest raises on non-ASCII\n        # input, which would surface as a 500 instead of a clean 401.\n        self.expected = token.encode(\"utf-8\")\n\n    async def __call__(self, scope: dict[str, Any], receive: Any, send: Any) -> None:\n        scope_type = scope.get(\"type\")\n        if scope_type not in (\"http\", \"websocket\"):\n            await self.app(scope, receive, send)\n            return\n\n        headers = dict(scope.get(\"headers\", []))\n        raw_auth = headers.get(b\"authorization\", b\"\")\n        scheme, _, supplied = raw_auth.partition(b\" \")\n        if scheme.lower() != b\"bearer\" or not hmac.compare_digest(supplied, self.expected):\n            await _send_unauthorized(send, scope_type)\n            return\n","sourceCodeStart":10,"sourceCodeEnd":46,"githubUrl":"https://github.com/unslothai/unsloth/blob/203007d19051dcd2ae33876786d117c99f6b0368/studio/backend/mcp_server.py#L10-L46","documentation":"ValueError from BearerTokenMiddleware.__init__ when the supplied MCP bearer token contains non-ASCII characters. HTTP header values cannot carry non-ASCII bytes, and the middleware later compares raw header bytes with hmac.compare_digest, which raises on non-ASCII str input — so a non-ASCII token is rejected up front with a clear message instead of a 500 during a request.","triggerScenarios":"Setting UNSLOTH_STUDIO_MCP_TOKEN to a string with non-ASCII characters (e.g. a passphrase with 'é', emoji, or CJK characters), or a copy/paste from a document that introduced a smart quote or invisible Unicode character.","commonSituations":"Human-chosen passphrases in non-English locales; tokens pasted from rich-text editors that normalize quotes; files saved with a BOM or non-breaking space included in the value.","solutions":["Replace the token with a pure-ASCII secret, e.g. output of 'openssl rand -hex 32'.","Re-type or re-paste the value in a plain-text editor to strip invisible Unicode (smart quotes, NBSP, BOM).","Verify with: python -c \"import os; print(os.environ['UNSLOTH_STUDIO_MCP_TOKEN'].isascii())\" -> True."],"exampleFix":"# before\nexport UNSLOTH_STUDIO_MCP_TOKEN='pässphrase-é'\n# after\nexport UNSLOTH_STUDIO_MCP_TOKEN=\"$(openssl rand -hex 32)\"","handlingStrategy":"validation","validationCode":"def valid_mcp_token(token: str | None) -> bool:\n    return (\n        isinstance(token, str)\n        and token.strip() != \"\"\n        and token.isascii()\n    )","typeGuard":"def is_ascii_token(t: str | None) -> bool:\n    return isinstance(t, str) and t != \"\" and t.isascii()","tryCatchPattern":null,"preventionTips":["Always generate MCP tokens with 'openssl rand -hex 32' (pure ASCII by construction).","Paste secrets only via plain-text editors to avoid smart quotes / NBSP.","Add 'token.isascii()' to deployment preflight checks."],"tags":["mcp","auth","ascii","config","security"],"backgroundTag":null,"analyzedSha":"203007d19051dcd2ae33876786d117c99f6b0368","analyzedAt":"2026-08-15T02:48:39.846Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}