@lwrjs/security 0.15.0-alpha.8 → 0.15.0

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.
@@ -30,6 +30,10 @@ var import_shared_utils = __toModule(require("@lwrjs/shared-utils"));
30
30
  var import_content_security_policy = __toModule(require("./headers/content-security-policy.cjs"));
31
31
  var import_referrer_policy = __toModule(require("./headers/referrer-policy.cjs"));
32
32
  var import_strict_transport_security = __toModule(require("./headers/strict-transport-security.cjs"));
33
+ var import_diagnostics = __toModule(require("@lwrjs/diagnostics"));
34
+ var import_util = __toModule(require("util"));
35
+ var CSP_HEADER_NAME = "content-security-policy";
36
+ var MAX_HEADER_SIZE = 4096;
33
37
  function getResources(viewDefinition) {
34
38
  const {viewRecord} = viewDefinition;
35
39
  const resources = [];
@@ -75,9 +79,6 @@ async function getResourceHashes(viewResponse) {
75
79
  }
76
80
  return hashes;
77
81
  }
78
- function normalizeHeaders(headers = {}) {
79
- return Object.fromEntries(Object.entries(headers).map(([key, value]) => [key.toLowerCase(), value]));
80
- }
81
82
  function normalizeOptions(options = {}) {
82
83
  for (const [option, value] of Object.entries(options)) {
83
84
  if (value === true) {
@@ -88,23 +89,31 @@ function normalizeOptions(options = {}) {
88
89
  }
89
90
  async function resolveHeaders(viewResponse, options) {
90
91
  options = normalizeOptions(options);
91
- const headers = normalizeHeaders(viewResponse.headers);
92
+ const headers = (0, import_shared_utils.normalizeHeaders)(viewResponse.headers);
92
93
  if (options.contentSecurityPolicy === void 0 || typeof options.contentSecurityPolicy === "object") {
93
- const headerName = options.contentSecurityPolicy?.reportOnly ? "content-security-policy-report-only" : "content-security-policy";
94
+ const headerName = options.contentSecurityPolicy?.reportOnly ? "content-security-policy-report-only" : CSP_HEADER_NAME;
94
95
  let hashes = [];
95
96
  if (options.contentSecurityPolicy?.resourceHashing === void 0) {
96
97
  hashes = await getResourceHashes(viewResponse);
97
98
  }
98
99
  headers[headerName] = (0, import_content_security_policy.default)(headers[headerName], options.contentSecurityPolicy, hashes);
100
+ const size = byteSize(headers[headerName]);
101
+ if (size > MAX_HEADER_SIZE) {
102
+ import_diagnostics.logger.warn(`[security] Header "${headerName}" size (${size}) exceeds the recommended limit of ${MAX_HEADER_SIZE}.`);
103
+ }
99
104
  }
100
105
  if (typeof options.contentSecurityPolicy === "string") {
101
106
  const parsedOptions = {
102
107
  directives: options.contentSecurityPolicy,
103
108
  useDefault: false
104
109
  };
105
- const headerName = "content-security-policy";
110
+ const headerName = CSP_HEADER_NAME;
106
111
  const hashes = await getResourceHashes(viewResponse);
107
112
  headers[headerName] = (0, import_content_security_policy.default)(headers[headerName], parsedOptions, hashes);
113
+ const size = byteSize(headers[headerName]);
114
+ if (size > MAX_HEADER_SIZE) {
115
+ import_diagnostics.logger.warn(`[security] Header "${headerName}" size (${size}) exceeds the recommended limit of ${MAX_HEADER_SIZE}.`);
116
+ }
108
117
  }
109
118
  if (options.referrerPolicy === void 0 || typeof options.referrerPolicy === "object") {
110
119
  const headerName = "referrer-policy";
@@ -140,3 +149,6 @@ async function resolveHeaders(viewResponse, options) {
140
149
  }
141
150
  return headers;
142
151
  }
152
+ function byteSize(str) {
153
+ return str ? new import_util.TextEncoder().encode(str).length : 0;
154
+ }
@@ -1,7 +1,12 @@
1
- import { createIntegrityHash, getFeatureFlags, streamToString } from '@lwrjs/shared-utils';
1
+ import { createIntegrityHash, getFeatureFlags, streamToString, normalizeHeaders } from '@lwrjs/shared-utils';
2
2
  import contentSecurityPolicy from './headers/content-security-policy.js';
3
3
  import referrerPolicy from './headers/referrer-policy.js';
4
4
  import strictTransportSecurity from './headers/strict-transport-security.js';
5
+ import { logger } from '@lwrjs/diagnostics';
6
+ import { TextEncoder } from 'util';
7
+ const CSP_HEADER_NAME = 'content-security-policy';
8
+ // Recommend CSP header should be < than 4k. There is a 10k MAX for all headers on AWS API gateway.
9
+ const MAX_HEADER_SIZE = 4096;
5
10
  function getResources(viewDefinition) {
6
11
  const { viewRecord } = viewDefinition;
7
12
  const resources = [];
@@ -51,9 +56,6 @@ async function getResourceHashes(viewResponse) {
51
56
  }
52
57
  return hashes;
53
58
  }
54
- function normalizeHeaders(headers = {}) {
55
- return Object.fromEntries(Object.entries(headers).map(([key, value]) => [key.toLowerCase(), value]));
56
- }
57
59
  function normalizeOptions(options = {}) {
58
60
  for (const [option, value] of Object.entries(options)) {
59
61
  // true values are the same as undefined
@@ -69,21 +71,31 @@ export async function resolveHeaders(viewResponse, options) {
69
71
  if (options.contentSecurityPolicy === undefined || typeof options.contentSecurityPolicy === 'object') {
70
72
  const headerName = options.contentSecurityPolicy?.reportOnly
71
73
  ? 'content-security-policy-report-only'
72
- : 'content-security-policy';
74
+ : CSP_HEADER_NAME;
73
75
  let hashes = [];
74
76
  if (options.contentSecurityPolicy?.resourceHashing === undefined) {
75
77
  hashes = await getResourceHashes(viewResponse);
76
78
  }
77
79
  headers[headerName] = contentSecurityPolicy(headers[headerName], options.contentSecurityPolicy, hashes);
80
+ // Warn if the header size is greater than the recommended limit
81
+ const size = byteSize(headers[headerName]);
82
+ if (size > MAX_HEADER_SIZE) {
83
+ logger.warn(`[security] Header "${headerName}" size (${size}) exceeds the recommended limit of ${MAX_HEADER_SIZE}.`);
84
+ }
78
85
  }
79
86
  if (typeof options.contentSecurityPolicy === 'string') {
80
87
  const parsedOptions = {
81
88
  directives: options.contentSecurityPolicy,
82
89
  useDefault: false,
83
90
  };
84
- const headerName = 'content-security-policy';
91
+ const headerName = CSP_HEADER_NAME;
85
92
  const hashes = await getResourceHashes(viewResponse);
86
93
  headers[headerName] = contentSecurityPolicy(headers[headerName], parsedOptions, hashes);
94
+ // Warn if the header size is greater than the recommended limit
95
+ const size = byteSize(headers[headerName]);
96
+ if (size > MAX_HEADER_SIZE) {
97
+ logger.warn(`[security] Header "${headerName}" size (${size}) exceeds the recommended limit of ${MAX_HEADER_SIZE}.`);
98
+ }
87
99
  }
88
100
  if (options.referrerPolicy === undefined || typeof options.referrerPolicy === 'object') {
89
101
  const headerName = 'referrer-policy';
@@ -125,4 +137,8 @@ export async function resolveHeaders(viewResponse, options) {
125
137
  }
126
138
  return headers;
127
139
  }
140
+ // Get number of bytes from a string. Different char encodings can effect size per char.
141
+ function byteSize(str) {
142
+ return str ? new TextEncoder().encode(str).length : 0;
143
+ }
128
144
  //# sourceMappingURL=headers.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lwrjs/security",
3
- "version": "0.15.0-alpha.8",
3
+ "version": "0.15.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "types": "build/es/index.d.ts",
@@ -32,13 +32,14 @@
32
32
  "build": "tsc -b"
33
33
  },
34
34
  "dependencies": {
35
- "@lwrjs/shared-utils": "0.15.0-alpha.8"
35
+ "@lwrjs/diagnostics": "0.15.0",
36
+ "@lwrjs/shared-utils": "0.15.0"
36
37
  },
37
38
  "devDependencies": {
38
- "@lwrjs/types": "0.15.0-alpha.8"
39
+ "@lwrjs/types": "0.15.0"
39
40
  },
40
41
  "engines": {
41
42
  "node": ">=18.0.0"
42
43
  },
43
- "gitHead": "79fa28a99f3d89eca6a254c53a2a944778203ef2"
44
+ "gitHead": "ee374df435d5342f63e4da126a09461e761837f3"
44
45
  }