"use strict";
// helper to create respective manifests for manifest_version 2 and 3
function createSandboxManifest({ manifest_version, csp, pages }) {
const manifest = {
manifest_version,
sandbox: {
pages,
},
};
if (manifest_version < 3) {
manifest.sandbox.content_security_policy = csp;
} else {
manifest.content_security_policy = { sandbox: csp };
}
return manifest;
}
add_task(async function test_default_sandbox_csp() {
const defaultSandboxCSP = "sandbox allow-scripts; script-src 'self';";
for (const manifest_version of [2, 3]) {
info(
`Testing default sandbox CSP for manifest version ${manifest_version}`
);
const extension = ExtensionTestUtils.loadExtension({});
await extension.startup();
const policy = WebExtensionPolicy.getByID(extension.id);
equal(policy.sandboxPageCSP, defaultSandboxCSP, "sandboxCSP is correct.");
await extension.unload();
}
});
add_task(async function test_sandbox_null_origin() {
const server = createHttpServer();
server.registerPathHandler("/return_origin_header", (request, response) => {
response.setHeader("Content-Type", "text/plain");
response.setHeader("Access-Control-Allow-Origin", "*", false);
response.write(request.getHeader("Origin"));
});
server.registerPathHandler("/message_origin.html", (request, response) => {
response.setHeader("Content-Type", "text/html");
response.setHeader("Access-Control-Allow-Origin", "*", false);
response.write(
``
);
});
const BASE_URL = `http://localhost:${server.identity.primaryPort}`;
// required to load iframe
allow_unsafe_parent_loads_when_extensions_not_remote();
for (const manifest_version of [2, 3]) {
info(
`Testing sandbox "null" origin for manifest version ${manifest_version}`
);
const extension = ExtensionTestUtils.loadExtension({
manifest: createSandboxManifest({
manifest_version,
pages: ["sandbox.html"],
}),
files: {
"sandbox.html": `
x`,
},
});
await extension.startup();
const contentPage = await ExtensionTestUtils.loadContentPage(
extension.extension.getURL("sandbox.html")
);
const noExtensionAPI = await contentPage.spawn([], () => {
const w = content.window.wrappedJSObject;
return w.browser === undefined && w.chrome === undefined;
});
equal(noExtensionAPI, true, "Check that extension API is not exposed");
const windowOrigin = await contentPage.spawn([], () => {
return content.window.wrappedJSObject.origin;
});
equal(windowOrigin, "null", "Check window origin");
const requestOrigin = await contentPage.spawn([BASE_URL], async base => {
const response = await content.window.wrappedJSObject.fetch(
`${base}/return_origin_header`
);
return response.text();
});
equal(requestOrigin, "null", "Check origin request header");
const iframeOrigin = await contentPage.spawn([BASE_URL], base => {
const { window, document } = content.window.wrappedJSObject;
return new Promise((resolve, reject) => {
window.addEventListener("message", event => resolve(event.data), {
once: true,
});
const iframe = document.createElement("iframe");
iframe.src = `${base}/message_origin.html`;
iframe.onerror = reject;
document.body.append(iframe);
});
});
equal(
iframeOrigin,
"null",
"Web page in iframe inherits CSP sandbox from sandboxed extension document"
);
await contentPage.close();
await extension.unload();
}
revert_allow_unsafe_parent_loads_when_extensions_not_remote();
});
add_task(async function test_sandbox_csp() {
const server = createHttpServer();
server.registerPathHandler("/script_sets_var.js", (request, response) => {
response.setHeader("Content-Type", "text/javascript");
response.setHeader("Access-Control-Allow-Origin", "*", false);
response.write(`window.testRemoteScript = true;`);
});
const BASE_URL = `http://localhost:${server.identity.primaryPort}`;
const sandboxCSP = "sandbox allow-scripts; script-src 'self'";
const TESTS = [
{
description: "Test eval.",
relaxedPageCSP: `${sandboxCSP} 'unsafe-eval';`,
restrictedPageCSP: sandboxCSP,
violatedDirective: "script-src",
injectInto: contentPage =>
contentPage.spawn([], () => {
const { window } = content.window.wrappedJSObject;
try {
// eslint-disable-next-line no-eval
return window.eval("true");
} catch (e) {
return false;
}
}),
},
{
description: "Test inline script injection.",
relaxedPageCSP: `${sandboxCSP} 'unsafe-inline';`,
restrictedPageCSP: sandboxCSP,
violatedDirective: "script-src-elem",
injectInto: contentPage =>
contentPage.spawn([], () => {
const { window, document } = content.window.wrappedJSObject;
const script = document.createElement("script");
script.textContent = "window.testInlineScript = true;";
document.body.append(script);
return window.testInlineScript ?? false;
}),
},
{
description: "Test data URL script injection.",
relaxedPageCSP: `${sandboxCSP} data:;`,
restrictedPageCSP: sandboxCSP,
violatedDirective: "script-src-elem",
injectInto: contentPage =>
contentPage.spawn([], () => {
const { window, document } = content.window.wrappedJSObject;
return new Promise(resolve => {
const script = document.createElement("script");
script.src =
"data:text/javascript;base64," +
window.btoa("window.testDataURLScript = true;");
script.onload = () => {
resolve(window.testDataURLScript ?? false);
};
script.onerror = () => resolve(false);
document.body.append(script);
});
}),
},
{
description: " Test remote script injection.",
relaxedPageCSP: `${sandboxCSP} http://localhost:*;`,
restrictedPageCSP: sandboxCSP,
violatedDirective: "script-src-elem",
injectInto: contentPage =>
contentPage.spawn([`${BASE_URL}/script_sets_var.js`], url => {
const { window, document } = content.window.wrappedJSObject;
return new Promise(resolve => {
const script = document.createElement("script");
script.src = url;
script.onload = () => {
resolve(window.testRemoteScript ?? false);
};
script.onerror = () => resolve(false);
document.body.append(script);
});
}),
},
];
async function runWithRelaxedCSP(test, manifest_version) {
const extension = ExtensionTestUtils.loadExtension({
manifest: {
...createSandboxManifest({
manifest_version,
pages: ["sandbox.html"],
csp: test.relaxedPageCSP,
}),
host_permissions: ["http://localhost/*"],
},
files: {
"sandbox.html": `x`,
},
});
await extension.startup();
const contentPage = await ExtensionTestUtils.loadContentPage(
extension.extension.getURL("sandbox.html")
);
const result = await test.injectInto(contentPage);
equal(result, true, test.description);
await contentPage.close();
await extension.unload();
}
async function runWithRestrictedCSP(test, manifest_version) {
const extension = ExtensionTestUtils.loadExtension({
manifest: {
...createSandboxManifest({
manifest_version,
pages: ["sandbox.html"],
csp: test.restrictedPageCSP,
}),
host_permissions: ["http://localhost/*"],
},
files: {
"sandbox.html": `x`,
},
});
await extension.startup();
const contentPage = await ExtensionTestUtils.loadContentPage(
extension.extension.getURL("sandbox.html")
);
const awaitViolation = contentPage.spawn([], () => {
return new Promise(resolve => {
content.document.addEventListener(
"securitypolicyviolation",
e => {
resolve(e.violatedDirective);
},
{ once: true }
);
});
});
const result = await test.injectInto(contentPage);
equal(result, false, test.description);
equal(
await awaitViolation,
test.violatedDirective,
"violation in correct directive"
);
await contentPage.close();
await extension.unload();
}
for (const test of TESTS) {
info(`Running: ${test.description}`);
for (const manifest_version of [2, 3]) {
info(`Testing with manifest version ${manifest_version}`);
info(`Testing with relaxed CSP: ${test.relaxedPageCSP}`);
await runWithRelaxedCSP(test, manifest_version);
info(`Testing with restricted CSP: ${test.restrictedPageCSP}`);
await runWithRestrictedCSP(test, manifest_version);
}
}
});
async function embedExtensionPageFromSandbox({
manifest_version,
isWebAccessible = false,
}) {
const manifest = createSandboxManifest({
manifest_version,
pages: ["sandbox.html"],
});
if (isWebAccessible) {
manifest.web_accessible_resources =
manifest_version < 3
? ["privileged.html"]
: [
{
resources: ["privileged.html"],
// TODO bug 2052564: Stop requiring for exposing to a sandboxed document.
matches: [""],
},
];
}
const extension = ExtensionTestUtils.loadExtension({
manifest,
files: {
"sandbox.html": `
`,
// Cannot use browser.test here due to bug 1896824
"privileged.html": `