Commit 9b51e2a1 authored by tom's avatar tom

refactor csp policy generator

parent 56654f59
import _mergeWith from 'lodash/mergeWith';
import * as descriptors from './policies';
import { makePolicyString } from './utils';
const concat = (one: Array<unknown> | undefined, two: Array<unknown> | undefined) =>
one ? one.concat(two || []) : two;
function generateCspPolicy() {
const policyDescriptor = _mergeWith(
_mergeWith(
descriptors.app(),
descriptors.ad(),
descriptors.googleAnalytics(),
descriptors.googleFonts(),
concat,
),
_mergeWith(
descriptors.googleReCaptcha(),
descriptors.sentry(),
descriptors.walletConnect(),
concat,
),
concat,
);
return makePolicyString(policyDescriptor);
}
export default generateCspPolicy;
import type CspDev from 'csp-dev';
import isSelfHosted from 'lib/isSelfHosted';
export default function generateAdDescriptor(): CspDev.DirectiveDescriptor {
if (!isSelfHosted()) {
return {};
}
return {
'connect-src': [
'coinzilla.com',
'*.coinzilla.com',
'request-global.czilladx.com',
],
'frame-src': [
'request-global.czilladx.com',
],
'script-src': [
'coinzillatag.com',
'servedbyadbutler.com',
// what hash is this?
'\'sha256-wMOeDjJaOTjCfNjluteV+tSqHW547T89sgxd8W6tQJM=\'',
// what hash is this?
'\'sha256-FcyIn1h7zra8TVnnRhYrwrplxJW7dpD5TV7kP2AG/kI=\'',
],
'img-src': [
'servedbyadbutler.com',
'cdn.coinzilla.io',
],
'font-src': [
'request-global.czilladx.com',
],
};
}
import type CspDev from 'csp-dev';
import appConfig from 'configs/app/config'; import appConfig from 'configs/app/config';
import featuredNetworks from 'lib/networks/featuredNetworks'; import featuredNetworks from 'lib/networks/featuredNetworks';
const KEY_WORDS = { import { KEY_WORDS } from '../utils';
BLOB: 'blob:',
DATA: 'data:',
NONE: '\'none\'',
REPORT_SAMPLE: `'report-sample'`,
SELF: '\'self\'',
STRICT_DYNAMIC: `'strict-dynamic'`,
UNSAFE_INLINE: '\'unsafe-inline\'',
UNSAFE_EVAL: '\'unsafe-eval\'',
};
const MAIN_DOMAINS = [ const MAIN_DOMAINS = [
`*.${ appConfig.host }`, `*.${ appConfig.host }`,
...@@ -37,25 +30,12 @@ function getMarketplaceAppsHosts() { ...@@ -37,25 +30,12 @@ function getMarketplaceAppsHosts() {
}; };
} }
// we cannot use lodash/uniq in middleware code since it calls new Set() and it'is causing an error in Nextjs export default function generateAppDescriptor(): CspDev.DirectiveDescriptor {
// "Dynamic Code Evaluation (e. g. 'eval', 'new Function', 'WebAssembly.compile') not allowed in Edge Runtime"
function unique(array: Array<string | undefined>) {
const set: Record<string, boolean> = {};
for (const item of array) {
item && (set[item] = true);
}
return Object.keys(set);
}
function makePolicyMap() {
const marketplaceAppsHosts = getMarketplaceAppsHosts(); const marketplaceAppsHosts = getMarketplaceAppsHosts();
return { return {
'default-src': [ 'default-src': [
// KEY_WORDS.NONE, KEY_WORDS.NONE,
// temporarily, see if warnings for "/_next/static/chunks/8861-ad3efb7f624b7bc1.js" go away
...MAIN_DOMAINS,
], ],
'connect-src': [ 'connect-src': [
...@@ -65,9 +45,6 @@ function makePolicyMap() { ...@@ -65,9 +45,6 @@ function makePolicyMap() {
// webpack hmr in safari doesn't recognize localhost as 'self' for some reason // webpack hmr in safari doesn't recognize localhost as 'self' for some reason
appConfig.isDev ? 'ws://localhost:3000/_next/webpack-hmr' : '', appConfig.isDev ? 'ws://localhost:3000/_next/webpack-hmr' : '',
// client error monitoring
'sentry.io', '*.sentry.io',
// API // API
appConfig.api.endpoint, appConfig.api.endpoint,
appConfig.api.socket, appConfig.api.socket,
...@@ -75,67 +52,28 @@ function makePolicyMap() { ...@@ -75,67 +52,28 @@ function makePolicyMap() {
// chain RPC server // chain RPC server
appConfig.network.rpcUrl, appConfig.network.rpcUrl,
'https://infragrid.v.network', // RPC providers
// ad
'request-global.czilladx.com',
// walletconnect
'*.walletconnect.com',
'wss://*.bridge.walletconnect.org',
'wss://www.walletlink.org',
// RPC providers
'https://infragrid.v.network',
// github (spec for api-docs page) // github (spec for api-docs page)
'raw.githubusercontent.com', 'raw.githubusercontent.com',
].filter(Boolean),
// google analytics
'https://www.googletagmanager.com',
'https://www.google-analytics.com',
'https://stats.g.doubleclick.net',
],
'script-src': [ 'script-src': [
KEY_WORDS.SELF, KEY_WORDS.SELF,
...MAIN_DOMAINS,
// next.js generates and rebuilds source maps in dev using eval() // next.js generates and rebuilds source maps in dev using eval()
// https://github.com/vercel/next.js/issues/14221#issuecomment-657258278 // https://github.com/vercel/next.js/issues/14221#issuecomment-657258278
appConfig.isDev ? KEY_WORDS.UNSAFE_EVAL : '', appConfig.isDev ? KEY_WORDS.UNSAFE_EVAL : '',
...MAIN_DOMAINS,
// hash of ColorModeScript // hash of ColorModeScript
'\'sha256-e7MRMmTzLsLQvIy1iizO1lXf7VWYoQ6ysj5fuUzvRwE=\'', '\'sha256-e7MRMmTzLsLQvIy1iizO1lXf7VWYoQ6ysj5fuUzvRwE=\'',
// ad
'coinzillatag.com',
'servedbyadbutler.com',
'\'sha256-wMOeDjJaOTjCfNjluteV+tSqHW547T89sgxd8W6tQJM=\'',
'\'sha256-FcyIn1h7zra8TVnnRhYrwrplxJW7dpD5TV7kP2AG/kI=\'',
// reCAPTCHA from google
'https://www.google.com/recaptcha/api.js',
'https://www.gstatic.com',
'https://translate.google.com',
'\'sha256-FDyPg8CqqIpPAfGVKx1YeKduyLs0ghNYWII21wL+7HM=\'',
// google analytics
'\'sha256-NTmEg2dBnojQfTYrYJEmp3nG7V66756qPbQMCIBrctk=\'',
'https://www.googletagmanager.com',
'https://www.google-analytics.com',
], ],
'style-src': [ 'style-src': [
KEY_WORDS.SELF, KEY_WORDS.SELF,
...MAIN_DOMAINS, ...MAIN_DOMAINS,
// google fonts
'fonts.googleapis.com',
// reCAPTCHA from google
'https://www.gstatic.com',
// yes, it is unsafe as it stands, but // yes, it is unsafe as it stands, but
// - we cannot use hashes because all styles are generated dynamically // - we cannot use hashes because all styles are generated dynamically
// - we cannot use nonces since we are not following along SSR path // - we cannot use nonces since we are not following along SSR path
...@@ -147,7 +85,6 @@ function makePolicyMap() { ...@@ -147,7 +85,6 @@ function makePolicyMap() {
'img-src': [ 'img-src': [
KEY_WORDS.SELF, KEY_WORDS.SELF,
KEY_WORDS.DATA, KEY_WORDS.DATA,
...MAIN_DOMAINS, ...MAIN_DOMAINS,
// github assets (e.g trustwallet token icons) // github assets (e.g trustwallet token icons)
...@@ -165,34 +102,12 @@ function makePolicyMap() { ...@@ -165,34 +102,12 @@ function makePolicyMap() {
// marketplace apps logos // marketplace apps logos
...marketplaceAppsHosts.logos, ...marketplaceAppsHosts.logos,
// ad
'servedbyadbutler.com',
'cdn.coinzilla.io',
// walletconnect
'*.walletconnect.com',
// token's media // token's media
'ipfs.io', 'ipfs.io',
// reCAPTCHA from google
'https://translate.google.com',
'https://www.gstatic.com',
// google analytics
'https://www.google-analytics.com',
], ],
'font-src': [ 'font-src': [
KEY_WORDS.DATA, KEY_WORDS.DATA,
// google fonts
'fonts.gstatic.com',
'fonts.googleapis.com',
],
'prefetch-src': [
...MAIN_DOMAINS,
], ],
'object-src': [ 'object-src': [
...@@ -205,40 +120,12 @@ function makePolicyMap() { ...@@ -205,40 +120,12 @@ function makePolicyMap() {
'frame-src': [ 'frame-src': [
...marketplaceAppsHosts.frames, ...marketplaceAppsHosts.frames,
// ad
'request-global.czilladx.com',
// reCAPTCHA from google
// 'https://www.google.com/',
'https://www.google.com/recaptcha/api2/anchor',
'https://www.google.com/recaptcha/api2/bframe',
], ],
...(REPORT_URI ? { ...(REPORT_URI && !appConfig.isDev ? {
'report-uri': [ 'report-uri': [
REPORT_URI, REPORT_URI,
], ],
} : {}), } : {}),
}; };
} }
function getCspPolicy() {
const policyMap = makePolicyMap();
const policyString = Object.entries(policyMap)
.map(([ key, value ]) => {
if (!value || value.length === 0) {
return;
}
const uniqueValues = unique(value);
return [ key, uniqueValues.join(' ') ].join(' ');
})
.filter(Boolean)
.join(';');
return policyString;
}
export default getCspPolicy;
import type CspDev from 'csp-dev';
import appConfig from 'configs/app/config';
export default function generateGoogleAnalyticsDescriptor(): CspDev.DirectiveDescriptor {
if (!appConfig.googleAnalytics.propertyId) {
return {};
}
return {
'connect-src': [
'https://www.googletagmanager.com',
'https://www.google-analytics.com',
'https://stats.g.doubleclick.net',
],
'script-src': [
// inline script hash, see ui/shared/GoogleAnalytics.tsx
'\'sha256-NTmEg2dBnojQfTYrYJEmp3nG7V66756qPbQMCIBrctk=\'',
'https://www.googletagmanager.com',
'https://www.google-analytics.com',
],
'img-src': [
'https://www.google-analytics.com',
],
};
}
import type CspDev from 'csp-dev';
export default function generateGoogleFontsDescriptor(): CspDev.DirectiveDescriptor {
// we use Inter and Poppins in the app
return {
'style-src': [
'fonts.googleapis.com',
],
'font-src': [
'fonts.gstatic.com',
'fonts.googleapis.com',
],
};
}
import type CspDev from 'csp-dev';
import appConfig from 'configs/app/config';
export default function generateGoogleReCaptchaDescriptor(): CspDev.DirectiveDescriptor {
if (!appConfig.reCaptcha.siteKey) {
return {};
}
return {
'script-src': [
'https://www.google.com/recaptcha/api.js',
'https://www.gstatic.com',
'https://translate.google.com',
'\'sha256-FDyPg8CqqIpPAfGVKx1YeKduyLs0ghNYWII21wL+7HM=\'',
],
'style-src': [
'https://www.gstatic.com',
],
'img-src': [
'https://translate.google.com',
'https://www.gstatic.com',
],
'frame-src': [
'https://www.google.com/recaptcha/api2/anchor',
'https://www.google.com/recaptcha/api2/bframe',
],
};
}
import ad from './ad';
import app from './app';
import googleAnalytics from './googleAnalytics';
import googleFonts from './googleFonts';
import googleReCaptcha from './googleReCaptcha';
import sentry from './sentry';
import walletConnect from './walletConnect';
export {
ad,
app,
googleAnalytics,
googleFonts,
googleReCaptcha,
sentry,
walletConnect,
};
import type CspDev from 'csp-dev';
export default function generateSentryDescriptor(): CspDev.DirectiveDescriptor {
return {
'connect-src': [
'sentry.io',
'*.sentry.io',
],
};
}
import type CspDev from 'csp-dev';
import appConfig from 'configs/app/config';
export default function generateWalletConnectDescriptor(): CspDev.DirectiveDescriptor {
if (!appConfig.walletConnect.projectId || !appConfig.network.rpcUrl) {
return {};
}
return {
'connect-src': [
'*.walletconnect.com',
'wss://*.bridge.walletconnect.org',
'wss://www.walletlink.org',
],
'img-src': [
'*.walletconnect.com',
],
};
}
import type CspDev from 'csp-dev';
export const KEY_WORDS = {
BLOB: 'blob:',
DATA: 'data:',
NONE: '\'none\'',
REPORT_SAMPLE: `'report-sample'`,
SELF: '\'self\'',
STRICT_DYNAMIC: `'strict-dynamic'`,
UNSAFE_INLINE: '\'unsafe-inline\'',
UNSAFE_EVAL: '\'unsafe-eval\'',
};
// we cannot use lodash/uniq in middleware code since it calls new Set() and it'is causing an error in Nextjs
// "Dynamic Code Evaluation (e. g. 'eval', 'new Function', 'WebAssembly.compile') not allowed in Edge Runtime"
export function unique(array: Array<string | undefined>) {
const set: Record<string, boolean> = {};
for (const item of array) {
item && (set[item] = true);
}
return Object.keys(set);
}
export function makePolicyString(policyDescriptor: CspDev.DirectiveDescriptor) {
return Object.entries(policyDescriptor)
.map(([ key, value ]) => {
if (!value || value.length === 0) {
return;
}
const uniqueValues = unique(value);
return [ key, uniqueValues.join(' ') ].join(' ');
})
.filter(Boolean)
.join(';');
}
...@@ -4,9 +4,9 @@ import { route } from 'nextjs-routes'; ...@@ -4,9 +4,9 @@ import { route } from 'nextjs-routes';
import appConfig from 'configs/app/config'; import appConfig from 'configs/app/config';
import { NAMES } from 'lib/cookies'; import { NAMES } from 'lib/cookies';
import getCspPolicy from 'lib/csp/getCspPolicy'; import generateCspPolicy from 'lib/csp/generateCspPolicy';
const cspPolicy = getCspPolicy(); const cspPolicy = generateCspPolicy();
export function middleware(req: NextRequest) { export function middleware(req: NextRequest) {
const isPageRequest = req.headers.get('accept')?.includes('text/html'); const isPageRequest = req.headers.get('accept')?.includes('text/html');
......
...@@ -3897,6 +3897,11 @@ ...@@ -3897,6 +3897,11 @@
dependencies: dependencies:
"@types/node" "*" "@types/node" "*"
"@types/csp-dev@^1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@types/csp-dev/-/csp-dev-1.0.0.tgz#59e2fd69f276988b349765c2f6a39ea0a4a1a161"
integrity sha512-OTHJTGqXvgFu1AE4Eo8cu+jgkQChpzTOHpMDrVkxAmNORVz7/11Zjj2NXZyWQcxibf18C966bU1JWtANrR8uJQ==
"@types/d3-array@*": "@types/d3-array@*":
version "3.0.3" version "3.0.3"
resolved "https://registry.yarnpkg.com/@types/d3-array/-/d3-array-3.0.3.tgz#87d990bf504d14ad6b16766979d04e943c046dac" resolved "https://registry.yarnpkg.com/@types/d3-array/-/d3-array-3.0.3.tgz#87d990bf504d14ad6b16766979d04e943c046dac"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment