diff --git a/server/routes/app.ts b/server/routes/app.ts index 9596766ee..bf0c7b32f 100644 --- a/server/routes/app.ts +++ b/server/routes/app.ts @@ -67,24 +67,24 @@ export const renderApp = async ( const { shareId } = ctx.params; const page = await readIndexFile(); const environment = ` - `; const entry = "app/index.tsx"; const scriptTags = isProduction - ? `` - : `` + : ` - - + + `; ctx.body = page @@ -97,7 +97,8 @@ export const renderApp = async ( .replace(/\{prefetch\}/g, shareId ? "" : prefetchTags) .replace(/\{slack-app-id\}/g, env.SLACK_APP_ID || "") .replace(/\{cdn-url\}/g, env.CDN_URL || "") - .replace(/\{script-tags\}/g, scriptTags); + .replace(/\{script-tags\}/g, scriptTags) + .replace(/\{csp-nonce\}/g, ctx.state.cspNonce); }; export const renderShare = async (ctx: Context, next: Next) => { diff --git a/server/routes/index.ts b/server/routes/index.ts index 6daa6154c..f2694bf0f 100644 --- a/server/routes/index.ts +++ b/server/routes/index.ts @@ -156,10 +156,12 @@ koa.use(async (ctx, next) => { ctx.set("Timing-Allow-Origin", timingOrigins.join(", ")); await next(); }); + koa.use(apexRedirect()); if (env.ENVIRONMENT === "test") { koa.use(errors.routes()); } + koa.use(router.routes()); export default koa; diff --git a/server/services/web.ts b/server/services/web.ts index 377214f8d..bf2064dfc 100644 --- a/server/services/web.ts +++ b/server/services/web.ts @@ -1,4 +1,5 @@ /* eslint-disable @typescript-eslint/no-var-requires */ +import crypto from "crypto"; import { Server } from "https"; import Koa from "koa"; import { @@ -27,8 +28,6 @@ const isProduction = env.ENVIRONMENT === "production"; const defaultSrc = ["'self'"]; const scriptSrc = [ "'self'", - "'unsafe-inline'", - "'unsafe-eval'", "gist.github.com", "www.googletagmanager.com", "cdn.zapier.com", @@ -103,19 +102,22 @@ export default function init(app: Koa = new Koa(), server?: Server) { // Sets common security headers by default, such as no-sniff, hsts, hide powered // by etc, these are applied after auth and api so they are only returned on // standard non-XHR accessed routes - app.use( - contentSecurityPolicy({ + app.use((ctx, next) => { + ctx.state.cspNonce = crypto.randomBytes(16).toString("hex"); + + return contentSecurityPolicy({ directives: { defaultSrc, - scriptSrc, styleSrc, + scriptSrc: [...scriptSrc, `'nonce-${ctx.state.cspNonce}'`], imgSrc: ["*", "data:", "blob:"], frameSrc: ["*", "data:"], - connectSrc: ["*"], // Do not use connect-src: because self + websockets does not work in + // Do not use connect-src: because self + websockets does not work in // Safari, ref: https://bugs.webkit.org/show_bug.cgi?id=201591 + connectSrc: ["*"], }, - }) - ); + })(ctx, next); + }); // Allow DNS prefetching for performance, we do not care about leaking requests // to our own CDN's @@ -129,6 +131,8 @@ export default function init(app: Koa = new Koa(), server?: Server) { policy: "no-referrer", }) ); + app.use(mount(routes)); + return app; } diff --git a/server/static/index.html b/server/static/index.html index b457231a4..7da726054 100644 --- a/server/static/index.html +++ b/server/static/index.html @@ -53,7 +53,7 @@
{env} -