@angular/ssr 20.3.24 → 20.3.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,17 @@
1
+ /**
2
+ * Common X-Forwarded-* headers.
3
+ */
4
+ const X_FORWARDED_HEADERS = new Set([
5
+ 'x-forwarded-for',
6
+ 'x-forwarded-host',
7
+ 'x-forwarded-port',
8
+ 'x-forwarded-proto',
9
+ 'x-forwarded-prefix',
10
+ ]);
1
11
  /**
2
12
  * The set of headers that should be validated for host header injection attacks.
3
13
  */
4
- const HOST_HEADERS_TO_VALIDATE = new Set(['host', 'x-forwarded-host']);
14
+ const HOST_HEADERS_TO_VALIDATE = ['host', 'x-forwarded-host'];
5
15
  /**
6
16
  * Regular expression to validate that the port is a numeric value.
7
17
  */
@@ -10,14 +20,10 @@ const VALID_PORT_REGEX = /^\d+$/;
10
20
  * Regular expression to validate that the protocol is either http or https (case-insensitive).
11
21
  */
12
22
  const VALID_PROTO_REGEX = /^https?$/i;
13
- /**
14
- * Regular expression to validate that the host is a valid hostname.
15
- */
16
- const VALID_HOST_REGEX = /^[a-z0-9.:-]+$/i;
17
23
  /**
18
24
  * Regular expression to validate that the prefix is valid.
19
25
  */
20
- const INVALID_PREFIX_REGEX = /^(?:\\|\/[/\\])|(?:^|[/\\])\.\.?(?:[/\\]|$)/;
26
+ const VALID_PREFIX_REGEX = /^\/([a-z0-9_-]+\/)*[a-z0-9_-]*$/i;
21
27
  /**
22
28
  * Extracts the first value from a multi-value header string.
23
29
  *
@@ -42,9 +48,11 @@ function getFirstHeaderValue(value) {
42
48
  * @param allowedHosts - A set of allowed hostnames.
43
49
  * @throws Error if any of the validated headers contain invalid values.
44
50
  */
