{"record":{"id":"6504f237171b9409","repo":"sveltejs/kit","slug":"the-protocol-header-header-specified-protocol-6504f2","errorCode":null,"errorMessage":"The ${protocol_header} header specified ${protocol} which is an invalid because it includes `:`. It should only contain the protocol scheme (e.g. `https`)","messagePattern":"The (.+?) header specified (.+?) which is an invalid because it includes `:`\\. It should only contain the protocol scheme \\(e\\.g\\. `https`\\)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/adapter-node/src/handler.js","lineNumber":250,"sourceCode":"\t\tthrow new Error(\n\t\t\t`Multiple values provided for ${name} header where only one expected: ${value}`\n\t\t);\n\t}\n\treturn value;\n}\n\n/**\n * @param {IncomingHttpHeaders} headers\n * @returns {string}\n */\nfunction get_origin(headers) {\n\tconst protocol = decodeURIComponent(\n\t\tnormalise_header(protocol_header, headers[protocol_header]) || 'https'\n\t);\n\n\t// this helps us avoid host injections through the protocol header\n\tif (protocol.includes(':')) {\n\t\tthrow new Error(\n\t\t\t`The ${protocol_header} header specified ${protocol} which is an invalid because it includes \\`:\\`. It should only contain the protocol scheme (e.g. \\`https\\`)`\n\t\t);\n\t}\n\n\tconst host =\n\t\tnormalise_header(host_header, headers[host_header]) ||\n\t\tnormalise_header('host', headers['host']);\n\tif (!host) {\n\t\tconst header_names = host_header ? `${host_header} or host headers` : 'host header';\n\t\tthrow new Error(\n\t\t\t`Could not determine host. The request must have a value provided by the ${header_names}`\n\t\t);\n\t}\n\n\tconst port = normalise_header(port_header, headers[port_header]);\n\tif (port && isNaN(+port)) {\n\t\tthrow new Error(\n\t\t\t`The ${port_header} header specified ${port} which is an invalid port because it is not a number. The value should only contain the port number (e.g. 443)`","sourceCodeStart":232,"sourceCodeEnd":268,"githubUrl":"https://github.com/sveltejs/kit/blob/03f1687fe612ce3d2d9131139b5b188d9cf90c64/packages/adapter-node/src/handler.js#L232-L268","documentation":"adapter-node builds the request origin from the configured PROTOCOL_HEADER value. To prevent host/protocol injection, the value must be a bare scheme; if the decoded protocol contains ':' (e.g. a full URL 'https://evil.com') the server rejects it.","triggerScenarios":"A request arrives whose PROTOCOL_HEADER (e.g. x-forwarded-proto) value includes a colon after decodeURIComponent — typically someone injecting 'https://attacker' instead of 'https'.","commonSituations":"Proxies passing a full origin URL in x-forwarded-proto, or malicious clients sending crafted headers when the header is publicly settable.","solutions":["Fix the proxy to send only the scheme, e.g. x-forwarded-proto: https","Restrict which headers are trusted via PROTOCOL_HEADER to ones your proxy overwrites on every request","Sanitize/normalize the header at the proxy layer, stripping anything after the scheme"],"exampleFix":"// before (proxy)\nproxy_set_header x-forwarded-proto $scheme://$host;\n// after\nproxy_set_header x-forwarded-proto $scheme;","handlingStrategy":"validation","validationCode":"const proto = req.headers['x-forwarded-proto'];\nif (typeof proto === 'string' && proto.includes(':')) {\n  throw new Error('x-forwarded-proto must be a bare scheme like https');\n}","typeGuard":"function isBareScheme(v) {\n  return typeof v === 'string' && /^[a-z][a-z0-9+.-]*$/.test(v);\n}","tryCatchPattern":"try {\n  origin = getOrigin(headers);\n} catch (err) {\n  if (String(err.message).includes('includes `:`')) {\n    console.error('Protocol header carries a full URL; proxy must send only the scheme');\n  } else {\n    throw err;\n  }\n}","preventionTips":["Proxy must send only the scheme (e.g. $scheme), never a URL","Only trust headers your own proxy overwrites per request","Add an integration test asserting the built origin for sample headers"],"tags":["http","security","header-injection","proxy"],"backgroundTag":"protocol-header-injection","analyzedSha":"03f1687fe612ce3d2d9131139b5b188d9cf90c64","analyzedAt":"2026-09-02T02:01:50.504Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-09T06:17:21.866Z"}