{"record":{"id":"01c78e3acc25fc26","repo":"Budibase/budibase","slug":"unexpected-header-format","errorCode":null,"errorMessage":"Unexpected header format","messagePattern":"Unexpected header format","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/backend-core/src/middleware/authenticated.ts","lineNumber":103,"sourceCode":"    if (userId) {\n      return {\n        valid: true,\n        user: await getUser({\n          userId,\n          tenantId,\n          populateUser,\n        }),\n      }\n    } else {\n      throw new InvalidAPIKeyWarning()\n    }\n  })\n}\n\nfunction getHeader(ctx: Ctx, header: Header): string | undefined {\n  const contents = ctx.request.headers[header]\n  if (Array.isArray(contents)) {\n    throw new Error(\"Unexpected header format\")\n  }\n  return contents\n}\n\n/**\n * This middleware is tenancy aware, so that it does not depend on other middlewares being used.\n * The tenancy modules should not be used here and it should be assumed that the tenancy context\n * has not yet been populated.\n */\nexport function authenticated(\n  noAuthPatterns: EndpointMatcher[] = [],\n  opts: { publicAllowed?: boolean; populateUser?: Function } = {\n    publicAllowed: false,\n  }\n) {\n  const noAuthOptions = noAuthPatterns ? buildMatcherRegex(noAuthPatterns) : []\n  return (async (ctx: Ctx, next: Next) => {\n    let publicEndpoint = false","sourceCodeStart":85,"sourceCodeEnd":121,"githubUrl":"https://github.com/Budibase/budibase/blob/a81a902e9a8fe55b467d106765f6638f12e35c49/packages/backend-core/src/middleware/authenticated.ts#L85-L121","documentation":"getHeader reads a single request header from the Koa context and throws 'Unexpected header format' if Node returned an array of values instead of a string, which happens when a header is sent multiple times in one request.","triggerScenarios":"A client (or proxy) sends the same header twice, e.g. duplicate Authorization or api-key headers, causing ctx.request.headers[header] to be string[].","commonSituations":"Reverse proxies appending rather than overwriting headers; HTTP/2 clients and middleware that join headers; fetch wrappers adding Authorization twice (manual + interceptor); curl -H passed twice.","solutions":["Fix the client to send the header exactly once (remove duplicate interceptor/default header)","Configure the proxy to overwrite instead of append duplicate headers","Deduplicate the raw header before middleware runs","Use a different, singular header name to avoid collisions with existing defaults"],"exampleFix":"// before\nfetch(url, { headers: { Authorization: `Bearer ${t}` } }) // interceptor also adds Authorization\n// after\nclient.defaults.headers.common.Authorization = undefined\nfetch(url, { headers: { Authorization: `Bearer ${t}` } })","handlingStrategy":"type-guard","validationCode":"const h = req.headers[\"authorization\"]\nif (Array.isArray(h)) throw new Error(`Duplicate authorization header: ${h.length} values`)","typeGuard":"function singleHeader(v: string | string[] | undefined): string | undefined {\n  return Array.isArray(v) ? v[0] : v\n}","tryCatchPattern":"try {\n  return getHeader(ctx, Header.API_KEY)\n} catch (e) {\n  if (e.message === \"Unexpected header format\") {\n    console.warn(\"Client sent duplicate header; taking first value\")\n    return (ctx.request.headers[header] as string[])[0]\n  }\n  throw e\n}","preventionTips":["Audit clients/proxies for duplicate header injection","Avoid adding the same header in both a default and a per-request layer","Deduplicate headers at the reverse proxy (nginx: proxy_set_header once)"],"tags":["http","headers","middleware"],"backgroundTag":"duplicate-http-header","analyzedSha":"a81a902e9a8fe55b467d106765f6638f12e35c49","analyzedAt":"2026-08-29T01:03:10.972Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}