@angular/ssr
Advanced tools
@@ -102,3 +102,3 @@ const HOST_HEADERS_TO_VALIDATE = new Set(['host', 'x-forwarded-host']); | ||
| function isHostAllowed(hostname, allowedHosts) { | ||
| if (allowedHosts.has(hostname)) { | ||
| if (allowedHosts.has('*') || allowedHosts.has(hostname)) { | ||
| return true; | ||
@@ -105,0 +105,0 @@ } |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"_validation-chunk.mjs","sources":["../../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/angular/ssr/src/utils/validation.ts"],"sourcesContent":["/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n\n/**\n * The set of headers that should be validated for host header injection attacks.\n */\nconst HOST_HEADERS_TO_VALIDATE: ReadonlySet<string> = new Set(['host', 'x-forwarded-host']);\n\n/**\n * Regular expression to validate that the port is a numeric value.\n */\nconst VALID_PORT_REGEX = /^\\d+$/;\n\n/**\n * Regular expression to validate that the protocol is either http or https (case-insensitive).\n */\nconst VALID_PROTO_REGEX = /^https?$/i;\n\n/**\n * Regular expression to validate that the host is a valid hostname.\n */\nconst VALID_HOST_REGEX = /^[a-z0-9.:-]+$/i;\n\n/**\n * Regular expression to validate that the prefix is valid.\n */\nconst INVALID_PREFIX_REGEX = /^(?:\\\\|\\/[/\\\\])|(?:^|[/\\\\])\\.\\.?(?:[/\\\\]|$)/;\n\n/**\n * Extracts the first value from a multi-value header string.\n *\n * @param value - A string or an array of strings representing the header values.\n * If it's a string, values are expected to be comma-separated.\n * @returns The first trimmed value from the multi-value header, or `undefined` if the input is invalid or empty.\n *\n * @example\n * ```typescript\n * getFirstHeaderValue(\"value1, value2, value3\"); // \"value1\"\n * getFirstHeaderValue([\"value1\", \"value2\"]); // \"value1\"\n * getFirstHeaderValue(undefined); // undefined\n * ```\n */\nexport function getFirstHeaderValue(\n value: string | string[] | undefined | null,\n): string | undefined {\n return value?.toString().split(',', 1)[0]?.trim();\n}\n\n/**\n * Validates a request.\n *\n * @param request - The incoming `Request` object to validate.\n * @param allowedHosts - A set of allowed hostnames.\n * @param disableHostCheck - Whether to disable the host check.\n * @throws Error if any of the validated headers contain invalid values.\n */\nexport function validateRequest(\n request: Request,\n allowedHosts: ReadonlySet<string>,\n disableHostCheck: boolean,\n): void {\n validateHeaders(request);\n\n if (!disableHostCheck) {\n validateUrl(new URL(request.url), allowedHosts);\n }\n}\n\n/**\n * Validates that the hostname of a given URL is allowed.\n *\n * @param url - The URL object to validate.\n * @param allowedHosts - A set of allowed hostnames.\n * @throws Error if the hostname is not in the allowlist.\n */\nexport function validateUrl(url: URL, allowedHosts: ReadonlySet<string>): void {\n const { hostname } = url;\n if (!isHostAllowed(hostname, allowedHosts)) {\n throw new Error(`URL with hostname \"${hostname}\" is not allowed.`);\n }\n}\n\n/**\n * Clones a request and patches the `get` method of the request headers to validate the host headers.\n * @param request - The request to validate.\n * @param allowedHosts - A set of allowed hostnames.\n * @returns An object containing the cloned request and a promise that resolves to an error\n * if any of the validated headers contain invalid values.\n */\nexport function cloneRequestAndPatchHeaders(\n request: Request,\n allowedHosts: ReadonlySet<string>,\n): { request: Request; onError: Promise<Error> } {\n let onError: (value: Error) => void;\n const onErrorPromise = new Promise<Error>((resolve) => {\n onError = resolve;\n });\n\n const clonedReq = new Request(request.clone(), {\n signal: request.signal,\n });\n\n const headers = clonedReq.headers;\n\n const originalGet = headers.get;\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion\n (headers.get as typeof originalGet) = function (name) {\n const value = originalGet.call(headers, name);\n if (!value) {\n return value;\n }\n\n validateHeader(name, value, allowedHosts, onError);\n\n return value;\n };\n\n const originalValues = headers.values;\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion\n (headers.values as typeof originalValues) = function () {\n for (const name of HOST_HEADERS_TO_VALIDATE) {\n validateHeader(name, originalGet.call(headers, name), allowedHosts, onError);\n }\n\n return originalValues.call(headers);\n };\n\n const originalEntries = headers.entries;\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion\n (headers.entries as typeof originalEntries) = function () {\n const iterator = originalEntries.call(headers);\n\n return {\n next() {\n const result = iterator.next();\n if (!result.done) {\n const [key, value] = result.value;\n validateHeader(key, value, allowedHosts, onError);\n }\n\n return result;\n },\n [Symbol.iterator]() {\n return this;\n },\n };\n };\n\n // Ensure for...of loops use the new patched entries\n (headers[Symbol.iterator] as typeof originalEntries) = headers.entries;\n\n return { request: clonedReq, onError: onErrorPromise };\n}\n\n/**\n * Validates a specific header value against the allowed hosts.\n * @param name - The name of the header to validate.\n * @param value - The value of the header to validate.\n * @param allowedHosts - A set of allowed hostnames.\n * @param onError - A callback function to call if the header value is invalid.\n * @throws Error if the header value is invalid.\n */\nfunction validateHeader(\n name: string,\n value: string | null,\n allowedHosts: ReadonlySet<string>,\n onError: (value: Error) => void,\n): void {\n if (!value) {\n return;\n }\n\n if (!HOST_HEADERS_TO_VALIDATE.has(name.toLowerCase())) {\n return;\n }\n\n try {\n verifyHostAllowed(name, value, allowedHosts);\n } catch (error) {\n onError(error as Error);\n\n throw error;\n }\n}\n\n/**\n * Validates a specific host header value against the allowed hosts.\n *\n * @param headerName - The name of the header to validate (e.g., 'host', 'x-forwarded-host').\n * @param headerValue - The value of the header to validate.\n * @param allowedHosts - A set of allowed hostnames.\n * @throws Error if the header value is invalid or the hostname is not in the allowlist.\n */\nfunction verifyHostAllowed(\n headerName: string,\n headerValue: string,\n allowedHosts: ReadonlySet<string>,\n): void {\n const value = getFirstHeaderValue(headerValue);\n if (!value) {\n return;\n }\n\n const url = `http://${value}`;\n if (!URL.canParse(url)) {\n throw new Error(`Header \"${headerName}\" contains an invalid value and cannot be parsed.`);\n }\n\n const { hostname } = new URL(url);\n if (!isHostAllowed(hostname, allowedHosts)) {\n throw new Error(`Header \"${headerName}\" with value \"${value}\" is not allowed.`);\n }\n}\n\n/**\n * Checks if the hostname is allowed.\n * @param hostname - The hostname to check.\n * @param allowedHosts - A set of allowed hostnames.\n * @returns `true` if the hostname is allowed, `false` otherwise.\n */\nfunction isHostAllowed(hostname: string, allowedHosts: ReadonlySet<string>): boolean {\n if (allowedHosts.has(hostname)) {\n return true;\n }\n\n for (const allowedHost of allowedHosts) {\n if (!allowedHost.startsWith('*.')) {\n continue;\n }\n\n const domain = allowedHost.slice(1);\n if (hostname.endsWith(domain)) {\n return true;\n }\n }\n\n return false;\n}\n\n/**\n * Validates the headers of an incoming request.\n *\n * @param request - The incoming `Request` object containing the headers to validate.\n * @throws Error if any of the validated headers contain invalid values.\n */\nfunction validateHeaders(request: Request): void {\n const headers = request.headers;\n for (const headerName of HOST_HEADERS_TO_VALIDATE) {\n const headerValue = getFirstHeaderValue(headers.get(headerName));\n if (headerValue && !VALID_HOST_REGEX.test(headerValue)) {\n throw new Error(`Header \"${headerName}\" contains characters that are not allowed.`);\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 && INVALID_PREFIX_REGEX.test(xForwardedPrefix)) {\n throw new Error(\n 'Header \"x-forwarded-prefix\" must not start with \"\\\\\" or multiple \"/\" or contain \".\", \"..\" path segments.',\n );\n }\n}\n"],"names":["HOST_HEADERS_TO_VALIDATE","Set","VALID_PORT_REGEX","VALID_PROTO_REGEX","VALID_HOST_REGEX","INVALID_PREFIX_REGEX","getFirstHeaderValue","value","toString","split","trim","validateRequest","request","allowedHosts","disableHostCheck","validateHeaders","validateUrl","URL","url","hostname","isHostAllowed","Error","cloneRequestAndPatchHeaders","onError","onErrorPromise","Promise","resolve","clonedReq","Request","clone","signal","headers","originalGet","get","name","call","validateHeader","originalValues","values","originalEntries","entries","iterator","next","result","done","key","Symbol","has","toLowerCase","verifyHostAllowed","error","headerName","headerValue","canParse","allowedHost","startsWith","domain","slice","endsWith","test","xForwardedPort","xForwardedProto","xForwardedPrefix"],"mappings":"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"} | ||
| {"version":3,"file":"_validation-chunk.mjs","sources":["../../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/angular/ssr/src/utils/validation.ts"],"sourcesContent":["/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n\n/**\n * The set of headers that should be validated for host header injection attacks.\n */\nconst HOST_HEADERS_TO_VALIDATE: ReadonlySet<string> = new Set(['host', 'x-forwarded-host']);\n\n/**\n * Regular expression to validate that the port is a numeric value.\n */\nconst VALID_PORT_REGEX = /^\\d+$/;\n\n/**\n * Regular expression to validate that the protocol is either http or https (case-insensitive).\n */\nconst VALID_PROTO_REGEX = /^https?$/i;\n\n/**\n * Regular expression to validate that the host is a valid hostname.\n */\nconst VALID_HOST_REGEX = /^[a-z0-9.:-]+$/i;\n\n/**\n * Regular expression to validate that the prefix is valid.\n */\nconst INVALID_PREFIX_REGEX = /^(?:\\\\|\\/[/\\\\])|(?:^|[/\\\\])\\.\\.?(?:[/\\\\]|$)/;\n\n/**\n * Extracts the first value from a multi-value header string.\n *\n * @param value - A string or an array of strings representing the header values.\n * If it's a string, values are expected to be comma-separated.\n * @returns The first trimmed value from the multi-value header, or `undefined` if the input is invalid or empty.\n *\n * @example\n * ```typescript\n * getFirstHeaderValue(\"value1, value2, value3\"); // \"value1\"\n * getFirstHeaderValue([\"value1\", \"value2\"]); // \"value1\"\n * getFirstHeaderValue(undefined); // undefined\n * ```\n */\nexport function getFirstHeaderValue(\n value: string | string[] | undefined | null,\n): string | undefined {\n return value?.toString().split(',', 1)[0]?.trim();\n}\n\n/**\n * Validates a request.\n *\n * @param request - The incoming `Request` object to validate.\n * @param allowedHosts - A set of allowed hostnames.\n * @param disableHostCheck - Whether to disable the host check.\n * @throws Error if any of the validated headers contain invalid values.\n */\nexport function validateRequest(\n request: Request,\n allowedHosts: ReadonlySet<string>,\n disableHostCheck: boolean,\n): void {\n validateHeaders(request);\n\n if (!disableHostCheck) {\n validateUrl(new URL(request.url), allowedHosts);\n }\n}\n\n/**\n * Validates that the hostname of a given URL is allowed.\n *\n * @param url - The URL object to validate.\n * @param allowedHosts - A set of allowed hostnames.\n * @throws Error if the hostname is not in the allowlist.\n */\nexport function validateUrl(url: URL, allowedHosts: ReadonlySet<string>): void {\n const { hostname } = url;\n if (!isHostAllowed(hostname, allowedHosts)) {\n throw new Error(`URL with hostname \"${hostname}\" is not allowed.`);\n }\n}\n\n/**\n * Clones a request and patches the `get` method of the request headers to validate the host headers.\n * @param request - The request to validate.\n * @param allowedHosts - A set of allowed hostnames.\n * @returns An object containing the cloned request and a promise that resolves to an error\n * if any of the validated headers contain invalid values.\n */\nexport function cloneRequestAndPatchHeaders(\n request: Request,\n allowedHosts: ReadonlySet<string>,\n): { request: Request; onError: Promise<Error> } {\n let onError: (value: Error) => void;\n const onErrorPromise = new Promise<Error>((resolve) => {\n onError = resolve;\n });\n\n const clonedReq = new Request(request.clone(), {\n signal: request.signal,\n });\n\n const headers = clonedReq.headers;\n\n const originalGet = headers.get;\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion\n (headers.get as typeof originalGet) = function (name) {\n const value = originalGet.call(headers, name);\n if (!value) {\n return value;\n }\n\n validateHeader(name, value, allowedHosts, onError);\n\n return value;\n };\n\n const originalValues = headers.values;\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion\n (headers.values as typeof originalValues) = function () {\n for (const name of HOST_HEADERS_TO_VALIDATE) {\n validateHeader(name, originalGet.call(headers, name), allowedHosts, onError);\n }\n\n return originalValues.call(headers);\n };\n\n const originalEntries = headers.entries;\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion\n (headers.entries as typeof originalEntries) = function () {\n const iterator = originalEntries.call(headers);\n\n return {\n next() {\n const result = iterator.next();\n if (!result.done) {\n const [key, value] = result.value;\n validateHeader(key, value, allowedHosts, onError);\n }\n\n return result;\n },\n [Symbol.iterator]() {\n return this;\n },\n };\n };\n\n // Ensure for...of loops use the new patched entries\n (headers[Symbol.iterator] as typeof originalEntries) = headers.entries;\n\n return { request: clonedReq, onError: onErrorPromise };\n}\n\n/**\n * Validates a specific header value against the allowed hosts.\n * @param name - The name of the header to validate.\n * @param value - The value of the header to validate.\n * @param allowedHosts - A set of allowed hostnames.\n * @param onError - A callback function to call if the header value is invalid.\n * @throws Error if the header value is invalid.\n */\nfunction validateHeader(\n name: string,\n value: string | null,\n allowedHosts: ReadonlySet<string>,\n onError: (value: Error) => void,\n): void {\n if (!value) {\n return;\n }\n\n if (!HOST_HEADERS_TO_VALIDATE.has(name.toLowerCase())) {\n return;\n }\n\n try {\n verifyHostAllowed(name, value, allowedHosts);\n } catch (error) {\n onError(error as Error);\n\n throw error;\n }\n}\n\n/**\n * Validates a specific host header value against the allowed hosts.\n *\n * @param headerName - The name of the header to validate (e.g., 'host', 'x-forwarded-host').\n * @param headerValue - The value of the header to validate.\n * @param allowedHosts - A set of allowed hostnames.\n * @throws Error if the header value is invalid or the hostname is not in the allowlist.\n */\nfunction verifyHostAllowed(\n headerName: string,\n headerValue: string,\n allowedHosts: ReadonlySet<string>,\n): void {\n const value = getFirstHeaderValue(headerValue);\n if (!value) {\n return;\n }\n\n const url = `http://${value}`;\n if (!URL.canParse(url)) {\n throw new Error(`Header \"${headerName}\" contains an invalid value and cannot be parsed.`);\n }\n\n const { hostname } = new URL(url);\n if (!isHostAllowed(hostname, allowedHosts)) {\n throw new Error(`Header \"${headerName}\" with value \"${value}\" is not allowed.`);\n }\n}\n\n/**\n * Checks if the hostname is allowed.\n * @param hostname - The hostname to check.\n * @param allowedHosts - A set of allowed hostnames.\n * @returns `true` if the hostname is allowed, `false` otherwise.\n */\nfunction isHostAllowed(hostname: string, allowedHosts: ReadonlySet<string>): boolean {\n if (allowedHosts.has('*') || allowedHosts.has(hostname)) {\n return true;\n }\n\n for (const allowedHost of allowedHosts) {\n if (!allowedHost.startsWith('*.')) {\n continue;\n }\n\n const domain = allowedHost.slice(1);\n if (hostname.endsWith(domain)) {\n return true;\n }\n }\n\n return false;\n}\n\n/**\n * Validates the headers of an incoming request.\n *\n * @param request - The incoming `Request` object containing the headers to validate.\n * @throws Error if any of the validated headers contain invalid values.\n */\nfunction validateHeaders(request: Request): void {\n const headers = request.headers;\n for (const headerName of HOST_HEADERS_TO_VALIDATE) {\n const headerValue = getFirstHeaderValue(headers.get(headerName));\n if (headerValue && !VALID_HOST_REGEX.test(headerValue)) {\n throw new Error(`Header \"${headerName}\" contains characters that are not allowed.`);\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 && INVALID_PREFIX_REGEX.test(xForwardedPrefix)) {\n throw new Error(\n 'Header \"x-forwarded-prefix\" must not start with \"\\\\\" or multiple \"/\" or contain \".\", \"..\" path segments.',\n );\n }\n}\n"],"names":["HOST_HEADERS_TO_VALIDATE","Set","VALID_PORT_REGEX","VALID_PROTO_REGEX","VALID_HOST_REGEX","INVALID_PREFIX_REGEX","getFirstHeaderValue","value","toString","split","trim","validateRequest","request","allowedHosts","disableHostCheck","validateHeaders","validateUrl","URL","url","hostname","isHostAllowed","Error","cloneRequestAndPatchHeaders","onError","onErrorPromise","Promise","resolve","clonedReq","Request","clone","signal","headers","originalGet","get","name","call","validateHeader","originalValues","values","originalEntries","entries","iterator","next","result","done","key","Symbol","has","toLowerCase","verifyHostAllowed","error","headerName","headerValue","canParse","allowedHost","startsWith","domain","slice","endsWith","test","xForwardedPort","xForwardedProto","xForwardedPrefix"],"mappings":"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"} |
+12
-8
@@ -1480,4 +1480,11 @@ import { validateRequest, cloneRequestAndPatchHeaders } from './_validation-chunk.mjs'; | ||
| constructor(options) { | ||
| this.allowedHosts = new Set([...(options?.allowedHosts ?? []), ...this.manifest.allowedHosts]); | ||
| this.allowedHosts = this.getAllowedHosts(options); | ||
| } | ||
| getAllowedHosts(options) { | ||
| const allowedHosts = new Set([...(options?.allowedHosts ?? []), ...this.manifest.allowedHosts]); | ||
| if (allowedHosts.has('*')) { | ||
| console.warn('Allowing all hosts via "*" is a security risk. This configuration should only be used when ' + 'validation for "Host" and "X-Forwarded-Host" headers is performed in another layer, such as a load balancer or reverse proxy. ' + 'For more information see: https://angular.dev/best-practices/security#preventing-server-side-request-forgery-ssrf'); | ||
| } | ||
| return allowedHosts; | ||
| } | ||
| async handle(request, requestContext) { | ||
@@ -1508,3 +1515,3 @@ const allowedHost = this.allowedHosts; | ||
| if (this.supportedLocales.length > 1) { | ||
| return this.redirectBasedOnAcceptLanguage(request); | ||
| return this.redirectBasedOnAcceptLanguage(securedRequest); | ||
| } | ||
@@ -1528,8 +1535,5 @@ return null; | ||
| if (subPath !== undefined) { | ||
| return new Response(null, { | ||
| status: 302, | ||
| headers: { | ||
| 'Location': joinUrlParts(pathname, subPath), | ||
| 'Vary': 'Accept-Language' | ||
| } | ||
| const prefix = request.headers.get('X-Forwarded-Prefix') ?? ''; | ||
| return createRedirectResponse(joinUrlParts(prefix, pathname, subPath), 302, { | ||
| 'Vary': 'Accept-Language' | ||
| }); | ||
@@ -1536,0 +1540,0 @@ } |
+1
-1
| { | ||
| "name": "@angular/ssr", | ||
| "version": "22.0.0-next.2", | ||
| "version": "22.0.0-next.3", | ||
| "description": "Angular server side rendering utilities", | ||
@@ -5,0 +5,0 @@ "type": "module", |
@@ -126,2 +126,3 @@ /** | ||
| constructor(options?: AngularAppEngineOptions); | ||
| private getAllowedHosts; | ||
| /** | ||
@@ -128,0 +129,0 @@ * Handles an incoming HTTP request by serving prerendered content, performing server-side rendering, |
Sorry, the diff of this file is too big to display
1640422
0.11%15241
0.04%