{"record":{"id":"f9071458374211c0","repo":"Tencent/WeKnora","slug":"unsafe-minio-endpoint-w","errorCode":null,"errorMessage":"unsafe MinIO endpoint: %w","messagePattern":"unsafe MinIO endpoint: %w","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/application/service/file/minio.go","lineNumber":32,"sourceCode":"\t\"github.com/Tencent/WeKnora/internal/types/interfaces\"\n\t\"github.com/Tencent/WeKnora/internal/utils\"\n\t\"github.com/google/uuid\"\n\t\"github.com/minio/minio-go/v7\"\n\t\"github.com/minio/minio-go/v7/pkg/credentials\"\n)\n\n// minioFileService MinIO file service implementation\ntype minioFileService struct {\n\tclient     *minio.Client\n\tbucketName string\n}\n\n// newMinioClient creates a bare minioFileService with just the SDK client initialised.\n// Shared by NewMinioFileService (which also ensures the bucket exists) and\n// CheckMinioConnectivity (read-only probe).\nfunc newMinioClient(endpoint, accessKeyID, secretAccessKey, bucketName string, useSSL bool) (*minioFileService, error) {\n\tif err := utils.ValidateURLForSSRF(endpoint); err != nil {\n\t\treturn nil, fmt.Errorf(\"unsafe MinIO endpoint: %w\", err)\n\t}\n\thttpConfig := utils.DefaultSSRFSafeHTTPClientConfig()\n\tclient, err := minio.New(endpoint, &minio.Options{\n\t\tCreds:  credentials.NewStaticV4(accessKeyID, secretAccessKey, \"\"),\n\t\tSecure: useSSL,\n\t\tTransport: &utils.SSRFValidatingRoundTripper{\n\t\t\tBase: utils.NewSSRFSafeTransport(httpConfig),\n\t\t},\n\t})\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to initialize MinIO client: %w\", err)\n\t}\n\treturn &minioFileService{client: client, bucketName: bucketName}, nil\n}\n\n// NewMinioFileService creates a MinIO file service.\n// It verifies that the bucket exists and creates it if missing.\nfunc NewMinioFileService(endpoint,","sourceCodeStart":14,"sourceCodeEnd":50,"githubUrl":"https://github.com/Tencent/WeKnora/blob/988cbb03305e055d8ebb7d46d9ac6cc0803cd074/internal/application/service/file/minio.go#L14-L50","documentation":"newMinioClient validates the configured MinIO endpoint with utils.ValidateURLForSSRF before constructing the minio-go client, and wraps any validation failure as \"unsafe MinIO endpoint: %w\". This is a deliberate SSRF guard: endpoints pointing at loopback, link-local, private/metadata addresses, or malformed URLs are rejected before any network connection is made.","triggerScenarios":"Calling NewMinioFileService or CheckMinioConnectivity with an endpoint that fails ValidateURLForSSRF: e.g. http://127.0.0.1:9000, http://169.254.169.254, localhost, 10.x/172.16-31.x/192.168.x addresses, missing scheme, or an otherwise invalid URL.","commonSituations":"Developers pointing MINIO_ENDPOINT at localhost for local development while the SSRF validator rejects private/loopback hosts; misconfigured env var left empty or containing a scheme-less value; Kubernetes setups where MinIO is reached via a cluster-internal .svc address that the validator considers private.","solutions":["Set the endpoint to a public, resolvable, non-private host (or an HTTPS domain fronting MinIO) that passes ValidateURLForSSRF","Log/inspect the wrapped cause (errors.Unwrap) to see exactly which SSRF rule failed and correct the URL format accordingly","If MinIO genuinely must live on an internal network, front it with an approved internal gateway/domain that the SSRF allowlist accepts, or extend utils.ValidateURLForSSRF allowlisting deliberately (with security review)","Ensure the env var carries scheme+host consistently (e.g. minio.example.com vs https://minio.example.com) matching what the validator expects"],"exampleFix":"// before\nMINIO_ENDPOINT=http://127.0.0.1:9000  // rejected: loopback\n// after\nMINIO_ENDPOINT=https://minio.example.com  // public endpoint, passes SSRF validation","handlingStrategy":"validation","validationCode":"u, err := url.Parse(endpoint)\nif err != nil || u.Host == \"\" {\n    return fmt.Errorf(\"endpoint must be scheme+host, got %q\", endpoint)\n}\nhost := u.Hostname()\nip := net.ParseIP(host)\nif host == \"localhost\" || (ip != nil && (ip.IsLoopback() || ip.IsPrivate() || ip.IsLinkLocalUnicast() || ip.IsUnspecified())) {\n    return fmt.Errorf(\"endpoint %q points to a private/loopback address and will be rejected by SSRF validation\", endpoint)\n}","typeGuard":null,"tryCatchPattern":"svc, err := file.NewMinioFileService(endpoint, ak, sk, bucket, useSSL)\nif err != nil && strings.Contains(err.Error(), \"unsafe MinIO endpoint\") {\n    // fail fast at startup with a clear config message; do not retry\n    return fmt.Errorf(\"invalid MINIO_ENDPOINT %q: %w\", endpoint, err)\n} else if err != nil {\n    return err\n}","preventionTips":["Validate MINIO_ENDPOINT at config-load/startup time, not at first use","Never point the endpoint at localhost, 127.0.0.1, 169.254.169.254, or RFC1918 addresses in deployments with SSRF validation","Use a public DNS name or an explicitly allowlisted internal domain for MinIO","Review utils.ValidateURLForSSRF rules when planning network topology for MinIO"],"tags":["security","ssrf","configuration","minio"],"backgroundTag":"ssrf-endpoint-rejected","analyzedSha":"988cbb03305e055d8ebb7d46d9ac6cc0803cd074","analyzedAt":"2026-09-02T14:41:08.344Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}