From ad3abf5577bae9be420b7ddf376337a5b8817869 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Correa=20Casablanca?= Date: Tue, 26 Mar 2024 23:54:40 +0100 Subject: [PATCH] refactor: introduce new sri options Signed-off-by: Andres Correa Casablanca --- src/core.mjs | 45 +++++++++++++++++---------------------------- src/main.d.ts | 34 ++++++++++++++++++++++++++++++++++ src/main.mjs | 6 ++++++ tests/core.test.mts | 4 ++-- 4 files changed, 59 insertions(+), 30 deletions(-) diff --git a/src/core.mjs b/src/core.mjs index 820d6dd..38bbe84 100644 --- a/src/core.mjs +++ b/src/core.mjs @@ -701,16 +701,14 @@ const resolvedMiddlewareVirtualModuleId = `\0${middlewareVirtualModuleId}` /** * @param {Logger} logger - * @param {boolean} enableStatic_SRI - * @param {string | undefined} sriHashesModule + * @param {Required} sri * @param {SecurityHeadersOptions | undefined} securityHeadersOptions * @param {string} publicDir * @returns {Promise} */ const loadVirtualMiddlewareModule = async ( logger, - enableStatic_SRI, - sriHashesModule, + sri, securityHeadersOptions, publicDir, ) => { @@ -718,9 +716,9 @@ const loadVirtualMiddlewareModule = async ( let staticHashesModuleLoader = '' if ( - enableStatic_SRI && - sriHashesModule && - !(await doesFileExist(sriHashesModule)) + sri.enableStatic && + sri.hashesModule && + !(await doesFileExist(sri.hashesModule)) ) { const h = /** @satisfies {HashesCollection} */ { inlineScriptHashes: new Set(), @@ -740,17 +738,17 @@ const loadVirtualMiddlewareModule = async ( await generateSRIHashesModule( logger, h, - sriHashesModule, + sri.hashesModule, false, // So we don't get redundant warnings ) } if ( - enableStatic_SRI && - sriHashesModule && - (await doesFileExist(sriHashesModule)) + sri.enableStatic && + sri.hashesModule && + (await doesFileExist(sri.hashesModule)) ) { - extraImports = `import { perResourceSriHashes } from '${sriHashesModule}'` + extraImports = `import { perResourceSriHashes } from '${sri.hashesModule}'` staticHashesModuleLoader = ` try { if (perResourceSriHashes) { @@ -769,11 +767,11 @@ try { console.error('Failed to load static hashes module:', err) } ` - } else if (enableStatic_SRI && sriHashesModule) { + } else if (sri.enableStatic && sri.hashesModule) { // Highly unlikely that this happens because of the provisional hashes // module, but the world is a strange place. logger.warn( - `The SRI hashes module "${sriHashesModule}" did not exist at build time. You may have to run the build step again`, + `The SRI hashes module "${sri.hashesModule}" did not exist at build time. You may have to run the build step again`, ) } @@ -805,19 +803,12 @@ export const onRequest = await (async () => { /** * @param {Logger} logger - * @param {boolean} enableStatic_SRI - * @param {string | undefined} sriHashesModule + * @param {Required} sri * @param {SecurityHeadersOptions | undefined} securityHeaders * @param {string} publicDir * @return {import('vite').Plugin} */ -const getViteMiddlewarePlugin = ( - logger, - enableStatic_SRI, - sriHashesModule, - securityHeaders, - publicDir, -) => { +const getViteMiddlewarePlugin = (logger, sri, securityHeaders, publicDir) => { return { name: 'vite-plugin-astro-shield', resolveId(id) { @@ -831,8 +822,7 @@ const getViteMiddlewarePlugin = ( case resolvedMiddlewareVirtualModuleId: return await loadVirtualMiddlewareModule( logger, - enableStatic_SRI, - sriHashesModule, + sri, securityHeaders, publicDir, ) @@ -844,7 +834,7 @@ const getViteMiddlewarePlugin = ( } /** - * @param {SRIOptions} sri + * @param {Required} sri * @param {SecurityHeadersOptions | undefined} securityHeaders * @returns */ @@ -854,8 +844,7 @@ export const getAstroConfigSetup = (sri, securityHeaders) => { const publicDir = fileURLToPath(config.publicDir) const plugin = getViteMiddlewarePlugin( logger, - sri.enableStatic ?? true, - sri.hashesModule, + sri, securityHeaders, publicDir, ) diff --git a/src/main.d.ts b/src/main.d.ts index 506bb50..9b22fe5 100644 --- a/src/main.d.ts +++ b/src/main.d.ts @@ -83,6 +83,40 @@ export type SRIOptions = { * artifact. */ hashesModule?: string | undefined + + /** + * Inline styles are usually considered unsafe because they could make it + * easier for an attacker to inject CSS rules in dynamic pages. However, they + * don't pose a serious security risk for _most_ static pages. + * + * You can disable this option in case you want to enforce a stricter policy. + * + * Defaults to 'all'. + */ + allowInlineStyles?: 'all' | 'static' | false + + /** + * Inline scripts are usually considered unsafe because they could make it + * easier for an attacker to inject JS code in dynamic pages. However, they + * don't pose a serious security risk for _most_ static pages. + * + * You can disable this option in case you want to enforce a stricter policy. + * + * Defaults to 'all'. + */ + allowInlineScripts?: 'all' | 'static' | false + + /** + * Cross-Origin scripts must be explicitly allow-listed by URL in order to be + * allowed by the Content Security Policy. + */ + scriptsAllowListUrls?: string[] + + /** + * Cross-Origin styles must be explicitly allow-listed by URL in order to be + * allowed by the Content Security Policy. + */ + stylesAllowListUrls?: string[] } export type SecurityHeadersOptions = { diff --git a/src/main.mjs b/src/main.mjs index 973ec32..e97d7e7 100644 --- a/src/main.mjs +++ b/src/main.mjs @@ -63,6 +63,12 @@ export const shield = ({ enableMiddleware: sri?.enableMiddleware ?? enableMiddleware_SRI ?? false, enableStatic: sri?.enableStatic ?? enableStatic_SRI ?? true, hashesModule: sri?.hashesModule ?? sriHashesModule, + + allowInlineScripts: sri?.allowInlineScripts ?? 'all', + allowInlineStyles: sri?.allowInlineStyles ?? 'all', + + scriptsAllowListUrls: sri?.scriptsAllowListUrls ?? [], + stylesAllowListUrls: sri?.stylesAllowListUrls ?? [], } if (_sri.hashesModule && _sri.enableStatic === false) { diff --git a/tests/core.test.mts b/tests/core.test.mts index 933839e..4d8a6d5 100644 --- a/tests/core.test.mts +++ b/tests/core.test.mts @@ -362,7 +362,7 @@ describe('updateStaticPageSriHashes', () => { My Test Page - + ` @@ -379,7 +379,7 @@ describe('updateStaticPageSriHashes', () => { expect(h.extScriptHashes.size).toBe(1) expect( h.extScriptHashes.has( - 'sha256-KrxzzNH5AjdyG84oIMGj043N5e4ZnvFjIC7HKOVJMv4=', + 'sha256-n5QiD5rG5p3P6N6SMn4S3Oc0MRSrqJdbCxTiOQHNdiU=', ), ).toBe(true) expect(h.inlineScriptHashes.size).toBe(0)