// Adapted from Sendico edge/shared/storage/s3key; see backend/PROVENANCE.md. package evidence import ( "errors" "path" "strings" "unicode" ) var ErrInvalidKey = errors.New("invalid evidence key") // JoinKey confines a server-generated object reference to a relative prefix. // It is not an ownership check or a local-filesystem path sanitizer. Inputs must // not be URL-decoded or interpreted as filesystem paths after validation. func JoinKey(prefix, reference string) (string, error) { a, err := relative(prefix) if err != nil { return "", err } b, err := relative(reference) if err != nil { return "", err } return path.Join(a, b), nil } func relative(value string) (string, error) { value = strings.TrimSpace(value) if strings.ContainsAny(value, "\\%") || strings.ContainsFunc(value, unicode.IsControl) { return "", ErrInvalidKey } for _, segment := range strings.Split(value, "/") { if segment == ".." { return "", ErrInvalidKey } } clean := path.Clean(value) if value == "" || clean == "." || path.IsAbs(clean) { return "", ErrInvalidKey } return clean, nil }