diff --git a/src/steps/csp.js b/src/steps/csp.js
index d6e48658..991a7a5a 100644
--- a/src/steps/csp.js
+++ b/src/steps/csp.js
@@ -56,10 +56,19 @@ function shouldApplyNonce(metaCSPText, headersCSPText) {
/**
* Create a nonce for CSP
+ * Constraints:
+ * - we can only use web crypto functions to be compatible with cloudflare.
+ * - we need at least 128bits of entropy, according to the documentation.
* @returns {string}
*/
function createNonce() {
- return cryptoImpl.randomBytes(18).toString('base64');
+ // 1 UUIDv4 = 122 bits entropy + 4 hex characters/16 bits from second UUIDv4 = 138 bits entropy
+ const randomHex = cryptoImpl.randomUUID().replaceAll('-', '')
+ + cryptoImpl.randomUUID().slice(0, 4);
+
+ // transform into byte array before encoding for compression
+ const byteArray = new Uint8Array(randomHex.match(/.{2}/g).map((byte) => parseInt(byte, 16)));
+ return btoa(String.fromCharCode(...byteArray));
}
/**
diff --git a/test/rendering.test.js b/test/rendering.test.js
index a70ff1f5..061610f8 100644
--- a/test/rendering.test.js
+++ b/test/rendering.test.js
@@ -496,198 +496,139 @@ describe('Rendering', () => {
});
});
- describe('Miscellaneous', () => {
- it('sets the surrogate-keys correctly', async () => {
- const resp = await testRender('page-block-empty-cols');
- assert.strictEqual(resp.headers.get('x-surrogate-key'), 'rDFj9gBeGHx_FI2T foo-id_metadata super-test--helix-pages--adobe_head foo-id');
- });
-
- it('sets the surrogate-keys correctly for plain', async () => {
- const resp = await testRenderPlain('one-section');
- assert.strictEqual(resp.headers.get('x-surrogate-key'), 'oHjg_WDu20CBS4rD foo-id_metadata super-test--helix-pages--adobe_head foo-id');
- });
-
- it('sets the surrogate-keys correctly for index.plain.html', async () => {
- const resp = await testRenderPlain('one-section/index', 'one-section/index');
- assert.strictEqual(resp.headers.get('x-surrogate-key'), 'Vp-I6NB8PSor1sI6 foo-id_metadata super-test--helix-pages--adobe_head foo-id');
- });
-
- it('renders the fedpub header correctly', async () => {
- await testRenderPlain('fedpub-header');
- });
-
- it('renders styling test document correctly', async () => {
- await testRenderPlain('styling');
- });
+ describe('csp', () => {
+ const originalRandomUUID = cryptoImpl.randomUUID;
+ const uuids = [
+ '7241346e-6430-6d6d-6d72-41346e64306d',
+ '6d6d0000-0000-0000-0000-000000000000',
+ ];
+ let uuidIdx = 0;
- it('renders document with gridtables correctly', async () => {
- await testRender('page-with-gridtables');
+ beforeEach(() => {
+ uuidIdx = 0;
+ // eslint-disable-next-line no-plusplus
+ cryptoImpl.randomUUID = () => uuids[uuidIdx++];
});
- it('renders document with many image references quickly', async () => {
- await testRender('gt-many-refs');
+ afterEach(() => {
+ cryptoImpl.randomUUID = originalRandomUUID;
});
- it('renders header correctly if head is missing', async () => {
+ it('renders csp nonce meta', async () => {
config = {
...DEFAULT_CONFIG,
- head: null,
+ head: {
+ // eslint-disable-next-line quotes
+ html: `\n`
+ + '\n'
+ + '\n'
+ + '\n'
+ + '\n'
+ + '',
+ },
};
- await testRender('no-head-html', 'html');
+ const { headers } = await testRender('nonce-meta', 'html');
+ assert.ok(!headers.get('content-security-policy'));
});
- it('renders header correctly if head has linefeed', async () => {
+ it('renders csp nonce headers', async () => {
+ cryptoImpl.randomBytes = () => Buffer.from('rA4nd0mmmrA4nd0mmm');
config = {
...DEFAULT_CONFIG,
+ headers: {
+ '/**': [
+ {
+ key: 'Content-Security-Policy',
+ // eslint-disable-next-line quotes
+ value: `script-src 'nonce-aem' 'strict-dynamic'; style-src 'nonce-aem'; base-uri 'self'; object-src 'none';`,
+ },
+ ],
+ },
head: {
- html: '\n',
+ html: '\n'
+ + '\n'
+ + '\n'
+ + '\n'
+ + '',
},
};
- await testRender('head-with-script', 'html');
- });
-
- it('renders csp nonce meta', async () => {
- const originalRandomBytes = cryptoImpl.randomBytes;
- try {
- cryptoImpl.randomBytes = () => Buffer.from('rA4nd0mmmrA4nd0mmm');
- config = {
- ...DEFAULT_CONFIG,
- head: {
- // eslint-disable-next-line quotes
- html: `\n`
- + '\n'
- + '\n'
- + '\n'
- + '\n'
- + '',
- },
- };
- const { headers } = await testRender('nonce-meta', 'html');
- assert.ok(!headers.get('content-security-policy'));
- } finally {
- cryptoImpl.randomBytes = originalRandomBytes;
- }
- });
-
- it('renders csp nonce headers', async () => {
- const originalRandomBytes = cryptoImpl.randomBytes;
- try {
- cryptoImpl.randomBytes = () => Buffer.from('rA4nd0mmmrA4nd0mmm');
- config = {
- ...DEFAULT_CONFIG,
- headers: {
- '/**': [
- {
- key: 'Content-Security-Policy',
- // eslint-disable-next-line quotes
- value: `script-src 'nonce-aem' 'strict-dynamic'; style-src 'nonce-aem'; base-uri 'self'; object-src 'none';`,
- },
- ],
- },
- head: {
- html: '\n'
- + '\n'
- + '\n'
- + '\n'
- + '',
- },
- };
- const { headers } = await testRender('nonce-headers', 'html');
- // eslint-disable-next-line quotes
- assert.strictEqual(headers.get('content-security-policy'), `script-src 'nonce-ckE0bmQwbW1tckE0bmQwbW1t' 'strict-dynamic'; style-src 'nonce-ckE0bmQwbW1tckE0bmQwbW1t'; base-uri 'self'; object-src 'none';`);
- } finally {
- cryptoImpl.randomBytes = originalRandomBytes;
- }
+ const { headers } = await testRender('nonce-headers', 'html');
+ // eslint-disable-next-line quotes
+ assert.strictEqual(headers.get('content-security-policy'), `script-src 'nonce-ckE0bmQwbW1tckE0bmQwbW1t' 'strict-dynamic'; style-src 'nonce-ckE0bmQwbW1tckE0bmQwbW1t'; base-uri 'self'; object-src 'none';`);
});
it('renders csp nonce metadata - move as header', async () => {
- const originalRandomBytes = cryptoImpl.randomBytes;
- try {
- cryptoImpl.randomBytes = () => Buffer.from('rA4nd0mmmrA4nd0mmm');
- config = {
- ...DEFAULT_CONFIG,
- head: {
- // eslint-disable-next-line quotes
- html: `\n`
- + '\n'
- + '\n'
- + '\n'
- + '\n'
- + '',
- },
- };
- const { headers } = await testRender('nonce-meta-move-as-header', 'html');
- // eslint-disable-next-line quotes
- assert.strictEqual(headers.get('content-security-policy'), `script-src 'nonce-ckE0bmQwbW1tckE0bmQwbW1t' 'strict-dynamic'; style-src 'nonce-ckE0bmQwbW1tckE0bmQwbW1t'; base-uri 'self'; object-src 'none';`);
- } finally {
- cryptoImpl.randomBytes = originalRandomBytes;
- }
+ cryptoImpl.randomBytes = () => Buffer.from('rA4nd0mmmrA4nd0mmm');
+ config = {
+ ...DEFAULT_CONFIG,
+ head: {
+ // eslint-disable-next-line quotes
+ html: `\n`
+ + '\n'
+ + '\n'
+ + '\n'
+ + '\n'
+ + '',
+ },
+ };
+ const { headers } = await testRender('nonce-meta-move-as-header', 'html');
+ // eslint-disable-next-line quotes
+ assert.strictEqual(headers.get('content-security-policy'), `script-src 'nonce-ckE0bmQwbW1tckE0bmQwbW1t' 'strict-dynamic'; style-src 'nonce-ckE0bmQwbW1tckE0bmQwbW1t'; base-uri 'self'; object-src 'none';`);
});
it('renders csp nonce headers and metadata - move as header', async () => {
- const originalRandomBytes = cryptoImpl.randomBytes;
- try {
- cryptoImpl.randomBytes = () => Buffer.from('rA4nd0mmmrA4nd0mmm');
- config = {
- ...DEFAULT_CONFIG,
- headers: {
- '/**': [
- {
- key: 'content-security-policy',
- value: 'frame-ancestors \'self\'',
- },
- ],
- },
- head: {
- // eslint-disable-next-line quotes
- html: `\n`
- + '\n'
- + '\n'
- + '\n'
- + '\n'
- + '',
- },
- };
- const { headers } = await testRender('nonce-headers-meta', 'html');
- assert.strictEqual(headers.get('content-security-policy'), 'frame-ancestors \'self\'');
- } finally {
- cryptoImpl.randomBytes = originalRandomBytes;
- }
+ cryptoImpl.randomBytes = () => Buffer.from('rA4nd0mmmrA4nd0mmm');
+ config = {
+ ...DEFAULT_CONFIG,
+ headers: {
+ '/**': [
+ {
+ key: 'content-security-policy',
+ value: 'frame-ancestors \'self\'',
+ },
+ ],
+ },
+ head: {
+ // eslint-disable-next-line quotes
+ html: `\n`
+ + '\n'
+ + '\n'
+ + '\n'
+ + '\n'
+ + '',
+ },
+ };
+ const { headers } = await testRender('nonce-headers-meta', 'html');
+ assert.strictEqual(headers.get('content-security-policy'), 'frame-ancestors \'self\'');
});
it('renders csp nonce script only', async () => {
- const originalRandomBytes = cryptoImpl.randomBytes;
- try {
- cryptoImpl.randomBytes = () => Buffer.from('rA4nd0mmmrA4nd0mmm');
- config = {
- ...DEFAULT_CONFIG,
- headers: {
- '/**': [
- {
- key: 'content-security-policy',
- // eslint-disable-next-line quotes
- value: `script-src 'nonce-aem' 'strict-dynamic'; base-uri 'self'; object-src 'none';`,
- },
- ],
- },
- head: {
- html: '\n'
- + '\n'
- + '\n'
- + '\n'
- + '',
- },
- };
- const { headers } = await testRender('nonce-script-only', 'html');
- // eslint-disable-next-line quotes
- assert.strictEqual(headers.get('content-security-policy'), `script-src 'nonce-ckE0bmQwbW1tckE0bmQwbW1t' 'strict-dynamic'; base-uri 'self'; object-src 'none';`);
- } finally {
- cryptoImpl.randomBytes = originalRandomBytes;
- }
+ cryptoImpl.randomBytes = () => Buffer.from('rA4nd0mmmrA4nd0mmm');
+ config = {
+ ...DEFAULT_CONFIG,
+ headers: {
+ '/**': [
+ {
+ key: 'content-security-policy',
+ // eslint-disable-next-line quotes
+ value: `script-src 'nonce-aem' 'strict-dynamic'; base-uri 'self'; object-src 'none';`,
+ },
+ ],
+ },
+ head: {
+ html: '\n'
+ + '\n'
+ + '\n'
+ + '\n'
+ + '',
+ },
+ };
+ const { headers } = await testRender('nonce-script-only', 'html');
+ // eslint-disable-next-line quotes
+ assert.strictEqual(headers.get('content-security-policy'), `script-src 'nonce-ckE0bmQwbW1tckE0bmQwbW1t' 'strict-dynamic'; base-uri 'self'; object-src 'none';`);
});
it('does not alter csp nonce if already set to a different value by meta', async () => {
- cryptoImpl.randomBytes = () => Buffer.from('rA4nd0mmmrA4nd0mmm');
config = {
...DEFAULT_CONFIG,
head: {
@@ -705,7 +646,6 @@ describe('Rendering', () => {
});
it('does not alter csp nonce if already set to a different value by header', async () => {
- cryptoImpl.randomBytes = () => Buffer.from('rA4nd0mmmrA4nd0mmm');
config = {
...DEFAULT_CONFIG,
headers: {
@@ -730,6 +670,109 @@ describe('Rendering', () => {
assert.strictEqual(headers.get('content-security-policy'), `script-src 'nonce-r4nD0m' 'strict-dynamic'; style-src 'nonce-r4nD0m'; base-uri 'self'; object-src 'none';`);
});
+ it('renders static html from the codebus and applies csp from header with nonce', async () => {
+ config = {
+ ...DEFAULT_CONFIG,
+ headers: {
+ '/**': [
+ {
+ key: 'content-security-policy',
+ // eslint-disable-next-line quotes
+ value: `script-src 'nonce-aem' 'strict-dynamic'; style-src 'nonce-aem'; base-uri 'self'; object-src 'none';`,
+ },
+ ],
+ },
+ };
+
+ const { headers } = await testRenderCode(new URL('https://helix-pages.com/static-nonce-header.html'));
+ // eslint-disable-next-line quotes
+ assert.strictEqual(headers.get('content-security-policy'), `script-src 'nonce-ckE0bmQwbW1tckE0bmQwbW1t' 'strict-dynamic'; style-src 'nonce-ckE0bmQwbW1tckE0bmQwbW1t'; base-uri 'self'; object-src 'none';`);
+ });
+
+ it('renders static html from the codebus and applies csp from meta with nonce', async () => {
+ const { headers } = await testRenderCode(new URL('https://helix-pages.com/static-nonce-meta.html'));
+ assert.ok(!headers.get('content-security-policy'));
+ });
+
+ it('renders static html from the codebus and applies csp from meta with nonce moved as header', async () => {
+ cryptoImpl.randomBytes = () => Buffer.from('rA4nd0mmmrA4nd0mmm');
+ const { headers } = await testRenderCode(new URL('https://helix-pages.com/static-nonce-meta-move-as-header.html'));
+ // eslint-disable-next-line quotes
+ assert.strictEqual(headers.get('content-security-policy'), `script-src 'nonce-ckE0bmQwbW1tckE0bmQwbW1t' 'strict-dynamic'; style-src 'nonce-ckE0bmQwbW1tckE0bmQwbW1t'; base-uri 'self'; object-src 'none';`);
+ });
+
+ it('renders static html from the codebus and applies csp with different nonce without altering', async () => {
+ const { headers } = await testRenderCode(new URL('https://helix-pages.com/static-nonce-meta-different.html'));
+ assert.ok(!headers.get('content-security-policy'));
+ });
+
+ it('renders static html from the codebus and applies csp without altering the HTML structure', async () => {
+ cryptoImpl.randomBytes = () => Buffer.from('rA4nd0mmmrA4nd0mmm');
+ const { headers } = await testRenderCode(new URL('https://helix-pages.com/static-nonce-fragment.html'));
+ assert.ok(!headers.get('content-security-policy'));
+ });
+
+ it('renders 404 html from codebus and applies csp', async () => {
+ loader
+ .rewrite('404.html', 'super-test/404-csp-nonce.html')
+ .headers('super-test/404-test.html', 'x-amz-meta-x-source-last-modified', 'Mon, 12 Oct 2009 17:50:00 GMT');
+ cryptoImpl.randomBytes = () => Buffer.from('rA4nd0mmmrA4nd0mmm');
+ const { headers } = await testRenderCode('not-found', 404, '404-csp-nonce', true);
+ // eslint-disable-next-line quotes
+ assert.strictEqual(headers.get('content-security-policy'), `script-src 'nonce-ckE0bmQwbW1tckE0bmQwbW1t' 'strict-dynamic'; base-uri 'self'; object-src 'none';`);
+ });
+ });
+
+ describe('Miscellaneous', () => {
+ it('sets the surrogate-keys correctly', async () => {
+ const resp = await testRender('page-block-empty-cols');
+ assert.strictEqual(resp.headers.get('x-surrogate-key'), 'rDFj9gBeGHx_FI2T foo-id_metadata super-test--helix-pages--adobe_head foo-id');
+ });
+
+ it('sets the surrogate-keys correctly for plain', async () => {
+ const resp = await testRenderPlain('one-section');
+ assert.strictEqual(resp.headers.get('x-surrogate-key'), 'oHjg_WDu20CBS4rD foo-id_metadata super-test--helix-pages--adobe_head foo-id');
+ });
+
+ it('sets the surrogate-keys correctly for index.plain.html', async () => {
+ const resp = await testRenderPlain('one-section/index', 'one-section/index');
+ assert.strictEqual(resp.headers.get('x-surrogate-key'), 'Vp-I6NB8PSor1sI6 foo-id_metadata super-test--helix-pages--adobe_head foo-id');
+ });
+
+ it('renders the fedpub header correctly', async () => {
+ await testRenderPlain('fedpub-header');
+ });
+
+ it('renders styling test document correctly', async () => {
+ await testRenderPlain('styling');
+ });
+
+ it('renders document with gridtables correctly', async () => {
+ await testRender('page-with-gridtables');
+ });
+
+ it('renders document with many image references quickly', async () => {
+ await testRender('gt-many-refs');
+ });
+
+ it('renders header correctly if head is missing', async () => {
+ config = {
+ ...DEFAULT_CONFIG,
+ head: null,
+ };
+ await testRender('no-head-html', 'html');
+ });
+
+ it('renders header correctly if head has linefeed', async () => {
+ config = {
+ ...DEFAULT_CONFIG,
+ head: {
+ html: '\n',
+ },
+ };
+ await testRender('head-with-script', 'html');
+ });
+
it('renders 404 if content not found', async () => {
await testRender('not-found', 'html');
// preview (code coverage)
@@ -1025,84 +1068,5 @@ describe('Rendering', () => {
link: '; rel=modulepreload; as=script; crossorigin=use-credentials',
});
});
-
- it('renders static html from the codebus and applies csp from header with nonce', async () => {
- const originalRandomBytes = cryptoImpl.randomBytes;
- try {
- cryptoImpl.randomBytes = () => Buffer.from('rA4nd0mmmrA4nd0mmm');
- config = {
- ...DEFAULT_CONFIG,
- headers: {
- '/**': [
- {
- key: 'content-security-policy',
- // eslint-disable-next-line quotes
- value: `script-src 'nonce-aem' 'strict-dynamic'; style-src 'nonce-aem'; base-uri 'self'; object-src 'none';`,
- },
- ],
- },
- };
-
- const { headers } = await testRenderCode(new URL('https://helix-pages.com/static-nonce-header.html'));
- // eslint-disable-next-line quotes
- assert.strictEqual(headers.get('content-security-policy'), `script-src 'nonce-ckE0bmQwbW1tckE0bmQwbW1t' 'strict-dynamic'; style-src 'nonce-ckE0bmQwbW1tckE0bmQwbW1t'; base-uri 'self'; object-src 'none';`);
- } finally {
- cryptoImpl.randomBytes = originalRandomBytes;
- }
- });
-
- it('renders static html from the codebus and applies csp from meta with nonce', async () => {
- const originalRandomBytes = cryptoImpl.randomBytes;
- try {
- cryptoImpl.randomBytes = () => Buffer.from('rA4nd0mmmrA4nd0mmm');
- const { headers } = await testRenderCode(new URL('https://helix-pages.com/static-nonce-meta.html'));
- assert.ok(!headers.get('content-security-policy'));
- } finally {
- cryptoImpl.randomBytes = originalRandomBytes;
- }
- });
-
- it('renders static html from the codebus and applies csp from meta with nonce moved as header', async () => {
- const originalRandomBytes = cryptoImpl.randomBytes;
- try {
- cryptoImpl.randomBytes = () => Buffer.from('rA4nd0mmmrA4nd0mmm');
- const { headers } = await testRenderCode(new URL('https://helix-pages.com/static-nonce-meta-move-as-header.html'));
- // eslint-disable-next-line quotes
- assert.strictEqual(headers.get('content-security-policy'), `script-src 'nonce-ckE0bmQwbW1tckE0bmQwbW1t' 'strict-dynamic'; style-src 'nonce-ckE0bmQwbW1tckE0bmQwbW1t'; base-uri 'self'; object-src 'none';`);
- } finally {
- cryptoImpl.randomBytes = originalRandomBytes;
- }
- });
-
- it('renders static html from the codebus and applies csp with different nonce without altering', async () => {
- const { headers } = await testRenderCode(new URL('https://helix-pages.com/static-nonce-meta-different.html'));
- assert.ok(!headers.get('content-security-policy'));
- });
-
- it('renders static html from the codebus and applies csp without altering the HTML structure', async () => {
- const originalRandomBytes = cryptoImpl.randomBytes;
- try {
- cryptoImpl.randomBytes = () => Buffer.from('rA4nd0mmmrA4nd0mmm');
- const { headers } = await testRenderCode(new URL('https://helix-pages.com/static-nonce-fragment.html'));
- assert.ok(!headers.get('content-security-policy'));
- } finally {
- cryptoImpl.randomBytes = originalRandomBytes;
- }
- });
-
- it('renders 404 html from codebus and applies csp', async () => {
- loader
- .rewrite('404.html', 'super-test/404-csp-nonce.html')
- .headers('super-test/404-test.html', 'x-amz-meta-x-source-last-modified', 'Mon, 12 Oct 2009 17:50:00 GMT');
- const originalRandomBytes = cryptoImpl.randomBytes;
- try {
- cryptoImpl.randomBytes = () => Buffer.from('rA4nd0mmmrA4nd0mmm');
- const { headers } = await testRenderCode('not-found', 404, '404-csp-nonce', true);
- // eslint-disable-next-line quotes
- assert.strictEqual(headers.get('content-security-policy'), `script-src 'nonce-ckE0bmQwbW1tckE0bmQwbW1t' 'strict-dynamic'; base-uri 'self'; object-src 'none';`);
- } finally {
- cryptoImpl.randomBytes = originalRandomBytes;
- }
- });
});
});