diff --git a/packages/wxt/src/core/utils/__tests__/manifest.test.ts b/packages/wxt/src/core/utils/__tests__/manifest.test.ts index 1af508dc5..459025f72 100644 --- a/packages/wxt/src/core/utils/__tests__/manifest.test.ts +++ b/packages/wxt/src/core/utils/__tests__/manifest.test.ts @@ -1581,6 +1581,41 @@ describe('Manifest Utils', () => { permissions: ['tabs', 'scripting'], }); }); + + it('should convert MV3 CSP object to MV2 CSP string with localhost for MV2', async () => { + const entrypoints: Entrypoint[] = []; + const buildOutput = fakeBuildOutput(); + const inputCsp = + "script-src 'self' 'wasm-unsafe-eval'; object-src 'self';"; + const expectedCsp = + "script-src 'self' 'wasm-unsafe-eval' http://localhost:3000; object-src 'self';"; + + // Setup WXT for Firefox and serve command + setFakeWxt({ + config: { + browser: 'firefox', + command: 'serve', + manifestVersion: 2, + manifest: { + content_security_policy: { + extension_pages: inputCsp, + }, + }, + }, + server: fakeWxtDevServer({ + port: 3000, + hostname: 'localhost', + origin: 'http://localhost:3000', + }), + }); + + const { manifest: actual } = await generateManifest( + entrypoints, + buildOutput, + ); + + expect(actual.content_security_policy).toEqual(expectedCsp); + }); }); }); diff --git a/packages/wxt/src/core/utils/manifest.ts b/packages/wxt/src/core/utils/manifest.ts index 17a9feb1f..98ca5da59 100644 --- a/packages/wxt/src/core/utils/manifest.ts +++ b/packages/wxt/src/core/utils/manifest.ts @@ -117,11 +117,12 @@ export async function generateManifest( if (wxt.config.manifestVersion === 2) { convertWebAccessibleResourcesToMv2(manifest); convertActionToMv2(manifest); + convertCspToMv2(manifest); moveHostPermissionsToPermissions(manifest); } if (wxt.config.manifestVersion === 3) { - validateMv3WebAccessbileResources(manifest); + validateMv3WebAccessibleResources(manifest); } stripKeys(manifest); @@ -143,7 +144,7 @@ export async function generateManifest( } /** - * Removes suffixes from the version, like X.Y.Z-alpha1 (which brosers don't allow), so it's a + * Removes suffixes from the version, like X.Y.Z-alpha1 (which browsers don't allow), so it's a * simple version number, like X or X.Y or X.Y.Z, which browsers allow. */ function simplifyVersion(versionName: string): string { @@ -467,34 +468,28 @@ function addDevModeCsp(manifest: Manifest.WebExtensionManifest): void { } const extensionPagesCsp = new ContentSecurityPolicy( - manifest.manifest_version === 3 - ? // @ts-expect-error: extension_pages is not typed - (manifest.content_security_policy?.extension_pages ?? - "script-src 'self' 'wasm-unsafe-eval'; object-src 'self';") // default extension_pages CSP for MV3 - : (manifest.content_security_policy ?? - "script-src 'self'; object-src 'self';"), // default CSP for MV2 + // @ts-expect-error: extension_pages exists, we convert MV2 CSPs to this earlier in the process + manifest.content_security_policy?.extension_pages ?? + (manifest.manifest_version === 3 + ? DEFAULT_MV3_EXTENSION_PAGES_CSP + : DEFAULT_MV2_CSP), ); const sandboxCsp = new ContentSecurityPolicy( // @ts-expect-error: sandbox is not typed - manifest.content_security_policy?.sandbox ?? - "sandbox allow-scripts allow-forms allow-popups allow-modals; script-src 'self' 'unsafe-inline' 'unsafe-eval'; child-src 'self';", // default sandbox CSP for MV3 + manifest.content_security_policy?.sandbox ?? DEFAULT_MV3_SANDBOX_CSP, ); - if (wxt.server) { + if (wxt.config.command === 'serve') { extensionPagesCsp.add('script-src', allowedCsp); sandboxCsp.add('script-src', allowedCsp); } - if (manifest.manifest_version === 3) { - manifest.content_security_policy ??= {}; - // @ts-expect-error: extension_pages is not typed - manifest.content_security_policy.extension_pages = - extensionPagesCsp.toString(); - // @ts-expect-error: sandbox is not typed - manifest.content_security_policy.sandbox = sandboxCsp.toString(); - } else { - manifest.content_security_policy = extensionPagesCsp.toString(); - } + manifest.content_security_policy ??= {}; + // @ts-expect-error: extension_pages is not typed + manifest.content_security_policy.extension_pages = + extensionPagesCsp.toString(); + // @ts-expect-error: sandbox is not typed + manifest.content_security_policy.sandbox = sandboxCsp.toString(); } function addDevModePermissions(manifest: Manifest.WebExtensionManifest) { @@ -613,7 +608,7 @@ export function stripPathFromMatchPattern(pattern: string) { /** * Converts all MV3 web accessible resources to their MV2 forms. MV3 web accessible resources are * generated in this file, and may be defined by the user in their manifest. In both cases, when - * targetting MV2, automatically convert their definitions down to the basic MV2 array. + * targeting MV2, automatically convert their definitions down to the basic MV2 array. */ export function convertWebAccessibleResourcesToMv2( manifest: Manifest.WebExtensionManifest, @@ -652,10 +647,21 @@ function convertActionToMv2(manifest: Manifest.WebExtensionManifest): void { manifest.browser_action = manifest.action; } +function convertCspToMv2(manifest: Manifest.WebExtensionManifest): void { + if ( + typeof manifest.content_security_policy === 'string' || + manifest.content_security_policy?.extension_pages == null + ) + return; + + manifest.content_security_policy = + manifest.content_security_policy.extension_pages; +} + /** * Make sure all resources are in MV3 format. If not, add a wanring */ -export function validateMv3WebAccessbileResources( +export function validateMv3WebAccessibleResources( manifest: Manifest.WebExtensionManifest, ): void { if (manifest.web_accessible_resources == null) return; @@ -718,3 +724,9 @@ const mv3OnlyKeys = [ 'side_panel', ]; const firefoxMv3OnlyKeys = ['host_permissions']; + +const DEFAULT_MV3_EXTENSION_PAGES_CSP = + "script-src 'self' 'wasm-unsafe-eval'; object-src 'self';"; +const DEFAULT_MV3_SANDBOX_CSP = + "sandbox allow-scripts allow-forms allow-popups allow-modals; script-src 'self' 'unsafe-inline' 'unsafe-eval'; child-src 'self';"; +const DEFAULT_MV2_CSP = "script-src 'self'; object-src 'self';"; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ddf65b694..cd620fddb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3041,10 +3041,12 @@ packages: glob@6.0.4: resolution: {integrity: sha512-MKZeRNyYZAVVVG1oZeLaWie1uweH40m9AZwIwxyPbTSX4hHrVYSzLg0Ro5Z5R7XKkIX+Cc6oD1rqeDJnwsB8/A==} + deprecated: Glob versions prior to v9 are no longer supported glob@8.1.0: resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} engines: {node: '>=12'} + deprecated: Glob versions prior to v9 are no longer supported global-dirs@3.0.1: resolution: {integrity: sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==} @@ -3174,6 +3176,7 @@ packages: inflight@1.0.6: resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} @@ -4283,6 +4286,7 @@ packages: rimraf@2.4.5: resolution: {integrity: sha512-J5xnxTyqaiw06JjMftq7L9ouA448dw/E7dKghkP9WpKNuwmARNNg+Gk8/u5ryb9N/Yo2+z3MCwuqFK/+qPOPfQ==} + deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true rollup-plugin-dts@6.1.1: