mirror of
https://github.com/Tencent/WeKnora.git
synced 2026-08-28 17:43:11 +08:00
feat(url): 支持docreader不上传替换白名单url的图片
This commit is contained in:
@@ -493,6 +493,11 @@ DOCREADER_TRANSPORT=grpc
|
||||
# Weaviate gRPC 地址(Docker 内:weaviate:50051;宿主机访问:localhost:50052)
|
||||
# WEAVIATE_GRPC_ADDRESS=weaviate:50051
|
||||
|
||||
# 保留原始 URL 的图片域名白名单(可选,逗号分隔)
|
||||
# 配置后,这些域名的图片仍会被下载和分析(OCR/字幕),但 markdown 中保留原始 URL,
|
||||
# 不会被替换为对象存储的 provider:// URL。适用于内部稳定服务(如自建 MinerU)。
|
||||
# IMAGE_HOST_KEEP_URL=mineru.internal.example.com
|
||||
|
||||
# Weaviate 架构模式
|
||||
# WEAVIATE_SCHEME=http
|
||||
|
||||
|
||||
@@ -43,6 +43,8 @@ NEO4J_ENABLE=false
|
||||
WEKNORA_SANDBOX_MODE=disabled
|
||||
ENABLE_GRAPH_RAG=false
|
||||
DISABLE_REGISTRATION=false
|
||||
# 保留原始 URL 的图片域名白名单(可选)
|
||||
# IMAGE_HOST_KEEP_URL=mineru.internal.example.com
|
||||
|
||||
# === Langfuse 可观测性(可选) ===
|
||||
# 追踪 chat / embedding / rerank / VLM / ASR 的 prompt、响应与 token 消耗。
|
||||
|
||||
@@ -149,6 +149,8 @@ services:
|
||||
- TENANT_AES_KEY=${TENANT_AES_KEY:-}
|
||||
- SYSTEM_AES_KEY=${SYSTEM_AES_KEY:-}
|
||||
- SSRF_WHITELIST=${SSRF_WHITELIST:-}
|
||||
# 保留原始 URL 的图片域名白名单(逗号分隔,不替换为 provider://)
|
||||
- IMAGE_HOST_KEEP_URL=${IMAGE_HOST_KEEP_URL:-}
|
||||
# Always allow the optional searxng sidecar (compose service hostname);
|
||||
# merged on top of SSRF_WHITELIST so user overrides don't clobber it.
|
||||
- SSRF_WHITELIST_EXTRA=${SSRF_WHITELIST_EXTRA:-searxng}
|
||||
|
||||
@@ -13,6 +13,8 @@ import (
|
||||
"log"
|
||||
"mime"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
@@ -195,6 +197,35 @@ func isProviderScheme(p string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// isWhitelistedImageHost checks if the image URL's host is in the whitelist.
|
||||
// Whitelisted hosts are trusted (e.g. internal MinerU service) — images are
|
||||
// still downloaded for validation and OCR/caption analysis, but not uploaded
|
||||
// to object storage. The markdown keeps the original URL.
|
||||
// Configure via IMAGE_HOST_KEEP_URL env var (comma-separated hosts).
|
||||
func isWhitelistedImageHost(rawURL string) bool {
|
||||
whitelist := strings.TrimSpace(os.Getenv("IMAGE_HOST_KEEP_URL"))
|
||||
if whitelist == "" {
|
||||
return false
|
||||
}
|
||||
u, err := url.Parse(rawURL)
|
||||
if err != nil || u.Host == "" {
|
||||
return false
|
||||
}
|
||||
host := strings.ToLower(u.Host)
|
||||
hostname := strings.ToLower(u.Hostname())
|
||||
for _, h := range strings.Split(whitelist, ",") {
|
||||
h = strings.ToLower(strings.TrimSpace(h))
|
||||
if h == "" {
|
||||
continue
|
||||
}
|
||||
// Exact host match (includes port) or hostname match (any port)
|
||||
if host == h || hostname == h {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helper functions for base64 image handling
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -670,10 +701,18 @@ func (r *ImageResolver) ResolveRemoteImages(
|
||||
continue
|
||||
}
|
||||
|
||||
// --- SSRF check (centralised entry-point with whitelist support) ---
|
||||
if err := secutils.ValidateURLForSSRF(imgURL); err != nil {
|
||||
log.Printf("WARN: remote image blocked by SSRF check (%v): %s", err, imgURL)
|
||||
continue
|
||||
// For whitelisted hosts: download to validate (mime type, icon check),
|
||||
// create StoredImage for downstream OCR/caption analysis, but do NOT
|
||||
// upload to storage and keep the original URL in markdown.
|
||||
// The multimodal service will download from the original URL later.
|
||||
whitelisted := isWhitelistedImageHost(imgURL)
|
||||
|
||||
// --- SSRF check (skip for whitelisted) ---
|
||||
if !whitelisted {
|
||||
if err := secutils.ValidateURLForSSRF(imgURL); err != nil {
|
||||
log.Printf("WARN: remote image blocked by SSRF check (%v): %s", err, imgURL)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// --- Download ---
|
||||
@@ -697,12 +736,20 @@ func (r *ImageResolver) ResolveRemoteImages(
|
||||
ext = ".png" // safe default
|
||||
}
|
||||
|
||||
// --- Upload to storage ---
|
||||
fileName := uuid.New().String() + ext
|
||||
servingURL, saveErr := fileSvc.SaveBytes(ctx, data, tenantID, fileName, false)
|
||||
if saveErr != nil {
|
||||
log.Printf("WARN: failed to save remote image %s: %v", imgURL, saveErr)
|
||||
continue
|
||||
var servingURL string
|
||||
if whitelisted {
|
||||
// Keep the original URL — ImageMultimodalService will download it
|
||||
// directly for OCR/caption analysis.
|
||||
servingURL = imgURL
|
||||
} else {
|
||||
// --- Upload to storage ---
|
||||
fileName := uuid.New().String() + ext
|
||||
var saveErr error
|
||||
servingURL, saveErr = fileSvc.SaveBytes(ctx, data, tenantID, fileName, false)
|
||||
if saveErr != nil {
|
||||
log.Printf("WARN: failed to save remote image %s: %v", imgURL, saveErr)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
images = append(images, StoredImage{
|
||||
@@ -711,8 +758,10 @@ func (r *ImageResolver) ResolveRemoteImages(
|
||||
MimeType: mimeType,
|
||||
})
|
||||
|
||||
// Replace URL in markdown.
|
||||
markdown = markdown[:m[4]] + servingURL + markdown[m[5]:]
|
||||
if !whitelisted {
|
||||
// Replace URL in markdown.
|
||||
markdown = markdown[:m[4]] + servingURL + markdown[m[5]:]
|
||||
}
|
||||
processed++
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user