Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/libs/ui/style-sheet-manager.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export const CspStyleSheetManager = ({ children }: PropsWithChildren) => (
// styled-components stamps this nonce on its injected <style> tags so they pass the
// Chrome-prod `style-src 'self' 'nonce-<value>'` CSP; value comes from webpack DefinePlugin
// (inert on dev + Firefox/Safari, whose CSP keeps 'unsafe-inline'). See webpack.config.js.
nonce={process.env.CSP_NONCE}
nonce={process.env.CSP_NONCE ?? undefined}
>
{children}
</StyleSheetManager>
Expand Down
17 changes: 11 additions & 6 deletions src/set-webpack-nonce.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,14 @@
// <style> tags with the build-time CSP nonce (Chrome prod style-src is
// 'self' 'nonce-<value>' with no 'unsafe-inline'). Must be imported FIRST in every
// app entry, before any style-loader CSS import or styled-components injection runs.
declare let __webpack_nonce__: string;
// Ambient webpack global: style-loader's runtime reads it off the global scope (not
// from this module), so ESLint can't see the "use", and it must stay `let` since it's
// assigned after declaration rather than initialized inline.
// eslint-disable-next-line prefer-const, @typescript-eslint/no-unused-vars
__webpack_nonce__ = process.env.CSP_NONCE as string;
declare let __webpack_nonce__: string | undefined;
const nonce = process.env.CSP_NONCE;
// Only Chrome-production defines a nonce; elsewhere style-src keeps
// 'unsafe-inline' and style-loader must not stamp a `nonce="null"`.
if (nonce) {
// Ambient webpack global: style-loader's runtime reads it off the global scope (not
// from this module), so ESLint can't see the "use", and it must stay `let` since it's
// assigned after declaration rather than initialized inline.
// eslint-disable-next-line prefer-const, @typescript-eslint/no-unused-vars
__webpack_nonce__ = nonce;
}
14 changes: 9 additions & 5 deletions webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,15 @@ if (fileSystem.existsSync(secretsPath)) {
alias['secrets'] = secretsPath;
}

// One nonce per build. Chrome-production style-src pins to it instead of
// 'unsafe-inline'; the same value is wired to __webpack_nonce__ (style-loader)
// and process.env.CSP_NONCE (styled-components' CspStyleSheetManager) below,
// so the manifest CSP and the injected <style> tags always match.
const CSP_NONCE = crypto.randomBytes(16).toString('base64');
// One nonce per build, and ONLY for Chrome production — that is the sole CSP arm
// that pins style-src to it (see getCSP below). Emitting a random literal into
// Firefox/Safari bundles that never read it makes those builds irreproducible,
// which AMO source review requires (it rebuilds and compares byte-for-byte).
// Where it does apply, the same value is wired to __webpack_nonce__ (style-loader)
// and process.env.CSP_NONCE (styled-components' CspStyleSheetManager) below, so
// the manifest CSP and the injected <style> tags always match.
const CSP_NONCE =
isChrome && !isDev ? crypto.randomBytes(16).toString('base64') : null;

const getCSP = () => {
// Chrome-production locks <style>/<link> ELEMENTS to the build nonce (style-src,
Expand Down
Loading