From b0cd1bac18cb6bb454545cf6e0333fc82fcbc445 Mon Sep 17 00:00:00 2001
From: Tymoteusz Czech <2625371+Tymek@users.noreply.github.com>
Date: Tue, 6 Jun 2023 12:31:09 +0200
Subject: [PATCH] add CSP nonce to styles
---
frontend/index.html | 1 +
frontend/src/assets/img/logo.svg | 10 +++---
frontend/src/assets/img/logoDark.svg | 9 ++++-
frontend/src/assets/img/logoDarkWithText.svg | 26 +++++++++++++-
.../img/logoWhiteTransparentHorizontal.svg | 34 ++++++++++++++++++-
frontend/src/assets/img/logoWithWhiteText.svg | 26 +++++++++++++-
frontend/src/themes/ThemeProvider.tsx | 5 +++
src/lib/app.ts | 6 ++--
src/lib/middleware/secure-headers.ts | 7 ++--
src/lib/util/load-index-html.ts | 9 ++++-
src/lib/util/rewriteHTML.ts | 10 ++++++
.../img/Symbol_DarkBlue_Transparent.svg | 10 +++++-
.../static/img/Symbol_White_Transparent.svg | 11 +++++-
website/static/img/logo.svg | 20 ++++++++++-
14 files changed, 167 insertions(+), 17 deletions(-)
diff --git a/frontend/index.html b/frontend/index.html
index a74fbe07c1..7629c273ca 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -7,6 +7,7 @@
+
Unleash
diff --git a/frontend/src/assets/img/logo.svg b/frontend/src/assets/img/logo.svg
index 843669fff4..761ab03730 100644
--- a/frontend/src/assets/img/logo.svg
+++ b/frontend/src/assets/img/logo.svg
@@ -1,7 +1,7 @@
diff --git a/frontend/src/assets/img/logoDark.svg b/frontend/src/assets/img/logoDark.svg
index b79f917031..306c574001 100644
--- a/frontend/src/assets/img/logoDark.svg
+++ b/frontend/src/assets/img/logoDark.svg
@@ -1 +1,8 @@
-
\ No newline at end of file
+
diff --git a/frontend/src/assets/img/logoDarkWithText.svg b/frontend/src/assets/img/logoDarkWithText.svg
index 51862398e8..e590964ba7 100644
--- a/frontend/src/assets/img/logoDarkWithText.svg
+++ b/frontend/src/assets/img/logoDarkWithText.svg
@@ -1 +1,25 @@
-
\ No newline at end of file
+
diff --git a/frontend/src/assets/img/logoWhiteTransparentHorizontal.svg b/frontend/src/assets/img/logoWhiteTransparentHorizontal.svg
index 8f78bf75a2..7ce72f39f9 100644
--- a/frontend/src/assets/img/logoWhiteTransparentHorizontal.svg
+++ b/frontend/src/assets/img/logoWhiteTransparentHorizontal.svg
@@ -1 +1,33 @@
-
\ No newline at end of file
+
diff --git a/frontend/src/assets/img/logoWithWhiteText.svg b/frontend/src/assets/img/logoWithWhiteText.svg
index c966754f9e..705deff71d 100644
--- a/frontend/src/assets/img/logoWithWhiteText.svg
+++ b/frontend/src/assets/img/logoWithWhiteText.svg
@@ -1 +1,25 @@
-
\ No newline at end of file
+
diff --git a/frontend/src/themes/ThemeProvider.tsx b/frontend/src/themes/ThemeProvider.tsx
index ed50d60300..a226dce4af 100644
--- a/frontend/src/themes/ThemeProvider.tsx
+++ b/frontend/src/themes/ThemeProvider.tsx
@@ -5,9 +5,14 @@ import createCache from '@emotion/cache';
import { CacheProvider } from '@emotion/react';
import { useThemeMode } from 'hooks/useThemeMode';
+const nonce =
+ document.querySelector('meta[name=cspNonce]')?.getAttribute('content') ||
+ undefined;
+
export const muiCache = createCache({
key: 'mui',
prepend: true,
+ nonce,
});
export const ThemeProvider: FC = ({ children }) => {
diff --git a/src/lib/app.ts b/src/lib/app.ts
index 580e2b9d00..e03299a15a 100644
--- a/src/lib/app.ts
+++ b/src/lib/app.ts
@@ -1,3 +1,4 @@
+import crypto from 'crypto';
import express, { Application, RequestHandler } from 'express';
import compression from 'compression';
import favicon from 'serve-favicon';
@@ -41,7 +42,8 @@ export default async function getApp(
const baseUriPath = config.server.baseUriPath || '';
const publicFolder = config.publicFolder || findPublicFolder();
- let indexHTML = await loadIndexHTML(config, publicFolder);
+ const cspNonce = crypto.randomBytes(16).toString('hex');
+ let indexHTML = await loadIndexHTML(config, publicFolder, cspNonce);
app.set('trust proxy', true);
app.disable('x-powered-by');
@@ -84,7 +86,7 @@ export default async function getApp(
if (unleashSession) {
app.use(unleashSession);
}
- app.use(secureHeaders(config));
+ app.use(secureHeaders(config, cspNonce));
app.use(express.urlencoded({ extended: true }));
app.use(favicon(path.join(publicFolder, 'favicon.ico')));
app.use(baseUriPath, favicon(path.join(publicFolder, 'favicon.ico')));
diff --git a/src/lib/middleware/secure-headers.ts b/src/lib/middleware/secure-headers.ts
index 3a1812bae7..ed39bab368 100644
--- a/src/lib/middleware/secure-headers.ts
+++ b/src/lib/middleware/secure-headers.ts
@@ -3,7 +3,10 @@ import { RequestHandler } from 'express';
import { IUnleashConfig } from '../types';
import { hoursToSeconds } from 'date-fns';
-const secureHeaders: (config: IUnleashConfig) => RequestHandler = (config) => {
+const secureHeaders: (
+ config: IUnleashConfig,
+ cspNonce?: string,
+) => RequestHandler = (config, cspNonce) => {
if (config.secureHeaders) {
return helmet({
hsts: {
@@ -28,7 +31,7 @@ const secureHeaders: (config: IUnleashConfig) => RequestHandler = (config) => {
],
styleSrc: [
"'self'",
- "'unsafe-inline'",
+ ...(cspNonce ? [`'nonce-${cspNonce}'`] : []),
'cdn.getunleash.io',
'fonts.googleapis.com',
'fonts.gstatic.com',
diff --git a/src/lib/util/load-index-html.ts b/src/lib/util/load-index-html.ts
index 865162af0e..ce36be13d3 100644
--- a/src/lib/util/load-index-html.ts
+++ b/src/lib/util/load-index-html.ts
@@ -7,6 +7,7 @@ import fetch from 'make-fetch-happen';
export async function loadIndexHTML(
config: IUnleashConfig,
publicFolder: string,
+ cspNonce?: string,
): Promise {
const { cdnPrefix, baseUriPath = '' } = config.server;
const uiFlags = JSON.stringify(config.ui.flags);
@@ -23,5 +24,11 @@ export async function loadIndexHTML(
.toString();
}
- return rewriteHTML(indexHTML, baseUriPath, cdnPrefix, uiFlags);
+ return rewriteHTML(
+ indexHTML,
+ baseUriPath,
+ cdnPrefix,
+ uiFlags,
+ config.secureHeaders ? cspNonce : undefined,
+ );
}
diff --git a/src/lib/util/rewriteHTML.ts b/src/lib/util/rewriteHTML.ts
index 58de0c11c4..30daf44437 100644
--- a/src/lib/util/rewriteHTML.ts
+++ b/src/lib/util/rewriteHTML.ts
@@ -3,6 +3,7 @@ export const rewriteHTML = (
rewriteValue: string,
cdnPrefix?: string,
uiFlags?: string,
+ cspNonce?: string,
): string => {
let result = input;
result = result.replace(/::baseUriPath::/gi, rewriteValue);
@@ -18,5 +19,14 @@ export const rewriteHTML = (
`${cdnPrefix || rewriteValue}/static`,
);
+ if (cspNonce) {
+ result = result.replace(/::cspNonce::/gi, cspNonce);
+ } else {
+ result = result.replace(
+ '',
+ '',
+ );
+ }
+
return result;
};
diff --git a/website/static/img/Symbol_DarkBlue_Transparent.svg b/website/static/img/Symbol_DarkBlue_Transparent.svg
index b79f917031..b0fe294dbe 100644
--- a/website/static/img/Symbol_DarkBlue_Transparent.svg
+++ b/website/static/img/Symbol_DarkBlue_Transparent.svg
@@ -1 +1,9 @@
-
\ No newline at end of file
+
diff --git a/website/static/img/Symbol_White_Transparent.svg b/website/static/img/Symbol_White_Transparent.svg
index 86084db7fa..e7285ada8c 100644
--- a/website/static/img/Symbol_White_Transparent.svg
+++ b/website/static/img/Symbol_White_Transparent.svg
@@ -1 +1,10 @@
-
\ No newline at end of file
+
diff --git a/website/static/img/logo.svg b/website/static/img/logo.svg
index b79f917031..f7e0e566dc 100644
--- a/website/static/img/logo.svg
+++ b/website/static/img/logo.svg
@@ -1 +1,19 @@
-
\ No newline at end of file
+