mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-08-01 21:37:33 +08:00
Go: fix SSRF (#17641)
### Summary Check the URL to prevent SSRF attack. --------- Signed-off-by: Jin Hai <haijin.chn@gmail.com>
This commit is contained in:
@@ -203,6 +203,7 @@ func TestResolveMarkdownImage_NoImage(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestResolveMarkdownImage_HTTPImage(t *testing.T) {
|
||||
withSSRFBypass(t)
|
||||
// httptest servers bind loopback, which the SSRF guard rejects by
|
||||
// default. Allow loopback for this test so the HTTP fetch path is
|
||||
// exercised (production keeps ssrfAllowLoopback == false).
|
||||
@@ -236,6 +237,7 @@ func TestFetchImageAsBase64_RejectsCredentials(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFetchImageAsBase64_InvalidURL(t *testing.T) {
|
||||
withSSRFBypass(t)
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
}))
|
||||
|
||||
@@ -88,7 +88,7 @@ func parsePDFWithDocling(ctx context.Context, filename string, data []byte, pars
|
||||
var lastErr error
|
||||
for _, candidate := range payloads {
|
||||
url := baseURL + candidate.endpoint
|
||||
resp, err := models.PostJSONRequest(context.Background(), models.NewDriverHTTPClient(), url, auth, candidate.body())
|
||||
resp, err := models.PostJSONRequest(context.Background(), models.NewDriverHTTPClient(false), url, auth, candidate.body())
|
||||
if err != nil {
|
||||
lastErr = fmt.Errorf("%s: %w", candidate.endpoint, err)
|
||||
continue
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
)
|
||||
|
||||
func TestPDFParser_ParseWithResult_DoclingChunkedMarkdownIntegration(t *testing.T) {
|
||||
withSSRFBypass(t)
|
||||
var requestCount atomic.Int32
|
||||
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -84,6 +85,7 @@ func TestPDFParser_ParseWithResult_DoclingChunkedMarkdownIntegration(t *testing.
|
||||
}
|
||||
|
||||
func TestPDFParser_ParseWithResult_DoclingFallbackToStandardJSONIntegration(t *testing.T) {
|
||||
withSSRFBypass(t)
|
||||
var requestCount atomic.Int32
|
||||
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
)
|
||||
|
||||
func TestPDFParser_ParseWithResult_MinerUMarkdownIntegration(t *testing.T) {
|
||||
withSSRFBypass(t)
|
||||
var submitCalled atomic.Bool
|
||||
var resultCalled atomic.Bool
|
||||
|
||||
@@ -100,6 +101,7 @@ func TestPDFParser_ParseWithResult_MinerUMarkdownIntegration(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestPDFParser_ParseWithResult_MinerUJSONIntegration(t *testing.T) {
|
||||
withSSRFBypass(t)
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch {
|
||||
case r.Method == http.MethodPost && r.URL.Path == "/file_parse":
|
||||
|
||||
@@ -41,7 +41,7 @@ func parsePDFWithOpenDataLoader(ctx context.Context, filename string, data []byt
|
||||
if apiKey != "" {
|
||||
req.Header.Set("Authorization", "Bearer "+apiKey)
|
||||
}
|
||||
resp, err := models.NewDriverHTTPClient().Do(req)
|
||||
resp, err := models.NewDriverHTTPClient(false).Do(req)
|
||||
if err != nil {
|
||||
return ParseResult{Err: fmt.Errorf("parser: OpenDataLoader submit: %w", err)}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
)
|
||||
|
||||
func TestPDFParser_ParseWithResult_OpenDataLoaderJSONIntegration(t *testing.T) {
|
||||
withSSRFBypass(t)
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost || r.URL.Path != "/file_parse" {
|
||||
http.NotFound(w, r)
|
||||
@@ -84,6 +85,7 @@ func TestPDFParser_ParseWithResult_OpenDataLoaderJSONIntegration(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestPDFParser_ParseWithResult_OpenDataLoaderMarkdownFallback(t *testing.T) {
|
||||
withSSRFBypass(t)
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != "/file_parse" {
|
||||
http.NotFound(w, r)
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
)
|
||||
|
||||
func TestPDFParser_ParseWithResult_PaddleOCRMarkdownIntegration(t *testing.T) {
|
||||
withSSRFBypass(t)
|
||||
var called atomic.Bool
|
||||
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -82,6 +83,7 @@ func TestPDFParser_ParseWithResult_PaddleOCRMarkdownIntegration(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestPDFParser_ParseWithResult_PaddleOCRJSONIntegration(t *testing.T) {
|
||||
withSSRFBypass(t)
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost || r.URL.Path != "/layout-parsing" {
|
||||
http.NotFound(w, r)
|
||||
|
||||
@@ -84,7 +84,7 @@ func soMarkSubmit(baseURL, filename string, data []byte, parser *PDFParser, apiK
|
||||
return "", fmt.Errorf("parser: SoMark request: %w", err)
|
||||
}
|
||||
req.Header.Set("Content-Type", writer.FormDataContentType())
|
||||
resp, err := models.NewDriverHTTPClient().Do(req)
|
||||
resp, err := models.NewDriverHTTPClient(false).Do(req)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("parser: SoMark submit: %w", err)
|
||||
}
|
||||
@@ -122,7 +122,7 @@ func soMarkPoll(baseURL, taskID, apiKey string) (map[string]any, error) {
|
||||
return nil, fmt.Errorf("parser: SoMark poll request: %w", err)
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
resp, err := models.NewDriverHTTPClient().Do(req)
|
||||
resp, err := models.NewDriverHTTPClient(false).Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parser: SoMark poll: %w", err)
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
)
|
||||
|
||||
func TestPDFParser_ParseWithResult_SoMarkJSONIntegration(t *testing.T) {
|
||||
withSSRFBypass(t)
|
||||
var submitSeen bool
|
||||
var pollSeen bool
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -78,6 +79,7 @@ func TestPDFParser_ParseWithResult_SoMarkJSONIntegration(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestPDFParser_ParseWithResult_SoMarkMarkdownIntegration(t *testing.T) {
|
||||
withSSRFBypass(t)
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
case "/parse/async":
|
||||
@@ -123,6 +125,7 @@ func TestSoMarkBlockToItem_DropsHeaderByDefault(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSoMarkSubmitMultipartShape(t *testing.T) {
|
||||
withSSRFBypass(t)
|
||||
var form multipart.Form
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
_ = r.ParseMultipartForm(1 << 20)
|
||||
|
||||
@@ -40,7 +40,7 @@ func parsePDFWithTCADP(filename string, data []byte, parser *PDFParser) ParseRes
|
||||
"MarkdownImageResponseType": parser.TCADPMarkdownImageResponseType,
|
||||
},
|
||||
}
|
||||
resp, err := models.PostJSONRequest(context.Background(), models.NewDriverHTTPClient(), strings.TrimRight(baseURL, "/")+"/reconstruct_document", bearer(apiKey), requestBody)
|
||||
resp, err := models.PostJSONRequest(context.Background(), models.NewDriverHTTPClient(false), strings.TrimRight(baseURL, "/")+"/reconstruct_document", bearer(apiKey), requestBody)
|
||||
if err != nil {
|
||||
return ParseResult{Err: fmt.Errorf("parser: TCADP submit: %w", err)}
|
||||
}
|
||||
@@ -68,7 +68,7 @@ func parsePDFWithTCADP(filename string, data []byte, parser *PDFParser) ParseRes
|
||||
if auth := bearer(apiKey); auth != "" {
|
||||
downloadReq.Header.Set("Authorization", auth)
|
||||
}
|
||||
downloadResp, err := models.NewDriverHTTPClient().Do(downloadReq)
|
||||
downloadResp, err := models.NewDriverHTTPClient(false).Do(downloadReq)
|
||||
if err != nil {
|
||||
return ParseResult{Err: fmt.Errorf("parser: TCADP download: %w", err)}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
)
|
||||
|
||||
func TestPDFParser_ParseWithResult_TCADPJSONIntegration(t *testing.T) {
|
||||
withSSRFBypass(t)
|
||||
zipPayload := tcadpZipFixture(t)
|
||||
var server *httptest.Server
|
||||
server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -47,6 +48,7 @@ func TestPDFParser_ParseWithResult_TCADPJSONIntegration(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestPDFParser_ParseWithResult_TCADPMarkdownIntegration(t *testing.T) {
|
||||
withSSRFBypass(t)
|
||||
zipPayload := tcadpZipFixture(t)
|
||||
var server *httptest.Server
|
||||
server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
@@ -135,6 +135,7 @@ type tcadpPresentationParser interface {
|
||||
}
|
||||
|
||||
func testPresentationTCADPIntegration(t *testing.T, p tcadpPresentationParser, wantFileType, filename string) {
|
||||
withSSRFBypass(t)
|
||||
t.Helper()
|
||||
zipPayload := tcadpZipFixture(t)
|
||||
var gotFileType string
|
||||
@@ -189,6 +190,7 @@ func testPresentationTCADPIntegration(t *testing.T, p tcadpPresentationParser, w
|
||||
// from the TCADP download endpoint is surfaced as an explicit error rather
|
||||
// than parsed as a (malformed) ZIP artifact.
|
||||
func TestPPTXParser_TCADPDownloadHTTPError(t *testing.T) {
|
||||
withSSRFBypass(t)
|
||||
var server *httptest.Server
|
||||
server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
|
||||
@@ -45,7 +45,7 @@ func parsePresentationWithTCADP(ctx context.Context, filename string, data []byt
|
||||
"MarkdownImageResponseType": markdownImageResponseType,
|
||||
},
|
||||
}
|
||||
resp, err := models.PostJSONRequest(ctx, models.NewDriverHTTPClient(),
|
||||
resp, err := models.PostJSONRequest(ctx, models.NewDriverHTTPClient(false),
|
||||
strings.TrimRight(baseURL, "/")+"/reconstruct_document", bearer(apiKey), requestBody)
|
||||
if err != nil {
|
||||
return ParseResult{Err: fmt.Errorf("parser: TCADP submit: %w", err)}
|
||||
@@ -75,7 +75,7 @@ func parsePresentationWithTCADP(ctx context.Context, filename string, data []byt
|
||||
if auth := bearer(apiKey); auth != "" {
|
||||
downloadReq.Header.Set("Authorization", auth)
|
||||
}
|
||||
downloadResp, err := models.NewDriverHTTPClient().Do(downloadReq)
|
||||
downloadResp, err := models.NewDriverHTTPClient(false).Do(downloadReq)
|
||||
if err != nil {
|
||||
return ParseResult{Err: fmt.Errorf("parser: TCADP download: %w", err)}
|
||||
}
|
||||
|
||||
19
internal/parser/parser/ssrf_test_helper_test.go
Normal file
19
internal/parser/parser/ssrf_test_helper_test.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"ragflow/internal/utility"
|
||||
)
|
||||
|
||||
// withSSRFBypass enables the test-only SSRF bypass for the duration of a test
|
||||
// and restores the previous value in t.Cleanup. Parser tests exercise parsing
|
||||
// logic against httptest servers bound to 127.0.0.1, which the strict SSRF
|
||||
// guard rejects. SSRF enforcement has its own dedicated coverage in
|
||||
// internal/utility/ssrf_test.go.
|
||||
func withSSRFBypass(t *testing.T) {
|
||||
t.Helper()
|
||||
prev := utility.AllowAnyHostForTest
|
||||
utility.AllowAnyHostForTest = true
|
||||
t.Cleanup(func() { utility.AllowAnyHostForTest = prev })
|
||||
}
|
||||
@@ -38,7 +38,7 @@ func parseSpreadsheetWithTCADP(filename string, data []byte, fileType string, tc
|
||||
"MarkdownImageResponseType": markdownImageResponseType,
|
||||
},
|
||||
}
|
||||
resp, err := models.PostJSONRequest(context.Background(), models.NewDriverHTTPClient(), strings.TrimRight(baseURL, "/")+"/reconstruct_document", bearer(apiKey), requestBody)
|
||||
resp, err := models.PostJSONRequest(context.Background(), models.NewDriverHTTPClient(false), strings.TrimRight(baseURL, "/")+"/reconstruct_document", bearer(apiKey), requestBody)
|
||||
if err != nil {
|
||||
return ParseResult{Err: fmt.Errorf("parser: TCADP submit: %w", err)}
|
||||
}
|
||||
@@ -66,7 +66,7 @@ func parseSpreadsheetWithTCADP(filename string, data []byte, fileType string, tc
|
||||
if auth := bearer(apiKey); auth != "" {
|
||||
downloadReq.Header.Set("Authorization", auth)
|
||||
}
|
||||
downloadResp, err := models.NewDriverHTTPClient().Do(downloadReq)
|
||||
downloadResp, err := models.NewDriverHTTPClient(false).Do(downloadReq)
|
||||
if err != nil {
|
||||
return ParseResult{Err: fmt.Errorf("parser: TCADP download: %w", err)}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
)
|
||||
|
||||
func TestXLSXParser_ParseWithResult_TCADPJSONIntegration(t *testing.T) {
|
||||
withSSRFBypass(t)
|
||||
ctx := t.Context()
|
||||
zipPayload := tcadpZipFixture(t)
|
||||
var server *httptest.Server
|
||||
@@ -45,6 +46,7 @@ func TestXLSXParser_ParseWithResult_TCADPJSONIntegration(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestXLSParser_ParseWithResult_TCADPJSONIntegration(t *testing.T) {
|
||||
withSSRFBypass(t)
|
||||
ctx := t.Context()
|
||||
zipPayload := tcadpZipFixture(t)
|
||||
var server *httptest.Server
|
||||
@@ -80,6 +82,7 @@ func TestXLSParser_ParseWithResult_TCADPJSONIntegration(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCSVParser_ParseWithResult_TCADPJSONIntegration(t *testing.T) {
|
||||
withSSRFBypass(t)
|
||||
ctx := t.Context()
|
||||
zipPayload := tcadpZipFixture(t)
|
||||
var server *httptest.Server
|
||||
@@ -112,6 +115,7 @@ func TestCSVParser_ParseWithResult_TCADPJSONIntegration(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestXLSXParser_ParseWithResult_TCADPMarkdownIntegration(t *testing.T) {
|
||||
withSSRFBypass(t)
|
||||
ctx := t.Context()
|
||||
zipPayload := tcadpZipFixture(t)
|
||||
var server *httptest.Server
|
||||
|
||||
Reference in New Issue
Block a user