mirror of
https://github.com/mozilla-firefox/firefox
synced 2026-08-12 12:35:33 +00:00
Bug 1685123 - Implement manifest sandbox support r=robwu
Differential Revision: https://phabricator.services.mozilla.com/D308216
This commit is contained in:
committed by
rob@robwu.nl
parent
83d64ccda0
commit
bfa5336c09
@@ -85,8 +85,7 @@ interface nsIAddonContentPolicy : nsISupports
|
||||
/* options to pass to validateAddonCSP
|
||||
*
|
||||
* Manifest V2 uses CSP_ALLOW_ANY.
|
||||
* In Manifest V3, extension_pages would use CSP_ALLOW_WASM
|
||||
* and sandbox would use CSP_ALLOW_EVAL.
|
||||
* In Manifest V3, extension_pages would use CSP_ALLOW_WASM.
|
||||
*/
|
||||
const unsigned long CSP_ALLOW_ANY = 0xFFFF;
|
||||
const unsigned long CSP_ALLOW_LOCALHOST = (1<<0);
|
||||
@@ -100,4 +99,11 @@ interface nsIAddonContentPolicy : nsISupports
|
||||
* string describing the error for invalid policies.
|
||||
*/
|
||||
AString validateAddonCSP(in AString aPolicyString, in unsigned long aPermittedPolicy);
|
||||
|
||||
/**
|
||||
* Checks a custom content security policy string, to ensure that it meets
|
||||
* minimum security requirements for sandboxed extensions. Returns null for
|
||||
* valid policies, or a string describing the error for invalid policies.
|
||||
*/
|
||||
AString validateAddonSandboxCSP(in AString aPolicyString);
|
||||
};
|
||||
@@ -3860,9 +3860,12 @@ nsresult Document::InitCSP(nsIChannel* aChannel) {
|
||||
|
||||
// ----- if the doc is an addon, apply its CSP.
|
||||
if (addonPolicy) {
|
||||
csp->AppendPolicy(addonPolicy->BaseCSP(), false, false);
|
||||
|
||||
csp->AppendPolicy(addonPolicy->ExtensionPageCSP(), false, false);
|
||||
if (addonPolicy->Core()->IsSandboxPage(mDocumentURI)) {
|
||||
csp->AppendPolicy(addonPolicy->SandboxPageCSP(), false, false);
|
||||
} else {
|
||||
csp->AppendPolicy(addonPolicy->BaseCSP(), false, false);
|
||||
csp->AppendPolicy(addonPolicy->ExtensionPageCSP(), false, false);
|
||||
}
|
||||
}
|
||||
|
||||
// ----- if there's a full-strength CSP header, apply it.
|
||||
|
||||
@@ -91,6 +91,15 @@ interface WebExtensionPolicy {
|
||||
[Constant]
|
||||
readonly attribute DOMString extensionPageCSP;
|
||||
|
||||
/**
|
||||
* The content security policy string to apply to all sandboxed pages loaded from the
|
||||
* extension. This is set in the extension manifest.
|
||||
* If one is not provided by the extension it falls back to:
|
||||
* "sandbox allow-scripts; script-src 'self';".
|
||||
*/
|
||||
[Constant]
|
||||
readonly attribute DOMString sandboxPageCSP;
|
||||
|
||||
/**
|
||||
* The list of currently-active permissions for the extension, as specified
|
||||
* in its manifest.json file. May be updated to reflect changes in the
|
||||
@@ -377,6 +386,9 @@ dictionary WebExtensionInit {
|
||||
unsigned long manifestVersion = 2;
|
||||
DOMString? extensionPageCSP = null;
|
||||
|
||||
DOMString? sandboxPageCSP = null;
|
||||
sequence<MatchGlobOrString>? sandboxPages = null;
|
||||
|
||||
sequence<DOMString>? backgroundScripts = null;
|
||||
DOMString? backgroundWorkerScript = null;
|
||||
|
||||
|
||||
@@ -2224,6 +2224,14 @@ export class ExtensionData {
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
const sandboxPages = manifest.sandbox?.pages;
|
||||
if (sandboxPages) {
|
||||
// Normalize all paths to contain a single leading /
|
||||
result.sandboxPages = sandboxPages.map(path =>
|
||||
path.replace(/^\/*/, "/")
|
||||
);
|
||||
}
|
||||
} else if (this.type == "locale") {
|
||||
// Langpack startup is performance critical, so we want to compute as much
|
||||
// as possible here to make startup not trigger async DB reads.
|
||||
@@ -2354,6 +2362,7 @@ export class ExtensionData {
|
||||
await this.apiManager.lazyInit();
|
||||
|
||||
this.webAccessibleResources = manifestData.webAccessibleResources;
|
||||
this.sandboxPages = manifestData.sandboxPages;
|
||||
|
||||
this.originControls = manifestData.originControls;
|
||||
this.allowedOrigins = new MatchPatternSet(manifestData.originPermissions, {
|
||||
@@ -3510,6 +3519,7 @@ export class Extension extends ExtensionData {
|
||||
this.allowedOrigins = null;
|
||||
this._optionalOrigins = null;
|
||||
this.webAccessibleResources = null;
|
||||
this.sandboxPages = null;
|
||||
|
||||
this.registeredContentScripts = new Map();
|
||||
|
||||
@@ -3763,6 +3773,13 @@ export class Extension extends ExtensionData {
|
||||
return content_security_policy;
|
||||
}
|
||||
|
||||
get sandboxPageCSP() {
|
||||
if (this.manifestVersion === 2) {
|
||||
return this.manifest.sandbox?.content_security_policy;
|
||||
}
|
||||
return this.manifest.content_security_policy?.sandbox;
|
||||
}
|
||||
|
||||
get backgroundScripts() {
|
||||
return this.manifest.background?.scripts;
|
||||
}
|
||||
@@ -3825,6 +3842,8 @@ export class Extension extends ExtensionData {
|
||||
type: this.type,
|
||||
manifestVersion: this.manifestVersion,
|
||||
extensionPageCSP: this.extensionPageCSP,
|
||||
sandboxPageCSP: this.sandboxPageCSP,
|
||||
sandboxPages: this.sandboxPages,
|
||||
instanceId: this.instanceId,
|
||||
resourceURL: this.resourceURL,
|
||||
contentScripts: this.contentScripts,
|
||||
|
||||
@@ -159,7 +159,8 @@ ExtensionManager = {
|
||||
|
||||
manifestVersion: extension.manifestVersion,
|
||||
extensionPageCSP: extension.extensionPageCSP,
|
||||
|
||||
sandboxPageCSP: extension.sandboxPageCSP,
|
||||
sandboxPages: extension.sandboxPages,
|
||||
localizeCallback,
|
||||
|
||||
backgroundScripts,
|
||||
|
||||
@@ -1278,8 +1278,8 @@ const FORMATS = {
|
||||
},
|
||||
|
||||
contentSecurityPolicy(string, context) {
|
||||
// Manifest V3 extension_pages allows WASM. When sandbox is
|
||||
// implemented, or any other V3 or later directive, the flags
|
||||
// Manifest V3 extension_pages allows WASM. When any other V3
|
||||
// or later directive is implemented, the flags
|
||||
// logic will need to be updated.
|
||||
|
||||
let flags =
|
||||
@@ -1322,6 +1322,14 @@ const FORMATS = {
|
||||
return string;
|
||||
},
|
||||
|
||||
contentSecurityPolicySandbox(string) {
|
||||
const error = lazy.contentPolicyService.validateAddonSandboxCSP(string);
|
||||
if (error != null) {
|
||||
throw new Error(error);
|
||||
}
|
||||
return string;
|
||||
},
|
||||
|
||||
date(string) {
|
||||
// A valid ISO 8601 timestamp.
|
||||
const PATTERN =
|
||||
|
||||
@@ -68,6 +68,8 @@ static const char kBackgroundPageHTMLEnd[] =
|
||||
"script-src 'self' 'wasm-unsafe-eval' http://localhost:* " \
|
||||
"http://127.0.0.1:*;"
|
||||
|
||||
#define DEFAULT_SANDBOX_CSP "sandbox allow-scripts; script-src 'self';"
|
||||
|
||||
static inline ExtensionPolicyService& EPS() {
|
||||
return ExtensionPolicyService::GetSingleton();
|
||||
}
|
||||
@@ -93,13 +95,14 @@ static nsISubstitutingProtocolHandler* Proto() {
|
||||
|
||||
bool ParseGlobs(GlobalObject& aGlobal,
|
||||
Sequence<OwningMatchGlobOrUTF8String> aGlobs,
|
||||
nsTArray<RefPtr<MatchGlobCore>>& aResult, ErrorResult& aRv) {
|
||||
nsTArray<RefPtr<MatchGlobCore>>& aResult, ErrorResult& aRv,
|
||||
bool aAllowQuestion = true) {
|
||||
for (auto& elem : aGlobs) {
|
||||
if (elem.IsMatchGlob()) {
|
||||
aResult.AppendElement(elem.GetAsMatchGlob()->Core());
|
||||
} else {
|
||||
RefPtr<MatchGlobCore> glob =
|
||||
new MatchGlobCore(elem.GetAsUTF8String(), true, false, aRv);
|
||||
new MatchGlobCore(elem.GetAsUTF8String(), aAllowQuestion, false, aRv);
|
||||
if (aRv.Failed()) {
|
||||
return false;
|
||||
}
|
||||
@@ -213,6 +216,7 @@ WebExtensionPolicyCore::WebExtensionPolicyCore(GlobalObject& aGlobal,
|
||||
mType(NS_AtomizeMainThread(aInit.mType)),
|
||||
mManifestVersion(aInit.mManifestVersion),
|
||||
mExtensionPageCSP(aInit.mExtensionPageCSP),
|
||||
mSandboxPageCSP(aInit.mSandboxPageCSP),
|
||||
mIsPrivileged(aInit.mIsPrivileged),
|
||||
mTemporarilyInstalled(aInit.mTemporarilyInstalled),
|
||||
mBackgroundWorkerScript(aInit.mBackgroundWorkerScript),
|
||||
@@ -243,6 +247,17 @@ WebExtensionPolicyCore::WebExtensionPolicyCore(GlobalObject& aGlobal,
|
||||
}
|
||||
}
|
||||
|
||||
if (!aInit.mSandboxPages.IsNull()) {
|
||||
if (!ParseGlobs(aGlobal, aInit.mSandboxPages.Value(),
|
||||
mSandboxPages.SetValue(), aRv, false)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (mSandboxPageCSP.IsVoid()) {
|
||||
mSandboxPageCSP.AssignLiteral(DEFAULT_SANDBOX_CSP);
|
||||
}
|
||||
|
||||
if (mExtensionPageCSP.IsVoid()) {
|
||||
if (mManifestVersion < 3) {
|
||||
EPS().GetDefaultCSP(mExtensionPageCSP);
|
||||
@@ -267,6 +282,14 @@ WebExtensionPolicyCore::WebExtensionPolicyCore(GlobalObject& aGlobal,
|
||||
}
|
||||
}
|
||||
|
||||
bool WebExtensionPolicyCore::IsSandboxPage(nsIURI* aURI) const {
|
||||
extensions::URLInfo urlInfo(aURI);
|
||||
return aURI && !mSandboxPages.IsNull() &&
|
||||
urlInfo.Scheme() == nsGkAtoms::moz_extension &&
|
||||
MozExtensionHostname().Equals(urlInfo.Host()) &&
|
||||
mSandboxPages.Value().Matches(urlInfo.FilePath());
|
||||
}
|
||||
|
||||
bool WebExtensionPolicyCore::SourceMayAccessPath(
|
||||
const URLInfo& aURI, const nsACString& aPath) const {
|
||||
if (aURI.Scheme() == nsGkAtoms::moz_extension &&
|
||||
|
||||
@@ -94,6 +94,8 @@ class WebExtensionPolicyCore final {
|
||||
|
||||
const nsString& BaseCSP() const { return mBaseCSP; }
|
||||
|
||||
const nsString& SandboxPageCSP() const { return mSandboxPageCSP; }
|
||||
|
||||
const nsString& BackgroundWorkerScript() const {
|
||||
return mBackgroundWorkerScript;
|
||||
}
|
||||
@@ -107,6 +109,8 @@ class WebExtensionPolicyCore final {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsSandboxPage(nsIURI* aURL) const;
|
||||
|
||||
bool SourceMayAccessPath(const URLInfo& aURI, const nsACString& aPath) const;
|
||||
|
||||
bool HasPermission(const nsAtom* aPermission) const {
|
||||
@@ -198,6 +202,9 @@ class WebExtensionPolicyCore final {
|
||||
/* const */ nsString mExtensionPageCSP;
|
||||
/* const */ nsString mBaseCSP;
|
||||
|
||||
/* const */ nsString mSandboxPageCSP;
|
||||
/* const */ dom::Nullable<MatchGlobSet> mSandboxPages;
|
||||
|
||||
const bool mIsPrivileged;
|
||||
const bool mTemporarilyInstalled;
|
||||
|
||||
@@ -319,6 +326,9 @@ class WebExtensionPolicy final : public nsISupports, public nsWrapperCache {
|
||||
const nsString& BaseCSP() const { return mCore->BaseCSP(); }
|
||||
void GetBaseCSP(nsAString& aCSP) const { aCSP = BaseCSP(); }
|
||||
|
||||
const nsString& SandboxPageCSP() const { return mCore->SandboxPageCSP(); }
|
||||
void GetSandboxPageCSP(nsAString& aCSP) const { aCSP = SandboxPageCSP(); }
|
||||
|
||||
already_AddRefed<MatchPatternSet> AllowedOrigins() {
|
||||
return do_AddRef(mHostPermissions);
|
||||
}
|
||||
|
||||
@@ -231,12 +231,40 @@
|
||||
"optional": true,
|
||||
"format": "contentSecurityPolicy",
|
||||
"description": "The Content Security Policy used for extension pages."
|
||||
},
|
||||
"sandbox": {
|
||||
"type": "string",
|
||||
"optional": true,
|
||||
"format": "contentSecurityPolicySandbox",
|
||||
"description": "The content security policy used for sandboxed extension pages."
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
"sandbox": {
|
||||
"type": "object",
|
||||
"optional": true,
|
||||
"additionalProperties": {
|
||||
"$ref": "UnrecognizedProperty"
|
||||
},
|
||||
"properties": {
|
||||
"pages": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" },
|
||||
"description": "The list of pages in the form of globs to serve as sandboxed extension pages."
|
||||
},
|
||||
"content_security_policy": {
|
||||
"type": "string",
|
||||
"optional": true,
|
||||
"max_manifest_version": 2,
|
||||
"format": "contentSecurityPolicySandbox",
|
||||
"description": "The content security policy used for sandboxed extension pages."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"permissions": {
|
||||
"default": [],
|
||||
"optional": true,
|
||||
|
||||
+65
-12
@@ -33,6 +33,7 @@ async function testExecuteScript({
|
||||
manifest_version,
|
||||
userScript = false,
|
||||
activeTabPermission = false,
|
||||
sandbox = false,
|
||||
}) {
|
||||
const TEST_SUBFRAME_URL = "https://example.com/?test=mozExtIframe";
|
||||
const EXPECTED_ERROR_MESSAGE = "Missing host permission for the tab";
|
||||
@@ -62,6 +63,12 @@ async function testExecuteScript({
|
||||
manifestPart.host_permissions = ["https://example.com/*"];
|
||||
}
|
||||
|
||||
if (sandbox) {
|
||||
manifestPart.sandbox = {
|
||||
pages: ["extpage.html", "extpage-sandboxed.html"],
|
||||
};
|
||||
}
|
||||
|
||||
const web_accessible_resources =
|
||||
manifest_version === 3
|
||||
? [
|
||||
@@ -172,10 +179,18 @@ async function testExecuteScript({
|
||||
files: {
|
||||
"extpage.html": `<script src='extpage.js'><\/script>`,
|
||||
"extpage.js": function () {
|
||||
browser.test.sendMessage("extpage:ready");
|
||||
window.top.postMessage("extpage:ready", "*");
|
||||
},
|
||||
"extpage-sandboxed.html": `<script src='extpage-sandboxed.js'><\/script>`,
|
||||
"extpage-sandboxed.js": function () {
|
||||
window.top.postMessage("sandboxed-subframe:ready", "*");
|
||||
},
|
||||
"extpage-sandboxed.html": `<h1>sandboxed iframe</h1>`,
|
||||
"createSubFrame.js": function () {
|
||||
// forward post message events
|
||||
// eslint-disable-next-line mozilla/balanced-listeners
|
||||
window.addEventListener("message", event => {
|
||||
browser.test.sendMessage(event.data);
|
||||
});
|
||||
const iframeExt = document.createElement("iframe");
|
||||
iframeExt.src = browser.runtime.getURL("extpage.html");
|
||||
const iframeExtSandboxed = document.createElement("iframe");
|
||||
@@ -183,14 +198,7 @@ async function testExecuteScript({
|
||||
"extpage-sandboxed.html"
|
||||
);
|
||||
iframeExtSandboxed.setAttribute("sandbox", "allow-scripts");
|
||||
const promiseSandboxFrameLoaded = new Promise(resolve =>
|
||||
iframeExtSandboxed.addEventListener("load", resolve, { once: true })
|
||||
);
|
||||
document.body.append(iframeExtSandboxed);
|
||||
document.body.append(iframeExt);
|
||||
promiseSandboxFrameLoaded.then(() =>
|
||||
browser.test.sendMessage("sandboxed-subframe:ready")
|
||||
);
|
||||
document.body.append(iframeExtSandboxed, iframeExt);
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -216,8 +224,6 @@ async function testExecuteScript({
|
||||
? "userScripts.execute"
|
||||
: "scripting.executeScript";
|
||||
|
||||
await extension.awaitMessage("extpage:ready");
|
||||
|
||||
if (manifest_version < 3) {
|
||||
extension.sendMessage("tabs.executeScript", {
|
||||
activeTabPermission,
|
||||
@@ -340,6 +346,53 @@ add_task(
|
||||
}
|
||||
);
|
||||
|
||||
add_task(function test_csp_sandbox_mv2_executeScript() {
|
||||
return testExecuteScript({
|
||||
manifest_version: 2,
|
||||
sandbox: true,
|
||||
});
|
||||
});
|
||||
|
||||
add_task(function test_csp_sandbox_mv3_executeScript() {
|
||||
return testExecuteScript({
|
||||
manifest_version: 3,
|
||||
sandbox: true,
|
||||
});
|
||||
});
|
||||
|
||||
add_task(function test_csp_sandbox_activeTab_mv2_executeScript() {
|
||||
return testExecuteScript({
|
||||
manifest_version: 2,
|
||||
sandbox: true,
|
||||
activeTabPermission: true,
|
||||
});
|
||||
});
|
||||
|
||||
add_task(function test_csp_sandbox_activeTab_mv3_executeScript() {
|
||||
return testExecuteScript({
|
||||
manifest_version: 3,
|
||||
sandbox: true,
|
||||
activeTabPermission: true,
|
||||
});
|
||||
});
|
||||
|
||||
add_task(function test_csp_sandbox_mv3_userScripts_execute() {
|
||||
return testExecuteScript({
|
||||
manifest_version: 3,
|
||||
sandbox: true,
|
||||
userScript: true,
|
||||
});
|
||||
});
|
||||
|
||||
add_task(function test_csp_sandbox_activeTab_mv3_userScripts_execute() {
|
||||
return testExecuteScript({
|
||||
manifest_version: 3,
|
||||
sandbox: true,
|
||||
userScript: true,
|
||||
activeTabPermission: true,
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function testContentScriptsAndUserScriptsRegister() {
|
||||
const extensionMV2 = ExtensionTestUtils.loadExtension({
|
||||
manifest: {
|
||||
|
||||
@@ -329,3 +329,43 @@ add_task(async function test_csp_validator_extension_pages() {
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
add_task(async function test_csp_validator_sandbox_pages() {
|
||||
const checkPolicy = (policy, expectedResult) => {
|
||||
info(`Checking policy: ${policy}`);
|
||||
|
||||
const result = cps.validateAddonSandboxCSP(policy);
|
||||
equal(result, expectedResult);
|
||||
};
|
||||
|
||||
// Validate hard coded default csp
|
||||
const defaultPolicy = "sandbox allow-scripts; script-src 'self';";
|
||||
checkPolicy(defaultPolicy, null);
|
||||
|
||||
for (const policy of [
|
||||
"script-src 'self';",
|
||||
"script-src 'self'; default-src 'self';",
|
||||
]) {
|
||||
checkPolicy(
|
||||
policy,
|
||||
"Policy is missing a required \u2018sandbox\u2019 directive"
|
||||
);
|
||||
checkPolicy(`sandbox allow-scripts; ${policy}`, null);
|
||||
}
|
||||
|
||||
for (const policy of [
|
||||
"sandbox allow-same-origin;",
|
||||
"sandbox allow-scripts allow-same-origin;",
|
||||
]) {
|
||||
checkPolicy(
|
||||
policy,
|
||||
"‘sandbox’ directive contains a forbidden 'allow-same-origin' keyword"
|
||||
);
|
||||
}
|
||||
|
||||
// check with all existing sandbox flags except for allow-same-origin
|
||||
checkPolicy(
|
||||
`sandbox allow-downloads allow-downloads-without-user-activation allow-forms allow-modals allow-orientation-lock allow-pointer-lock allow-popups allow-popups-to-escape-sandbox allow-presentation allow-scripts allow-storage-access-by-user-activation allow-top-navigation allow-top-navigation-by-user-activation allow-top-navigation-to-custom-protocols; script-src 'unsafe-inline' 'unsafe-eval' https: http: data: blob: 'self';`,
|
||||
null
|
||||
);
|
||||
});
|
||||
+114
@@ -160,3 +160,117 @@ add_task(async function test_manifest_csp_v3() {
|
||||
|
||||
await extension.unload();
|
||||
});
|
||||
|
||||
async function testForValidSandboxCSP({ manifest_version, csp }) {
|
||||
const manifest = {
|
||||
manifest_version,
|
||||
sandbox: {
|
||||
pages: ["sandbox.html"],
|
||||
},
|
||||
};
|
||||
if (manifest_version < 3) {
|
||||
manifest.sandbox.content_security_policy = csp;
|
||||
} else {
|
||||
manifest.content_security_policy = { sandbox: csp };
|
||||
}
|
||||
ExtensionTestUtils.failOnSchemaWarnings(false);
|
||||
const normalized = await ExtensionTestUtils.normalizeManifest(manifest);
|
||||
ExtensionTestUtils.failOnSchemaWarnings(true);
|
||||
|
||||
equal(normalized.error, undefined, "Should not have an error");
|
||||
equal(normalized.errors.length, 0, "Should not have warnings");
|
||||
equal(
|
||||
manifest_version < 3
|
||||
? normalized.value.sandbox.content_security_policy
|
||||
: normalized.value.content_security_policy.sandbox,
|
||||
csp,
|
||||
"Should have the expected policy string"
|
||||
);
|
||||
}
|
||||
|
||||
add_task(async function test_valid_sandbox_csp_mv2() {
|
||||
await testForValidSandboxCSP({
|
||||
manifest_version: 2,
|
||||
csp: "sandbox allow-scripts; script-src 'self' 'unsafe-eval' 'unsafe-inline';",
|
||||
});
|
||||
});
|
||||
add_task(async function test_valid_sandbox_csp_mv3() {
|
||||
await testForValidSandboxCSP({
|
||||
manifest_version: 3,
|
||||
csp: "sandbox allow-scripts; script-src 'self' 'unsafe-eval' 'unsafe-inline';",
|
||||
});
|
||||
});
|
||||
|
||||
async function testForInvalidSandboxCSP({
|
||||
manifest_version,
|
||||
csp,
|
||||
expectedError,
|
||||
}) {
|
||||
const manifest = {
|
||||
manifest_version,
|
||||
sandbox: {
|
||||
pages: ["sandbox.html"],
|
||||
},
|
||||
};
|
||||
if (manifest_version < 3) {
|
||||
manifest.sandbox.content_security_policy = csp;
|
||||
} else {
|
||||
manifest.content_security_policy = { sandbox: csp };
|
||||
}
|
||||
ExtensionTestUtils.failOnSchemaWarnings(false);
|
||||
const normalized = await ExtensionTestUtils.normalizeManifest(manifest);
|
||||
ExtensionTestUtils.failOnSchemaWarnings(true);
|
||||
|
||||
equal(normalized.errors.length, 0, "Should not have warnings");
|
||||
equal(normalized.error, expectedError, "Should have the expected error");
|
||||
equal(normalized.value, undefined, "Manifest parsing should have failed");
|
||||
}
|
||||
|
||||
add_task(async function test_empty_sandbox_csp_mv2() {
|
||||
await testForInvalidSandboxCSP({
|
||||
manifest_version: 2,
|
||||
csp: "",
|
||||
expectedError:
|
||||
"Error processing sandbox.content_security_policy: Error: Policy is missing a required ‘sandbox’ directive",
|
||||
});
|
||||
});
|
||||
add_task(async function test_empty_sandbox_csp_mv3() {
|
||||
await testForInvalidSandboxCSP({
|
||||
manifest_version: 3,
|
||||
csp: "",
|
||||
expectedError:
|
||||
"Error processing content_security_policy.sandbox: Error: Policy is missing a required ‘sandbox’ directive",
|
||||
});
|
||||
});
|
||||
add_task(async function test_missing_sandbox_csp_mv2() {
|
||||
await testForInvalidSandboxCSP({
|
||||
manifest_version: 2,
|
||||
csp: "script-src 'self'; object-src 'none'",
|
||||
expectedError:
|
||||
"Error processing sandbox.content_security_policy: Error: Policy is missing a required ‘sandbox’ directive",
|
||||
});
|
||||
});
|
||||
add_task(async function test_missing_sandbox_csp_mv3() {
|
||||
await testForInvalidSandboxCSP({
|
||||
manifest_version: 3,
|
||||
csp: "script-src 'self'; object-src 'none'",
|
||||
expectedError:
|
||||
"Error processing content_security_policy.sandbox: Error: Policy is missing a required ‘sandbox’ directive",
|
||||
});
|
||||
});
|
||||
add_task(async function test_disallowed_allow_same_origin_mv2() {
|
||||
await testForInvalidSandboxCSP({
|
||||
manifest_version: 2,
|
||||
csp: "sandbox allow-same-origin;",
|
||||
expectedError:
|
||||
"Error processing sandbox.content_security_policy: Error: ‘sandbox’ directive contains a forbidden 'allow-same-origin' keyword",
|
||||
});
|
||||
});
|
||||
add_task(async function test_disallowed_allow_same_origin_mv3() {
|
||||
await testForInvalidSandboxCSP({
|
||||
manifest_version: 3,
|
||||
csp: "sandbox allow-same-origin;",
|
||||
expectedError:
|
||||
"Error processing content_security_policy.sandbox: Error: ‘sandbox’ directive contains a forbidden 'allow-same-origin' keyword",
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,472 @@
|
||||
"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(
|
||||
`<script>window.top.postMessage(window.origin, "*");</script>`
|
||||
);
|
||||
});
|
||||
|
||||
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": `<!DOCTYPE html><title>x</title>`,
|
||||
},
|
||||
});
|
||||
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": `<!DOCTYPE html><title>x</title>`,
|
||||
},
|
||||
});
|
||||
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": `<!DOCTYPE html><title>x</title>`,
|
||||
},
|
||||
});
|
||||
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 <all_urls> for exposing to a sandboxed document.
|
||||
matches: ["<all_urls>"],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
const extension = ExtensionTestUtils.loadExtension({
|
||||
manifest,
|
||||
files: {
|
||||
"sandbox.html": `<!DOCTYPE html><html>
|
||||
<head><meta charset="UTF-8"></head>
|
||||
<body></body>
|
||||
`,
|
||||
// Cannot use browser.test here due to bug 1896824
|
||||
"privileged.html": `<script src='privileged.js'><\/script>`,
|
||||
"privileged.js": function () {
|
||||
window.top.postMessage(typeof browser != "undefined", "*");
|
||||
},
|
||||
},
|
||||
});
|
||||
await extension.startup();
|
||||
|
||||
const sandboxURL = extension.extension.getURL("sandbox.html");
|
||||
const sandboxPage = await ExtensionTestUtils.loadContentPage(sandboxURL);
|
||||
|
||||
const isPrivileged = await sandboxPage.spawn([], () => {
|
||||
return new Promise(resolve => {
|
||||
const { window, document } = content;
|
||||
window.addEventListener("message", event => resolve(event.data), {
|
||||
once: true,
|
||||
});
|
||||
const iframe = document.createElement("iframe");
|
||||
iframe.src = "privileged.html";
|
||||
iframe.onerror = () => resolve(null);
|
||||
document.body.append(iframe);
|
||||
});
|
||||
});
|
||||
|
||||
if (isWebAccessible) {
|
||||
equal(
|
||||
isPrivileged,
|
||||
false,
|
||||
"Privileged page was loaded without extension API access."
|
||||
);
|
||||
} else {
|
||||
equal(isPrivileged, null, "Privileged page loading was blocked.");
|
||||
}
|
||||
|
||||
await sandboxPage.close();
|
||||
await extension.unload();
|
||||
}
|
||||
|
||||
add_task(async function test_sandbox_embedding_privileged_page_mv2() {
|
||||
await embedExtensionPageFromSandbox({ manifest_version: 2 });
|
||||
});
|
||||
|
||||
add_task(async function test_sandbox_embedding_privileged_page_mv3() {
|
||||
await embedExtensionPageFromSandbox({ manifest_version: 3 });
|
||||
});
|
||||
|
||||
add_task(async function test_sandbox_embedding_web_accessible_page_mv2() {
|
||||
await embedExtensionPageFromSandbox({
|
||||
manifest_version: 2,
|
||||
isWebAccessible: true,
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function test_sandbox_embedding_web_accessible_page_mv3() {
|
||||
await embedExtensionPageFromSandbox({
|
||||
manifest_version: 3,
|
||||
isWebAccessible: true,
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function test_sandbox_pages() {
|
||||
const TESTS = [
|
||||
{
|
||||
pages: [],
|
||||
isSandboxed: {
|
||||
page01: false,
|
||||
page02: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
pages: ["page01.html"],
|
||||
isSandboxed: {
|
||||
page01: true,
|
||||
page02: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
pages: ["/page01.html"],
|
||||
isSandboxed: {
|
||||
page01: true,
|
||||
page02: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
pages: ["/page01.html", "/page02.html"],
|
||||
isSandboxed: {
|
||||
page01: true,
|
||||
page02: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
pages: ["/page*.html"],
|
||||
isSandboxed: {
|
||||
page01: true,
|
||||
page02: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
pages: ["*"],
|
||||
isSandboxed: {
|
||||
page01: true,
|
||||
page02: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
pages: ["/page??.html"],
|
||||
isSandboxed: {
|
||||
page01: false,
|
||||
page02: false,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
for (const test of TESTS) {
|
||||
for (const manifest_version of [2, 3]) {
|
||||
info(
|
||||
`Testing sandbox pages: ${test.pages.join(", ")} for manifest version ${manifest_version}`
|
||||
);
|
||||
const extension = ExtensionTestUtils.loadExtension({
|
||||
manifest: createSandboxManifest({
|
||||
manifest_version,
|
||||
pages: test.pages,
|
||||
}),
|
||||
files: {
|
||||
"page01.html": `<!DOCTYPE html><title>01</title>`,
|
||||
"page02.html": `<!DOCTYPE html><title>02</title>`,
|
||||
},
|
||||
});
|
||||
await extension.startup();
|
||||
|
||||
const contentPage01 = await ExtensionTestUtils.loadContentPage(
|
||||
extension.extension.getURL("page01.html")
|
||||
);
|
||||
const page01IsSandboxed = await contentPage01.spawn(
|
||||
[],
|
||||
() => content.window.wrappedJSObject.origin === "null"
|
||||
);
|
||||
equal(
|
||||
page01IsSandboxed,
|
||||
test.isSandboxed.page01,
|
||||
"page01.html is sandboxed"
|
||||
);
|
||||
|
||||
const contentPage02 = await ExtensionTestUtils.loadContentPage(
|
||||
extension.extension.getURL("page02.html?test=param#testHash")
|
||||
);
|
||||
const page02IsSandboxed = await contentPage02.spawn(
|
||||
[],
|
||||
() => content.window.wrappedJSObject.origin === "null"
|
||||
);
|
||||
equal(
|
||||
page02IsSandboxed,
|
||||
test.isSandboxed.page02,
|
||||
"page02.html is sandboxed"
|
||||
);
|
||||
|
||||
await contentPage01.close();
|
||||
await contentPage02.close();
|
||||
await extension.unload();
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -100,6 +100,8 @@ run-if = [
|
||||
|
||||
["test_ext_runtime_sendMessage_args.js"]
|
||||
|
||||
["test_ext_sandbox_csp.js"]
|
||||
|
||||
["test_ext_schemas.js"]
|
||||
head = "head.js head_schemas.js"
|
||||
|
||||
@@ -137,8 +139,8 @@ skip-if = [
|
||||
skip-if = [
|
||||
"os == 'win' && os_version == '10.2009' && arch == 'x86_64'", # Bug 1996467
|
||||
"os == 'win' && os_version == '11.26100' && arch == 'x86'", # Bug 1996467
|
||||
"os == 'win' && os_version == '11.26200' && arch == 'x86'", # Bug 1996467
|
||||
"os == 'win' && os_version == '11.26100' && arch == 'x86_64'", # Bug 1996467
|
||||
"os == 'win' && os_version == '11.26200' && arch == 'x86'", # Bug 1996467
|
||||
"os == 'win' && os_version == '11.26200' && arch == 'x86_64'", # Bug 1996467
|
||||
]
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "nsIURI.h"
|
||||
#include "nsNetCID.h"
|
||||
#include "nsNetUtil.h"
|
||||
#include "nsSandboxFlags.h"
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::intl;
|
||||
@@ -367,3 +368,67 @@ AddonContentPolicy::ValidateAddonCSP(const nsAString& aPolicyString,
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a custom content security policy string for use by sandboxed add-on
|
||||
* pages. The CSP must not contain allow-same-origin.
|
||||
*/
|
||||
NS_IMETHODIMP
|
||||
AddonContentPolicy::ValidateAddonSandboxCSP(const nsAString& aPolicyString,
|
||||
nsAString& aResult) {
|
||||
nsresult rv;
|
||||
|
||||
// Validate against a randomly-generated extension origin.
|
||||
// There is no add-on-specific behavior in the CSP code, beyond the ability
|
||||
// for add-ons to specify a custom policy, but the parser requires a valid
|
||||
// origin in order to operate correctly.
|
||||
nsAutoString url(u"moz-extension://");
|
||||
{
|
||||
nsCOMPtr<nsIUUIDGenerator> uuidgen = components::UUIDGenerator::Service();
|
||||
NS_ENSURE_TRUE(uuidgen, NS_ERROR_FAILURE);
|
||||
|
||||
nsID id;
|
||||
rv = uuidgen->GenerateUUIDInPlace(&id);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
char idString[NSID_LENGTH];
|
||||
id.ToProvidedString(idString);
|
||||
|
||||
MOZ_RELEASE_ASSERT(idString[0] == '{' && idString[NSID_LENGTH - 2] == '}',
|
||||
"UUID generator did not return a valid UUID");
|
||||
|
||||
url.AppendASCII(idString + 1, NSID_LENGTH - 3);
|
||||
}
|
||||
|
||||
RefPtr<BasePrincipal> principal =
|
||||
BasePrincipal::CreateContentPrincipal(NS_ConvertUTF16toUTF8(url));
|
||||
|
||||
nsCOMPtr<nsIURI> selfURI;
|
||||
principal->GetURI(getter_AddRefs(selfURI));
|
||||
RefPtr<nsCSPContext> csp = new nsCSPContext();
|
||||
rv = csp->SetRequestContextWithPrincipal(principal, selfURI, ""_ns, 0);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
csp->AppendPolicy(aPolicyString, false, false);
|
||||
|
||||
uint32_t sandboxFlags = 0;
|
||||
rv = csp->GetCSPSandboxFlags(&sandboxFlags);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
// ensure sandbox without allow-same-origin
|
||||
if (sandboxFlags & SANDBOXED_ORIGIN) {
|
||||
aResult.SetIsVoid(true);
|
||||
} else {
|
||||
CSPDirective directive = nsIContentSecurityPolicy::SANDBOX_DIRECTIVE;
|
||||
CSPValidator validator(url, directive, true,
|
||||
nsIAddonContentPolicy::CSP_ALLOW_ANY);
|
||||
// provide more detailed error when allow-same-origin is set
|
||||
if (sandboxFlags != SANDBOXED_NONE) {
|
||||
validator.FormatError("csp-error-illegal-keyword"_ns, "keyword"_ns,
|
||||
u"'allow-same-origin'"_ns);
|
||||
} else {
|
||||
MOZ_ASSERT(!validator.GetError().IsVoid(),
|
||||
"CSPValidator should have raised an error.");
|
||||
}
|
||||
aResult.Assign(validator.GetError());
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
+2
@@ -4663,6 +4663,8 @@ interface WebExtensionInit {
|
||||
baseURL: string;
|
||||
contentScripts?: WebExtensionContentScriptInit[];
|
||||
extensionPageCSP?: string | null;
|
||||
sandboxPageCSP?: string | null;
|
||||
sandboxPages?: MatchGlobOrString[] | null;
|
||||
hasRecommendedState?: boolean;
|
||||
id: string;
|
||||
ignoreQuarantine?: boolean;
|
||||
|
||||
+2
@@ -1177,6 +1177,8 @@ interface nsIAddonContentPolicy extends nsISupports {
|
||||
readonly CSP_ALLOW_WASM?: 8;
|
||||
|
||||
validateAddonCSP(aPolicyString: string, aPermittedPolicy: u32): string;
|
||||
|
||||
validateAddonSandboxCSP(aPolicyString: string): string;
|
||||
}
|
||||
|
||||
// https://searchfox.org/firefox-main/source/caps/nsIDomainPolicy.idl
|
||||
|
||||
Reference in New Issue
Block a user