UNPKG

csp-helper

Version:

Helpers for managing Content Security Policy (CSP)

388 lines (367 loc) 17.7 kB
//#region src/constants.ts const CSP_HEADER_NAME = "Content-Security-Policy"; /** * Full list of google supported domains. * * To be used in place of `https://*.google.<TLD>` in CSP directives * * @see https://www.google.com/supported_domains */ const GOOGLE_SUPPORTED_DOMAINS = ".google.com .google.ad .google.ae .google.com.af .google.com.ag .google.al .google.am .google.co.ao .google.com.ar .google.as .google.at .google.com.au .google.az .google.ba .google.com.bd .google.be .google.bf .google.bg .google.com.bh .google.bi .google.bj .google.com.bn .google.com.bo .google.com.br .google.bs .google.bt .google.co.bw .google.by .google.com.bz .google.ca .google.cd .google.cf .google.cg .google.ch .google.ci .google.co.ck .google.cl .google.cm .google.cn .google.com.co .google.co.cr .google.com.cu .google.cv .google.com.cy .google.cz .google.de .google.dj .google.dk .google.dm .google.com.do .google.dz .google.com.ec .google.ee .google.com.eg .google.es .google.com.et .google.fi .google.com.fj .google.fm .google.fr .google.ga .google.ge .google.gg .google.com.gh .google.com.gi .google.gl .google.gm .google.gr .google.com.gt .google.gy .google.com.hk .google.hn .google.hr .google.ht .google.hu .google.co.id .google.ie .google.co.il .google.im .google.co.in .google.iq .google.is .google.it .google.je .google.com.jm .google.jo .google.co.jp .google.co.ke .google.com.kh .google.ki .google.kg .google.co.kr .google.com.kw .google.kz .google.la .google.com.lb .google.li .google.lk .google.co.ls .google.lt .google.lu .google.lv .google.com.ly .google.co.ma .google.md .google.me .google.mg .google.mk .google.ml .google.com.mm .google.mn .google.com.mt .google.mu .google.mv .google.mw .google.com.mx .google.com.my .google.co.mz .google.com.na .google.com.ng .google.com.ni .google.ne .google.nl .google.no .google.com.np .google.nr .google.nu .google.co.nz .google.com.om .google.com.pa .google.com.pe .google.com.pg .google.com.ph .google.com.pk .google.pl .google.pn .google.com.pr .google.ps .google.pt .google.com.py .google.com.qa .google.ro .google.ru .google.rw .google.com.sa .google.com.sb .google.sc .google.se .google.com.sg .google.sh .google.si .google.sk .google.com.sl .google.sn .google.so .google.sm .google.sr .google.st .google.com.sv .google.td .google.tg .google.co.th .google.com.tj .google.tl .google.tm .google.tn .google.to .google.com.tr .google.tt .google.com.tw .google.co.tz .google.com.ua .google.co.ug .google.co.uk .google.com.uy .google.co.uz .google.com.vc .google.co.ve .google.co.vi .google.com.vn .google.vu .google.ws .google.rs .google.co.za .google.co.zm .google.co.zw .google.cat"; //#endregion //#region src/merge-csp-configs-to-set.ts /** * Helper to merge multiple CSP configs to configs set. */ const mergeCspConfigsToSet = (configs) => { const configsSet = {}; for (const config of configs) Object.entries(config).forEach(([key, value]) => { const directive = key; configsSet[directive] ??= /* @__PURE__ */ new Set(); value.split(" ").forEach((v) => configsSet[directive]?.add(v)); }); return configsSet; }; //#endregion //#region src/create-csp-header.ts /** * Helper to create a Content-Security-Policy header. */ const createCspHeader = (config, { includeHeaderName = false, presets = [] } = {}) => { const mergedConfigsSet = mergeCspConfigsToSet([config, ...presets]); const headerValue = Object.entries(mergedConfigsSet).map(([key, value]) => { const valueString = [...value].join(" "); return valueString ? `${key} ${valueString}` : key; }).join("; "); return includeHeaderName ? `${CSP_HEADER_NAME}: ${headerValue}` : headerValue; }; //#endregion //#region src/merge-csp-configs.ts /** * Helper to merge multiple CSP configs. */ const mergeCspConfigs = (configs) => { const mergedConfigsSet = mergeCspConfigsToSet(configs); return Object.fromEntries(Object.entries(mergedConfigsSet).map(([key, value]) => [key, [...value].join(" ")])); }; //#endregion //#region src/presets/datadog.ts /** * CSP preset for datadog intake URLs * * @see https://docs.datadoghq.com/integrations/content_security_policy_logs/?tab=firefox#intake-urls */ const CSP_PRESET_DATADOG_INTAKE_URLS = { "connect-src": `https://*.datadoghq.com https://browser-intake-datadoghq.com` }; /** * CSP preset for datadog web worker * * @see https://docs.datadoghq.com/integrations/content_security_policy_logs/?tab=firefox#web-worker */ const CSP_PRESET_DATADOG_WEB_WORKER = { "worker-src": `blob:` }; /** * CSP preset for datadog CDN bundle URL * * @see https://docs.datadoghq.com/integrations/content_security_policy_logs/?tab=firefox#cdn-bundle-url */ const CSP_PRESET_DATADOG_CDN_BUNDLE_URL = { "script-src": `https://www.datadoghq-browser-agent.com` }; //#endregion //#region src/presets/google-ads.ts /** * CSP directives for Google Ads Conversion, Remarketing, or Conversion Linker tag * * This preset only includes the basic directives. * * You could add Google top-level domains (TLDs) to `img-src` as needed. * * @see https://developers.google.com/tag-platform/security/guides/csp#google_ads */ const CSP_PRESET_GOOGLE_ADS = { "connect-src": `https://pagead2.googlesyndication.com https://www.googleadservices.com https://www.google.com https://google.com`, "frame-src": `https://www.googletagmanager.com https://td.doubleclick.net`, "img-src": `https://www.googletagmanager.com https://googleads.g.doubleclick.net https://www.google.com https://pagead2.googlesyndication.com https://www.googleadservices.com https://google.com`, "script-src": `https://www.googleadservices.com https://www.google.com https://www.googletagmanager.com https://pagead2.googlesyndication.com https://googleads.g.doubleclick.net` }; /** * The hosts string to be used in CSP directives for google analytics 4 */ const GOOGLE_SUPPORTED_DOMAINS_DIRECTIVE_HOSTS_STRING$1 = GOOGLE_SUPPORTED_DOMAINS.split(" ").map((item) => `https://www${item}`).join(" "); /** * CSP directives for Google Ads Conversion, Remarketing, or Conversion Linker tag * * This preset includes the full list of Google top-level domains (TLDs) in `img-src`. * * You may not need all of them if your site is not targeting all countries. * * The full list will make the CSP header too large, and you may need to update your server configs to allow large headers. * * @see https://developers.google.com/tag-platform/security/guides/csp#google_ads */ const CSP_PRESET_GOOGLE_ADS_FULL_TLD = { "img-src": GOOGLE_SUPPORTED_DOMAINS_DIRECTIVE_HOSTS_STRING$1 }; /** * CSP directives for google ads user data beacon * * @see https://developers.google.com/tag-platform/security/guides/csp#google_ads_user_data_beacon */ const CSP_PRESET_GOOGLE_ADS_USER_DATA_BEACON = { "connect-src": `https://google.com https://www.google.com`, "frame-src": `https://www.googletagmanager.com`, "script-src": `https://www.googletagmanager.com` }; //#endregion //#region src/presets/google-analytics-4.ts /** * CSP directives for Google Analytics 4 (Google Analytics) * * @see https://developers.google.com/tag-platform/security/guides/csp#google_analytics_4_google_analytics */ const CSP_PRESET_GOOGLE_ANALYTICS_4 = { "connect-src": `https://*.google-analytics.com https://*.analytics.google.com https://*.googletagmanager.com`, "img-src": `https://*.google-analytics.com https://*.googletagmanager.com`, "script-src": `https://*.googletagmanager.com` }; /** * CSP directives for Google Analytics 4 (Google Analytics) deployments using Google Signals * * This preset only includes the basic directives. * * You could add Google top-level domains (TLDs) to `connect-src` and `img-src` as needed. * * @see https://developers.google.com/tag-platform/security/guides/csp#google_analytics_4_google_analytics */ const CSP_PRESET_GOOGLE_ANALYTICS_4_GOOGLE_SIGNALS = { "connect-src": `https://*.google-analytics.com https://*.googletagmanager.com https://*.g.doubleclick.net https://*.google.com https://pagead2.googlesyndication.com`, "frame-src": `https://td.doubleclick.net https://www.googletagmanager.com`, "img-src": `https://*.google-analytics.com https://*.googletagmanager.com https://*.g.doubleclick.net https://*.google.com`, "script-src": `https://*.googletagmanager.com` }; /** * The hosts string to be used in CSP directives for google analytics 4 */ const GOOGLE_SUPPORTED_DOMAINS_DIRECTIVE_HOSTS_STRING = GOOGLE_SUPPORTED_DOMAINS.split(" ").map((item) => `https://*${item}`).join(" "); /** * CSP directives for Google Analytics 4 (Google Analytics) deployments using Google Signals * * This preset includes the full list of Google top-level domains (TLDs) in `connect-src` and `img-src`. * * You may not need all of them if your site is not targeting all countries. * * The full list will make the CSP header too large, and you may need to update your server configs to allow large headers. * * @see https://developers.google.com/tag-platform/security/guides/csp#google_analytics_4_google_analytics */ const CSP_PRESET_GOOGLE_ANALYTICS_4_GOOGLE_SIGNALS_FULL_TLD = { "connect-src": GOOGLE_SUPPORTED_DOMAINS_DIRECTIVE_HOSTS_STRING, "img-src": GOOGLE_SUPPORTED_DOMAINS_DIRECTIVE_HOSTS_STRING }; //#endregion //#region src/presets/google-fonts.ts /** * CSP directives for google fonts * * @see https://content-security-policy.com/examples/google-fonts/ */ const CSP_PRESET_GOOGLE_FONTS = { "font-src": `https://fonts.gstatic.com`, "style-src": `https://fonts.googleapis.com` }; //#endregion //#region src/presets/google-identity.ts /** * CSP directives for google identity * * @see https://developers.google.com/identity/gsi/web/guides/get-google-api-clientid#content_security_policy */ const CSP_PRESET_GOOGLE_IDENTITY = { "connect-src": `https://accounts.google.com/gsi/`, "frame-src": `https://accounts.google.com/gsi/`, "script-src": `https://accounts.google.com/gsi/client`, "style-src": `https://accounts.google.com/gsi/style` }; //#endregion //#region src/presets/google-tag-manager.ts /** * CSP directives for google tag manager with nonce. * * Notice that `script-src 'nonce-<value>'` is not included, which should be added yourself. * * @see https://developers.google.com/tag-platform/security/guides/csp#enable_the_container_tag_to_use_csp */ const CSP_PRESET_GOOGLE_TAG_MANAGER_NONCE = { "connect-src": `https://www.googletagmanager.com`, "img-src": `https://www.googletagmanager.com` }; /** * CSP directives for google tag manager with 'unsafe-inline' * * @see https://developers.google.com/tag-platform/security/guides/csp#enable_the_container_tag_to_use_csp */ const CSP_PRESET_GOOGLE_TAG_MANAGER_UNSAFE_INLINE = { "connect-src": `https://www.googletagmanager.com`, "img-src": `https://www.googletagmanager.com`, "script-src": `'unsafe-inline' https://www.googletagmanager.com` }; /** * CSP directives for google tag manager with custom javascript variables * * @see https://developers.google.com/tag-platform/security/guides/csp#custom_javascript_variables */ const CSP_PRESET_GOOGLE_TAG_MANAGER_CUSTOM_JAVASCRIPT_VARIABLES = { "script-src": `'unsafe-eval'` }; /** * CSP directives for google tag manager preview mode * * Notice that the GTM preview mode is not properly maintained by Google: * - The docs and the actual requests are inconsistent. You may need to manually update the `img-src` and `style-src` directives. * - The css file and some requests of preview mode would be blocked by COEP due to lack of CORP header. You may need to disable COEP for preview mode. * * @see https://developers.google.com/tag-platform/security/guides/csp#preview_mode */ const CSP_PRESET_GOOGLE_TAG_MANAGER_PREVIEW_MODE = { "font-src": `https://fonts.gstatic.com data:`, "img-src": `https://googletagmanager.com https://ssl.gstatic.com https://www.gstatic.com`, "script-src": `https://googletagmanager.com https://tagmanager.google.com`, "style-src": `https://googletagmanager.com https://tagmanager.google.com https://fonts.googleapis.com` }; //#endregion //#region src/presets/google-universal-analytics.ts /** * CSP directives for google universal analytics * * @see https://developers.google.com/tag-platform/security/guides/csp#universal_analytics_google_analytics */ const CSP_PRESET_GOOGLE_UNIVERSAL_ANALYTICS = { "connect-src": `https://www.google-analytics.com`, "img-src": `https://www.google-analytics.com`, "script-src": `https://www.google-analytics.com https://ssl.google-analytics.com` }; //#endregion //#region src/presets/hotjar.ts /** * CSP directives for hotjar * * @see https://help.hotjar.com/hc/en-us/articles/115011640307-Content-Security-Policies */ const CSP_PRESET_HOTJAR = { "connect-src": `https://*.hotjar.com https://*.hotjar.io wss://*.hotjar.com`, "font-src": `https://script.hotjar.com`, "img-src": `https://static.hotjar.com https://script.hotjar.com https://survey-images.hotjar.com`, "script-src": `https://static.hotjar.com https://script.hotjar.com 'unsafe-inline'`, "style-src": `https://static.hotjar.com https://script.hotjar.com 'unsafe-inline'` }; //#endregion //#region src/presets/infogram.ts /** * CSP directives for infogram embed * * @see https://support.infogram.com/hc/en-us/sections/360000124013-Embed */ const CSP_PRESET_INFOGRAM_EMBED = { "frame-src": `https://e.infogram.com`, "script-src": `https://e.infogram.com 'unsafe-inline'` }; //#endregion //#region src/presets/podscribe.ts /** * CSP directives for podscribe * * Notice that the official documentation is incorrect: * * - `img-src` should include `https://verifi.podscribe.com` * - `connect-src` should not include `https://pixel.tapad.com`, as it is not seen in their source code nor in the network requests. * - `connect-src` might include `https://const.uno` because it is used in the source code and will be request with `with_stid` parameter enabled. * However, it is not necessary and always returns an empty result for now. * * @see https://podscribe.helpkit.so/attribution-and-incrementality/g4g8w3hppouCfkXZdcx6FV/advertiser-tracking-via-javascript-pixel/dpbUfAVkZgu9ehyP1uztZv */ const CSP_PRESET_PODSCRIBE = { "connect-src": `https://verifi.podscribe.com https://ipv4.podscribe.com`, "img-src": `https://verifi.podscribe.com`, "script-src": `https://d34r8q7sht0t9k.cloudfront.net` }; //#endregion //#region src/presets/reddit.ts /** * CSP directives for reddit embed */ const CSP_PRESET_REDDIT_EMBED = { "frame-src": `https://embed.reddit.com`, "script-src": `https://embed.reddit.com` }; //#endregion //#region src/presets/sentry.ts /** * CSP preset for sentry session replay * * @see https://docs.sentry.io/platforms/javascript/session-replay/#content-security-policy-csp */ const CSP_PRESET_SENTRY_SESSION_REPLAY = { "worker-src": `blob:` }; //#endregion //#region src/presets/tiktok.ts /** * CSP directives for tiktok embed */ const CSP_PRESET_TIKTOK_EMBED = { "frame-src": `https://www.tiktok.com`, "script-src": `https://www.tiktok.com` }; //#endregion //#region src/presets/vimeo.ts /** * CSP directives for vimeo embed * * @see https://github.com/vimeo/player.js?tab=readme-ov-file#readme */ const CSP_PRESET_VIMEO_EMBED = { "connect-src": `https://vimeo.com`, "frame-src": `https://player.vimeo.com`, "img-src": `https://i.vimeocdn.com`, "script-src": `https://player.vimeo.com` }; //#endregion //#region src/presets/x.ts /** * CSP directives for x embed */ const CSP_PRESET_X_EMBED = { "frame-src": `https://platform.twitter.com`, "script-src": `https://platform.twitter.com` }; //#endregion //#region src/presets/youtube.ts /** * CSP directives for youtube embed */ const CSP_PRESET_YOUTUBE_EMBED = { "frame-src": `https://www.youtube.com https://www.youtube-nocookie.com`, "img-src": `https://i.ytimg.com` }; //#endregion exports.CSP_HEADER_NAME = CSP_HEADER_NAME; exports.CSP_PRESET_DATADOG_CDN_BUNDLE_URL = CSP_PRESET_DATADOG_CDN_BUNDLE_URL; exports.CSP_PRESET_DATADOG_INTAKE_URLS = CSP_PRESET_DATADOG_INTAKE_URLS; exports.CSP_PRESET_DATADOG_WEB_WORKER = CSP_PRESET_DATADOG_WEB_WORKER; exports.CSP_PRESET_GOOGLE_ADS = CSP_PRESET_GOOGLE_ADS; exports.CSP_PRESET_GOOGLE_ADS_FULL_TLD = CSP_PRESET_GOOGLE_ADS_FULL_TLD; exports.CSP_PRESET_GOOGLE_ADS_USER_DATA_BEACON = CSP_PRESET_GOOGLE_ADS_USER_DATA_BEACON; exports.CSP_PRESET_GOOGLE_ANALYTICS_4 = CSP_PRESET_GOOGLE_ANALYTICS_4; exports.CSP_PRESET_GOOGLE_ANALYTICS_4_GOOGLE_SIGNALS = CSP_PRESET_GOOGLE_ANALYTICS_4_GOOGLE_SIGNALS; exports.CSP_PRESET_GOOGLE_ANALYTICS_4_GOOGLE_SIGNALS_FULL_TLD = CSP_PRESET_GOOGLE_ANALYTICS_4_GOOGLE_SIGNALS_FULL_TLD; exports.CSP_PRESET_GOOGLE_FONTS = CSP_PRESET_GOOGLE_FONTS; exports.CSP_PRESET_GOOGLE_IDENTITY = CSP_PRESET_GOOGLE_IDENTITY; exports.CSP_PRESET_GOOGLE_TAG_MANAGER_CUSTOM_JAVASCRIPT_VARIABLES = CSP_PRESET_GOOGLE_TAG_MANAGER_CUSTOM_JAVASCRIPT_VARIABLES; exports.CSP_PRESET_GOOGLE_TAG_MANAGER_NONCE = CSP_PRESET_GOOGLE_TAG_MANAGER_NONCE; exports.CSP_PRESET_GOOGLE_TAG_MANAGER_PREVIEW_MODE = CSP_PRESET_GOOGLE_TAG_MANAGER_PREVIEW_MODE; exports.CSP_PRESET_GOOGLE_TAG_MANAGER_UNSAFE_INLINE = CSP_PRESET_GOOGLE_TAG_MANAGER_UNSAFE_INLINE; exports.CSP_PRESET_GOOGLE_UNIVERSAL_ANALYTICS = CSP_PRESET_GOOGLE_UNIVERSAL_ANALYTICS; exports.CSP_PRESET_HOTJAR = CSP_PRESET_HOTJAR; exports.CSP_PRESET_INFOGRAM_EMBED = CSP_PRESET_INFOGRAM_EMBED; exports.CSP_PRESET_PODSCRIBE = CSP_PRESET_PODSCRIBE; exports.CSP_PRESET_REDDIT_EMBED = CSP_PRESET_REDDIT_EMBED; exports.CSP_PRESET_SENTRY_SESSION_REPLAY = CSP_PRESET_SENTRY_SESSION_REPLAY; exports.CSP_PRESET_TIKTOK_EMBED = CSP_PRESET_TIKTOK_EMBED; exports.CSP_PRESET_VIMEO_EMBED = CSP_PRESET_VIMEO_EMBED; exports.CSP_PRESET_X_EMBED = CSP_PRESET_X_EMBED; exports.CSP_PRESET_YOUTUBE_EMBED = CSP_PRESET_YOUTUBE_EMBED; exports.GOOGLE_SUPPORTED_DOMAINS = GOOGLE_SUPPORTED_DOMAINS; exports.createCspHeader = createCspHeader; exports.mergeCspConfigs = mergeCspConfigs; exports.mergeCspConfigsToSet = mergeCspConfigsToSet;