fix(安全): 关闭白名单时保留最小校验与默认白名单
实现 allow_insecure_http 并在关闭校验时执行最小格式验证 - 关闭 allowlist 时要求 URL 可解析且 scheme 合规 - 响应头过滤关闭时使用默认白名单策略 - 更新相关文档、示例与测试覆盖
This commit is contained in:
@@ -73,7 +73,7 @@ func (s *AccountTestService) validateUpstreamBaseURL(raw string) (string, error)
|
||||
return "", errors.New("config is not available")
|
||||
}
|
||||
if !s.cfg.Security.URLAllowlist.Enabled {
|
||||
return strings.TrimSpace(raw), nil
|
||||
return urlvalidator.ValidateURLFormat(raw, s.cfg.Security.URLAllowlist.AllowInsecureHTTP)
|
||||
}
|
||||
normalized, err := urlvalidator.ValidateHTTPSURL(raw, urlvalidator.ValidationOptions{
|
||||
AllowedHosts: s.cfg.Security.URLAllowlist.UpstreamHosts,
|
||||
|
||||
@@ -201,6 +201,12 @@ func (s *CRSSyncService) SyncFromCRS(ctx context.Context, input SyncFromCRSInput
|
||||
return nil, err
|
||||
}
|
||||
baseURL = normalized
|
||||
} else {
|
||||
normalized, err := urlvalidator.ValidateURLFormat(baseURL, s.cfg.Security.URLAllowlist.AllowInsecureHTTP)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid base_url: %w", err)
|
||||
}
|
||||
baseURL = normalized
|
||||
}
|
||||
if strings.TrimSpace(input.Username) == "" || strings.TrimSpace(input.Password) == "" {
|
||||
return nil, errors.New("username and password are required")
|
||||
|
||||
@@ -2206,7 +2206,11 @@ func (s *GatewayService) countTokensError(c *gin.Context, status int, errType, m
|
||||
|
||||
func (s *GatewayService) validateUpstreamBaseURL(raw string) (string, error) {
|
||||
if s.cfg != nil && !s.cfg.Security.URLAllowlist.Enabled {
|
||||
return strings.TrimSpace(raw), nil
|
||||
normalized, err := urlvalidator.ValidateURLFormat(raw, s.cfg.Security.URLAllowlist.AllowInsecureHTTP)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("invalid base_url: %w", err)
|
||||
}
|
||||
return normalized, nil
|
||||
}
|
||||
normalized, err := urlvalidator.ValidateHTTPSURL(raw, urlvalidator.ValidationOptions{
|
||||
AllowedHosts: s.cfg.Security.URLAllowlist.UpstreamHosts,
|
||||
|
||||
@@ -238,7 +238,11 @@ func (s *GeminiMessagesCompatService) GetAntigravityGatewayService() *Antigravit
|
||||
|
||||
func (s *GeminiMessagesCompatService) validateUpstreamBaseURL(raw string) (string, error) {
|
||||
if s.cfg != nil && !s.cfg.Security.URLAllowlist.Enabled {
|
||||
return strings.TrimSpace(raw), nil
|
||||
normalized, err := urlvalidator.ValidateURLFormat(raw, s.cfg.Security.URLAllowlist.AllowInsecureHTTP)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("invalid base_url: %w", err)
|
||||
}
|
||||
return normalized, nil
|
||||
}
|
||||
normalized, err := urlvalidator.ValidateHTTPSURL(raw, urlvalidator.ValidationOptions{
|
||||
AllowedHosts: s.cfg.Security.URLAllowlist.UpstreamHosts,
|
||||
|
||||
@@ -1048,7 +1048,11 @@ func (s *OpenAIGatewayService) handleNonStreamingResponse(ctx context.Context, r
|
||||
|
||||
func (s *OpenAIGatewayService) validateUpstreamBaseURL(raw string) (string, error) {
|
||||
if s.cfg != nil && !s.cfg.Security.URLAllowlist.Enabled {
|
||||
return strings.TrimSpace(raw), nil
|
||||
normalized, err := urlvalidator.ValidateURLFormat(raw, s.cfg.Security.URLAllowlist.AllowInsecureHTTP)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("invalid base_url: %w", err)
|
||||
}
|
||||
return normalized, nil
|
||||
}
|
||||
normalized, err := urlvalidator.ValidateHTTPSURL(raw, urlvalidator.ValidationOptions{
|
||||
AllowedHosts: s.cfg.Security.URLAllowlist.UpstreamHosts,
|
||||
|
||||
@@ -174,7 +174,7 @@ func TestOpenAIStreamingHeadersOverride(t *testing.T) {
|
||||
Body: pr,
|
||||
Header: http.Header{
|
||||
"Cache-Control": []string{"upstream"},
|
||||
"X-Test": []string{"value"},
|
||||
"X-Request-Id": []string{"req-123"},
|
||||
"Content-Type": []string{"application/custom"},
|
||||
},
|
||||
}
|
||||
@@ -196,8 +196,8 @@ func TestOpenAIStreamingHeadersOverride(t *testing.T) {
|
||||
if rec.Header().Get("Content-Type") != "text/event-stream" {
|
||||
t.Fatalf("expected Content-Type override, got %q", rec.Header().Get("Content-Type"))
|
||||
}
|
||||
if rec.Header().Get("X-Test") != "value" {
|
||||
t.Fatalf("expected X-Test passthrough, got %q", rec.Header().Get("X-Test"))
|
||||
if rec.Header().Get("X-Request-Id") != "req-123" {
|
||||
t.Fatalf("expected X-Request-Id passthrough, got %q", rec.Header().Get("X-Request-Id"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -226,7 +226,7 @@ func TestOpenAIInvalidBaseURLWhenAllowlistDisabled(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestOpenAIValidateUpstreamBaseURLDisabledSkipsValidation(t *testing.T) {
|
||||
func TestOpenAIValidateUpstreamBaseURLDisabledRequiresHTTPS(t *testing.T) {
|
||||
cfg := &config.Config{
|
||||
Security: config.SecurityConfig{
|
||||
URLAllowlist: config.URLAllowlistConfig{Enabled: false},
|
||||
@@ -234,9 +234,32 @@ func TestOpenAIValidateUpstreamBaseURLDisabledSkipsValidation(t *testing.T) {
|
||||
}
|
||||
svc := &OpenAIGatewayService{cfg: cfg}
|
||||
|
||||
if _, err := svc.validateUpstreamBaseURL("http://not-https.example.com"); err == nil {
|
||||
t.Fatalf("expected http to be rejected when allow_insecure_http is false")
|
||||
}
|
||||
normalized, err := svc.validateUpstreamBaseURL("https://example.com")
|
||||
if err != nil {
|
||||
t.Fatalf("expected https to be allowed when allowlist disabled, got %v", err)
|
||||
}
|
||||
if normalized != "https://example.com" {
|
||||
t.Fatalf("expected raw url passthrough, got %q", normalized)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOpenAIValidateUpstreamBaseURLDisabledAllowsHTTP(t *testing.T) {
|
||||
cfg := &config.Config{
|
||||
Security: config.SecurityConfig{
|
||||
URLAllowlist: config.URLAllowlistConfig{
|
||||
Enabled: false,
|
||||
AllowInsecureHTTP: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
svc := &OpenAIGatewayService{cfg: cfg}
|
||||
|
||||
normalized, err := svc.validateUpstreamBaseURL("http://not-https.example.com")
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error when allowlist disabled, got %v", err)
|
||||
t.Fatalf("expected http allowed when allow_insecure_http is true, got %v", err)
|
||||
}
|
||||
if normalized != "http://not-https.example.com" {
|
||||
t.Fatalf("expected raw url passthrough, got %q", normalized)
|
||||
|
||||
@@ -410,7 +410,11 @@ func (s *PricingService) fetchRemoteHash() (string, error) {
|
||||
|
||||
func (s *PricingService) validatePricingURL(raw string) (string, error) {
|
||||
if s.cfg != nil && !s.cfg.Security.URLAllowlist.Enabled {
|
||||
return strings.TrimSpace(raw), nil
|
||||
normalized, err := urlvalidator.ValidateURLFormat(raw, s.cfg.Security.URLAllowlist.AllowInsecureHTTP)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("invalid pricing url: %w", err)
|
||||
}
|
||||
return normalized, nil
|
||||
}
|
||||
normalized, err := urlvalidator.ValidateHTTPSURL(raw, urlvalidator.ValidationOptions{
|
||||
AllowedHosts: s.cfg.Security.URLAllowlist.PricingHosts,
|
||||
|
||||
Reference in New Issue
Block a user