{"record":{"id":"ae2716d106a1adfe","repo":"toeverything/AFFiNE","slug":"bad-request-ae2716","errorCode":"bad_request","errorMessage":"Invalid origin: ${origin}, referer: ${referer}","messagePattern":"Invalid origin: (.+?), referer: (.+?)","errorType":"exception","errorClass":"BadRequest","httpStatus":400,"severity":"warning","filePath":"packages/backend/server/src/core/telemetry/gateway.ts","lineNumber":37,"sourceCode":"type EventResponse<Data = any> = [Data] extends [never]\n  ? { data?: never }\n  : { data: Data };\n\n@WebSocketGateway()\n@UseInterceptors(ClsInterceptor)\nexport class TelemetryGateway {\n  constructor(private readonly telemetry: TelemetryService) {}\n\n  @SubscribeMessage('telemetry:batch')\n  async onBatch(\n    @CurrentUser() user: CurrentUser,\n    @ConnectedSocket() client: Socket,\n    @MessageBody() batch: TelemetryBatch\n  ): Promise<EventResponse<TelemetryAck>> {\n    const origin = client.handshake.headers.origin;\n    const referer = client.handshake.headers.referer;\n    if (!this.telemetry.isOriginAllowed(origin, referer)) {\n      throw new BadRequest(`Invalid origin: ${origin}, referer: ${referer}`);\n    }\n\n    const ack = await this.telemetry.collectBatch({\n      ...batch,\n      transport: 'ws',\n      events: batch?.events?.map(event => ({\n        ...event,\n        userId: event.userId ?? user?.id,\n      })),\n    });\n\n    return { data: ack };\n  }\n}\n","sourceCodeStart":19,"sourceCodeEnd":52,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/26c515e050211269e911f7d9cfe162a26c83ed98/packages/backend/server/src/core/telemetry/gateway.ts#L19-L52","documentation":"Thrown by TelemetryGateway.onBatch() (the 'telemetry:batch' WebSocket handler) when isOriginAllowed() returns false for client.handshake.headers.origin/referer. This is the WebSocket equivalent of the HTTP origin check, validating that the socket connection's origin is allowlisted before collecting telemetry events over the socket.","triggerScenarios":"Client opens the sync socket from an origin not in telemetry.allowedOrigins and emits the 'telemetry:batch' event; the Referer origin is also not allowlisted.","commonSituations":"Electron/desktop client connecting from a custom origin not in the list; web client on a new domain; proxy that rewrites the handshake Origin header; missing allowlist entry for the canonical deployment.","solutions":["Add the client's handshake origin to telemetry.allowedOrigins.","Verify the socket handshake preserves the real Origin header through any load balancer/gateway.","For desktop clients, register their effective origin in the allowlist."],"exampleFix":"// config — include the desktop/web origin that appears in the handshake\ntelemetry:\n  allowedOrigins:\n    - https://app.example.com\n    - affine://desktop","handlingStrategy":"validation","validationCode":"// Validate handshake origin before emitting telemetry over WS\nconst origin = socket.handshake.headers.origin;\nif (origin && !ALLOWED_ORIGINS.includes(origin)) return;","typeGuard":"function isHandshakeOriginAllowed(handshakeOrigin: string | undefined, allowlist: string[]): boolean {\n  return !handshakeOrigin || allowlist.includes(handshakeOrigin);\n}","tryCatchPattern":"try {\n  await socket.emitWithAck('telemetry:batch', batch);\n} catch (e) {\n  if (e?.code === 'bad_request') { stopWsTelemetry(); return; }\n  throw e;\n}","preventionTips":["Register desktop and web origins that appear in the socket handshake.","Preserve the Origin header through the socket load balancer.","Skip WS telemetry when the origin is not allowlisted."],"tags":["telemetry","cors","websocket","config"],"backgroundTag":null,"analyzedSha":"26c515e050211269e911f7d9cfe162a26c83ed98","analyzedAt":"2026-08-12T13:15:16.447Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}