Combine RAGFLOW_MCP_ALLOW_PRIVATE_HOSTS into ALLOW_ANY_HOST (#21523)

This commit is contained in:
Wang Qi authored and GitHub committed 2026-10-27 13:06:05 +08:00
1 parent 1a6a382a2f
commit 0b9ce6ddda
10 files changed
+37 -19

No files matched your search

@@ -77,10 +77,10 @@ RAGFlow keeps **Save** disabled until the current connection settings have been
RAGFlow validates the endpoint before connecting:
- The URL must use `http://` or `https://` and contain a host. `stdio://` is not supported.
- Every address returned by DNS must be publicly routable. Loopback, private, link-local, reserved, Docker-network, and other non-public addresses are rejected.
- By default, every address returned by DNS must be publicly routable. Loopback, private, link-local, reserved, Docker-network, and other non-public addresses are rejected.
- The Go MCP client connects to the validated address directly and does not use HTTP proxy environment variables.
Consequently, endpoints such as `localhost`, `127.0.0.1`, `::1`, `10.x.x.x`, `172.16.x.x` through `172.31.x.x`, and `192.168.x.x` cannot be added through this path. `ALLOW_ANY_HOST` does not disable this validation. Publish the MCP endpoint on a public address whose DNS records all resolve to public addresses.
To connect to self-hosted MCP endpoints on private or Docker networks, set `ALLOW_ANY_HOST=true` in the RAGFlow backend environment and restart the backend. This shared setting also permits private database and connector hosts. Accepted true values are `1`, `true`, `yes`, and `on` (case-insensitive, with surrounding whitespace ignored). HTTP/HTTPS scheme validation, DNS resolution, and connection pinning remain enabled.
## Troubleshoot a failed connection
+4 -4
View File
@@ -29,10 +29,10 @@ import (
// truthy value (1, true, yes, on), matching the Python host guard.
//
// The flag is the development opt-in documented in docker/.env. It applies
// only to host-based database and connector dials (MySQL, PostgreSQL, IMAP,
// test_db_connection, ExeSQL), including host.docker.internal. URL and Invoke
// guards do not call this, so the flag cannot turn off DNS pinning for
// user-controlled URLs. Default is off.
// to host-based database and connector dials (MySQL, PostgreSQL, IMAP,
// test_db_connection, ExeSQL), including host.docker.internal, and configured
// MCP servers. Other URL and Invoke guards do not call this. DNS pinning
// remains enabled. Default is off.
func AllowConfiguredPrivateHost() bool {
switch strings.ToLower(strings.TrimSpace(GetEnv(EnvAllowAnyHost))) {
case "1", "true", "yes", "on":
-1
View File
@@ -84,7 +84,6 @@ const (
EnvRAGFlowTTSCacheTTLSeconds = "RAGFLOW_TTS_CACHE_TTL_SECONDS"
EnvRerankTokenLimitMode = "RERANK_TOKEN_LIMIT_MODE"
EnvComponentExecTimeout = "COMPONENT_EXEC_TIMEOUT"
EnvMCPAllowPrivateHosts = "RAGFLOW_MCP_ALLOW_PRIVATE_HOSTS"
EnvDocEngine = "DOC_ENGINE"
EnvMaxFileNumPerUser = "MAX_FILE_NUM_PER_USER"
EnvMaxContentLength = "MAX_CONTENT_LENGTH"
+3 -3
View File
@@ -467,9 +467,9 @@ var LookupHost = net.LookupHost
// a deployment mistake. The explicit "_ForTest" suffix is the
// signal that production code must never touch it.
//
// Host-based database and connector dials are a separate opt-in.
// AllowConfiguredPrivateHost reads ALLOW_ANY_HOST for those dials
// only. This function stays limited to the in-memory test hook.
// Host-based database and connector dials and configured MCP servers are a
// separate opt-in. AllowConfiguredPrivateHost reads ALLOW_ANY_HOST for those
// dials only. This function stays limited to the in-memory test hook.
var AllowAnyHostForTest = false
// allowAnyHost reads the test-only override. Kept as a private
+1 -1
View File
@@ -41,7 +41,7 @@ type CallOptions struct {
HTTPClient *http.Client
// AllowPrivateHosts relaxes the public-routability requirement so
// self-hosted MCP servers on private/docker networks work. Callers source
// it from the deployment flag (RAGFLOW_MCP_ALLOW_PRIVATE_HOSTS); scheme
// it from the deployment flag (ALLOW_ANY_HOST); scheme
// validation, DNS resolution, and IP pinning still apply.
AllowPrivateHosts bool
}
+1 -1
View File
@@ -79,7 +79,7 @@ type FetchOptions struct {
HTTPClient *http.Client
// AllowPrivateHosts relaxes the public-routability requirement so
// self-hosted MCP servers on private/docker networks work. Callers source
// it from the deployment flag (RAGFLOW_MCP_ALLOW_PRIVATE_HOSTS); scheme
// it from the deployment flag (ALLOW_ANY_HOST); scheme
// validation, DNS resolution, and IP pinning still apply.
AllowPrivateHosts bool
pinHostname string
+1 -1
View File
@@ -51,7 +51,7 @@ func sameOrigin(a, b *url.URL) bool {
// AssertMCPURLSafe validates an MCP server URL. It delegates to
// common.AssertURLSafe by default. When allowPrivateHosts is true (a
// deployment-level operator decision, sourced from
// RAGFLOW_MCP_ALLOW_PRIVATE_HOSTS at the call site), the
// ALLOW_ANY_HOST at the call site), the
// public-routability requirement is relaxed so self-hosted MCP servers on
// private/docker networks work — scheme validation, DNS resolution, and
// connection pinning to the resolved IP still apply.
+1 -1
View File
@@ -170,7 +170,7 @@ func GetConfig() *config.Config {
}
// MCPAllowPrivateHosts reports whether self-hosted MCP servers on private
// networks are permitted (RAGFLOW_MCP_ALLOW_PRIVATE_HOSTS). It is nil-safe:
// networks are permitted (ALLOW_ANY_HOST). It is nil-safe:
// an uninitialized global config yields false so callers fail closed instead
// of panicking on a nil *config.Config.
func MCPAllowPrivateHosts() bool {
+2 -5
View File
@@ -111,7 +111,7 @@ type Environments struct {
TavilyAPIKey string `mapstructure:"tavily_api_key"`
QueritAPIKey string `mapstructure:"querit_api_key"`
KeenableAPIURL string `mapstructure:"keenable_api_url"` // KEENABLE_API_URL
MCPAllowPrivateHosts bool `mapstructure:"mcp_allow_private_hosts"` // RAGFLOW_MCP_ALLOW_PRIVATE_HOSTS
MCPAllowPrivateHosts bool `mapstructure:"mcp_allow_private_hosts"` // ALLOW_ANY_HOST
}
type DefaultSuperUser struct {
@@ -298,10 +298,7 @@ func (c *Config) GetEnvironments() error {
c.environments.HomePath = homePath
}
mcpAllowPrivateHosts := common.GetEnvSmall(common.EnvMCPAllowPrivateHosts)
if mcpAllowPrivateHosts != "" {
c.environments.MCPAllowPrivateHosts = mcpAllowPrivateHosts == "true" || mcpAllowPrivateHosts == "1" || mcpAllowPrivateHosts == "yes"
}
c.environments.MCPAllowPrivateHosts = common.AllowConfiguredPrivateHost()
userProfile := common.GetEnv(common.EnvUserProfile)
if userProfile != "" {
+22
View File
@@ -2,6 +2,7 @@ package config
import (
"os"
"ragflow/internal/common"
"reflect"
"strings"
"testing"
@@ -20,6 +21,27 @@ func clearMCPEnv(t *testing.T) {
}
}
func TestMCPAllowPrivateHostsEnvironment(t *testing.T) {
for _, tc := range []struct {
value string
want bool
}{
{"", false}, {"0", false}, {"false", false}, {"no", false}, {"off", false}, {"invalid", false},
{"1", true}, {"true", true}, {"yes", true}, {"on", true}, {" TRUE ", true},
} {
t.Run(tc.value, func(t *testing.T) {
t.Setenv(common.EnvAllowAnyHost, tc.value)
var cfg Config
if err := cfg.GetEnvironments(); err != nil {
t.Fatal(err)
}
if got := cfg.GetMCPAllowPrivateHosts(); got != tc.want {
t.Fatalf("ALLOW_ANY_HOST=%q: MCPAllowPrivateHosts = %v, want %v", tc.value, got, tc.want)
}
})
}
}
func parseMCPYAML(t *testing.T, text string) (MCPConfig, error) {
t.Helper()
v := viper.New()