toeverything/AFFiNE · warning · BadRequest

bad_request

bad_request

Error message

Invalid origin: ${origin}, referer: ${referer}

What it means

The CORS preflight for POST /api/telemetry/collect validates the Origin header (falling back to the Referer's origin) against the server's allow-list, built from telemetry config origins plus the deployment's url.allowedOrigins. If either header is present but not allow-listed, the OPTIONS request fails with bad_request; requests carrying neither header are allowed through.

Solutions

  1. Add the serving origin to the telemetry allowed origins (and/or the deployment url.allowedOrigins) in server config
  2. Serve the app from the exact domain clients use so Origin matches the allow-list
  3. Fix the reverse proxy to pass the original Origin and Referer headers through unmodified
Defensive patterns

Strategy: validation

Validate before calling

// in the browser: only send telemetry when our origin is allow-listed
const { origins } = await fetch('/api/telemetry/allowed-origins').then(r => r.json());
const allowed = origins.includes(window.location.origin);

Type guard

function isOriginAllowed(origin: string, allowedOrigins: string[]): boolean {
  return allowedOrigins.includes(origin);
}

Prevention

When it happens

Trigger: OPTIONS /api/telemetry/collect with an Origin not present in allowedOrigins; a Referer whose URL origin is not allow-listed when Origin is absent; health checks or curl scripts that manually send a foreign Origin header.

Common situations: Self-hosting AFFiNE under an extra domain missing from config; embedding the web app on a third-party site; reverse proxies rewriting the Origin header; staging domains not added to the allow-list.

Understand the failure class

Background: BAD_REQUEST error code: request rejected as invalid (HTTP 400) - causes and fixes across libraries — this error's family across 8 libraries.

Related errors


AI-assisted analysis of toeverything/AFFiNE@b4c8548c09 (2026-08-18). Data as JSON: /api/errors/c43051bb0b956960. Report an issue: GitHub.

Appendix: source

Thrown at packages/backend/server/src/core/telemetry/controller.ts:25

  type CurrentUser as CurrentUserType,
  Public,
} from '../auth';
import { TelemetryService } from './service';
import { TelemetryAck, type TelemetryBatch } from './types';

@Public()
@UseNamedGuard('version')
@Throttle('default')
@Controller('/api/telemetry')
export class TelemetryController {
  constructor(private readonly telemetry: TelemetryService) {}

  @Options('/collect')
  collectOptions(@Req() req: Request, @Res() res: Response) {
    const origin = req.headers.origin;
    const referer = req.headers.referer;
    if (!this.telemetry.isOriginAllowed(origin, referer)) {
      throw new BadRequest(`Invalid origin: ${origin}, referer: ${referer}`);
    }

    return res
      .status(200)
      .header({
        ...this.telemetry.getCorsHeaders(origin),
        'Access-Control-Allow-Methods': 'POST, OPTIONS',
        'Access-Control-Allow-Headers': 'Content-Type, x-affine-version',
      })
      .send();
  }

  @Post('/collect')
  async collect(
    @Req() req: Request,
    @Res({ passthrough: true }) res: Response,
    @Body() batch: TelemetryBatch,
    @CurrentUser() user?: CurrentUserType

View on GitHub (pinned to b4c8548c09)