diff --git a/package.json b/package.json index bcdb20c6..4824bc37 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "scripts": { "analyze": "webpack --mode=none", "prebuild:dev": "yarn clean && node generate_translations.js", - "build:dev": "webpack --env COMMIT_HASH=$(git rev-parse --short HEAD) --mode=development", + "build:dev": "webpack --env LEMMY_UI_DISABLE_CSP=true --env COMMIT_HASH=$(git rev-parse --short HEAD) --mode=development", "prebuild:prod": "yarn clean && node generate_translations.js", "build:prod": "webpack --env COMMIT_HASH=$(git rev-parse --short HEAD) --mode=production", "clean": "yarn run rimraf dist", diff --git a/src/server/handlers/catch-all-handler.tsx b/src/server/handlers/catch-all-handler.tsx index 4b011045..06d38f31 100644 --- a/src/server/handlers/catch-all-handler.tsx +++ b/src/server/handlers/catch-all-handler.tsx @@ -120,7 +120,7 @@ export default async (req: Request, res: Response) => { const root = renderToString(wrapper); - res.send(await createSsrHtml(root, isoData)); + res.send(await createSsrHtml(root, isoData, res.locals.cspNonce)); } catch (err) { // If an error is caught here, the error page couldn't even be rendered console.error(err); diff --git a/src/server/middleware.ts b/src/server/middleware.ts index 0420e47e..a75d49ed 100644 --- a/src/server/middleware.ts +++ b/src/server/middleware.ts @@ -1,3 +1,4 @@ +import * as crypto from "crypto"; import type { NextFunction, Request, Response } from "express"; import { hasJwtCookie } from "./utils/has-jwt-cookie"; @@ -8,9 +9,20 @@ export function setDefaultCsp({ res: Response; next: NextFunction; }) { + res.locals.cspNonce = crypto.randomBytes(16).toString("hex"); + res.setHeader( "Content-Security-Policy", - `default-src 'self'; manifest-src *; connect-src *; img-src * data:; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; form-action 'self'; base-uri 'self'; frame-src *; media-src * data:` + `default-src 'self'; + manifest-src *; + connect-src *; + img-src * data:; + script-src 'self' 'nonce-${res.locals.cspNonce}'; + style-src 'self' 'unsafe-inline'; + form-action 'self'; + base-uri 'self'; + frame-src *; + media-src * data:`.replace(/\s+/g, " ") ); next(); diff --git a/src/server/utils/create-ssr-html.tsx b/src/server/utils/create-ssr-html.tsx index ba85228f..71fdb68f 100644 --- a/src/server/utils/create-ssr-html.tsx +++ b/src/server/utils/create-ssr-html.tsx @@ -4,7 +4,7 @@ import { renderToString } from "inferno-server"; import serialize from "serialize-javascript"; import sharp from "sharp"; import { favIconPngUrl, favIconUrl } from "../../shared/config"; -import { ILemmyConfig, IsoDataOptionalSite } from "../../shared/interfaces"; +import { IsoDataOptionalSite } from "../../shared/interfaces"; import { buildThemeList } from "./build-themes-list"; import { fetchIconPng } from "./fetch-icon-png"; @@ -14,7 +14,8 @@ let appleTouchIcon: string | undefined = undefined; export async function createSsrHtml( root: string, - isoData: IsoDataOptionalSite + isoData: IsoDataOptionalSite, + cspNonce: string ) { const site = isoData.site_res; @@ -22,6 +23,12 @@ export async function createSsrHtml( (await buildThemeList())[0] }.css" />`; + const customHtmlHeaderScriptTag = new RegExp(" - - + + ) : ""; const helmet = Helmet.renderStatic(); - const config: ILemmyConfig = { wsHost: process.env.LEMMY_UI_LEMMY_WS_HOST }; - return ` - - + ${erudaStr} - ${customHtmlHeader} + ${customHtmlHeaderWithNonce} ${helmet.title.toString()} ${helmet.meta.toString()} diff --git a/src/shared/interfaces.ts b/src/shared/interfaces.ts index b37dbd3e..7beeec99 100644 --- a/src/shared/interfaces.ts +++ b/src/shared/interfaces.ts @@ -18,14 +18,9 @@ export type IsoDataOptionalSite = Partial< > & Pick, Exclude, "site_res">>; -export interface ILemmyConfig { - wsHost?: string; -} - declare global { interface Window { isoData: IsoData; - lemmyConfig?: ILemmyConfig; } }