toeverything/AFFiNE · error · BlobInvalid

blob_invalid

blob_invalid

Error message

Invalid endpoint

What it means

Thrown by R2UploadController.getUploadProxyConfig when the configured blob storage provider is neither 'cloudflare-r2' nor 'aws-s3'. The presigned-URL proxy upload path only supports S3-compatible backends, so any other provider (filesystem, minio-via-other-name, etc.) is rejected before signing a token.

Source

Thrown at packages/backend/server/src/core/storage/r2-proxy.ts:42

type UploadProxyConfig = {
  signKey: string;
};

@Controller(STORAGE_PROXY_ROOT)
export class R2UploadController {
  private readonly logger = new Logger(R2UploadController.name);

  constructor(
    private readonly config: Config,
    private readonly models: Models,
    private readonly rt: StorageRuntimeProvider
  ) {}

  private getUploadProxyConfig(): UploadProxyConfig {
    const storage = this.config.storages.blob.storage as StorageProviderConfig;
    if (storage.provider !== 'cloudflare-r2' && storage.provider !== 'aws-s3') {
      throw new BlobInvalid('Invalid endpoint');
    }
    const uploadConfig = (storage.config as S3StorageConfig).usePresignedURL;
    const signKey = uploadConfig?.signKey;
    if (!uploadConfig?.enabled || !signKey) {
      throw new BlobInvalid('Invalid endpoint');
    }
    return { signKey };
  }

  private safeEqual(expected: string, actual: string) {
    const a = Buffer.from(expected);
    const b = Buffer.from(actual);

    if (a.length !== b.length) {
      return false;
    }

    return timingSafeEqual(a, b);

View on GitHub (pinned to 26c515e050)

Solutions

  1. Set the blob storage provider to 'cloudflare-r2' or 'aws-s3' if you want to use the proxy upload path.
  2. If using a different backend, disable the presigned-URL proxy flow and use the standard blob upload resolver instead.
  3. Double-check the loaded Config.storages.blob value at startup.

Example fix

// before
AFFiNE_STORAGE_BLOB_PROVIDER=fs

// after
AFFiNE_STORAGE_BLOB_PROVIDER=cloudflare-r2
AFFiNE_STORAGE_R2_CONFIG='{"usePresignedURL":{"enabled":true,"signKey":"..."}}'
Defensive patterns

Strategy: validation

Validate before calling

const provider = config.storages.blob.storage.provider;
if (provider !== 'cloudflare-r2' && provider !== 'aws-s3') {
  // disable the proxy upload route in the client; use standard blob upload
  setProxyUploadEnabled(false);
}

Type guard

type S3LikeProvider = 'cloudflare-r2' | 'aws-s3';
function isS3Like(p: string): p is S3LikeProvider {
  return p === 'cloudflare-r2' || p === 'aws-s3';
}

Try / catch

try {
  await proxyUpload(blob);
} catch (e) {
  if (e?.code === 'blob_invalid' && /endpoint/.test(e.message)) {
    // fall back to the GraphQL blob upload resolver
    return graphqlUpload(blob);
  }
  throw e;
}

Prevention

When it happens

Trigger: Client PUTs to the blob upload proxy while config.storages.blob.storage.provider is set to something other than cloudflare-r2/aws-s3 (e.g. 'fs', 'azure-blob'); misnamed provider string in the AFFiNE_STORAGE_R2 config.

Common situations: Self-host with filesystem storage but the client was routed to the proxy endpoint; migrating storage backends and forgetting to update client routing; typo in the provider env var.

Related errors


AI-assisted analysis of toeverything/AFFiNE@26c515e050 (2026-08-12). Data as JSON: /api/errors/44b1773cb97ca1af. Report an issue: GitHub.