@adobe/helix-html-pipeline 6.19.0 → 6.20.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.
- package/CHANGELOG.md +14 -0
- package/package.json +2 -2
- package/src/steps/csp.js +55 -36
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
# [6.20.0](https://github.com/adobe/helix-html-pipeline/compare/v6.19.1...v6.20.0) (2025-02-12)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* Enable CSP with nonce for Helix 5 ([#816](https://github.com/adobe/helix-html-pipeline/issues/816)) ([3f4895f](https://github.com/adobe/helix-html-pipeline/commit/3f4895ffc975ea4c35b3ba139bf0ead56a2d0000))
|
|
7
|
+
|
|
8
|
+
## [6.19.1](https://github.com/adobe/helix-html-pipeline/compare/v6.19.0...v6.19.1) (2025-02-11)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* Revert "feat: Enable CSP with nonce for Helix 5 ([#773](https://github.com/adobe/helix-html-pipeline/issues/773))" ([#815](https://github.com/adobe/helix-html-pipeline/issues/815)) ([60924f0](https://github.com/adobe/helix-html-pipeline/commit/60924f0cb50961496013be87ef76aaab17770b79))
|
|
14
|
+
|
|
1
15
|
# [6.19.0](https://github.com/adobe/helix-html-pipeline/compare/v6.18.4...v6.19.0) (2025-02-11)
|
|
2
16
|
|
|
3
17
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adobe/helix-html-pipeline",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.20.0",
|
|
4
4
|
"description": "Helix HTML Pipeline",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"types": "src/index.d.ts",
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"mdast-util-to-string": "4.0.0",
|
|
58
58
|
"micromark-util-subtokenize": "2.0.4",
|
|
59
59
|
"mime": "4.0.6",
|
|
60
|
-
"parse5
|
|
60
|
+
"parse5": "7.2.1",
|
|
61
61
|
"rehype-format": "5.0.1",
|
|
62
62
|
"rehype-parse": "9.0.1",
|
|
63
63
|
"remark-parse": "11.0.0",
|
package/src/steps/csp.js
CHANGED
|
@@ -9,11 +9,12 @@
|
|
|
9
9
|
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
10
|
* governing permissions and limitations under the License.
|
|
11
11
|
*/
|
|
12
|
-
import crypto from 'crypto';
|
|
13
12
|
import { select } from 'hast-util-select';
|
|
13
|
+
import { Tokenizer } from 'parse5';
|
|
14
14
|
import { remove } from 'unist-util-remove';
|
|
15
|
-
import { RewritingStream } from 'parse5-html-rewriting-stream';
|
|
16
15
|
import { visit } from 'unist-util-visit';
|
|
16
|
+
// eslint-disable-next-line import/no-unresolved
|
|
17
|
+
import cryptoImpl from '#crypto';
|
|
17
18
|
|
|
18
19
|
export const NONCE_AEM = '\'nonce-aem\'';
|
|
19
20
|
|
|
@@ -58,7 +59,7 @@ function shouldApplyNonce(metaCSPText, headersCSPText) {
|
|
|
58
59
|
* @returns {string}
|
|
59
60
|
*/
|
|
60
61
|
function createNonce() {
|
|
61
|
-
return
|
|
62
|
+
return cryptoImpl.randomBytes(18).toString('base64');
|
|
62
63
|
}
|
|
63
64
|
|
|
64
65
|
/**
|
|
@@ -166,47 +167,65 @@ export function contentSecurityPolicyOnCode(state, res) {
|
|
|
166
167
|
const nonce = createNonce();
|
|
167
168
|
let { scriptNonce, styleNonce } = shouldApplyNonce(null, cspHeader);
|
|
168
169
|
|
|
169
|
-
const
|
|
170
|
+
const html = res.body;
|
|
170
171
|
const chunks = [];
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
172
|
+
let lastOffset = 0;
|
|
173
|
+
|
|
174
|
+
const getRawHTML = (token) => html.slice(token.location.startOffset, token.location.endOffset);
|
|
175
|
+
|
|
176
|
+
const tokenizer = new Tokenizer({
|
|
177
|
+
sourceCodeLocationInfo: true,
|
|
178
|
+
}, {
|
|
179
|
+
onStartTag(tag) {
|
|
180
|
+
chunks.push(html.slice(lastOffset, tag.location.startOffset));
|
|
181
|
+
try {
|
|
182
|
+
if (tag.tagName === 'meta'
|
|
183
|
+
&& tag.attrs.find(
|
|
184
|
+
(attr) => attr.name.toLowerCase() === 'http-equiv' && attr.value.toLowerCase() === 'content-security-policy',
|
|
185
|
+
)
|
|
186
|
+
) {
|
|
187
|
+
const contentAttr = tag.attrs.find((attr) => attr.name.toLowerCase() === 'content');
|
|
188
|
+
if (contentAttr) {
|
|
189
|
+
({ scriptNonce, styleNonce } = shouldApplyNonce(contentAttr.value, cspHeader));
|
|
190
|
+
|
|
191
|
+
if (!cspHeader && tag.attrs.find((attr) => attr.name === 'move-as-header' && attr.value === 'true')) {
|
|
192
|
+
res.headers.set('content-security-policy', contentAttr.value.replaceAll(NONCE_AEM, `'nonce-${nonce}'`));
|
|
193
|
+
return; // don't push the chunk so it gets removed from the response body
|
|
194
|
+
}
|
|
195
|
+
chunks.push(getRawHTML(tag).replaceAll(NONCE_AEM, `'nonce-${nonce}'`));
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
185
198
|
}
|
|
186
|
-
chunks.push(rawHTML.replaceAll(NONCE_AEM, `'nonce-${nonce}'`));
|
|
187
|
-
return;
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
199
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
200
|
+
if (scriptNonce && tag.tagName === 'script' && tag.attrs.find((attr) => attr.name === 'nonce' && attr.value === 'aem')) {
|
|
201
|
+
chunks.push(getRawHTML(tag).replace(/nonce="aem"/i, `nonce="${nonce}"`));
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
195
204
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
205
|
+
if (styleNonce && (tag.tagName === 'style' || tag.tagName === 'link') && tag.attrs.find((attr) => attr.name === 'nonce' && attr.value === 'aem')) {
|
|
206
|
+
chunks.push(getRawHTML(tag).replace(/nonce="aem"/i, `nonce="${nonce}"`));
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
200
209
|
|
|
201
|
-
|
|
210
|
+
chunks.push(getRawHTML(tag));
|
|
211
|
+
} finally {
|
|
212
|
+
lastOffset = tag.location.endOffset;
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
// no-op callbacks. onStartTag will take care of these
|
|
216
|
+
onComment(_) {},
|
|
217
|
+
onDoctype(_) {},
|
|
218
|
+
onEndTag(_) {},
|
|
219
|
+
onEof(_) {},
|
|
220
|
+
onCharacter(_) {},
|
|
221
|
+
onNullCharacter(_) {},
|
|
222
|
+
onWhitespaceCharacter(_) {},
|
|
223
|
+
onParseError(_) {},
|
|
202
224
|
});
|
|
203
225
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
});
|
|
226
|
+
tokenizer.write(html);
|
|
227
|
+
chunks.push(html.slice(lastOffset));
|
|
207
228
|
|
|
208
|
-
rewriter.write(res.body);
|
|
209
|
-
rewriter.end();
|
|
210
229
|
res.body = chunks.join('');
|
|
211
230
|
if (cspHeader) {
|
|
212
231
|
res.headers.set('content-security-policy', cspHeader.replaceAll(NONCE_AEM, `'nonce-${nonce}'`));
|