{"record":{"id":"d6705be6fe87d450","repo":"Tencent/WeKnora","slug":"jsonl-url-blocked-by-ssrf-check-v","errorCode":null,"errorMessage":"jsonl URL blocked by SSRF check: %v","messagePattern":"jsonl URL blocked by SSRF check: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/infrastructure/docparser/paddleocr_vl_cloud_converter.go","lineNumber":266,"sourceCode":"\treturn \"\", fmt.Errorf(\"task timed out after %d polls\", pollCount)\n}\n\n// --- result parsing ---\n\ntype paddleOCRVLCloudResultLine struct {\n\tResult struct {\n\t\tLayoutParsingResults []struct {\n\t\t\tMarkdown struct {\n\t\t\t\tText   string            `json:\"text\"`\n\t\t\t\tImages map[string]string `json:\"images\"`\n\t\t\t} `json:\"markdown\"`\n\t\t} `json:\"layoutParsingResults\"`\n\t} `json:\"result\"`\n}\n\nfunc (c *PaddleOCRVLCloudReader) fetchResults(jsonlURL string) (string, map[string]string, error) {\n\tif err := utils.ValidateURLForSSRF(jsonlURL); err != nil {\n\t\treturn \"\", nil, fmt.Errorf(\"jsonl URL blocked by SSRF check: %v\", err)\n\t}\n\tclient := utils.NewSSRFSafeHTTPClient(utils.SSRFSafeHTTPClientConfig{Timeout: 120 * time.Second, MaxRedirects: 5})\n\tresp, err := client.Get(jsonlURL)\n\tif err != nil {\n\t\treturn \"\", nil, fmt.Errorf(\"download jsonl: %w\", err)\n\t}\n\tdefer resp.Body.Close()\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn \"\", nil, fmt.Errorf(\"download jsonl status %d\", resp.StatusCode)\n\t}\n\tdata, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn \"\", nil, fmt.Errorf(\"read jsonl body: %w\", err)\n\t}\n\n\ttexts := make([]string, 0)\n\timages := make(map[string]string)\n\tfor _, line := range strings.Split(strings.TrimSpace(string(data)), \"\\n\") {","sourceCodeStart":248,"sourceCodeEnd":284,"githubUrl":"https://github.com/Tencent/WeKnora/blob/988cbb03305e055d8ebb7d46d9ac6cc0803cd074/internal/infrastructure/docparser/paddleocr_vl_cloud_converter.go#L248-L284","documentation":"fetchResults validates the JSONL result URL returned by the cloud service with utils.ValidateURLForSSRF before downloading. If the URL points at a private/loopback/link-local address or otherwise fails SSRF policy, the download is aborted with this error. This is an intentional security guard protecting the host network from a provider-controlled (or attacker-influenced) URL.","triggerScenarios":"Read succeeds through pollJob, but the returned jsonl URL fails ValidateURLForSSRF — e.g. it resolves to 127.0.0.1, 10.x/172.16.x/192.168.x, 169.254.x, or uses a blocked scheme/port.","commonSituations":"Self-hosted PaddleOCR deployment behind an internal network where the genuine result URL is a private IP (SSRF guard is correct but blocks a legitimate internal host); DNS rebinding or a compromised/misconfigured provider returning internal URLs; a stub/mock server returning placeholder URLs like http://localhost/result.jsonl in tests.","solutions":["Inspect the URL in the error (%v wraps the SSRF validation reason) and confirm who produced it — a private IP from the public cloud provider signals compromise or misconfiguration.","For self-hosted deployments, add the internal result host to the SSRF allowlist (utils.ValidateURLForSSRF allowlist configuration) instead of disabling the check.","Ensure the provider returns public, HTTPS result URLs (check base-URL/endpoint configuration).","Never disable the SSRF check to make the error go away on internet-facing deployments.","In tests, point the SSRF allowlist at the local mock rather than disabling validation."],"exampleFix":"// before\nurl, err := reader.Read(ctx, file)\n// err: jsonl URL blocked by SSRF check: private address\n\n// after: allowlist the self-hosted result host before calling\nutils.AddSSRFAllowedHost(\"results.ocr.internal\")\nurl, err := reader.Read(ctx, file)\nif err != nil {\n    if strings.Contains(err.Error(), \"SSRF\") {\n        return fmt.Errorf(\"provider returned non-public result URL; refusing download: %w\", err)\n    }\n    return err\n}","handlingStrategy":"validation","validationCode":"// caller-side sanity check mirroring the library guard\nu, err := url.Parse(resultURL)\nif err != nil || (u.Scheme != \"https\" && u.Scheme != \"http\") {\n    return fmt.Errorf(\"suspicious result URL: %q\", resultURL)\n}\nif utils.ValidateURLForSSRF(resultURL) != nil {\n    return fmt.Errorf(\"result URL is not publicly reachable; refusing download\")\n}","typeGuard":"func isPublicHTTPResultURL(raw string) bool {\n    u, err := url.Parse(raw)\n    if err != nil || (u.Scheme != \"https\" && u.Scheme != \"http\") {\n        return false\n    }\n    return utils.ValidateURLForSSRF(raw) == nil\n}","tryCatchPattern":"if err := reader.Read(ctx, file); err != nil {\n    if strings.Contains(err.Error(), \"SSRF\") {\n        log.Warnf(\"provider returned blocked result URL: %v\", err)\n        return ErrUntrustedResultURL\n    }\n    return err\n}","preventionTips":["Never disable ValidateURLForSSRF on internet-facing deployments.","For self-hosted deployments, use the SSRF allowlist for internal result hosts.","Treat provider-supplied URLs as untrusted input everywhere, not just here.","Alert when this error fires — it may indicate a compromised or misconfigured provider."],"tags":["ssrf","security","http-api","ocr"],"backgroundTag":"ssrf-url-blocked","analyzedSha":"988cbb03305e055d8ebb7d46d9ac6cc0803cd074","analyzedAt":"2026-09-02T14:41:08.344Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}