{"record":{"id":"56c143fe62425249","repo":"Tencent/WeKnora","slug":"invalid-qqbot-api-base-url-w-for-private-deploy","errorCode":null,"errorMessage":"invalid qqbot api_base_url: %w (for private deployments, add the hostname to SSRF_WHITELIST)","messagePattern":"invalid qqbot api_base_url: %w \\(for private deployments, add the hostname to SSRF_WHITELIST\\)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/im/qqbot/client.go","lineNumber":87,"sourceCode":"\tif result.URL == \"\" {\n\t\treturn \"\", fmt.Errorf(\"empty qqbot gateway url\")\n\t}\n\tif err := validateGatewayURL(result.URL); err != nil {\n\t\treturn \"\", fmt.Errorf(\"invalid qqbot gateway url: %w\", err)\n\t}\n\treturn result.URL, nil\n}\n\nfunc validateHTTPAPIBaseURL(raw string) error {\n\tu, err := url.Parse(raw)\n\tif err != nil || u.Host == \"\" {\n\t\treturn fmt.Errorf(\"invalid qqbot api_base_url: must be a valid http(s) URL\")\n\t}\n\tif u.Scheme != \"http\" && u.Scheme != \"https\" {\n\t\treturn fmt.Errorf(\"invalid qqbot api_base_url: must use http or https\")\n\t}\n\tif err := secutils.ValidateURLForSSRF(raw); err != nil {\n\t\treturn fmt.Errorf(\"invalid qqbot api_base_url: %w (for private deployments, add the hostname to SSRF_WHITELIST)\", err)\n\t}\n\treturn nil\n}\n\nfunc validateGatewayURL(raw string) error {\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tu, err := url.Parse(raw)\n\tif err != nil || u.Host == \"\" {\n\t\treturn fmt.Errorf(\"gateway_url must be a valid wss URL\")\n\t}\n\tif u.Scheme != \"wss\" {\n\t\treturn fmt.Errorf(\"gateway_url must use wss\")\n\t}\n\tcheckURL := *u\n\tcheckURL.Scheme = \"https\"\n\tif err := secutils.ValidateURLForSSRF(checkURL.String()); err != nil {","sourceCodeStart":69,"sourceCodeEnd":105,"githubUrl":"https://github.com/Tencent/WeKnora/blob/988cbb03305e055d8ebb7d46d9ac6cc0803cd074/internal/im/qqbot/client.go#L69-L105","documentation":"The QQ bot client validates its HTTP API base URL for SSRF before use. If secutils.ValidateURLForSSRF rejects the host, NewClient fails with this wrapped error explaining how to allow private deployments. This protects against using the bot credentials to reach internal services.","triggerScenarios":"NewClient is called with an api_base_url whose hostname is private/loopback/otherwise SSRF-blocked and not in SSRF_WHITELIST.","commonSituations":"Pointing the client at an internal QQ bot proxy or mock server; localhost in development; SSRF_WHITELIST missing from the container/deployment env.","solutions":["Add the hostname to the SSRF_WHITELIST environment variable","Use the official QQ bot API base URL (https://api.sgroup.qq.com)","Verify api_base_url parses as a valid http(s) URL with a public host"],"exampleFix":"// before\nclient, err := NewClient(cfg) // api_base_url: http://qqbot-proxy.internal\n// after\n// export SSRF_WHITELIST=qqbot-proxy.internal\nclient, err := NewClient(cfg)","handlingStrategy":"validation","validationCode":"u, err := url.Parse(cfg.APIBaseURL)\nif err != nil || (u.Scheme != \"http\" && u.Scheme != \"https\") || u.Host == \"\" {\n    return fmt.Errorf(\"invalid qqbot api_base_url: %q\", cfg.APIBaseURL)\n}","typeGuard":null,"tryCatchPattern":"client, err := NewClient(cfg)\nif err != nil && strings.Contains(err.Error(), \"SSRF_WHITELIST\") {\n    return fmt.Errorf(\"qqbot api host blocked: %w\", err)\n}","preventionTips":["Use the official QQ API base URL by default","List private hosts in SSRF_WHITELIST only when self-hosting","Add config validation before client construction"],"tags":["ssrf","security","configuration","qqbot"],"backgroundTag":"ssrf-validation-failed","analyzedSha":"988cbb03305e055d8ebb7d46d9ac6cc0803cd074","analyzedAt":"2026-09-02T14:41:08.344Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}