@adobe/helix-html-pipeline 6.23.0 → 6.24.1

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.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ ## [6.24.1](https://github.com/adobe/helix-html-pipeline/compare/v6.24.0...v6.24.1) (2025-03-28)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * support aem-gcp.page|live internal domain ([f6307bb](https://github.com/adobe/helix-html-pipeline/commit/f6307bb9a3704a5020738b1664a01b5201064f5a))
7
+
8
+ # [6.24.0](https://github.com/adobe/helix-html-pipeline/compare/v6.23.0...v6.24.0) (2025-03-27)
9
+
10
+
11
+ ### Features
12
+
13
+ * Enable CSP with Nonce in the Report Only Header ([#840](https://github.com/adobe/helix-html-pipeline/issues/840)) ([33ab7e1](https://github.com/adobe/helix-html-pipeline/commit/33ab7e17ca4f63bd0ddf550200a8f0e982451317))
14
+
1
15
  # [6.23.0](https://github.com/adobe/helix-html-pipeline/compare/v6.22.0...v6.23.0) (2025-03-20)
2
16
 
3
17
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adobe/helix-html-pipeline",
3
- "version": "6.23.0",
3
+ "version": "6.24.1",
4
4
  "description": "Helix HTML Pipeline",
5
5
  "main": "src/index.js",
6
6
  "types": "src/index.d.ts",
@@ -54,9 +54,11 @@ const INTERNAL_DOMAINS = [
54
54
  '.aem.page',
55
55
  '.aem-fastly.page',
56
56
  '.aem-cloudflare.page',
57
+ '.aem-gcp.page',
57
58
  '.aem.live',
58
59
  '.aem-fastly.live',
59
60
  '.aem-cloudflare.live',
61
+ '.aem-gcp.live',
60
62
  '.hlx.page',
61
63
  '.hlx-fastly.page',
62
64
  '.hlx-cloudflare.page',
package/src/steps/csp.js CHANGED
@@ -40,17 +40,22 @@ function parseCSP(csp) {
40
40
  /**
41
41
  * Computes where nonces should be applied
42
42
  * @param {string | null | undefined} metaCSPText The actual CSP value from the meta tag
43
- * @param {string | null | undefined} headersCSPText The actual CSP value from the headers
43
+ * @param {string | null | undefined} headerCSPText The actual CSP value from the header
44
+ * @param {string | null | undefined} headerCSPROText The actual CSP value from report-only header
44
45
  * @returns {scriptNonce: boolean, styleNonce: boolean}
45
46
  */
46
- function shouldApplyNonce(metaCSPText, headersCSPText) {
47
+ function shouldApplyNonce(metaCSPText, headerCSPText, headerCSPROText) {
47
48
  const metaBased = parseCSP(metaCSPText);
48
- const headersBased = parseCSP(headersCSPText);
49
+ const headerBased = parseCSP(headerCSPText);
50
+ const headerROBased = parseCSP(headerCSPROText);
51
+
49
52
  return {
50
53
  scriptNonce: metaBased['script-src']?.includes(NONCE_AEM)
51
- || headersBased['script-src']?.includes(NONCE_AEM),
54
+ || headerBased['script-src']?.includes(NONCE_AEM)
55
+ || headerROBased['script-src']?.includes(NONCE_AEM),
52
56
  styleNonce: metaBased['style-src']?.includes(NONCE_AEM)
53
- || headersBased['style-src']?.includes(NONCE_AEM),
57
+ || headerBased['style-src']?.includes(NONCE_AEM)
58
+ || headerROBased['style-src']?.includes(NONCE_AEM),
54
59
  };
55
60
  }
56
61
 
@@ -73,23 +78,36 @@ export function getHeaderCSP(res) {
73
78
  return res.headers?.get('content-security-policy');
74
79
  }
75
80
 
81
+ export function getHeaderCSPRO(res) {
82
+ return res.headers?.get('content-security-policy-report-only');
83
+ }
84
+
76
85
  /**
77
86
  * Apply CSP with nonces on an AST
78
87
  * @param {PipelineResponse} res
79
88
  * @param {Object} tree
80
89
  * @param {Object} metaCSP
81
- * @param {string} headersCSP
90
+ * @param {string} headerCSP
91
+ * @param {string} headerCSPRO
82
92
  */
83
- function createAndApplyNonceOnAST(res, tree, metaCSP, headersCSP) {
93
+ function createAndApplyNonceOnAST(res, tree, metaCSP, headerCSP, headerCSPRO) {
84
94
  const nonce = createNonce();
85
- const { scriptNonce, styleNonce } = shouldApplyNonce(metaCSP?.properties.content, headersCSP);
95
+ const { scriptNonce, styleNonce } = shouldApplyNonce(
96
+ metaCSP?.properties.content,
97
+ headerCSP,
98
+ headerCSPRO,
99
+ );
86
100
 
87
101
  if (metaCSP) {
88
102
  metaCSP.properties.content = metaCSP.properties.content.replaceAll(NONCE_AEM, `'nonce-${nonce}'`);
89
103
  }
90
104
 
91
- if (headersCSP) {
92
- res.headers.set('content-security-policy', headersCSP.replaceAll(NONCE_AEM, `'nonce-${nonce}'`));
105
+ if (headerCSP) {
106
+ res.headers.set('content-security-policy', headerCSP.replaceAll(NONCE_AEM, `'nonce-${nonce}'`));
107
+ }
108
+
109
+ if (headerCSPRO) {
110
+ res.headers.set('content-security-policy-report-only', headerCSPRO.replaceAll(NONCE_AEM, `'nonce-${nonce}'`));
93
111
  }
94
112
 
95
113
  visit(tree, (node) => {
@@ -130,15 +148,18 @@ export function getMetaCSP(tree) {
130
148
  export function contentSecurityPolicyOnAST(res, tree) {
131
149
  const metaCSP = getMetaCSP(tree);
132
150
  const headersCSP = getHeaderCSP(res);
133
-
134
- if (!metaCSP && !headersCSP) {
151
+ const headersCSPRO = getHeaderCSPRO(res);
152
+ if (!metaCSP && !headersCSP && !headersCSPRO) {
135
153
  // No CSP defined
136
154
  return;
137
155
  }
138
156
 
139
157
  // CSP with nonce
140
- if (metaCSP?.properties.content.includes(NONCE_AEM) || headersCSP?.includes(NONCE_AEM)) {
141
- createAndApplyNonceOnAST(res, tree, metaCSP, headersCSP);
158
+ if (metaCSP?.properties.content.includes(NONCE_AEM)
159
+ || headersCSP?.includes(NONCE_AEM)
160
+ || headersCSPRO?.includes(NONCE_AEM)
161
+ ) {
162
+ createAndApplyNonceOnAST(res, tree, metaCSP, headersCSP, headersCSPRO);
142
163
  }
143
164
 
144
165
  if (metaCSP?.properties['move-as-header'] === 'true') {
@@ -159,15 +180,17 @@ export function contentSecurityPolicyOnCode(state, res) {
159
180
  }
160
181
 
161
182
  const cspHeader = getHeaderCSP(res);
183
+ const cspROHeader = getHeaderCSPRO(res);
162
184
  if (!(
163
185
  cspHeader?.includes(NONCE_AEM)
186
+ || cspROHeader?.includes(NONCE_AEM)
164
187
  || (checkResponseBodyForMetaBasedCSP(res) && checkResponseBodyForAEMNonce(res))
165
188
  )) {
166
189
  return;
167
190
  }
168
191
 
169
192
  const nonce = createNonce();
170
- let { scriptNonce, styleNonce } = shouldApplyNonce(null, cspHeader);
193
+ let { scriptNonce, styleNonce } = shouldApplyNonce(null, cspHeader, cspROHeader);
171
194
 
172
195
  const html = res.body;
173
196
  const chunks = [];
@@ -232,4 +255,8 @@ export function contentSecurityPolicyOnCode(state, res) {
232
255
  if (cspHeader) {
233
256
  res.headers.set('content-security-policy', cspHeader.replaceAll(NONCE_AEM, `'nonce-${nonce}'`));
234
257
  }
258
+
259
+ if (cspROHeader) {
260
+ res.headers.set('content-security-policy-report-only', cspROHeader.replaceAll(NONCE_AEM, `'nonce-${nonce}'`));
261
+ }
235
262
  }