Tencent/WeKnora · warning
URL rejected for security reasons: %v
Error message
URL rejected for security reasons: %v
What it means
downloadFromURL enforces SSRF protection: URLs not on the WeCom API host allowlist are passed to secutils.ValidateURLForSSRF, which rejects private/internal addresses. If that validation fails, the URL is rejected with 'URL rejected for security reasons: <reason>'. This stops attacker-controlled callback fields (e.g. a forged PicUrl) from making the server fetch internal resources.
Source
Thrown at internal/im/wecom/webhook_adapter.go:572
if err != nil {
return nil, "", fmt.Errorf("get access token: %w", err)
}
apiURL := fmt.Sprintf("%s/cgi-bin/media/get?access_token=%s&media_id=%s",
a.apiBaseURL, accessToken, msg.FileKey)
return downloadFromURL(ctx, apiURL, fileName, a.extraAllowedHost)
}
// downloadFromURL performs a GET request and returns the response body.
// It tries to resolve the real filename from HTTP response headers:
// 1. Content-Disposition: attachment; filename="xxx.pdf"
// 2. Content-Type → extension mapping (fallback for platforms like WeCom that
// don't provide the original filename in the callback JSON)
func downloadFromURL(ctx context.Context, rawURL, fileName string, extraAllowedHost string) (io.ReadCloser, string, error) {
// SSRF protection: reject internal/private URLs unless on the WeCom API allowlist.
if !isAllowedIMAPIHost(rawURL, extraAllowedHost) {
if err := secutils.ValidateURLForSSRF(rawURL); err != nil {
return nil, "", fmt.Errorf("URL rejected for security reasons: %v", err)
}
}
req, err := http.NewRequestWithContext(ctx, http.MethodGet, rawURL, nil)
if err != nil {
return nil, "", fmt.Errorf("create request: %w", err)
}
resp, err := httpClient.Do(req)
if err != nil {
return nil, "", fmt.Errorf("download: %w", err)
}
if resp.StatusCode != http.StatusOK {
resp.Body.Close()
return nil, "", fmt.Errorf("download failed: status=%d", resp.StatusCode)
}
View on GitHub (pinned to 988cbb0330)
Solutions
- Only download URLs that come from verified WeCom callbacks (msg_signature verified) and use official WeCom media hosts
- For tests/dev, pass the local host via the extraAllowedHost parameter of downloadFromURL / adapter configuration instead of bypassing validation
- If a new legitimate WeCom host is rejected, add it to the allowlist configuration — never disable SSRF validation
- Inspect the wrapped %v reason (e.g. private IP, bad scheme) to understand which rule fired
Example fix
// before: test fails against local server rc, _, err := downloadFromURL(ctx, "http://localhost:8080/file", name, "") // after rc, _, err := downloadFromURL(ctx, "http://localhost:8080/file", name, "localhost:8080")
Defensive patterns
Strategy: validation
Validate before calling
u, err := url.Parse(msg.FileKey)
if err != nil || (u.Scheme != "https" && u.Scheme != "http") || isPrivateHost(u.Hostname()) {
return errors.New("untrusted file URL")
} Type guard
func isPublicHTTPURL(raw string) bool {
u, err := url.Parse(raw)
return err == nil && (u.Scheme == "http" || u.Scheme == "https") &&
net.ParseIP(u.Hostname()) != nil && !isPrivateIP(net.ParseIP(u.Hostname()))
} Try / catch
rc, name, err := adapter.DownloadFile(ctx, msg)
if err != nil && strings.Contains(err.Error(), "URL rejected for security reasons") {
logger.Warnf("blocked suspicious media URL: %v", err)
return ErrUntrustedURL
} Prevention
- Never disable SSRF validation in production
- Configure extraAllowedHost explicitly for tests instead of loosening checks
- Verify msg_signature so attacker-chosen PicUrl values never reach the downloader
- Keep the WeCom media host allowlist updated with security review
When it happens
Trigger: A message whose PicUrl/FileKey points to a private IP (10.x, 192.168.x, 169.254.169.254, localhost), a non-HTTP scheme, or any non-WeCom host not in extraAllowedHost — typically forged or replayed callbacks, or self-hosted test URLs.
Common situations: Local development pointing PicUrl at a local file server; test fixtures using example.com or internal hosts; genuinely malicious callbacks probing SSRF; WeCom changing media CDN hostnames so new hosts fail the allowlist check.
Related errors
- attachment URL rejected: %w
- URL rejected: %w
- base_url SSRF validation failed: %w
- docreader address failed SSRF validation: %w
- blocked by SSRF policy: %w
AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02).
Data as JSON: /api/errors/beb6b28471be90ab.
Report an issue: GitHub.