45
- function validateRequest(request, allowedHosts) {
46
- validateHeaders(request);
47
- validateUrl(new URL(request.url), allowedHosts);
51
+ function validateRequest(request, allowedHosts, disableHostCheck) {
52
+ validateHeaders(request, allowedHosts, disableHostCheck);
53
+ if (!disableHostCheck) {
54
+ validateUrl(new URL(request.url), allowedHosts);
55
+ }
48
56
  }
49
57
  /**
50
58
  * Validates that the hostname of a given URL is allowed.
@@ -60,83 +68,37 @@ function validateUrl(url, allowedHosts) {
60
68
  }
61
69
  }
62
70
  /**
63
- * Clones a request and patches the `get` method of the request headers to validate the host headers.
64
- * @param request - The request to validate.
65
- * @param allowedHosts - A set of allowed hostnames.
66
- * @returns An object containing the cloned request and a promise that resolves to an error
67
- * if any of the validated headers contain invalid values.
71
+ * Sanitizes the proxy headers of a request by removing unallowed `X-Forwarded-*` headers.
72
+ * If no headers need to be removed, the original request is returned without cloning.
73
+ *
74
+ * @param request - The incoming `Request` object to sanitize.
75
+ * @param trustProxyHeaders - A set of allowed proxy headers.
76
+ * @returns An object containing the sanitized request, or the original request if no changes were needed.
68
77
  */
69
- function cloneRequestAndPatchHeaders(request, allowedHosts) {
70
- let onError;
71
- const onErrorPromise = new Promise((resolve) => {
72
- onError = resolve;
73
- });
78
+ function sanitizeRequestHeaders(request, trustProxyHeaders) {
79
+ const keysToDelete = [];
80
+ let deoptToCSR = false;
81
+ for (const [key] of request.headers) {
82
+ const lowerKey = key.toLowerCase();
83
+ if (lowerKey.startsWith('x-forwarded-') && !isProxyHeaderAllowed(lowerKey, trustProxyHeaders)) {
84
+ // eslint-disable-next-line no-console
85
+ console.warn(`Received "${key}" header but "trustProxyHeaders" was not set up to allow it.\n` +
86
+ `For more information, see https://angular.dev/best-practices/security#configuring-trusted-proxy-headers`);
87
+ deoptToCSR = true;
88
+ keysToDelete.push(key);
89
+ }
90
+ }
91
+ if (keysToDelete.length === 0) {
92
+ return { request, deoptToCSR };
93
+ }
74
94
  const clonedReq = new Request(request.clone(), {
75
95
  signal: request.signal,
76
96
  });
77
97
  const headers = clonedReq.headers;
78
- const originalGet = headers.get;
79
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
80
- headers.get = function (name) {
81
- const value = originalGet.call(headers, name);
82
- if (!value) {
83
- return value;
84
- }
85
- validateHeader(name, value, allowedHosts, onError);
86
- return value;
87
- };
88
- const originalValues = headers.values;
89
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
90
- headers.values = function () {
91
- for (const name of HOST_HEADERS_TO_VALIDATE) {
92
- validateHeader(name, originalGet.call(headers, name), allowedHosts, onError);
93
- }
94
- return originalValues.call(headers);
95
- };
96
- const originalEntries = headers.entries;
97
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
98
- headers.entries = function () {
99
- const iterator = originalEntries.call(headers);
100
- return {
101
- next() {
102
- const result = iterator.next();
103
- if (!result.done) {
104
- const [key, value] = result.value;
105
- validateHeader(key, value, allowedHosts, onError);
106
- }
107
- return result;
108
- },
109
- [Symbol.iterator]() {
110
- return this;
111
- },
112
- };
113
- };
114
- // Ensure for...of loops use the new patched entries
115
- headers[Symbol.iterator] = headers.entries;
116
- return { request: clonedReq, onError: onErrorPromise };
117
- }
118
- /**
119
- * Validates a specific header value against the allowed hosts.
120
- * @param name - The name of the header to validate.
121
- * @param value - The value of the header to validate.
122
- * @param allowedHosts - A set of allowed hostnames.
123
- * @param onError - A callback function to call if the header value is invalid.
124
- * @throws Error if the header value is invalid.
125
- */
126
- function validateHeader(name, value, allowedHosts, onError) {
127
- if (!value) {
128
- return;
129
- }
130
- if (!HOST_HEADERS_TO_VALIDATE.has(name.toLowerCase())) {
131
- return;
132
- }
133
- try {
134
- verifyHostAllowed(name, value, allowedHosts);
135
- }
136
- catch (error) {
137
- onError(error);
138
- throw error;
98
+ for (const key of keysToDelete) {
99
+ headers.delete(key);
139
100
  }
101
+ return { request: clonedReq, deoptToCSR };
140
102
  }
141
103
  /**
142
104
  * Validates a specific host header value against the allowed hosts.
@@ -147,17 +109,16 @@ function validateHeader(name, value, allowedHosts, onError) {
147
109
  * @throws Error if the header value is invalid or the hostname is not in the allowlist.
148
110
  */
149
111
  function verifyHostAllowed(headerName, headerValue, allowedHosts) {
150
- const value = getFirstHeaderValue(headerValue);
151
- if (!value) {
152
- return;
153
- }
154
- const url = `http://${value}`;
112
+ const url = `http://${headerValue}`;
155
113
  if (!URL.canParse(url)) {
156
114
  throw new Error(`Header "${headerName}" contains an invalid value and cannot be parsed.`);
157
115
  }
158
- const { hostname } = new URL(url);
116
+ const { hostname, pathname, search, hash, username, password } = new URL(url);
117
+ if (pathname !== '/' || search || hash || username || password) {
118
+ throw new Error(`Header "${headerName}" with value "${headerValue}" contains characters that are not allowed.`);
119
+ }
159
120
  if (!isHostAllowed(hostname, allowedHosts)) {
160
- throw new Error(`Header "${headerName}" with value "${value}" is not allowed.`);
121
+ throw new Error(`Header "${headerName}" with value "${headerValue}" is not allowed.`);
161
122
  }
162
123
  }
163
124
  /**
@@ -185,14 +146,16 @@ function isHostAllowed(hostname, allowedHosts) {
185
146
  * Validates the headers of an incoming request.
186
147
  *
187
148
  * @param request - The incoming `Request` object containing the headers to validate.
149
+ * @param allowedHosts - A set of allowed hostnames.
150
+ * @param disableHostCheck - Whether to disable the host check.
188
151
  * @throws Error if any of the validated headers contain invalid values.
189
152
  */
190
- function validateHeaders(request) {
153
+ function validateHeaders(request, allowedHosts, disableHostCheck) {
191
154
  const headers = request.headers;
192
155
  for (const headerName of HOST_HEADERS_TO_VALIDATE) {
193
156
  const headerValue = getFirstHeaderValue(headers.get(headerName));
194
- if (headerValue && !VALID_HOST_REGEX.test(headerValue)) {
195
- throw new Error(`Header "${headerName}" contains characters that are not allowed.`);
157
+ if (headerValue && !disableHostCheck) {
158
+ verifyHostAllowed(headerName, headerValue, allowedHosts);
196
159
  }
197
160
  }
198
161
  const xForwardedPort = getFirstHeaderValue(headers.get('x-forwarded-port'));
@@ -204,10 +167,38 @@ function validateHeaders(request) {
204
167
  throw new Error('Header "x-forwarded-proto" must be either "http" or "https".');
205
168
  }
206
169
  const xForwardedPrefix = getFirstHeaderValue(headers.get('x-forwarded-prefix'));
207
- if (xForwardedPrefix && INVALID_PREFIX_REGEX.test(xForwardedPrefix)) {
208
- throw new Error('Header "x-forwarded-prefix" must not start with "\\" or multiple "/" or contain ".", ".." path segments.');
170
+ if (xForwardedPrefix && !VALID_PREFIX_REGEX.test(xForwardedPrefix)) {
171
+ throw new Error('Header "x-forwarded-prefix" is invalid. It must start with a "/" and contain ' +
172
+ 'only alphanumeric characters, hyphens, and underscores, separated by single slashes.');
173
+ }
174
+ }
175
+ /**
176
+ * Checks if a specific proxy header is allowed.
177
+ *
178
+ * @param headerName - The name of the proxy header to check.
179
+ * @param trustProxyHeaders - A set of allowed proxy headers.
180
+ * @returns `true` if the header is allowed, `false` otherwise.
181
+ */
182
+ function isProxyHeaderAllowed(headerName, trustProxyHeaders) {
183
+ return trustProxyHeaders.has(headerName.toLowerCase());
184
+ }
185
+ /**
186
+ * Normalizes the `trustProxyHeaders` option to a consistent representation.
187
+ * @param trustProxyHeaders The input `trustProxyHeaders` value.
188
+ * @returns A `Set<string>` of normalized header names.
189
+ */
190
+ function normalizeTrustProxyHeaders(trustProxyHeaders) {
191
+ if (trustProxyHeaders === undefined) {
192
+ return new Set(['x-forwarded-host', 'x-forwarded-proto']);
193
+ }
194
+ if (trustProxyHeaders === false) {
195
+ return new Set();
196
+ }
197
+ if (trustProxyHeaders === true) {
198
+ return X_FORWARDED_HEADERS;
209
199
  }
200
+ return new Set(trustProxyHeaders.map((h) => h.toLowerCase()));
210
201
  }
211
202
 
212
- export { cloneRequestAndPatchHeaders, getFirstHeaderValue, validateRequest, validateUrl };
203
+ export { getFirstHeaderValue, isProxyHeaderAllowed, normalizeTrustProxyHeaders, sanitizeRequestHeaders, validateRequest, validateUrl };
213
204
  //# sourceMappingURL=validation.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"validation.mjs","sources":["../../../../../../darwin_arm64-fastbuild-ST-199a4f3c4e20/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 * @throws Error if any of the validated headers contain invalid values.\n */\nexport function validateRequest(request: Request, allowedHosts: ReadonlySet<string>): void {\n validateHeaders(request);\n validateUrl(new URL(request.url), allowedHosts);\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":[],"mappings":"AAQA;;AAEG;AACH,MAAM,wBAAwB,GAAwB,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;AAE3F;;AAEG;AACH,MAAM,gBAAgB,GAAG,OAAO;AAEhC;;AAEG;AACH,MAAM,iBAAiB,GAAG,WAAW;AAErC;;AAEG;AACH,MAAM,gBAAgB,GAAG,iBAAiB;AAE1C;;AAEG;AACH,MAAM,oBAAoB,GAAG,6CAA6C;AAE1E;;;;;;;;;;;;;AAaG;AACG,SAAU,mBAAmB,CACjC,KAA2C,EAAA;AAE3C,IAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE;AACnD;AAEA;;;;;;AAMG;AACa,SAAA,eAAe,CAAC,OAAgB,EAAE,YAAiC,EAAA;IACjF,eAAe,CAAC,OAAO,CAAC;IACxB,WAAW,CAAC,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,YAAY,CAAC;AACjD;AAEA;;;;;;AAMG;AACa,SAAA,WAAW,CAAC,GAAQ,EAAE,YAAiC,EAAA;AACrE,IAAA,MAAM,EAAE,QAAQ,EAAE,GAAG,GAAG;IACxB,IAAI,CAAC,aAAa,CAAC,QAAQ,EAAE,YAAY,CAAC,EAAE;AAC1C,QAAA,MAAM,IAAI,KAAK,CAAC,sBAAsB,QAAQ,CAAA,iBAAA,CAAmB,CAAC;;AAEtE;AAEA;;;;;;AAMG;AACa,SAAA,2BAA2B,CACzC,OAAgB,EAChB,YAAiC,EAAA;AAEjC,IAAA,IAAI,OAA+B;IACnC,MAAM,cAAc,GAAG,IAAI,OAAO,CAAQ,CAAC,OAAO,KAAI;QACpD,OAAO,GAAG,OAAO;AACnB,KAAC,CAAC;IAEF,MAAM,SAAS,GAAG,IAAI,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE;QAC7C,MAAM,EAAE,OAAO,CAAC,MAAM;AACvB,KAAA,CAAC;AAEF,IAAA,MAAM,OAAO,GAAG,SAAS,CAAC,OAAO;AAEjC,IAAA,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG;;AAE9B,IAAA,OAAO,CAAC,GAA0B,GAAG,UAAU,IAAI,EAAA;QAClD,MAAM,KAAK,GAAG,WAAW,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC;QAC7C,IAAI,CAAC,KAAK,EAAE;AACV,YAAA,OAAO,KAAK;;QAGd,cAAc,CAAC,IAAI,EAAE,KAAK,EAAE,YAAY,EAAE,OAAO,CAAC;AAElD,QAAA,OAAO,KAAK;AACd,KAAC;AAED,IAAA,MAAM,cAAc,GAAG,OAAO,CAAC,MAAM;;IAEpC,OAAO,CAAC,MAAgC,GAAG,YAAA;AAC1C,QAAA,KAAK,MAAM,IAAI,IAAI,wBAAwB,EAAE;AAC3C,YAAA,cAAc,CAAC,IAAI,EAAE,WAAW,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,EAAE,YAAY,EAAE,OAAO,CAAC;;AAG9E,QAAA,OAAO,cAAc,CAAC,IAAI,CAAC,OAAO,CAAC;AACrC,KAAC;AAED,IAAA,MAAM,eAAe,GAAG,OAAO,CAAC,OAAO;;IAEtC,OAAO,CAAC,OAAkC,GAAG,YAAA;QAC5C,MAAM,QAAQ,GAAG,eAAe,CAAC,IAAI,CAAC,OAAO,CAAC;QAE9C,OAAO;YACL,IAAI,GAAA;AACF,gBAAA,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,EAAE;AAC9B,gBAAA,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE;oBAChB,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,GAAG,MAAM,CAAC,KAAK;oBACjC,cAAc,CAAC,GAAG,EAAE,KAAK,EAAE,YAAY,EAAE,OAAO,CAAC;;AAGnD,gBAAA,OAAO,MAAM;aACd;YACD,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAA;AACf,gBAAA,OAAO,IAAI;aACZ;SACF;AACH,KAAC;;IAGA,OAAO,CAAC,MAAM,CAAC,QAAQ,CAA4B,GAAG,OAAO,CAAC,OAAO;IAEtE,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,cAAc,EAAE;AACxD;AAEA;;;;;;;AAOG;AACH,SAAS,cAAc,CACrB,IAAY,EACZ,KAAoB,EACpB,YAAiC,EACjC,OAA+B,EAAA;IAE/B,IAAI,CAAC,KAAK,EAAE;QACV;;IAGF,IAAI,CAAC,wBAAwB,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,EAAE;QACrD;;AAGF,IAAA,IAAI;AACF,QAAA,iBAAiB,CAAC,IAAI,EAAE,KAAK,EAAE,YAAY,CAAC;;IAC5C,OAAO,KAAK,EAAE;QACd,OAAO,CAAC,KAAc,CAAC;AAEvB,QAAA,MAAM,KAAK;;AAEf;AAEA;;;;;;;AAOG;AACH,SAAS,iBAAiB,CACxB,UAAkB,EAClB,WAAmB,EACnB,YAAiC,EAAA;AAEjC,IAAA,MAAM,KAAK,GAAG,mBAAmB,CAAC,WAAW,CAAC;IAC9C,IAAI,CAAC,KAAK,EAAE;QACV;;AAGF,IAAA,MAAM,GAAG,GAAG,CAAU,OAAA,EAAA,KAAK,EAAE;IAC7B,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE;AACtB,QAAA,MAAM,IAAI,KAAK,CAAC,WAAW,UAAU,CAAA,iDAAA,CAAmD,CAAC;;IAG3F,MAAM,EAAE,QAAQ,EAAE,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC;IACjC,IAAI,CAAC,aAAa,CAAC,QAAQ,EAAE,YAAY,CAAC,EAAE;QAC1C,MAAM,IAAI,KAAK,CAAC,CAAA,QAAA,EAAW,UAAU,CAAiB,cAAA,EAAA,KAAK,CAAmB,iBAAA,CAAA,CAAC;;AAEnF;AAEA;;;;;AAKG;AACH,SAAS,aAAa,CAAC,QAAgB,EAAE,YAAiC,EAAA;AACxE,IAAA,IAAI,YAAY,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE;AAC9B,QAAA,OAAO,IAAI;;AAGb,IAAA,KAAK,MAAM,WAAW,IAAI,YAAY,EAAE;QACtC,IAAI,CAAC,WAAW,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE;YACjC;;QAGF,MAAM,MAAM,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC;AACnC,QAAA,IAAI,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE;AAC7B,YAAA,OAAO,IAAI;;;AAIf,IAAA,OAAO,KAAK;AACd;AAEA;;;;;AAKG;AACH,SAAS,eAAe,CAAC,OAAgB,EAAA;AACvC,IAAA,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO;AAC/B,IAAA,KAAK,MAAM,UAAU,IAAI,wBAAwB,EAAE;QACjD,MAAM,WAAW,GAAG,mBAAmB,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QAChE,IAAI,WAAW,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE;AACtD,YAAA,MAAM,IAAI,KAAK,CAAC,WAAW,UAAU,CAAA,2CAAA,CAA6C,CAAC;;;IAIvF,MAAM,cAAc,GAAG,mBAAmB,CAAC,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;IAC3E,IAAI,cAAc,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,cAAc,CAAC,EAAE;AAC5D,QAAA,MAAM,IAAI,KAAK,CAAC,oDAAoD,CAAC;;IAGvE,MAAM,eAAe,GAAG,mBAAmB,CAAC,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC;IAC7E,IAAI,eAAe,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,eAAe,CAAC,EAAE;AAC/D,QAAA,MAAM,IAAI,KAAK,CAAC,8DAA8D,CAAC;;IAGjF,MAAM,gBAAgB,GAAG,mBAAmB,CAAC,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC;IAC/E,IAAI,gBAAgB,IAAI,oBAAoB,CAAC,IAAI,CAAC,gBAAgB,CAAC,EAAE;AACnE,QAAA,MAAM,IAAI,KAAK,CACb,0GAA0G,CAC3G;;AAEL;;;;"}
1
+ {"version":3,"file":"validation.mjs","sources":["../../../../../../k8-fastbuild-ST-199a4f3c4e20/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 * Common X-Forwarded-* headers.\n */\nconst X_FORWARDED_HEADERS = new Set([\n 'x-forwarded-for',\n 'x-forwarded-host',\n 'x-forwarded-port',\n 'x-forwarded-proto',\n 'x-forwarded-prefix',\n]);\n\n/**\n * The set of headers that should be validated for host header injection attacks.\n */\nconst HOST_HEADERS_TO_VALIDATE: ReadonlyArray<string> = ['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 prefix is valid.\n */\nconst VALID_PREFIX_REGEX = /^\\/([a-z0-9_-]+\\/)*[a-z0-9_-]*$/i;\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 * @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, allowedHosts, disableHostCheck);\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 * Sanitizes the proxy headers of a request by removing unallowed `X-Forwarded-*` headers.\n * If no headers need to be removed, the original request is returned without cloning.\n *\n * @param request - The incoming `Request` object to sanitize.\n * @param trustProxyHeaders - A set of allowed proxy headers.\n * @returns An object containing the sanitized request, or the original request if no changes were needed.\n */\nexport function sanitizeRequestHeaders(\n request: Request,\n trustProxyHeaders: ReadonlySet<string>,\n): { request: Request; deoptToCSR: boolean } {\n const keysToDelete: string[] = [];\n let deoptToCSR = false;\n\n for (const [key] of request.headers) {\n const lowerKey = key.toLowerCase();\n if (lowerKey.startsWith('x-forwarded-') && !isProxyHeaderAllowed(lowerKey, trustProxyHeaders)) {\n // eslint-disable-next-line no-console\n console.warn(\n `Received \"${key}\" header but \"trustProxyHeaders\" was not set up to allow it.\\n` +\n `For more information, see https://angular.dev/best-practices/security#configuring-trusted-proxy-headers`,\n );\n deoptToCSR = true;\n keysToDelete.push(key);\n }\n }\n\n if (keysToDelete.length === 0) {\n return { request, deoptToCSR };\n }\n\n const clonedReq = new Request(request.clone(), {\n signal: request.signal,\n });\n\n const headers = clonedReq.headers;\n for (const key of keysToDelete) {\n headers.delete(key);\n }\n\n return { request: clonedReq, deoptToCSR };\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 url = `http://${headerValue}`;\n if (!URL.canParse(url)) {\n throw new Error(`Header \"${headerName}\" contains an invalid value and cannot be parsed.`);\n }\n\n const { hostname, pathname, search, hash, username, password } = new URL(url);\n if (pathname !== '/' || search || hash || username || password) {\n throw new Error(\n `Header \"${headerName}\" with value \"${headerValue}\" contains characters that are not allowed.`,\n );\n }\n\n if (!isHostAllowed(hostname, allowedHosts)) {\n throw new Error(`Header \"${headerName}\" with value \"${headerValue}\" 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 * @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 */\nfunction validateHeaders(\n request: Request,\n allowedHosts: ReadonlySet<string>,\n disableHostCheck: boolean,\n): 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 && !disableHostCheck) {\n verifyHostAllowed(headerName, headerValue, allowedHosts);\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 */\nexport function isProxyHeaderAllowed(\n headerName: string,\n trustProxyHeaders: ReadonlySet<string>,\n): boolean {\n return trustProxyHeaders.has(headerName.toLowerCase());\n}\n\n/**\n * Normalizes the `trustProxyHeaders` option to a consistent representation.\n * @param trustProxyHeaders The input `trustProxyHeaders` value.\n * @returns A `Set<string>` of normalized header names.\n */\nexport function normalizeTrustProxyHeaders(\n trustProxyHeaders: boolean | readonly string[] | undefined,\n): ReadonlySet<string> {\n if (trustProxyHeaders === undefined) {\n return new Set(['x-forwarded-host', 'x-forwarded-proto']);\n }\n\n if (trustProxyHeaders === false) {\n return new Set();\n }\n\n if (trustProxyHeaders === true) {\n return X_FORWARDED_HEADERS;\n }\n\n return new Set(trustProxyHeaders.map((h) => h.toLowerCase()));\n}\n"],"names":[],"mappings":"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"}
package/node/index.d.ts CHANGED
@@ -70,6 +70,7 @@ interface AngularNodeAppEngineOptions extends AngularAppEngineOptions {
70
70
  */
71
71
  declare class AngularNodeAppEngine {
72
72
  private readonly angularAppEngine;
73
+ private readonly trustProxyHeaders?;
73
74
  /**
74
75
  * Creates a new instance of the Angular Node.js server application engine.
75
76
  * @param options Options for the Angular Node.js server application engine.
@@ -181,9 +182,16 @@ declare function writeResponseToNodeResponse(source: Response, destination: Serv
181
182
  * be used by web platform APIs.
182
183
  *
183
184
  * @param nodeRequest - The Node.js request object (`IncomingMessage` or `Http2ServerRequest`) to convert.
185
+ * @param trustProxyHeaders - A boolean or an array of proxy headers to trust when constructing the request URL.
186
+ *
187
+ * @remarks
188
+ * When `trustProxyHeaders` is enabled, headers such as `X-Forwarded-Host` and
189
+ * `X-Forwarded-Prefix` should ideally be strictly validated at a higher infrastructure
190
+ * level (e.g., at the reverse proxy or API gateway) before reaching the application.
191
+ *
184
192
  * @returns A Web Standard `Request` object.
185
193
  */
186
- declare function createWebRequestFromNodeRequest(nodeRequest: IncomingMessage | Http2ServerRequest): Request;
194
+ declare function createWebRequestFromNodeRequest(nodeRequest: IncomingMessage | Http2ServerRequest, trustProxyHeaders?: boolean | readonly string[]): Request;
187
195
 
188
196
  /**
189
197
  * Determines whether the provided URL represents the main entry point module.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@angular/ssr",
3
- "version": "20.3.24",
3
+ "version": "20.3.26",
4
4
  "description": "Angular server side rendering utilities",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -449,7 +449,7 @@ License: MIT
449
449
 
450
450
  The MIT License (MIT)
451
451
 
452
- Copyright 2013 Andrey Sitnik <andrey@sitnik.ru>
452
+ Copyright 2013 Andrey Sitnik <andrey@sitnik.es>
453
453
 
454
454
  Permission is hereby granted, free of charge, to any person obtaining a copy of
455
455
  this software and associated documentation files (the "Software"), to deal in