{"record":{"id":"962d5b328dd37c7a","repo":"OtterMind/Chat2DB","slug":"prepared-qq-notification-contains-a-onebot-cq-code","errorCode":null,"errorMessage":"Prepared QQ notification contains a OneBot CQ code","messagePattern":"Prepared QQ notification contains a OneBot CQ code","errorType":"validation","errorClass":"ConfigurationError","httpStatus":null,"severity":"error","filePath":"script/github/notify_qq.py","lineNumber":265,"sourceCode":"        raise ConfigurationError(\"Prepared QQ notification is missing or too large\")\n    try:\n        envelope = json.loads(path.read_text(encoding=\"utf-8\"))\n    except (OSError, UnicodeError, json.JSONDecodeError) as error:\n        raise ConfigurationError(\"Prepared QQ notification is not valid JSON\") from error\n    if not isinstance(envelope, Mapping) or envelope.get(\"version\") != 1:\n        raise ConfigurationError(\"Prepared QQ notification has an invalid format\")\n    if envelope.get(\"repository\") != repository:\n        raise ConfigurationError(\"Prepared QQ notification repository does not match\")\n    event_name = str(envelope.get(\"event_name\") or \"\")\n    if event_name not in COLLECTED_EVENT_NAMES:\n        raise ConfigurationError(\"Prepared QQ notification event is not allowed\")\n    message = envelope.get(\"message\")\n    if not isinstance(message, str) or not message or len(message) > 900:\n        raise ConfigurationError(\"Prepared QQ notification message is invalid\")\n    if re.search(r\"[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]\", message):\n        raise ConfigurationError(\"Prepared QQ notification contains control characters\")\n    if re.search(r\"(?i)\\[CQ:\", message):\n        raise ConfigurationError(\"Prepared QQ notification contains a OneBot CQ code\")\n    return event_name, message\n\n\ndef _event_detail(event_name: str, action: str, payload: Mapping[str, Any]) -> str:\n    if action in {\"labeled\", \"unlabeled\"}:\n        label = payload.get(\"label\") or {}\n        return f\"标签：{_clean_text(label.get('name'), 80)}\"\n    if action in {\"assigned\", \"unassigned\"}:\n        return f\"处理人：{_login(payload.get('assignee'))}\"\n    if action in {\"milestoned\", \"demilestoned\"}:\n        milestone = payload.get(\"milestone\") or {}\n        return f\"里程碑：{_clean_text(milestone.get('title'), 100)}\"\n    if action in {\"review_requested\", \"review_request_removed\"}:\n        reviewer = _login(payload.get(\"requested_reviewer\"))\n        team = payload.get(\"requested_team\") or {}\n        target = reviewer or _clean_text(team.get(\"name\"), 80)\n        return f\"评审人：{target}\"\n    if event_name == \"pull_request_target\" and action == \"synchronize\":","sourceCodeStart":247,"sourceCodeEnd":283,"githubUrl":"https://github.com/OtterMind/Chat2DB/blob/5ee1e990e73fbcae1969dc554be254fedb3ab888/script/github/notify_qq.py#L247-L283","documentation":"Raised by _read_prepared_message (notify_qq.py:265) when message contains the OneBot 11 CQ-code marker \"[CQ:\" (case-insensitive). CQ codes are markup that OneBot interprets as commands (images, mentions, etc.), so injecting one is an injection vector; the relay and prepared path both forbid it.","triggerScenarios":"A crafted message containing \"[CQ:...\" reaches the prepared envelope. The live build path defangs it via _clean_text (replacing with \"[CQ :\"), so this only fires through the QQ_PREPARED_MESSAGE_PATH read path with a tampered envelope.","commonSituations":"Hand-editing the JSON to inject a mention/at-all/image CQ code; testing the relay with a payload meant to trigger OneBot features; an envelope from tooling that did not sanitize.","solutions":["Regenerate the envelope through the collect step, which defangs CQ markers in _clean_text.","Remove or escape the literal \"[CQ:\" sequence in the message field.","Treat this as a security signal: investigate how untrusted text entered the envelope."],"exampleFix":"import re\nsafe = re.sub(r'(?i)\\[CQ:', '[CQ :', message)","handlingStrategy":"validation","validationCode":"import re\nif re.search(r'(?i)\\[CQ:', message):\n    message = re.sub(r'(?i)\\[CQ:', '[CQ :', message)","typeGuard":"import re\nCQ = re.compile(r'(?i)\\[CQ:')\ndef is_safe_from_cq(message: str) -> bool:\n    return not CQ.search(message)","tryCatchPattern":null,"preventionTips":["Defang CQ markers with _clean_text before storing in an envelope.","Treat an injected CQ code as a security incident and trace its source."],"tags":["security","config","github-actions","notification","validation"],"backgroundTag":null,"analyzedSha":"5ee1e990e73fbcae1969dc554be254fedb3ab888","analyzedAt":"2026-08-14T07:05:03.077Z","schemaVersion":2},"datasetVersion":"2026-08-14T10:17:34.591Z"}