Tencent/WeKnora · error

unsafe S3 endpoint: %w

Error message

unsafe S3 endpoint: %w

What it means

newS3Client runs utils.ValidateURLForSSRF on the configured S3 endpoint before creating the SDK client. This error means the endpoint URL failed SSRF validation — typically a private/loopback/link-local IP, a disallowed scheme, or a non-URL value. WeKnora refuses to build an S3 client against endpoints that could be used for server-side request forgery.

Source

Thrown at internal/application/service/file/s3.go:35

	"github.com/aws/aws-sdk-go-v2/aws"
	"github.com/aws/aws-sdk-go-v2/config"
	"github.com/aws/aws-sdk-go-v2/credentials"
	"github.com/aws/aws-sdk-go-v2/service/s3"
	"github.com/aws/aws-sdk-go-v2/service/s3/types"
	"github.com/google/uuid"
)

// s3FileService AWS S3 file service implementation
type s3FileService struct {
	client     *s3.Client
	bucketName string
	pathPrefix string
}

// newS3Client creates a bare s3FileService with just the SDK client initialised.
func newS3Client(endpoint, accessKey, secretKey, bucketName, region, pathPrefix string, forcePathStyle bool) (*s3FileService, error) {
	if err := utils.ValidateURLForSSRF(endpoint); err != nil {
		return nil, fmt.Errorf("unsafe S3 endpoint: %w", err)
	}
	var cfg aws.Config
	var err error

	// With no explicit AK/SK, keep the AWS default credential chain intact. This
	// supports IAM roles for EC2/ECS/EKS (IRSA), web identity, shared config, and
	// environment credentials without persisting long-lived keys in WeKnora.
	loadOptions := []func(*config.LoadOptions) error{config.WithRegion(region)}
	if accessKey != "" || secretKey != "" {
		if accessKey == "" || secretKey == "" {
			return nil, fmt.Errorf("S3 access key and secret key must be provided together")
		}
		loadOptions = append(loadOptions, config.WithCredentialsProvider(
			credentials.NewStaticCredentialsProvider(accessKey, secretKey, ""),
		))
	}
	cfg, err = config.LoadDefaultConfig(context.Background(), loadOptions...)

View on GitHub (pinned to 988cbb0330)

Solutions

  1. Use a publicly resolvable https endpoint, or the internal address is blocked — deploy with an allowed DNS name/IP
  2. If internal S3-compatible storage is intentional, adjust utils.ValidateURLForSSRF allowlist configuration
  3. Check the endpoint value for typos and include the http(s):// scheme
  4. Ensure the endpoint is not accidentally set to metadata or proxy addresses

Example fix

// before
svc, err := NewS3FileService("http://127.0.0.1:9000", ak, sk, bucket, region, prefix) // SSRF check fails
// after
svc, err := NewS3FileService("https://s3.example.com", ak, sk, bucket, region, prefix)
Defensive patterns

Strategy: validation

Validate before calling

u, err := url.Parse(endpoint)
if err != nil || (u.Scheme != "http" && u.Scheme != "https") { return errors.New("endpoint must be an absolute http(s) URL") }
if err := utils.ValidateURLForSSRF(endpoint); err != nil { return err }

Type guard

func isPublicEndpoint(endpoint string) bool {
    return utils.ValidateURLForSSRF(endpoint) == nil
}

Prevention

When it happens

Trigger: Configuring file storage with an endpoint like http://127.0.0.1:9000, http://169.254.169.254, localhost, an internal 10.x/192.168.x address, or a scheme other than http/https in newS3Client (used by NewS3FileService and connectivity checks).

Common situations: Local MinIO/MinIO-style development setups using localhost endpoints, container-internal hostnames resolving to private IPs, typos in the endpoint URL, environments that intentionally allow internal S3-compatible stores but have not widened the SSRF allowlist.

Related errors


AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02). Data as JSON: /api/errors/9a4ea9f2149ef97f. Report an issue: GitHub.