{"record":{"id":"21e8cf1e37a9d4d7","repo":"angular/angular-cli","slug":"header-x-forwarded-proto-must-be-either-http-o","errorCode":null,"errorMessage":"Header \"x-forwarded-proto\" must be either \"http\" or \"https\".","messagePattern":"Header \"x-forwarded-proto\" must be either \"http\" or \"https\"\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/angular/ssr/src/utils/validation.ts","lineNumber":220,"sourceCode":"  const forwarded = headers.get('forwarded');\n  if (forwarded) {\n    const forwardedParams = parseForwardedHeader(forwarded);\n    if (forwardedParams.host && !disableHostCheck) {\n      verifyHostAllowed('Forwarded \"host\"', forwardedParams.host, allowedHosts);\n    }\n    if (forwardedParams.proto && !VALID_PROTO_REGEX.test(forwardedParams.proto)) {\n      throw new Error('Header \"forwarded\" proto parameter must be either \"http\" or \"https\".');\n    }\n  }\n\n  const xForwardedPort = getFirstHeaderValue(headers.get('x-forwarded-port'));\n  if (xForwardedPort && !VALID_PORT_REGEX.test(xForwardedPort)) {\n    throw new Error('Header \"x-forwarded-port\" must be a numeric value.');\n  }\n\n  const xForwardedProto = getFirstHeaderValue(headers.get('x-forwarded-proto'));\n  if (xForwardedProto && !VALID_PROTO_REGEX.test(xForwardedProto)) {\n    throw new Error('Header \"x-forwarded-proto\" must be either \"http\" or \"https\".');\n  }\n\n  const xForwardedPrefix = getFirstHeaderValue(headers.get('x-forwarded-prefix'));\n  if (xForwardedPrefix && !VALID_PREFIX_REGEX.test(xForwardedPrefix)) {\n    throw new Error(\n      'Header \"x-forwarded-prefix\" is invalid. It must start with a \"/\" and contain ' +\n        'only alphanumeric characters, hyphens, and underscores, separated by single slashes.',\n    );\n  }\n}\n\n/**\n * Checks if a specific proxy header is allowed.\n *\n * @param headerName - The name of the proxy header to check.\n * @param trustProxyHeaders - A set of allowed proxy headers.\n * @returns `true` if the header is allowed, `false` otherwise.\n */","sourceCodeStart":202,"sourceCodeEnd":238,"githubUrl":"https://github.com/angular/angular-cli/blob/bb72145f9ab45aee29f523236b3a25cd0813a841/packages/angular/ssr/src/utils/validation.ts#L202-L238","documentation":"Angular SSR validates the trusted `x-forwarded-proto` header against `/^https?$/i` (case-insensitive, first value only). Values like `https, http`, `wss`, or empty strings fail. This ensures the scheme used to rebuild absolute URLs in SSR is a safe http/https value.","triggerScenarios":"A request with a trusted `x-forwarded-proto` header whose first value is not `http` or `https` — e.g. `HTTP/2, https`, `wss`, or a comma-joined list where the first entry is invalid.","commonSituations":"CDNs/load balancers appending protocol lists; WebSocket terminators emitting `wss`; double proxying where the header accumulates values; stale proxies emitting `HTTP/1.1`.","solutions":["Configure the outermost proxy to overwrite x-forwarded-proto with exactly `http` or `https`.","Ensure only one proxy layer sets the header instead of appending.","Remove `x-forwarded-proto` from `trustProxyHeaders` if unused.","Fix load balancer rules (e.g. AWS ALB listeners) that inject non-standard values."],"exampleFix":"// before\nproxy_set_header X-Forwarded-Proto \"$scheme, https\";\n// after\nproxy_set_header X-Forwarded-Proto \"$scheme\";","handlingStrategy":"validation","validationCode":"const proto = request.headers.get('x-forwarded-proto')?.split(',')[0].trim();\nif (proto && !/^(http|https)$/i.test(proto)) throw new Error(`Invalid x-forwarded-proto: ${proto}`);","typeGuard":null,"tryCatchPattern":"try {\n  validateHeaders(headers, allowedHosts, disableHostCheck);\n} catch (e) {\n  if ((e as Error).message.includes('x-forwarded-proto')) {\n    return res.status(400).end('Invalid proto header');\n  }\n  throw e;\n}","preventionTips":["Set X-Forwarded-Proto with a single literal value at the outermost proxy.","Avoid proxies that append comma-joined protocol lists.","Verify header values with curl after each proxy/config change."],"tags":["angular","ssr","security","proxy","validation"],"backgroundTag":"invalid-proxy-header-value","analyzedSha":"bb72145f9ab45aee29f523236b3a25cd0813a841","analyzedAt":"2026-08-30T02:47:34.745Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}