{"record":{"id":"143f63b8f2e743d9","repo":"OtterMind/Chat2DB","slug":"relay-token-must-contain-at-least-32-characters","errorCode":null,"errorMessage":"RELAY_TOKEN must contain at least 32 characters","messagePattern":"RELAY_TOKEN must contain at least 32 characters","errorType":"validation","errorClass":"RuntimeError","httpStatus":null,"severity":"critical","filePath":"script/github/qq_relay/relay_server.py","lineNumber":60,"sourceCode":"\n@dataclass(frozen=True)\nclass RelayConfig:\n    relay_token: str\n    onebot_token: str\n    onebot_url: str\n    repository: str\n    group_id: int\n    max_message_length: int = 900\n    rate_limit: int = 30\n    rate_window_seconds: int = 60\n\n    @classmethod\n    def from_environment(cls) -> \"RelayConfig\":\n        relay_token = os.environ.get(\"RELAY_TOKEN\", \"\")\n        onebot_token = os.environ.get(\"ONEBOT_TOKEN\", \"\")\n        group_id = os.environ.get(\"QQ_GROUP_ID\", \"\")\n        if len(relay_token) < 32:\n            raise RuntimeError(\"RELAY_TOKEN must contain at least 32 characters\")\n        if len(onebot_token) < 32:\n            raise RuntimeError(\"ONEBOT_TOKEN must contain at least 32 characters\")\n        if not group_id.isdigit():\n            raise RuntimeError(\"QQ_GROUP_ID must be a numeric QQ group number\")\n        return cls(\n            relay_token=relay_token,\n            onebot_token=onebot_token,\n            onebot_url=os.environ.get(\"ONEBOT_URL\", \"http://napcat:3000\"),\n            repository=os.environ.get(\"RELAY_REPOSITORY\", \"OtterMind/Chat2DB\"),\n            group_id=int(group_id),\n            max_message_length=int(os.environ.get(\"RELAY_MAX_MESSAGE_LENGTH\", \"900\")),\n            rate_limit=int(os.environ.get(\"RELAY_RATE_LIMIT\", \"30\")),\n        )\n\n\nclass RateLimiter:\n    def __init__(self, limit: int, window_seconds: int):\n        self.limit = limit","sourceCodeStart":42,"sourceCodeEnd":78,"githubUrl":"https://github.com/OtterMind/Chat2DB/blob/5ee1e990e73fbcae1969dc554be254fedb3ab888/script/github/qq_relay/relay_server.py#L42-L78","documentation":"Raised by RelayConfig.from_environment (relay_server.py:60) as a RuntimeError when RELAY_TOKEN has fewer than 32 characters. The relay authenticates inbound requests by exact Bearer-token comparison, so a short token is treated as misconfiguration and the server refuses to start.","triggerScenarios":"Starting the relay server with RELAY_TOKEN unset, empty, or shorter than 32 chars. from_environment is called once at startup in main(), so this is fatal before the server binds.","commonSituations":"RELAY_TOKEN not set in the container/host environment; a short placeholder value used during setup; the secret truncated when copied; deploying without the env var mounted.","solutions":["Generate a token of at least 32 characters, e.g. python -c \"import secrets;print(secrets.token_urlsafe(48))\".","Set RELAY_TOKEN in the relay's environment (Compose env, systemd, or container env).","Use the same value for QQ_RELAY_TOKEN in the GitHub Actions notifier."],"exampleFix":"RELAY_TOKEN=$(python -c 'import secrets;print(secrets.token_urlsafe(48))')","handlingStrategy":"validation","validationCode":"import os, secrets\ntoken = os.environ.get(\"RELAY_TOKEN\", \"\")\nif len(token) < 32:\n    token = secrets.token_urlsafe(48)\n    print(f\"RELAY_TOKEN too short; generated a new one (set it permanently): {token}\")","typeGuard":"def is_strong_token(value: str) -> bool:\n    return len(value) >= 32","tryCatchPattern":null,"preventionTips":["Generate tokens with secrets.token_urlsafe(48).","Mount RELAY_TOKEN as an env var in the relay deployment.","Use the identical value for QQ_RELAY_TOKEN in the notifier."],"tags":["config","security","relay","startup","secrets"],"backgroundTag":null,"analyzedSha":"5ee1e990e73fbcae1969dc554be254fedb3ab888","analyzedAt":"2026-08-14T07:05:03.077Z","schemaVersion":2},"datasetVersion":"2026-08-14T10:17:34.591Z"}