Tencent/WeKnora · error · ErrCrossBackendCopy

obs copy rejected source %q: %w

Error message

obs copy rejected source %q: %w

What it means

CopyFile explicitly rejects source paths that do not start with this service's own prefix (proxy domain or obs:// scheme). Because parseObsFilePath silently returns unknown-format paths unchanged, this guard detects cross-backend copies (e.g. copying an OSS or local-disk path into OBS) and aborts with ErrCrossBackendCopy.

Source

Thrown at internal/application/service/file/obs.go:253

	if s.proxyDomain != "" {
		return s.proxyDomain + "/" + strings.TrimPrefix(objectKey, "/"), nil
	}

	return fmt.Sprintf("%s/%s/%s", s.endpoint, s.bucketName, strings.TrimPrefix(objectKey, "/")), nil
}

// CopyFile copies an existing OBS object to a new knowledge-owned object using a
// server-side CopyObject (OBS is S3-compatible). The destination uses the same
// layout as SaveFile. Returns ErrCrossBackendCopy when srcPath does not belong
// to this OBS service.
func (s *obsFileService) CopyFile(ctx context.Context,
	srcPath string, tenantID uint64, knowledgeID string,
) (string, error) {
	// Reject paths that do not use this service's prefix (proxy domain or obs://).
	// parseObsFilePath falls back to returning the raw input for unknown prefixes,
	// so guard explicitly here to detect cross-backend sources.
	if !strings.HasPrefix(srcPath, s.getPrifix()) {
		return "", fmt.Errorf("obs copy rejected source %q: %w", srcPath, ErrCrossBackendCopy)
	}
	srcKey, err := s.parseObsFilePath(srcPath)
	if err != nil {
		return "", fmt.Errorf("obs copy rejected source %q: %w", srcPath, ErrCrossBackendCopy)
	}

	ext := filepath.Ext(srcPath)
	var destKey string
	if s.pathPrefix != "" {
		destKey = fmt.Sprintf("%s/%d/%s/%s%s", s.pathPrefix, tenantID, knowledgeID, uuid.New().String(), ext)
	} else {
		destKey = fmt.Sprintf("%d/%s/%s%s", tenantID, knowledgeID, uuid.New().String(), ext)
	}

	// CopySource is "bucket/key"; the '/' separators must NOT be percent-encoded
	// (url.PathEscape would turn them into %2F and break the bucket/key split).
	_, err = s.client.CopyObject(ctx, &s3.CopyObjectInput{
		Bucket:     aws.String(s.bucketName),

View on GitHub (pinned to 988cbb0330)

Solutions

  1. Ensure srcPath was produced by this same OBS service (starts with proxyDomain prefix or obs:// prefix)
  2. For genuine cross-backend copies, download via the source service and upload via SaveFile/SaveBytes instead of CopyFile
  3. Check tenant configuration so each request uses the file service that owns the stored path
  4. Validate the path prefix before calling and surface a clear 'unsupported source backend' message to the user

Example fix

// before
newPath, err := obsSvc.CopyFile(ctx, ossPath, tenantID, kid) // rejected
// after
if isOSSPath(ossPath) {
    r, _ := ossSvc.GetFile(ctx, ossPath)
    defer r.Close()
    newPath, err = obsSvc.SaveBytes(ctx, tenantID, kid, readAll(r), ext)
} else {
    newPath, err = obsSvc.CopyFile(ctx, ossPath, tenantID, kid)
}
Defensive patterns

Strategy: validation

Validate before calling

func copySourceOK(srcPath, prefix string) bool {
    return strings.HasPrefix(srcPath, prefix)
}
// call before CopyFile:
if !copySourceOK(srcPath, obsPrefix) {
    return ErrCrossBackendCopy
}

Type guard

func isOBSBackendPath(p string) bool {
    return strings.HasPrefix(p, "obs://") || strings.HasPrefix(p, proxyDomain)
}

Try / catch

newPath, err := svc.CopyFile(ctx, src, tenantID, kid)
if errors.Is(err, file.ErrCrossBackendCopy) {
    http.Error(w, "source file belongs to a different storage backend", http.StatusBadRequest)
    return
}

Prevention

When it happens

Trigger: Calling CopyFile with srcPath that does not begin with s.getPrifix() — e.g. a path produced by the OSS service, a local file path like /data/uploads/x.png, or a URL pointing at another bucket's proxy domain.

Common situations: Migrating between storage backends and passing old OSS paths to the OBS service; multi-tenant configs where different tenants use different storage services; copy-pasting a file URL from a different environment.

Related errors


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