{"record":{"id":"9e02b04f012b8c2a","repo":"tursodatabase/turso","slug":"overwriting-the-host-header-is-not-supported-9e02b0","errorCode":null,"errorMessage":"overwriting the 'Host' header is not supported","messagePattern":"overwriting the 'Host' header is not supported","errorType":"exception","errorClass":"DatabaseError","httpStatus":null,"severity":"error","filePath":"serverless/javascript/src/session.ts","lineNumber":141,"sourceCode":" * A database session that manages the connection state and baton.\n * \n * Each session maintains its own connection state and can execute SQL statements\n * independently without interfering with other sessions.\n */\nexport class Session {\n  private config: SessionConfig;\n  private baton: string | null = null;\n  private baseUrl: string;\n  // Cached autocommit status from the server's last `get_autocommit` answer.\n  // A fresh connection is in autocommit (not in a transaction).\n  private autocommit: boolean = true;\n\n  constructor(config: SessionConfig) {\n    for (const name of Object.keys(config.requestHeaders ?? {})) {\n      // `Host` is a forbidden fetch header and would be silently dropped —\n      // reject it up front so the caller learns the override never takes effect.\n      if (name.toLowerCase() === 'host') {\n        throw new DatabaseError(\"overwriting the 'Host' header is not supported\");\n      }\n    }\n    this.config = config;\n    this.baseUrl = normalizeUrl(config.url);\n  }\n\n  private httpContext(queryOptions?: QueryOptions): HttpContext {\n    // Per-query headers are merged over the session-level ones, so a query\n    // can override a header the session sets (and both override the\n    // standard headers).\n    let requestHeaders = this.config.requestHeaders;\n    if (queryOptions?.requestHeaders) {\n      requestHeaders = { ...requestHeaders, ...queryOptions.requestHeaders };\n    }\n    return {\n      url: this.baseUrl,\n      authToken: this.config.authToken,\n      remoteEncryptionKey: this.config.remoteEncryptionKey,","sourceCodeStart":123,"sourceCodeEnd":159,"githubUrl":"https://github.com/tursodatabase/turso/blob/c1e59287258d99b309e362a63f48822256e2f65f/serverless/javascript/src/session.ts#L123-L159","documentation":"The Session constructor scans config.requestHeaders and throws when any key equals 'host' case-insensitively. Host is a forbidden fetch header — fetch would silently drop the override — so the library rejects it up front to make the failure loud instead of silent. The same check runs per-request in buildHeaders (protocol.ts:242) for headers that only arrive via QueryOptions.requestHeaders.","triggerScenarios":"Constructing a Session (or the client that builds one) with requestHeaders: { Host: '...' } or { host: '...' }; passing per-query requestHeaders containing Host via queryOptions, which skips the constructor check and throws at request time with the same message.","commonSituations":"Porting code that set Host for virtual-host routing with another HTTP client; copy-pasted header objects from curl -H 'Host: ...' recipes; multi-tenant routing attempts through a shared gateway.","solutions":["Remove the Host entry from requestHeaders — route by choosing the correct url instead","If you need host-based routing, put the target host in the URL and let fetch set Host itself","If the header object is dynamic, filter it before constructing the Session"],"exampleFix":"// before\nconst session = new Session({\n  url: 'https://db.example.com',\n  authToken,\n  requestHeaders: { Host: 'db.internal.example.com' }, // throws\n});\n\n// after\nconst session = new Session({\n  url: 'https://db.internal.example.com',\n  authToken,\n  requestHeaders: { 'x-custom-trace-id': traceId },\n});","handlingStrategy":"validation","validationCode":"// strip forbidden headers before constructing the Session\nfunction sanitizeHeaders(headers: Record<string, string> = {}): Record<string, string> {\n  const out: Record<string, string> = {};\n  for (const [k, v] of Object.entries(headers)) {\n    if (k.toLowerCase() === 'host') continue; // fetch forbids setting Host\n    out[k] = v;\n  }\n  return out;\n}","typeGuard":null,"tryCatchPattern":"try {\n  new Session(config);\n} catch (e) {\n  if (e instanceof DatabaseError && e.message.includes(\"'Host' header\")) {\n    delete config.requestHeaders?.Host;\n    // retry construction\n  }\n}","preventionTips":["Route by URL, never by Host header — fetch silently drops forbidden headers so the library rejects them loudly","Filter dynamic header objects through a deny-list before passing them to Session config or queryOptions","Remember per-query requestHeaders containing Host throw at request time (protocol.ts buildHeaders), not at construction"],"tags":["headers","fetch","validation","typescript"],"backgroundTag":"forbidden-http-header","analyzedSha":"c1e59287258d99b309e362a63f48822256e2f65f","analyzedAt":"2026-08-20T07:02:18.389Z","contentChangedAt":"2026-08-20T07:02:18.389Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}