From bd51eccfb6a78c04064430f92197927a5519e695 Mon Sep 17 00:00:00 2001 From: Richard Bloor Date: Tue, 14 Jul 2026 11:41:38 +1200 Subject: [PATCH 1/3] Bug 1685123 docs for implementation of WebEx manifest sandbox --- .../content_security_policy/index.md | 2 + .../content_security_policy/index.md | 2 +- .../webextensions/manifest.json/index.md | 1 + .../manifest.json/sandbox/index.md | 200 ++++++++++++++++++ .../mozilla/firefox/releases/154/index.md | 4 +- 5 files changed, 205 insertions(+), 4 deletions(-) create mode 100644 files/en-us/mozilla/add-ons/webextensions/manifest.json/sandbox/index.md diff --git a/files/en-us/mozilla/add-ons/webextensions/content_security_policy/index.md b/files/en-us/mozilla/add-ons/webextensions/content_security_policy/index.md index 55cf26f3d06cf38..346ba987ee7d8bb 100644 --- a/files/en-us/mozilla/add-ons/webextensions/content_security_policy/index.md +++ b/files/en-us/mozilla/add-ons/webextensions/content_security_policy/index.md @@ -110,6 +110,8 @@ setTimeout("alert('Hello World!');", 500); const f = new Function("console.log('foo');"); ``` +If an extension needs to run code that relies on `eval()`-like constructs, such as some templating libraries, it can isolate that code in a [sandboxed page](/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/sandbox) instead of loosening the extension's CSP. + ### Inline JavaScript Under the default CSP, inline JavaScript is not executed. This disallows both JavaScript placed directly in ` + + + + +``` + +`sandbox.js` listens for messages from the popup, renders a template using the sandboxed library, and posts the result back: + +```js +window.addEventListener("message", (event) => { + const { template, context } = event.data; + const render = TemplatingLibrary.compile(template); + event.source.postMessage({ result: render(context) }, event.origin); +}); +``` + +`popup.html` embeds the sandboxed page in a hidden iframe: + +```html + + + + + + + + + + +``` + +`popup.js` sends data to the sandboxed page and handles the result: + +```js +const sandbox = document.getElementById("sandbox"); + +sandbox.addEventListener("load", () => { + sandbox.contentWindow.postMessage( + { template: "Hello, {{name}}!", context: { name: "world" } }, + "*", + ); +}); + +window.addEventListener("message", (event) => { + console.log(event.data.result); +}); +``` + +## Browser compatibility + +{{Compat}} diff --git a/files/en-us/mozilla/firefox/releases/154/index.md b/files/en-us/mozilla/firefox/releases/154/index.md index ed49505a4cb195c..c6ec4bdf5daadc1 100644 --- a/files/en-us/mozilla/firefox/releases/154/index.md +++ b/files/en-us/mozilla/firefox/releases/154/index.md @@ -72,9 +72,7 @@ Firefox 154 is the current [Nightly version of Firefox](https://www.firefox.com/ ## Changes for add-on developers - - - +- Adds support for the [`sandbox`](/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/sandbox) manifest key, enabling extensions to designate pages that load with an opaque origin. A sandboxed page can use `eval()` and similar constructs that are otherwise blocked by the extension's [content security policy](/en-US/docs/Mozilla/Add-ons/WebExtensions/Content_Security_Policy). The sandboxed page can only access WebExtension APIs or the rest of the extension through {{domxref("Window.postMessage()")}}. ([Firefox bug 1685123](https://bugzil.la/1685123)) ## Experimental web features From 64c7213d2d53ba84f2908bb2ee2a62158a3be2c1 Mon Sep 17 00:00:00 2001 From: rebloor Date: Mon, 27 Jul 2026 04:31:55 +1200 Subject: [PATCH 2/3] Apply suggestions from @Rob--W review Co-authored-by: Rob Wu --- .../webextensions/manifest.json/sandbox/index.md | 14 +++++++++----- files/en-us/mozilla/firefox/releases/154/index.md | 2 +- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/files/en-us/mozilla/add-ons/webextensions/manifest.json/sandbox/index.md b/files/en-us/mozilla/add-ons/webextensions/manifest.json/sandbox/index.md index 4bf371686013435..680b4200532efbf 100644 --- a/files/en-us/mozilla/add-ons/webextensions/manifest.json/sandbox/index.md +++ b/files/en-us/mozilla/add-ons/webextensions/manifest.json/sandbox/index.md @@ -37,7 +37,7 @@ Use the `sandbox` key to designate one or more of an extension's pages as **sand Sandboxed pages are loaded with a unique, opaque origin, instead of the extension's usual `moz-extension://` origin. As a result: - Sandboxed pages can't access [WebExtension APIs](/en-US/docs/Mozilla/Add-ons/WebExtensions/API). The `browser` and `chrome` global objects are not available. -- Sandboxed pages can't access, and can't be accessed by, other pages in the extension, except by using {{DOMxRef("Window.postMessage()")}}. +- Sandboxed pages can't access, and can't be accessed by, other pages in the extension, except indirectly by communicating through APIs such as {{DOMxRef("Window.postMessage()")}}. A sandboxed page can be given a more permissive [content security policy (CSP)](#content_security_policy_for_sandboxed_pages) than the rest of the extension. This includes a CSP that permits [`eval()`](/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval) and similar constructs that are blocked by an extension's [default content security policy](/en-US/docs/Mozilla/Add-ons/WebExtensions/Content_Security_Policy#default_content_security_policy). Because a sandboxed page can't use WebExtension APIs or reach the rest of the extension directly, this can be done without weakening the security of the extension as a whole. @@ -106,10 +106,7 @@ If a policy is not supplied, sandboxed pages get this default content security p sandbox allow-scripts; script-src 'self'; ``` -This isolates a sandboxed page from the rest of the extension, but doesn't allow `eval()` or similar constructs. To permit these, include `'unsafe-eval'` (or `'wasm-unsafe-eval'` for [WebAssembly](/en-US/docs/WebAssembly)) in the `script-src` directive of a custom policy. - -> [!NOTE] -> Chrome has a more permissive default sandboxed pages CSP: `sandbox allow-scripts allow-forms allow-popups allow-modals; script-src 'self' 'unsafe-inline' 'unsafe-eval'; child-src 'self';`. +This isolates a sandboxed page from the rest of the extension, but doesn't allow `eval()` or similar constructs. To permit these, include `'unsafe-eval'` in the `script-src` directive of a custom policy. Any custom policy supplied for sandboxed pages must meet these requirements: @@ -191,6 +188,13 @@ sandbox.addEventListener("load", () => { }); window.addEventListener("message", (event) => { + if (event.origin !== location.origin) { + // Reject messages not coming from the extension. + // Although the popup origin is opaque, + // location.origin still reflects the true URL of + // the resource in the extension package. + return; + } console.log(event.data.result); }); ``` diff --git a/files/en-us/mozilla/firefox/releases/154/index.md b/files/en-us/mozilla/firefox/releases/154/index.md index c6ec4bdf5daadc1..ee82a431c74d75f 100644 --- a/files/en-us/mozilla/firefox/releases/154/index.md +++ b/files/en-us/mozilla/firefox/releases/154/index.md @@ -72,7 +72,7 @@ Firefox 154 is the current [Nightly version of Firefox](https://www.firefox.com/ ## Changes for add-on developers -- Adds support for the [`sandbox`](/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/sandbox) manifest key, enabling extensions to designate pages that load with an opaque origin. A sandboxed page can use `eval()` and similar constructs that are otherwise blocked by the extension's [content security policy](/en-US/docs/Mozilla/Add-ons/WebExtensions/Content_Security_Policy). The sandboxed page can only access WebExtension APIs or the rest of the extension through {{domxref("Window.postMessage()")}}. ([Firefox bug 1685123](https://bugzil.la/1685123)) +- Adds support for the [`sandbox`](/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/sandbox) manifest key, enabling extensions to designate pages that load with an opaque origin, without direct access to extension APIs. A sandboxed page can use `eval()` and similar constructs that are otherwise blocked by the extension's [content security policy](/en-US/docs/Mozilla/Add-ons/WebExtensions/Content_Security_Policy). ([Firefox bug 1685123](https://bugzil.la/1685123)) ## Experimental web features From ee1c9cab6d68b05bac359342aa5f334980a3d6fc Mon Sep 17 00:00:00 2001 From: Richard Bloor Date: Mon, 27 Jul 2026 07:08:33 +1200 Subject: [PATCH 3/3] Various feedback updates --- .../manifest.json/sandbox/index.md | 97 ++++++++++--------- 1 file changed, 52 insertions(+), 45 deletions(-) diff --git a/files/en-us/mozilla/add-ons/webextensions/manifest.json/sandbox/index.md b/files/en-us/mozilla/add-ons/webextensions/manifest.json/sandbox/index.md index 680b4200532efbf..a17265f0c368772 100644 --- a/files/en-us/mozilla/add-ons/webextensions/manifest.json/sandbox/index.md +++ b/files/en-us/mozilla/add-ons/webextensions/manifest.json/sandbox/index.md @@ -34,63 +34,49 @@ sidebar: addonsidebar Use the `sandbox` key to designate one or more of an extension's pages as **sandboxed pages**. -Sandboxed pages are loaded with a unique, opaque origin, instead of the extension's usual `moz-extension://` origin. As a result: +Sandboxed pages are loaded with a unique, [opaque origin](/en-US/docs/Glossary/Origin#opaque_origin), instead of the extension's usual `moz-extension://` origin. As a result: - Sandboxed pages can't access [WebExtension APIs](/en-US/docs/Mozilla/Add-ons/WebExtensions/API). The `browser` and `chrome` global objects are not available. -- Sandboxed pages can't access, and can't be accessed by, other pages in the extension, except indirectly by communicating through APIs such as {{DOMxRef("Window.postMessage()")}}. +- Sandboxed pages can't access, and can't be accessed by, other pages in the extension. However, they can communicate through APIs such as {{DOMxRef("Window.postMessage()")}}. +- Web platform APIs bound to the origin are unavailable. For examples, see the [`allow-same-origin`](/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy/sandbox#allow-same-origin) value of the `Content-Security-Policy: sandbox` directive. A sandboxed page can be given a more permissive [content security policy (CSP)](#content_security_policy_for_sandboxed_pages) than the rest of the extension. This includes a CSP that permits [`eval()`](/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval) and similar constructs that are blocked by an extension's [default content security policy](/en-US/docs/Mozilla/Add-ons/WebExtensions/Content_Security_Policy#default_content_security_policy). Because a sandboxed page can't use WebExtension APIs or reach the rest of the extension directly, this can be done without weakening the security of the extension as a whole. -This makes the `sandbox` key useful for including a third-party library that relies on `eval()` or `new Function()`, such as some templating engines: load the library in a sandboxed page, and use `postMessage()` to send it data from, and return results to, the rest of the extension. +This makes the `sandbox` key useful for including a third-party library that relies on `eval()` or `new Function()`: load the library in a sandboxed page, and use `postMessage()` to send it data from, and return results to, the rest of the extension. -## Manifest V2 syntax +## Syntax -In Manifest V2, `sandbox` is an object with these properties: +`sandbox` is an object with these properties: - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
pagesArray of String - Required. A list of paths, relative to manifest.json, to the extension's sandboxed pages. -
content_security_policyString - Optional. The content security policy applied to the sandboxed pages. See Content security policy for sandboxed pages. -
+
+
pages
+
+ Required. An array of strings. A list of paths, relative to manifest.json, to the extension's sandboxed pages. +
+
content_security_policy
+
+ Optional. A string. Manifest V2 only. The content security policy applied to the sandboxed pages. For the Manifest V3 equivalent, see the sandbox property of the content_security_policy key, described in Content security policy for sandboxed pages. +
+
```json +// Manifest V2 "sandbox": { "pages": ["sandbox.html"], "content_security_policy": "sandbox allow-scripts; script-src 'self' 'unsafe-eval';" } ``` -## Manifest V3 syntax - -In Manifest V3, `sandbox` only supports the `pages` property: - ```json +// Manifest V3 "sandbox": { "pages": ["sandbox.html"] } ``` -The content security policy for sandboxed pages is instead set using the `sandbox` property of the [`content_security_policy`](/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/content_security_policy) key: +## Content security policy for sandboxed pages + +In Manifest V3, the content security policy for sandboxed pages is set using the `sandbox` property of the [`content_security_policy`](/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/content_security_policy) key. **This property is Manifest V3 only.** In Manifest V2, use the `content_security_policy` property of the `sandbox` key instead, as described in [Syntax](#syntax) above. ```json "content_security_policy": { @@ -98,8 +84,6 @@ The content security policy for sandboxed pages is instead set using the `sandbo } ``` -## Content security policy for sandboxed pages - If a policy is not supplied, sandboxed pages get this default content security policy: ```plain @@ -113,6 +97,14 @@ Any custom policy supplied for sandboxed pages must meet these requirements: - It must include the {{CSP("sandbox")}} directive. - The {{CSP("sandbox")}} directive must not include the `allow-same-origin` keyword. Allowing this would give the page access to the rest of the extension's origin, defeating the purpose of sandboxing it. +## Best practices + +When using a sandboxed page, consider these best practices: + +- **Validate messages.** As a sandboxed page communicates with the rest of the extension using {{DOMxRef("Window.postMessage()")}}, it's exposed to the same risks as any other use of `postMessage()`. Check the origin and shape of every message before acting on it. See [security concerns](/en-US/docs/Web/API/Window/postMessage#security_concerns) in the `postMessage()` documentation. +- **Don't execute untrusted code.** Although a sandboxed page can't use WebExtension APIs, its `moz-extension://` URL identifies the extension. Therefore, untrusted code running in the sandbox could be used to fingerprint the extension or its user. +- **Don't relax the CSP only to enable WebAssembly.** If WebAssembly support is the only reason for using a sandboxed page, add `'wasm-unsafe-eval'` to the extension's [`content_security_policy`](/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/content_security_policy) instead of relaxing the CSP of a sandboxed page. + ## Example This example loads a third-party templating library that uses `eval()`-like constructs into a sandboxed page, and uses it from a popup. @@ -150,24 +142,42 @@ This example loads a third-party templating library that uses `eval()`-like cons ``` +`templating-library.js` stands in for a third-party library that compiles templates using `new Function()`, which is why it must run in a sandboxed page: + +```js +const TemplatingLibrary = { + compile: (template) => (context) => + new Function(...Object.keys(context), `return \`${template}\`;`)( + ...Object.values(context), + ), +}; +``` + `sandbox.js` listens for messages from the popup, renders a template using the sandboxed library, and posts the result back: ```js window.addEventListener("message", (event) => { + if (event.source !== window.parent) { + // Ignore messages from any window other than the + // extension page that embeds this sandboxed page. + // The sandboxed page has an opaque origin, so + // location.origin can't be used for this check. + return; + } const { template, context } = event.data; const render = TemplatingLibrary.compile(template); event.source.postMessage({ result: render(context) }, event.origin); }); ``` -`popup.html` embeds the sandboxed page in a hidden iframe: +`popup.html` embeds the sandboxed page in a hidden iframe. The `defer` attribute on `popup.js` ensures it only runs after the iframe has been parsed: ```html - + @@ -182,17 +192,14 @@ const sandbox = document.getElementById("sandbox"); sandbox.addEventListener("load", () => { sandbox.contentWindow.postMessage( - { template: "Hello, {{name}}!", context: { name: "world" } }, + { template: "Hello, ${name}!", context: { name: "world" } }, "*", ); }); window.addEventListener("message", (event) => { - if (event.origin !== location.origin) { - // Reject messages not coming from the extension. - // Although the popup origin is opaque, - // location.origin still reflects the true URL of - // the resource in the extension package. + if (event.source !== sandbox.contentWindow) { + // Ignore messages from any window other than the sandboxed page. return; } console.log(event.data.result);