import { isDev } from '../../core/env'; import ContentSecurityPolicy = Session.ContentSecurityPolicy; import CompleteContentSecurityPolicy = Session.CompleteContentSecurityPolicy; const defaultCsp: Session.ContentSecurityPolicy = { 'default-src': ["'self'"], 'style-src': ["'unsafe-inline'"], }; function stringifyCspHeader(csp: ContentSecurityPolicy): string { return Object.entries(csp) .map( (directive: [string, Session.CspValue[] | undefined]) => `${directive[0]} ${directive[1] ? directive[1]?.join(' ') : ''}` ) .join('; '); } export function mergeContentSecurityPolicy(...contentSecurityPolicies: ContentSecurityPolicy[]): ContentSecurityPolicy { return contentSecurityPolicies.reduce((mergedCsp, contentSecurityPolicy) => { Object.entries(contentSecurityPolicy).forEach(([policyName, policy]) => { const mergedPolicy = mergedCsp[policyName as keyof ContentSecurityPolicy]; if (mergedPolicy && policy) { mergedPolicy.push(...policy); } else if (policy) { mergedCsp[policyName as keyof ContentSecurityPolicy] = policy; } }); return mergedCsp; }, {}); } export function setWindowCsp(window: Electron.BrowserWindow, csp: CompleteContentSecurityPolicy): void { const mergedCsp: ContentSecurityPolicy = { ...defaultCsp, ...csp }; if (isDev()) { mergedCsp['default-src'] = ['devtools:'].concat(mergedCsp['default-src'] ?? []); mergedCsp['script-src'] = ["'unsafe-eval'"].concat(mergedCsp['script-src'] ?? []); mergedCsp['script-src-elem'] = ['file:', 'devtools:', "'unsafe-inline'"].concat(mergedCsp['script-src-elem'] ?? []); mergedCsp['style-src'] = ['devtools:', "'unsafe-inline'"].concat(mergedCsp['style-src'] ?? []); mergedCsp['img-src'] = ['devtools:'].concat(mergedCsp['img-src'] ?? []); mergedCsp['connect-src'] = ['devtools:', 'data:'].concat(mergedCsp['connect-src'] ?? []); mergedCsp['worker-src'] = ['devtools:'].concat(mergedCsp['worker-src'] ?? []); } window.webContents.session.webRequest.onHeadersReceived((details, callback) => { callback({ responseHeaders: { ...details.responseHeaders, 'Content-Security-Policy': stringifyCspHeader(mergedCsp), }, }); }); } /** * fills script-src-elem and script-src-attr with script-src when not present, * fills rest of missing policies with default-src or nothing when it is not present */ export function completeContentSecurityPolicy(csp: ContentSecurityPolicy): CompleteContentSecurityPolicy { const defaultSrc = csp['default-src'] ?? []; const scriptSrc = csp['script-src'] ?? []; return { 'child-src': csp['child-src'] ?? defaultSrc, 'connect-src': csp['connect-src'] ?? defaultSrc, 'default-src': csp['default-src'] ?? defaultSrc, 'font-src': csp['font-src'] ?? defaultSrc, 'frame-src': csp['frame-src'] ?? defaultSrc, 'img-src': csp['img-src'] ?? defaultSrc, 'media-src': csp['media-src'] ?? defaultSrc, 'object-src': ["'none'"], 'script-src': csp['script-src'] ?? defaultSrc, 'script-src-elem': csp['script-src-elem'] ?? scriptSrc ?? defaultSrc, 'script-src-attr': csp['script-src-attr'] ?? scriptSrc ?? defaultSrc, 'style-src': csp['style-src'] ?? defaultSrc, 'worker-src': csp['worker-src'] ?? defaultSrc, }; }