{"record":{"id":"44b1773cb97ca1af","repo":"toeverything/AFFiNE","slug":"blob-invalid","errorCode":"blob_invalid","errorMessage":"Invalid endpoint","messagePattern":"Invalid endpoint","errorType":"exception","errorClass":"BlobInvalid","httpStatus":400,"severity":"error","filePath":"packages/backend/server/src/core/storage/r2-proxy.ts","lineNumber":42,"sourceCode":"\ntype UploadProxyConfig = {\n  signKey: string;\n};\n\n@Controller(STORAGE_PROXY_ROOT)\nexport class R2UploadController {\n  private readonly logger = new Logger(R2UploadController.name);\n\n  constructor(\n    private readonly config: Config,\n    private readonly models: Models,\n    private readonly rt: StorageRuntimeProvider\n  ) {}\n\n  private getUploadProxyConfig(): UploadProxyConfig {\n    const storage = this.config.storages.blob.storage as StorageProviderConfig;\n    if (storage.provider !== 'cloudflare-r2' && storage.provider !== 'aws-s3') {\n      throw new BlobInvalid('Invalid endpoint');\n    }\n    const uploadConfig = (storage.config as S3StorageConfig).usePresignedURL;\n    const signKey = uploadConfig?.signKey;\n    if (!uploadConfig?.enabled || !signKey) {\n      throw new BlobInvalid('Invalid endpoint');\n    }\n    return { signKey };\n  }\n\n  private safeEqual(expected: string, actual: string) {\n    const a = Buffer.from(expected);\n    const b = Buffer.from(actual);\n\n    if (a.length !== b.length) {\n      return false;\n    }\n\n    return timingSafeEqual(a, b);","sourceCodeStart":24,"sourceCodeEnd":60,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/26c515e050211269e911f7d9cfe162a26c83ed98/packages/backend/server/src/core/storage/r2-proxy.ts#L24-L60","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Set the blob storage provider to 'cloudflare-r2' or 'aws-s3' if you want to use the proxy upload path.","If using a different backend, disable the presigned-URL proxy flow and use the standard blob upload resolver instead.","Double-check the loaded Config.storages.blob value at startup."],"exampleFix":"// before\nAFFiNE_STORAGE_BLOB_PROVIDER=fs\n\n// after\nAFFiNE_STORAGE_BLOB_PROVIDER=cloudflare-r2\nAFFiNE_STORAGE_R2_CONFIG='{\"usePresignedURL\":{\"enabled\":true,\"signKey\":\"...\"}}'","handlingStrategy":"validation","validationCode":"const provider = config.storages.blob.storage.provider;\nif (provider !== 'cloudflare-r2' && provider !== 'aws-s3') {\n  // disable the proxy upload route in the client; use standard blob upload\n  setProxyUploadEnabled(false);\n}","typeGuard":"type S3LikeProvider = 'cloudflare-r2' | 'aws-s3';\nfunction isS3Like(p: string): p is S3LikeProvider {\n  return p === 'cloudflare-r2' || p === 'aws-s3';\n}","tryCatchPattern":"try {\n  await proxyUpload(blob);\n} catch (e) {\n  if (e?.code === 'blob_invalid' && /endpoint/.test(e.message)) {\n    // fall back to the GraphQL blob upload resolver\n    return graphqlUpload(blob);\n  }\n  throw e;\n}","preventionTips":["Confirm the storage provider env var matches an S3-compatible backend before enabling the proxy.","Run a startup self-check that calls getUploadProxyConfig and logs the active provider.","Keep client routing in sync with the server's configured storage backend."],"tags":["storage","r2","s3","configuration","nestjs"],"backgroundTag":null,"analyzedSha":"26c515e050211269e911f7d9cfe162a26c83ed98","analyzedAt":"2026-08-12T13:15:16.447Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}