{"record":{"id":"c43051bb0b956960","repo":"toeverything/AFFiNE","slug":"bad-request-c43051","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/controller.ts","lineNumber":25,"sourceCode":"  type CurrentUser as CurrentUserType,\n  Public,\n} from '../auth';\nimport { TelemetryService } from './service';\nimport { TelemetryAck, type TelemetryBatch } from './types';\n\n@Public()\n@UseNamedGuard('version')\n@Throttle('default')\n@Controller('/api/telemetry')\nexport class TelemetryController {\n  constructor(private readonly telemetry: TelemetryService) {}\n\n  @Options('/collect')\n  collectOptions(@Req() req: Request, @Res() res: Response) {\n    const origin = req.headers.origin;\n    const referer = req.headers.referer;\n    if (!this.telemetry.isOriginAllowed(origin, referer)) {\n      throw new BadRequest(`Invalid origin: ${origin}, referer: ${referer}`);\n    }\n\n    return res\n      .status(200)\n      .header({\n        ...this.telemetry.getCorsHeaders(origin),\n        'Access-Control-Allow-Methods': 'POST, OPTIONS',\n        'Access-Control-Allow-Headers': 'Content-Type, x-affine-version',\n      })\n      .send();\n  }\n\n  @Post('/collect')\n  async collect(\n    @Req() req: Request,\n    @Res({ passthrough: true }) res: Response,\n    @Body() batch: TelemetryBatch,\n    @CurrentUser() user?: CurrentUserType","sourceCodeStart":7,"sourceCodeEnd":43,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/26c515e050211269e911f7d9cfe162a26c83ed98/packages/backend/server/src/core/telemetry/controller.ts#L7-L43","documentation":"Thrown by TelemetryController.collectOptions() (the CORS preflight handler for OPTIONS /api/telemetry/collect) when TelemetryService.isOriginAllowed() returns false for the request's Origin/Referer headers. The service allows the request only if the origin (or the referer's origin) is in the configured allowlist, or if both headers are absent. This guards the preflight response from being issued for disallowed cross-origin callers.","triggerScenarios":"Browser sends an OPTIONS preflight to /api/telemetry/collect with an Origin header whose value is not in telemetry.allowedOrigins, and the Referer origin also is not allowlisted.","commonSituations":"Deploying AFFiNE under a new domain without updating the telemetry allowed origins config; front-end loaded from a CDN/staging domain not in the allowlist; reverse proxy stripping or rewriting the Origin header.","solutions":["Add the front-end origin to the server's telemetry origin allowlist in config.","If behind a proxy, ensure the Origin header is forwarded unchanged to the backend.","Verify the deployment URL matches an entry in the configured allowed origins exactly (scheme + host + port)."],"exampleFix":"// config — add the deployed front-end origin\ntelemetry:\n  allowedOrigins:\n    - https://app.example.com\n    - https://staging.example.com","handlingStrategy":"validation","validationCode":"// Client-side: only send telemetry when origin is allowlisted\nif (!ALLOWED_ORIGINS.includes(window.location.origin)) return;\nfetch('/api/telemetry/collect', { method: 'OPTIONS', headers: { Origin: window.location.origin } });","typeGuard":"function isOriginAllowed(origin: string, allowlist: string[]): boolean {\n  return allowlist.includes(origin);\n}","tryCatchPattern":"try {\n  await fetch('/api/telemetry/collect', { method: 'OPTIONS' });\n} catch (e) {\n  if (e?.code === 'bad_request') { disableTelemetry(); return; }\n  throw e;\n}","preventionTips":["Keep the server's telemetry.allowedOrigins in sync with every deployed front-end domain.","Forward the Origin header unchanged through proxies.","Disable client telemetry collection when the origin is known not to be allowlisted."],"tags":["telemetry","cors","config","http"],"backgroundTag":null,"analyzedSha":"26c515e050211269e911f7d9cfe162a26c83ed98","analyzedAt":"2026-08-12T13:15:16.447Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}