Skip to content

Commit 7d6d684

Browse files
authored
feat(runtime): updates nonce fallback to use meta tag instead of window (#3955)
* feat(runtime): updates nonce fallback to use meta tag instead of window This commit updates our CSP nonce support logic to allow implementers to leverage a meta tag in the DOM head for setting nonce values during the Stencil runtime rather than pulling the value off of the global window object * fix(): PR feedback * feat(utils): update return type fallback to `undefined` only
1 parent c3a8da8 commit 7d6d684

10 files changed

+65
-11
lines changed

src/client/client-patch-browser.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { BUILD, NAMESPACE } from '@app-data';
22
import { consoleDevInfo, doc, H, plt, promiseResolve, win } from '@platform';
3-
import { getDynamicImportFunction } from '@utils';
3+
import { getDynamicImportFunction, queryNonceMetaTagContent } from '@utils';
44

55
import type * as d from '../declarations';
66

@@ -106,7 +106,7 @@ const patchDynamicImport = (base: string, orgScriptElm: HTMLScriptElement) => {
106106
);
107107

108108
// Apply CSP nonce to the script tag if it exists
109-
const nonce = plt.$nonce$ ?? (window as any).nonce;
109+
const nonce = plt.$nonce$ ?? queryNonceMetaTagContent(doc);
110110
if (nonce != null) {
111111
script.setAttribute('nonce', nonce);
112112
}

src/compiler/output-targets/dist-custom-elements/custom-elements-types.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,8 @@ const generateCustomElementsTypesOutput = async (
8484
`/**`,
8585
` * Used to specify a nonce value that corresponds with an application's CSP.`,
8686
` * When set, the nonce will be added to all dynamically created script and style tags at runtime.`,
87-
` * Alternatively, the nonce value can be set on the window object (window.nonce) which`,
87+
` * Alternatively, the nonce value can be set on a meta tag in the DOM head`,
88+
` * (<meta name="csp-nonce" content="{ nonce value here }" />) which`,
8889
` * will result in the same behavior.`,
8990
` */`,
9091
`export declare const setNonce: (nonce: string) => void`,

src/compiler/output-targets/output-lazy-loader.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,8 @@ export declare function applyPolyfills(): Promise<void>;
9393
/**
9494
* Used to specify a nonce value that corresponds with an application's CSP.
9595
* When set, the nonce will be added to all dynamically created script and style tags at runtime.
96-
* Alternatively, the nonce value can be set on the window object (window.nonce) which
96+
* Alternatively, the nonce value can be set on a meta tag in the DOM head
97+
* (<meta name="csp-nonce" content="{ nonce value here }" />) which
9798
* will result in the same behavior.
9899
*/
99100
export declare function setNonce(nonce: string): void;

src/compiler/output-targets/test/custom-elements-types.spec.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,8 @@ describe('Custom Elements Typedef generation', () => {
8787
'/**',
8888
` * Used to specify a nonce value that corresponds with an application's CSP.`,
8989
' * When set, the nonce will be added to all dynamically created script and style tags at runtime.',
90-
' * Alternatively, the nonce value can be set on the window object (window.nonce) which',
90+
' * Alternatively, the nonce value can be set on a meta tag in the DOM head',
91+
' * (<meta name="csp-nonce" content="{ nonce value here }" />) which',
9192
' * will result in the same behavior.',
9293
' */',
9394
'export declare const setNonce: (nonce: string) => void',

src/declarations/stencil-public-runtime.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -302,8 +302,8 @@ export declare function setAssetPath(path: string): string;
302302
* Used to specify a nonce value that corresponds with an application's
303303
* [Content Security Policy (CSP)](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP).
304304
* When set, the nonce will be added to all dynamically created script and style tags at runtime.
305-
* Alternatively, the nonce value can be set on the window object (window.nonce) and
306-
* will result in the same behavior.
305+
* Alternatively, the nonce value can be set on a `meta` tag in the DOM head
306+
* (<meta name="csp-nonce" content="{ nonce value here }" />) and will result in the same behavior.
307307
* @param nonce The value to be used for the nonce attribute.
308308
*/
309309
export declare function setNonce(nonce: string): void;

src/runtime/bootstrap-lazy.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { BUILD } from '@app-data';
22
import { doc, getHostRef, plt, registerHost, supportsShadow, win } from '@platform';
3-
import { CMP_FLAGS } from '@utils';
3+
import { CMP_FLAGS, queryNonceMetaTagContent } from '@utils';
44

55
import type * as d from '../declarations';
66
import { connectedCallback } from './connected-callback';
@@ -169,7 +169,7 @@ export const bootstrapLazy = (lazyBundles: d.LazyBundlesRuntimeData, options: d.
169169
visibilityStyle.setAttribute('data-styles', '');
170170

171171
// Apply CSP nonce to the style tag if it exists
172-
const nonce = plt.$nonce$ ?? (window as any).nonce;
172+
const nonce = plt.$nonce$ ?? queryNonceMetaTagContent(doc);
173173
if (nonce != null) {
174174
visibilityStyle.setAttribute('nonce', nonce);
175175
}

src/runtime/styles.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { BUILD } from '@app-data';
22
import { doc, plt, styles, supportsConstructableStylesheets, supportsShadow } from '@platform';
3-
import { CMP_FLAGS } from '@utils';
3+
import { CMP_FLAGS, queryNonceMetaTagContent } from '@utils';
44

55
import type * as d from '../declarations';
66
import { createTime } from './profile';
@@ -78,7 +78,7 @@ export const addStyle = (
7878
}
7979

8080
// Apply CSP nonce to the style tag if it exists
81-
const nonce = plt.$nonce$ ?? (window as any).nonce;
81+
const nonce = plt.$nonce$ ?? queryNonceMetaTagContent(doc);
8282
if (nonce != null) {
8383
styleElm.setAttribute('nonce', nonce);
8484
}

src/utils/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export * from './logger/logger-typescript';
99
export * from './logger/logger-utils';
1010
export * from './message-utils';
1111
export * from './normalize-path';
12+
export * from './query-nonce-meta-tag-content';
1213
export * from './sourcemaps';
1314
export * from './url-paths';
1415
export * from './util';
+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/**
2+
* Helper method for querying a `meta` tag that contains a nonce value
3+
* out of a DOM's head.
4+
*
5+
* @param doc The DOM containing the `head` to query against
6+
* @returns The content of the meta tag representing the nonce value, or `undefined` if no tag
7+
* exists or the tag has no content.
8+
*/
9+
export function queryNonceMetaTagContent(doc: Document): string | undefined {
10+
return doc.head?.querySelector('meta[name="csp-nonce"]')?.getAttribute('content') ?? undefined;
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { queryNonceMetaTagContent } from '../query-nonce-meta-tag-content';
2+
3+
describe('queryNonceMetaTagContent', () => {
4+
it('should return the nonce value if the tag exists', () => {
5+
const meta = document.createElement('meta');
6+
meta.setAttribute('name', 'csp-nonce');
7+
meta.setAttribute('content', '1234');
8+
document.head.appendChild(meta);
9+
10+
const nonce = queryNonceMetaTagContent(document);
11+
12+
expect(nonce).toEqual('1234');
13+
});
14+
15+
it('should return `undefined` if the tag does not exist', () => {
16+
const nonce = queryNonceMetaTagContent(document);
17+
18+
expect(nonce).toEqual(undefined);
19+
});
20+
21+
it('should return `undefined` if the document does not have a head element', () => {
22+
const head = document.querySelector('head');
23+
head.remove();
24+
25+
const nonce = queryNonceMetaTagContent(document);
26+
27+
expect(nonce).toEqual(undefined);
28+
});
29+
30+
it('should return `undefined` if the tag has no content', () => {
31+
const meta = document.createElement('meta');
32+
meta.setAttribute('name', 'csp-nonce');
33+
document.head.appendChild(meta);
34+
35+
const nonce = queryNonceMetaTagContent(document);
36+
37+
expect(nonce).toEqual(undefined);
38+
});
39+
});

0 commit comments

Comments
 (0)