summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authormihaip@chromium.org <mihaip@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-06-06 01:41:22 +0000
committermihaip@chromium.org <mihaip@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-06-06 01:41:22 +0000
commitdbb2416d77c8794278aa9e5c396addffee3025d0 (patch)
treeb7fb5e2b45f1318012848b3ee01520c490bd01a5
parent210c7bed9de215f7c6e14dcd72ab6544ed68b303 (diff)
downloadchromium_src-dbb2416d77c8794278aa9e5c396addffee3025d0.zip
chromium_src-dbb2416d77c8794278aa9e5c396addffee3025d0.tar.gz
chromium_src-dbb2416d77c8794278aa9e5c396addffee3025d0.tar.bz2
Add sandboxed_pages to allow extension/app pages to be served in a
sandboxed, unique origin. This allows manifest_version 2 extensions to have pages that are exempt from their Content Security Policy (but these pages can't call extension APIs either). Depends on http://webkit.org/b/88014 Review URL: https://chromiumcodereview.appspot.com/10458063 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@140689 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--chrome/browser/extensions/extension_protocols.cc6
-rw-r--r--chrome/browser/extensions/sandboxed_pages_apitest.cc9
-rw-r--r--chrome/chrome_tests.gypi6
-rw-r--r--chrome/common/extensions/api/_manifest_features.json7
-rw-r--r--chrome/common/extensions/csp_validator.cc47
-rw-r--r--chrome/common/extensions/csp_validator.h13
-rw-r--r--chrome/common/extensions/csp_validator_unittest.cc33
-rw-r--r--chrome/common/extensions/docs/manifest.html70
-rw-r--r--chrome/common/extensions/docs/static/manifest.html75
-rw-r--r--chrome/common/extensions/extension.cc65
-rw-r--r--chrome/common/extensions/extension.h23
-rw-r--r--chrome/common/extensions/extension_manifest_constants.cc8
-rw-r--r--chrome/common/extensions/extension_manifest_constants.h5
-rw-r--r--chrome/common/extensions/extension_set.cc15
-rw-r--r--chrome/common/extensions/extension_set.h6
-rw-r--r--chrome/common/extensions/manifest_tests/extension_manifests_sandboxed_unittest.cc57
-rw-r--r--chrome/renderer/extensions/extension_dispatcher.cc23
-rw-r--r--chrome/test/data/extensions/api_test/extension_resource_request_policy/web_accessible/iframe.js2
-rw-r--r--chrome/test/data/extensions/api_test/sandboxed_pages/main.html1
-rw-r--r--chrome/test/data/extensions/api_test/sandboxed_pages/main.js30
-rw-r--r--chrome/test/data/extensions/api_test/sandboxed_pages/manifest.json8
-rw-r--r--chrome/test/data/extensions/api_test/sandboxed_pages/sandboxed.html16
-rw-r--r--chrome/test/data/extensions/manifest_tests/sandboxed_pages_invalid_1.json8
-rw-r--r--chrome/test/data/extensions/manifest_tests/sandboxed_pages_invalid_2.json8
-rw-r--r--chrome/test/data/extensions/manifest_tests/sandboxed_pages_invalid_3.json10
-rw-r--r--chrome/test/data/extensions/manifest_tests/sandboxed_pages_invalid_4.json10
-rw-r--r--chrome/test/data/extensions/manifest_tests/sandboxed_pages_invalid_5.json14
-rw-r--r--chrome/test/data/extensions/manifest_tests/sandboxed_pages_valid_1.json8
-rw-r--r--chrome/test/data/extensions/manifest_tests/sandboxed_pages_valid_2.json5
-rw-r--r--chrome/test/data/extensions/manifest_tests/sandboxed_pages_valid_3.json9
30 files changed, 562 insertions, 35 deletions
diff --git a/chrome/browser/extensions/extension_protocols.cc b/chrome/browser/extensions/extension_protocols.cc
index e42c4c7..59e12d9 100644
--- a/chrome/browser/extensions/extension_protocols.cc
+++ b/chrome/browser/extensions/extension_protocols.cc
@@ -278,10 +278,12 @@ ExtensionProtocolHandler::MaybeCreateJob(net::URLRequest* request) const {
std::string content_security_policy;
bool send_cors_header = false;
if (extension) {
- content_security_policy = extension->content_security_policy();
+ std::string resource_path = request->url().path();
+ content_security_policy =
+ extension->GetResourceContentSecurityPolicy(resource_path);
if ((extension->manifest_version() >= 2 ||
extension->HasWebAccessibleResources()) &&
- extension->IsResourceWebAccessible(request->url().path()))
+ extension->IsResourceWebAccessible(resource_path))
send_cors_header = true;
}
diff --git a/chrome/browser/extensions/sandboxed_pages_apitest.cc b/chrome/browser/extensions/sandboxed_pages_apitest.cc
new file mode 100644
index 0000000..45ae865
--- /dev/null
+++ b/chrome/browser/extensions/sandboxed_pages_apitest.cc
@@ -0,0 +1,9 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/extensions/extension_apitest.h"
+
+IN_PROC_BROWSER_TEST_F(ExtensionApiTest, SandboxedPages) {
+ EXPECT_TRUE(RunExtensionSubtest("sandboxed_pages", "main.html")) << message_;
+}
diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi
index 4fba09d..3e6e74d 100644
--- a/chrome/chrome_tests.gypi
+++ b/chrome/chrome_tests.gypi
@@ -1886,6 +1886,7 @@
'common/extensions/manifest_tests/extension_manifests_pageaction_unittest.cc',
'common/extensions/manifest_tests/extension_manifests_platformapp_unittest.cc',
'common/extensions/manifest_tests/extension_manifests_portsinpermissions_unittest.cc',
+ 'common/extensions/manifest_tests/extension_manifests_sandboxed_unittest.cc',
'common/extensions/manifest_tests/extension_manifests_storage_unittest.cc',
'common/extensions/manifest_tests/extension_manifests_tts_unittest.cc',
'common/extensions/manifest_tests/extension_manifests_ui_unittest.cc',
@@ -2798,6 +2799,7 @@
'browser/extensions/platform_app_browsertest_util.cc',
'browser/extensions/platform_app_browsertest_util.h',
'browser/extensions/process_management_browsertest.cc',
+ 'browser/extensions/sandboxed_pages_apitest.cc',
'browser/extensions/settings/settings_apitest.cc',
'browser/extensions/stubs_apitest.cc',
'browser/extensions/system/system_apitest.cc',
@@ -3277,7 +3279,7 @@
# Mac, which does not use hunspell by default.
'browser/spellchecker/spellcheck_host_browsertest.cc',
# ProcessSingletonMac doesn't do anything.
- 'browser/process_singleton_browsertest.cc',
+ 'browser/process_singleton_browsertest.cc',
],
}, { # else: OS != "mac"
'sources!': [
@@ -3339,7 +3341,7 @@
'../content/browser/accessibility/dump_accessibility_tree_helper_win.cc',
'../content/browser/accessibility/dump_accessibility_tree_helper.cc',
]
- }, { # else: use_aura == 0
+ }, { # else: use_aura == 0
'sources!': [
'browser/ui/views/frame/app_non_client_frame_view_aura_browsertest.cc',
],
diff --git a/chrome/common/extensions/api/_manifest_features.json b/chrome/common/extensions/api/_manifest_features.json
index acd0a8c..772bd11 100644
--- a/chrome/common/extensions/api/_manifest_features.json
+++ b/chrome/common/extensions/api/_manifest_features.json
@@ -191,6 +191,13 @@
"extension", "packaged_app", "hosted_app", "platform_app"
]
},
+ "sandboxed_pages": {
+ "channel": "stable",
+ "extension_types": [
+ "extension", "platform_app"
+ ],
+ "min_manifest_version": 2
+ },
"signature": {
"channel": "stable",
"extension_types": "all"
diff --git a/chrome/common/extensions/csp_validator.cc b/chrome/common/extensions/csp_validator.cc
index 7b28bdf..a57bdb0 100644
--- a/chrome/common/extensions/csp_validator.cc
+++ b/chrome/common/extensions/csp_validator.cc
@@ -18,6 +18,11 @@ const char kDefaultSrc[] = "default-src";
const char kScriptSrc[] = "script-src";
const char kObjectSrc[] = "object-src";
+const char kSandboxDirectiveName[] = "sandbox";
+const char kAllowSameOriginToken[] = "allow-same-origin";
+const char kAllowTopNavigation[] = "allow-top-navigation";
+const char kAllowPopups[] = "allow-popups";
+
struct DirectiveStatus {
explicit DirectiveStatus(const char* name)
: directive_name(name)
@@ -119,6 +124,48 @@ bool ContentSecurityPolicyIsSecure(const std::string& policy) {
(script_src_status.seen_in_policy && object_src_status.seen_in_policy);
}
+bool ContentSecurityPolicyIsSandboxed(
+ const std::string& policy, Extension::Type type) {
+ // See http://www.w3.org/TR/CSP/#parse-a-csp-policy for parsing algorithm.
+ std::vector<std::string> directives;
+ base::SplitString(policy, ';', &directives);
+
+ bool seen_sandbox = false;
+
+ for (size_t i = 0; i < directives.size(); ++i) {
+ std::string& input = directives[i];
+ StringTokenizer tokenizer(input, " \t\r\n");
+ if (!tokenizer.GetNext())
+ continue;
+
+ std::string directive_name = tokenizer.token();
+ StringToLowerASCII(&directive_name);
+
+ if (directive_name != kSandboxDirectiveName)
+ continue;
+
+ seen_sandbox = true;
+
+ while (tokenizer.GetNext()) {
+ std::string token = tokenizer.token();
+ StringToLowerASCII(&token);
+
+ // The same origin token negates the sandboxing.
+ if (token == kAllowSameOriginToken)
+ return false;
+
+ // Platform apps don't allow navigation (and have a separate windowing
+ // API that should be used for popups)
+ if (type == Extension::TYPE_PLATFORM_APP) {
+ if (token == kAllowTopNavigation || token == kAllowPopups)
+ return false;
+ }
+ }
+ }
+
+ return seen_sandbox;
+}
+
} // csp_validator
} // extensions
diff --git a/chrome/common/extensions/csp_validator.h b/chrome/common/extensions/csp_validator.h
index 640efd5..4c3d267 100644
--- a/chrome/common/extensions/csp_validator.h
+++ b/chrome/common/extensions/csp_validator.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
@@ -8,6 +8,8 @@
#include <string>
+#include "chrome/common/extensions/extension.h"
+
namespace extensions {
namespace csp_validator {
@@ -26,6 +28,15 @@ bool ContentSecurityPolicyIsLegal(const std::string& policy);
// forbidden for scripts and objects.
bool ContentSecurityPolicyIsSecure(const std::string& policy);
+// Checks whether the given |policy| enforces a unique origin sandbox as
+// defined by http://www.whatwg.org/specs/web-apps/current-work/multipage/
+// the-iframe-element.html#attr-iframe-sandbox. The policy must have the
+// "sandbox" directive, and the sandbox tokens must not include
+// "allow-same-origin". Additional restrictions may be imposed depending on
+// |type|.
+bool ContentSecurityPolicyIsSandboxed(
+ const std::string& policy, Extension::Type type);
+
} // namespace csp_validator
} // namespace extensions
diff --git a/chrome/common/extensions/csp_validator_unittest.cc b/chrome/common/extensions/csp_validator_unittest.cc
index 5b9bfbe..33c3deb 100644
--- a/chrome/common/extensions/csp_validator_unittest.cc
+++ b/chrome/common/extensions/csp_validator_unittest.cc
@@ -7,6 +7,8 @@
using extensions::csp_validator::ContentSecurityPolicyIsLegal;
using extensions::csp_validator::ContentSecurityPolicyIsSecure;
+using extensions::csp_validator::ContentSecurityPolicyIsSandboxed;
+using extensions::Extension;
TEST(ExtensionCSPValidator, IsLegal) {
EXPECT_TRUE(ContentSecurityPolicyIsLegal("foo"));
@@ -75,3 +77,34 @@ TEST(ExtensionCSPValidator, IsSecure) {
EXPECT_TRUE(ContentSecurityPolicyIsSecure(
"default-src 'self' https://*.google.com"));
}
+
+TEST(ExtensionCSPValidator, IsSandboxed) {
+ EXPECT_FALSE(ContentSecurityPolicyIsSandboxed("", Extension::TYPE_EXTENSION));
+ EXPECT_FALSE(ContentSecurityPolicyIsSandboxed(
+ "img-src https://google.com", Extension::TYPE_EXTENSION));
+
+ // Sandbox directive is required.
+ EXPECT_TRUE(ContentSecurityPolicyIsSandboxed(
+ "sandbox", Extension::TYPE_EXTENSION));
+
+ // Additional sandbox tokens are OK.
+ EXPECT_TRUE(ContentSecurityPolicyIsSandboxed(
+ "sandbox allow-scripts", Extension::TYPE_EXTENSION));
+ // Except for allow-same-origin.
+ EXPECT_FALSE(ContentSecurityPolicyIsSandboxed(
+ "sandbox allow-same-origin", Extension::TYPE_EXTENSION));
+
+ // Additional directives are OK.
+ EXPECT_TRUE(ContentSecurityPolicyIsSandboxed(
+ "sandbox; img-src https://google.com", Extension::TYPE_EXTENSION));
+
+ // Extensions allow navigation and popups, platform apps don't.
+ EXPECT_TRUE(ContentSecurityPolicyIsSandboxed(
+ "sandbox allow-top-navigation", Extension::TYPE_EXTENSION));
+ EXPECT_FALSE(ContentSecurityPolicyIsSandboxed(
+ "sandbox allow-top-navigation", Extension::TYPE_PLATFORM_APP));
+ EXPECT_TRUE(ContentSecurityPolicyIsSandboxed(
+ "sandbox allow-popups", Extension::TYPE_EXTENSION));
+ EXPECT_FALSE(ContentSecurityPolicyIsSandboxed(
+ "sandbox allow-popups", Extension::TYPE_PLATFORM_APP));
+}
diff --git a/chrome/common/extensions/docs/manifest.html b/chrome/common/extensions/docs/manifest.html
index 38353bc..4e3ba9c 100644
--- a/chrome/common/extensions/docs/manifest.html
+++ b/chrome/common/extensions/docs/manifest.html
@@ -232,6 +232,8 @@
<a href="#manifest_version">manifest_version</a>
</li><li>
<a href="#web_accessible_resources">web_accessible_resources</a>
+ </li><li>
+ <a href="#sandboxed_pages">sandboxed_pages</a>
</li>
</ol>
</li>
@@ -288,7 +290,8 @@ are <b>name</b> and <b>version</b>.
"<a href="npapi.html">plugins</a>": [...],
"<a href="#requirements">requirements</a>": {...},
"<a href="autoupdate.html">update_url</a>": "http://<em>path/to/updateInfo</em>.xml",
- "<a href="#web_accessible_resources">web_accessible_resources</a>": [...]
+ "<a href="#web_accessible_resources">web_accessible_resources</a>": [...],
+ "<a href="#sandboxed_pages">sandboxed_pages</a>": [...]
}
</pre>
<a name="H2-1"></a><h2>Field details</h2>
@@ -428,7 +431,7 @@ A dictionary that specifies all intent handlers provided by this extension or ap
"version": "1",
"intents": {
"http://webintents.org/share": [
- {
+ {
"type": ["text/uri-list"],
"href": "/services/sharelink.html",
"title" : "Sample Link Sharing Intent",
@@ -441,11 +444,11 @@ A dictionary that specifies all intent handlers provided by this extension or ap
"disposition" : "window"
}
]
- }
+ }
}
</pre>
<p>
-The value of "type" is an array of mime types that is supported by this handler. The "href" indicates the URL of the page that handles the intent. For hosted apps, these URLs must be within the allowed set of URLs. For extensions, all URLs are inside the extension and considered relative to the extension root URL.
+The value of "type" is an array of mime types that is supported by this handler. The "href" indicates the URL of the page that handles the intent. For hosted apps, these URLs must be within the allowed set of URLs. For extensions, all URLs are inside the extension and considered relative to the extension root URL.
</p>
<p>
The "title" is displayed in the intent picker UI when the user initiates the action specific to the handler.
@@ -780,7 +783,7 @@ table.
<tr>
<td> "unlimitedStorage"</td>
<td> Provides an unlimited quota for storing HTML5 client-side data,
- such as databases and local storage files.
+ such as databases and local storage files.
Without this permission, the extension is limited to
5 MB of local storage.
<p class="note">
@@ -789,7 +792,7 @@ table.
(see issue <a href="http://crbug.com/58985">58985</a>).
Also, it doesn't currently work with wildcard subdomains such as
<code>http://*.example.com</code>.
- </p>
+ </p>
</td>
</tr><tr>
</tr><tr>
@@ -938,11 +941,10 @@ mechanisms like XHR.
<p>
Injected content scripts themselves do not need to be whitelisted.
</p>
-<h4>Default Availablility</h4>
+<h4>Default Availability</h4>
<p>
-Resources inside of packages using <a href="#manifest_version">
- <code>manifest_version</code>
-</a> 2 or above are <strong>blocked by default</strong>, and must be whitelisted
+Resources inside of packages using <a href="#manifest_version"><code>manifest_version</code></a>
+2 or above are <strong>blocked by default</strong>, and must be whitelisted
for use via this property.
</p>
<p>
@@ -951,6 +953,54 @@ by default, but <em>if</em> you do set this property, then it will be treated as
a complete list of all whitelisted resources. Resources not listed will be
blocked.
</p>
+<h3 id="sandboxed_pages">sandboxed_pages</h3>
+<p>
+A list of paths (relative to the package root) to pages that are to be served
+in a sandboxed unique origin, and optionally a Content Security Policy to use
+with them. Being in a sandbox has two implications:
+</p>
+<ol>
+<li>A sandboxed page will not have access to extension or app APIs, or
+direct access to non-sandboxed pages (it may communicate with them via
+<code>postMessage()</code>).</li>
+<li>A sandboxed page is not subject to the
+<a href="contentSecurityPolicy.html">Content Security Policy (CSP)</a> used
+by the rest of the app or extension (it has its own separate CSP value). This
+means that, for example, it can use inline script and <code>eval</code>.</li>
+</ol>
+<p>For example, here's how to specify that two extension pages are to be served
+in a sandbox with a custom CSP:</p>
+<pre>{
+ ...
+ "sandboxed_pages": {
+ "pages": [
+ "page1.html",
+ "directory/page2.html"
+ ]
+ <i>// content_security_policy is optional.</i>
+ "content_security_policy":
+ "sandbox allow-scripts: script-src https://www.google.com"
+ ],
+ ...
+}</pre>
+<p>
+The sandbox is enforced by using the
+<a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-iframe-element.html#attr-iframe-sandbox">HTML5 sandbox</a>
+with the tokens <code>allow-scripts allow-forms</code>. You can specify a
+different CSP value to use instead, but it must have the <code>sandbox</code>
+directive and may not have the <code>allow-same-origin</code> token.
+</p>
+<p>
+Note that you only need to list pages that you expected to be loaded in
+windows or frames. Resources used by sandboxed pages (e.g. stylesheets or
+JavaScript source files) do not need to appear in the
+<code>sandboxed_page</code> list, they will use the sandbox of the page
+that embeds them.
+</p>
+<p>
+Sandboxed page may only be specified when using
+<a href="#manifest_version"><code>manifest_version</code></a> 2 or above.
+</p>
</div>
<!-- API PAGE -->
<!-- /apiPage -->
diff --git a/chrome/common/extensions/docs/static/manifest.html b/chrome/common/extensions/docs/static/manifest.html
index 22fa116..57fc05b 100644
--- a/chrome/common/extensions/docs/static/manifest.html
+++ b/chrome/common/extensions/docs/static/manifest.html
@@ -55,7 +55,8 @@ are <b>name</b> and <b>version</b>.
"<a href="npapi.html">plugins</a>": [...],
"<a href="#requirements">requirements</a>": {...},
"<a href="autoupdate.html">update_url</a>": "http://<em>path/to/updateInfo</em>.xml",
- "<a href="#web_accessible_resources">web_accessible_resources</a>": [...]
+ "<a href="#web_accessible_resources">web_accessible_resources</a>": [...],
+ "<a href="#sandboxed_pages">sandboxed_pages</a>": [...]
}
</pre>
@@ -224,7 +225,7 @@ A dictionary that specifies all intent handlers provided by this extension or ap
"version": "1",
"intents": {
"http://webintents.org/share": [
- {
+ {
"type": ["text/uri-list"],
"href": "/services/sharelink.html",
"title" : "Sample Link Sharing Intent",
@@ -237,12 +238,12 @@ A dictionary that specifies all intent handlers provided by this extension or ap
"disposition" : "window"
}
]
- }
+ }
}
</pre>
<p>
-The value of "type" is an array of mime types that is supported by this handler. The "href" indicates the URL of the page that handles the intent. For hosted apps, these URLs must be within the allowed set of URLs. For extensions, all URLs are inside the extension and considered relative to the extension root URL.
+The value of "type" is an array of mime types that is supported by this handler. The "href" indicates the URL of the page that handles the intent. For hosted apps, these URLs must be within the allowed set of URLs. For extensions, all URLs are inside the extension and considered relative to the extension root URL.
</p>
<p>
@@ -615,7 +616,7 @@ table.
<tr>
<td> "unlimitedStorage"</td>
<td> Provides an unlimited quota for storing HTML5 client-side data,
- such as databases and local storage files.
+ such as databases and local storage files.
Without this permission, the extension is limited to
5 MB of local storage.
@@ -625,7 +626,7 @@ table.
(see issue <a href="http://crbug.com/58985">58985</a>).
Also, it doesn't currently work with wildcard subdomains such as
<code>http://*.example.com</code>.
- </p>
+ </p>
</td>
<tr>
<tr>
@@ -803,12 +804,11 @@ mechanisms like XHR.
Injected content scripts themselves do not need to be whitelisted.
</p>
-<h4>Default Availablility</h4>
+<h4>Default Availability</h4>
<p>
-Resources inside of packages using <a href="#manifest_version">
- <code>manifest_version</code>
-</a> 2 or above are <strong>blocked by default</strong>, and must be whitelisted
+Resources inside of packages using <a href="#manifest_version"><code>manifest_version</code></a>
+2 or above are <strong>blocked by default</strong>, and must be whitelisted
for use via this property.
</p>
@@ -819,3 +819,58 @@ a complete list of all whitelisted resources. Resources not listed will be
blocked.
</p>
+<h3 id="sandboxed_pages">sandboxed_pages</h3>
+
+<p>
+A list of paths (relative to the package root) to pages that are to be served
+in a sandboxed unique origin, and optionally a Content Security Policy to use
+with them. Being in a sandbox has two implications:
+</p>
+
+<ol>
+<li>A sandboxed page will not have access to extension or app APIs, or
+direct access to non-sandboxed pages (it may communicate with them via
+<code>postMessage()</code>).</li>
+<li>A sandboxed page is not subject to the
+<a href="contentSecurityPolicy.html">Content Security Policy (CSP)</a> used
+by the rest of the app or extension (it has its own separate CSP value). This
+means that, for example, it can use inline script and <code>eval</code>.</li>
+</ol>
+
+<p>For example, here's how to specify that two extension pages are to be served
+in a sandbox with a custom CSP:</p>
+
+<pre>{
+ ...
+ "sandboxed_pages": {
+ "pages": [
+ "page1.html",
+ "directory/page2.html"
+ ]
+ <i>// content_security_policy is optional.</i>
+ "content_security_policy":
+ "sandbox allow-scripts: script-src https://www.google.com"
+ ],
+ ...
+}</pre>
+
+<p>
+The sandbox is enforced by using the
+<a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-iframe-element.html#attr-iframe-sandbox">HTML5 sandbox</a>
+with the tokens <code>allow-scripts allow-forms</code>. You can specify a
+different CSP value to use instead, but it must have the <code>sandbox</code>
+directive and may not have the <code>allow-same-origin</code> token.
+</p>
+
+<p>
+Note that you only need to list pages that you expected to be loaded in
+windows or frames. Resources used by sandboxed pages (e.g. stylesheets or
+JavaScript source files) do not need to appear in the
+<code>sandboxed_page</code> list, they will use the sandbox of the page
+that embeds them.
+</p>
+
+<p>
+Sandboxed page may only be specified when using
+<a href="#manifest_version"><code>manifest_version</code></a> 2 or above.
+</p>
diff --git a/chrome/common/extensions/extension.cc b/chrome/common/extensions/extension.cc
index 54e0566..b6050b0 100644
--- a/chrome/common/extensions/extension.cc
+++ b/chrome/common/extensions/extension.cc
@@ -61,6 +61,7 @@ namespace info_keys = extension_info_keys;
namespace switch_utils = extensions::switch_utils;
using extensions::csp_validator::ContentSecurityPolicyIsLegal;
+using extensions::csp_validator::ContentSecurityPolicyIsSandboxed;
using extensions::csp_validator::ContentSecurityPolicyIsSecure;
namespace extensions {
@@ -102,6 +103,9 @@ const char kDefaultPlatformAppContentSecurityPolicy[] =
"frame-src " PLATFORM_APP_LOCAL_CSP_SOURCES ";"
"font-src " PLATFORM_APP_LOCAL_CSP_SOURCES ";";
+const char kDefaultSandboxedPageContentSecurityPolicy[] =
+ "sandbox allow-scripts allow-forms";
+
// Converts a normal hexadecimal string into the alphabet used by extensions.
// We use the characters 'a'-'p' instead of '0'-'f' to avoid ever having a
// completely numeric host, since some software interprets that as an IP
@@ -526,6 +530,17 @@ bool Extension::HasWebAccessibleResources() const {
return false;
}
+bool Extension::IsSandboxedPage(const std::string& relative_path) const {
+ return sandboxed_pages_.find(relative_path) != sandboxed_pages_.end();
+}
+
+
+std::string Extension::GetResourceContentSecurityPolicy(
+ const std::string& relative_path) const {
+ return IsSandboxedPage(relative_path) ?
+ sandboxed_pages_content_security_policy_ : content_security_policy_;
+}
+
bool Extension::GenerateId(const std::string& input, std::string* output) {
DCHECK(output);
uint8 hash[Extension::kIdSize];
@@ -1280,6 +1295,7 @@ bool Extension::LoadSharedFeatures(
!LoadPlugins(error) ||
!LoadNaClModules(error) ||
!LoadWebAccessibleResources(error) ||
+ !LoadSandboxedPages(error) ||
!CheckRequirements(error) ||
!LoadDefaultLocale(error) ||
!LoadOfflineEnabled(error) ||
@@ -1551,7 +1567,7 @@ bool Extension::LoadNaClModules(string16* error) {
bool Extension::LoadWebAccessibleResources(string16* error) {
if (!manifest_->HasKey(keys::kWebAccessibleResources))
return true;
- ListValue* list_value;
+ ListValue* list_value = NULL;
if (!manifest_->GetList(keys::kWebAccessibleResources, &list_value)) {
*error = ASCIIToUTF16(errors::kInvalidWebAccessibleResourcesList);
return false;
@@ -1571,6 +1587,53 @@ bool Extension::LoadWebAccessibleResources(string16* error) {
return true;
}
+bool Extension::LoadSandboxedPages(string16* error) {
+ // Can't use HasKey, since it doesn't do path expansion.
+ Value* ignored = NULL;
+ if (!manifest_->Get(keys::kSandboxedPages, &ignored))
+ return true;
+
+ ListValue* list_value = NULL;
+ if (!manifest_->GetList(keys::kSandboxedPages, &list_value)) {
+ *error = ASCIIToUTF16(errors::kInvalidSandboxedPagesList);
+ return false;
+ }
+ for (size_t i = 0; i < list_value->GetSize(); ++i) {
+ std::string relative_path;
+ if (!list_value->GetString(i, &relative_path)) {
+ *error = ExtensionErrorUtils::FormatErrorMessageUTF16(
+ errors::kInvalidSandboxedPage, base::IntToString(i));
+ return false;
+ }
+ if (relative_path[0] != '/')
+ relative_path = '/' + relative_path;
+ sandboxed_pages_.insert(relative_path);
+ }
+
+ if (manifest_->Get(keys::kSandboxedPagesCSP, &ignored)) {
+ if (!manifest_->GetString(
+ keys::kSandboxedPagesCSP, &sandboxed_pages_content_security_policy_)) {
+ *error = ASCIIToUTF16(errors::kInvalidSandboxedPagesCSP);
+ return false;
+ }
+
+ if (!ContentSecurityPolicyIsLegal(
+ sandboxed_pages_content_security_policy_) ||
+ !ContentSecurityPolicyIsSandboxed(
+ sandboxed_pages_content_security_policy_, GetType())) {
+ *error = ASCIIToUTF16(errors::kInvalidSandboxedPagesCSP);
+ return false;
+ }
+ } else {
+ sandboxed_pages_content_security_policy_ =
+ kDefaultSandboxedPageContentSecurityPolicy;
+ CHECK(ContentSecurityPolicyIsSandboxed(
+ sandboxed_pages_content_security_policy_, GetType()));
+ }
+
+ return true;
+}
+
// These are not actually persisted (they're only used by the store), but
// still validated.
bool Extension::CheckRequirements(string16* error) {
diff --git a/chrome/common/extensions/extension.h b/chrome/common/extensions/extension.h
index 9b2c83e..9713fd8 100644
--- a/chrome/common/extensions/extension.h
+++ b/chrome/common/extensions/extension.h
@@ -330,6 +330,15 @@ class Extension : public base::RefCountedThreadSafe<Extension> {
// Returns true if the specified resource is web accessible.
bool IsResourceWebAccessible(const std::string& relative_path) const;
+ // Returns true if the specified page is sandboxed (served in a unique
+ // origin).
+ bool IsSandboxedPage(const std::string& relative_path) const;
+
+ // Returns the Content Security Policy that the specified resource should be
+ // served with.
+ std::string GetResourceContentSecurityPolicy(const std::string& relative_path)
+ const;
+
// Returns true when 'web_accessible_resources' are defined for the extension.
bool HasWebAccessibleResources() const;
@@ -630,10 +639,6 @@ class Extension : public base::RefCountedThreadSafe<Extension> {
bool from_webstore() const { return (creation_flags_ & FROM_WEBSTORE) != 0; }
bool from_bookmark() const { return (creation_flags_ & FROM_BOOKMARK) != 0; }
- const std::string& content_security_policy() const {
- return content_security_policy_;
- }
-
// App-related.
bool is_app() const {
return is_packaged_app() || is_hosted_app() || is_platform_app();
@@ -742,6 +747,7 @@ class Extension : public base::RefCountedThreadSafe<Extension> {
bool LoadPlugins(string16* error);
bool LoadNaClModules(string16* error);
bool LoadWebAccessibleResources(string16* error);
+ bool LoadSandboxedPages(string16* error);
bool CheckRequirements(string16* error);
bool LoadDefaultLocale(string16* error);
bool LoadOfflineEnabled(string16* error);
@@ -950,6 +956,15 @@ class Extension : public base::RefCountedThreadSafe<Extension> {
// Optional list of web accessible extension resources.
base::hash_set<std::string> web_accessible_resources_;
+ // Optional list of extension pages that are sandboxed (served from a unique
+ // origin with a different Content Security Policy).
+ base::hash_set<std::string> sandboxed_pages_;
+
+ // Content Security Policy that should be used to enforce the sandbox used
+ // by sandboxed pages (guaranteed to have the "sandbox" directive without the
+ // "allow-same-origin" token).
+ std::string sandboxed_pages_content_security_policy_;
+
// Optional URL to a master page of which a single instance should be always
// loaded in the background.
GURL background_url_;
diff --git a/chrome/common/extensions/extension_manifest_constants.cc b/chrome/common/extensions/extension_manifest_constants.cc
index e19c30d..2fda6da 100644
--- a/chrome/common/extensions/extension_manifest_constants.cc
+++ b/chrome/common/extensions/extension_manifest_constants.cc
@@ -92,6 +92,8 @@ const char kPluginsPublic[] = "public";
const char kPublicKey[] = "key";
const char kRequirements[] = "requirements";
const char kRunAt[] = "run_at";
+const char kSandboxedPages[] = "sandboxed_pages.pages";
+const char kSandboxedPagesCSP[] = "sandboxed_pages.content_security_policy";
const char kShiftKey[] = "shiftKey";
const char kShortcutKey[] = "shortcutKey";
const char kSignature[] = "signature";
@@ -391,6 +393,12 @@ const char kInvalidRequirements[] =
"Invalid value for 'requirements'";
const char kInvalidRunAt[] =
"Invalid value for 'content_scripts[*].run_at'.";
+const char kInvalidSandboxedPagesList[] =
+ "Invalid value for 'sandboxed_pages.pages'.";
+const char kInvalidSandboxedPage[] =
+ "Invalid value for 'sandboxed_pages.pages[*]'.";
+const char kInvalidSandboxedPagesCSP[] =
+ "Invalid value for 'sandboxed_pages.content_security_policy'.";
const char kInvalidSignature[] =
"Value 'signature' is missing or invalid.";
const char kInvalidTheme[] =
diff --git a/chrome/common/extensions/extension_manifest_constants.h b/chrome/common/extensions/extension_manifest_constants.h
index d078a95..bb285f4 100644
--- a/chrome/common/extensions/extension_manifest_constants.h
+++ b/chrome/common/extensions/extension_manifest_constants.h
@@ -100,6 +100,8 @@ namespace extension_manifest_keys {
extern const char kPublicKey[];
extern const char kRequirements[];
extern const char kRunAt[];
+ extern const char kSandboxedPages[];
+ extern const char kSandboxedPagesCSP[];
extern const char kShiftKey[];
extern const char kShortcutKey[];
extern const char kSignature[];
@@ -275,6 +277,9 @@ namespace extension_manifest_errors {
extern const char kInvalidRequirement[];
extern const char kInvalidRequirements[];
extern const char kInvalidRunAt[];
+ extern const char kInvalidSandboxedPagesList[];
+ extern const char kInvalidSandboxedPage[];
+ extern const char kInvalidSandboxedPagesCSP[];
extern const char kInvalidSignature[];
extern const char kInvalidTheme[];
extern const char kInvalidThemeColors[];
diff --git a/chrome/common/extensions/extension_set.cc b/chrome/common/extensions/extension_set.cc
index 1eb931c..22f0ed2 100644
--- a/chrome/common/extensions/extension_set.cc
+++ b/chrome/common/extensions/extension_set.cc
@@ -128,7 +128,7 @@ const Extension* ExtensionSet::GetByID(const std::string& id) const {
bool ExtensionSet::ExtensionBindingsAllowed(
const ExtensionURLInfo& info) const {
- if (info.origin().isUnique())
+ if (info.origin().isUnique() || IsSandboxedPage(info))
return false;
if (info.url().SchemeIs(chrome::kExtensionScheme))
@@ -143,3 +143,16 @@ bool ExtensionSet::ExtensionBindingsAllowed(
return false;
}
+
+bool ExtensionSet::IsSandboxedPage(const ExtensionURLInfo& info) const {
+ if (info.origin().isUnique())
+ return true;
+
+ if (info.url().SchemeIs(chrome::kExtensionScheme)) {
+ const Extension* extension = GetByID(info.url().host());
+ if (extension) {
+ return extension->IsSandboxedPage(info.url().path());
+ }
+ }
+ return false;
+}
diff --git a/chrome/common/extensions/extension_set.h b/chrome/common/extensions/extension_set.h
index 428df7f..a516ca4 100644
--- a/chrome/common/extensions/extension_set.h
+++ b/chrome/common/extensions/extension_set.h
@@ -41,7 +41,7 @@ class ExtensionURLInfo {
// The one true extension container. Extensions are identified by their id.
// Only one extension can be in the set with a given ID.
class ExtensionSet {
-public:
+ public:
typedef std::pair<FilePath, std::string> ExtensionPathAndDefaultLocale;
typedef std::map<std::string, scoped_refptr<const extensions::Extension> >
ExtensionMap;
@@ -129,6 +129,10 @@ public:
// permissions the given extension has been granted.
bool ExtensionBindingsAllowed(const ExtensionURLInfo& info) const;
+ // Returns true if |info| is an extension page that is to be served in a
+ // unique sandboxed origin.
+ bool IsSandboxedPage(const ExtensionURLInfo& info) const;
+
private:
FRIEND_TEST_ALL_PREFIXES(ExtensionSetTest, ExtensionSet);
diff --git a/chrome/common/extensions/manifest_tests/extension_manifests_sandboxed_unittest.cc b/chrome/common/extensions/manifest_tests/extension_manifests_sandboxed_unittest.cc
new file mode 100644
index 0000000..5ae375a
--- /dev/null
+++ b/chrome/common/extensions/manifest_tests/extension_manifests_sandboxed_unittest.cc
@@ -0,0 +1,57 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/extensions/manifest_tests/extension_manifest_test.h"
+
+#include "chrome/common/extensions/extension_manifest_constants.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using extensions::Extension;
+
+namespace errors = extension_manifest_errors;
+
+TEST_F(ExtensionManifestTest, SandboxedPages) {
+ // Sandboxed pages specified, no custom CSP value.
+ scoped_refptr<Extension> extension1(
+ LoadAndExpectSuccess("sandboxed_pages_valid_1.json"));
+
+ // No sandboxed pages.
+ scoped_refptr<Extension> extension2(
+ LoadAndExpectSuccess("sandboxed_pages_valid_2.json"));
+
+ // Sandboxed pages specified with a custom CSP value.
+ scoped_refptr<Extension> extension3(
+ LoadAndExpectSuccess("sandboxed_pages_valid_3.json"));
+
+ const char kSandboxedCSP[] = "sandbox allow-scripts allow-forms";
+ const char kDefaultCSP[] =
+ "script-src 'self' chrome-extension-resource:; object-src 'self'";
+ const char kCustomSandboxedCSP[] =
+ "sandbox; script-src: https://www.google.com";
+
+ EXPECT_EQ(kSandboxedCSP,
+ extension1->GetResourceContentSecurityPolicy("/test"));
+ EXPECT_EQ(kDefaultCSP, extension1->GetResourceContentSecurityPolicy("/none"));
+ EXPECT_EQ(kDefaultCSP, extension2->GetResourceContentSecurityPolicy("/test"));
+ EXPECT_EQ(kCustomSandboxedCSP,
+ extension3->GetResourceContentSecurityPolicy("/test"));
+ EXPECT_EQ(kDefaultCSP, extension3->GetResourceContentSecurityPolicy("/none"));
+
+ Testcase testcases[] = {
+ Testcase("sandboxed_pages_invalid_1.json",
+ errors::kInvalidSandboxedPagesList),
+ Testcase("sandboxed_pages_invalid_2.json",
+ errors::kInvalidSandboxedPage),
+ Testcase("sandboxed_pages_invalid_3.json",
+ errors::kInvalidSandboxedPagesCSP),
+ Testcase("sandboxed_pages_invalid_4.json",
+ errors::kInvalidSandboxedPagesCSP),
+ Testcase("sandboxed_pages_invalid_5.json",
+ errors::kInvalidSandboxedPagesCSP)
+ };
+ RunTestcases(testcases, arraysize(testcases),
+ EXPECT_TYPE_ERROR);
+}
+
+
diff --git a/chrome/renderer/extensions/extension_dispatcher.cc b/chrome/renderer/extensions/extension_dispatcher.cc
index 8226de8..d1ed29a 100644
--- a/chrome/renderer/extensions/extension_dispatcher.cc
+++ b/chrome/renderer/extensions/extension_dispatcher.cc
@@ -896,6 +896,16 @@ Feature::Context ExtensionDispatcher::ClassifyJavaScriptContext(
if (extension_group == EXTENSION_GROUP_CONTENT_SCRIPTS)
return Feature::CONTENT_SCRIPT_CONTEXT;
+ // We have an explicit check for sandboxed pages first since:
+ // 1. Sandboxed pages run in the same process as regular extension pages, so
+ // the extension is considered active.
+ // 2. ScriptContext creation (which triggers bindings injection) happens
+ // before the SecurityContext is updated with the sandbox flags (after
+ // reading the CSP header), so url_info.url().securityOrigin() is not
+ // unique yet.
+ if (extensions_.IsSandboxedPage(url_info))
+ return Feature::WEB_PAGE_CONTEXT;
+
if (IsExtensionActive(extension_id))
return Feature::BLESSED_EXTENSION_CONTEXT;
@@ -934,8 +944,8 @@ bool ExtensionDispatcher::CheckCurrentContextAccessToExtensionAPI(
return false;
}
- if (!IsExtensionActive(context->extension()->id()) &&
- ExtensionAPI::GetSharedInstance()->IsPrivileged(function_name)) {
+ if (ExtensionAPI::GetSharedInstance()->IsPrivileged(function_name) &&
+ context->context_type() != Feature::BLESSED_EXTENSION_CONTEXT) {
static const char kMessage[] =
"%s can only be used in an extension process.";
std::string error_msg = base::StringPrintf(kMessage, function_name.c_str());
@@ -944,5 +954,14 @@ bool ExtensionDispatcher::CheckCurrentContextAccessToExtensionAPI(
return false;
}
+ // We should never end up with sandboxed contexts trying to invoke extension
+ // APIs, they don't get extension bindings injected. If we end up here it
+ // means that a sandboxed page somehow managed to invoke an API anyway, so
+ // we should abort.
+ WebKit::WebFrame* frame = context->web_frame();
+ ExtensionURLInfo url_info(frame->document().securityOrigin(),
+ UserScriptSlave::GetDataSourceURLForFrame(frame));
+ CHECK(!extensions_.IsSandboxedPage(url_info));
+
return true;
}
diff --git a/chrome/test/data/extensions/api_test/extension_resource_request_policy/web_accessible/iframe.js b/chrome/test/data/extensions/api_test/extension_resource_request_policy/web_accessible/iframe.js
index 82697d0..a8a2270 100644
--- a/chrome/test/data/extensions/api_test/extension_resource_request_policy/web_accessible/iframe.js
+++ b/chrome/test/data/extensions/api_test/extension_resource_request_policy/web_accessible/iframe.js
@@ -4,7 +4,7 @@
// Test that both static <iframe> tags and dynamically generated ones can be
// loaded by their parent extension (iframe-contents.html is not listed in
-// web_acessible_resources).
+// web_accessible_resources).
var staticIframeLoaded = false;
function iframeLoaded() {
if (staticIframeLoaded) {
diff --git a/chrome/test/data/extensions/api_test/sandboxed_pages/main.html b/chrome/test/data/extensions/api_test/sandboxed_pages/main.html
new file mode 100644
index 0000000..ee7be70
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/sandboxed_pages/main.html
@@ -0,0 +1 @@
+<script src="main.js"></script>
diff --git a/chrome/test/data/extensions/api_test/sandboxed_pages/main.js b/chrome/test/data/extensions/api_test/sandboxed_pages/main.js
new file mode 100644
index 0000000..a313b114
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/sandboxed_pages/main.js
@@ -0,0 +1,30 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+var secret = 'main_window_secret';
+
+onmessage = function(event) {
+ var sandboxedWindow = event.source;
+ // They can't read our secret.
+ chrome.test.assertEq(undefined, event.data);
+
+ // And we can't read theirs.
+ chrome.test.assertEq(undefined, sandboxedWindow.secret);
+
+ chrome.test.succeed();
+};
+
+onload = function() {
+ chrome.test.runTests([
+ function sandboxedWindow() {
+ var w = window.open('sandboxed.html');
+ },
+
+ function sandboxedFrame() {
+ var iframe = document.createElement('iframe');
+ iframe.src = 'sandboxed.html';
+ document.body.appendChild(iframe);
+ }
+ ]);
+};
diff --git a/chrome/test/data/extensions/api_test/sandboxed_pages/manifest.json b/chrome/test/data/extensions/api_test/sandboxed_pages/manifest.json
new file mode 100644
index 0000000..2def1b6
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/sandboxed_pages/manifest.json
@@ -0,0 +1,8 @@
+{
+ "name": "Extension with sandboxed pages",
+ "manifest_version": 2,
+ "version": "0.1",
+ "sandboxed_pages": {
+ "pages": ["sandboxed.html"]
+ }
+}
diff --git a/chrome/test/data/extensions/api_test/sandboxed_pages/sandboxed.html b/chrome/test/data/extensions/api_test/sandboxed_pages/sandboxed.html
new file mode 100644
index 0000000..78e79c1
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/sandboxed_pages/sandboxed.html
@@ -0,0 +1,16 @@
+This page should be sandboxed.
+
+<script>
+// We're not served with the extension default CSP, we can use inline script.
+onload = function() {
+ var secret = 'sandboxed_window_secret';
+
+ if (chrome.extension) {
+ chrome.test.notifyFail('Extension APIs should not be injected.');
+ return;
+ }
+
+ var mainWindow = window.opener || window.top;
+ mainWindow.postMessage(mainWindow.secret, '*');
+};
+</script>
diff --git a/chrome/test/data/extensions/manifest_tests/sandboxed_pages_invalid_1.json b/chrome/test/data/extensions/manifest_tests/sandboxed_pages_invalid_1.json
new file mode 100644
index 0000000..8023729
--- /dev/null
+++ b/chrome/test/data/extensions/manifest_tests/sandboxed_pages_invalid_1.json
@@ -0,0 +1,8 @@
+{
+ "name": "test",
+ "version": "0.1",
+ "manifest_version": 2,
+ "sandboxed_pages": {
+ "pages": 123
+ }
+}
diff --git a/chrome/test/data/extensions/manifest_tests/sandboxed_pages_invalid_2.json b/chrome/test/data/extensions/manifest_tests/sandboxed_pages_invalid_2.json
new file mode 100644
index 0000000..02eb859
--- /dev/null
+++ b/chrome/test/data/extensions/manifest_tests/sandboxed_pages_invalid_2.json
@@ -0,0 +1,8 @@
+{
+ "name": "test",
+ "version": "0.1",
+ "manifest_version": 2,
+ "sandboxed_pages": {
+ "pages": [123]
+ }
+}
diff --git a/chrome/test/data/extensions/manifest_tests/sandboxed_pages_invalid_3.json b/chrome/test/data/extensions/manifest_tests/sandboxed_pages_invalid_3.json
new file mode 100644
index 0000000..aeebbb3
--- /dev/null
+++ b/chrome/test/data/extensions/manifest_tests/sandboxed_pages_invalid_3.json
@@ -0,0 +1,10 @@
+{
+ "name": "test",
+ "version": "0.1",
+ "manifest_version": 2,
+ "sandboxed_pages": {
+ "pages": ["test"],
+ // No sandbox directive.
+ "content_security_policy": "script-src: https://www.google.com"
+ }
+}
diff --git a/chrome/test/data/extensions/manifest_tests/sandboxed_pages_invalid_4.json b/chrome/test/data/extensions/manifest_tests/sandboxed_pages_invalid_4.json
new file mode 100644
index 0000000..c136f5e
--- /dev/null
+++ b/chrome/test/data/extensions/manifest_tests/sandboxed_pages_invalid_4.json
@@ -0,0 +1,10 @@
+{
+ "name": "test",
+ "version": "0.1",
+ "manifest_version": 2,
+ "sandboxed_pages": {
+ "pages": ["test"],
+ // Sandbox directive that allows same-origin access.
+ "content_security_policy": "sandbox allow-same-origin"
+ }
+}
diff --git a/chrome/test/data/extensions/manifest_tests/sandboxed_pages_invalid_5.json b/chrome/test/data/extensions/manifest_tests/sandboxed_pages_invalid_5.json
new file mode 100644
index 0000000..845ef95
--- /dev/null
+++ b/chrome/test/data/extensions/manifest_tests/sandboxed_pages_invalid_5.json
@@ -0,0 +1,14 @@
+{
+ "name": "test",
+ "version": "0.1",
+ "manifest_version": 2,
+ "platform_app": true,
+ "background": {
+ "scripts": ["background.js"]
+ },
+ "sandboxed_pages": {
+ "pages": ["test"],
+ // Sandbox directive that allows top-level navigation in a platform app.
+ "content_security_policy": "sandbox allow-top-navigation"
+ }
+}
diff --git a/chrome/test/data/extensions/manifest_tests/sandboxed_pages_valid_1.json b/chrome/test/data/extensions/manifest_tests/sandboxed_pages_valid_1.json
new file mode 100644
index 0000000..ca179ed
--- /dev/null
+++ b/chrome/test/data/extensions/manifest_tests/sandboxed_pages_valid_1.json
@@ -0,0 +1,8 @@
+{
+ "name": "test",
+ "version": "0.1",
+ "manifest_version": 2,
+ "sandboxed_pages": {
+ "pages": ["test"]
+ }
+}
diff --git a/chrome/test/data/extensions/manifest_tests/sandboxed_pages_valid_2.json b/chrome/test/data/extensions/manifest_tests/sandboxed_pages_valid_2.json
new file mode 100644
index 0000000..5028162
--- /dev/null
+++ b/chrome/test/data/extensions/manifest_tests/sandboxed_pages_valid_2.json
@@ -0,0 +1,5 @@
+{
+ "name": "test",
+ "version": "0.1",
+ "manifest_version": 2
+}
diff --git a/chrome/test/data/extensions/manifest_tests/sandboxed_pages_valid_3.json b/chrome/test/data/extensions/manifest_tests/sandboxed_pages_valid_3.json
new file mode 100644
index 0000000..b906634
--- /dev/null
+++ b/chrome/test/data/extensions/manifest_tests/sandboxed_pages_valid_3.json
@@ -0,0 +1,9 @@
+{
+ "name": "test",
+ "version": "0.1",
+ "manifest_version": 2,
+ "sandboxed_pages": {
+ "pages": ["test"],
+ "content_security_policy": "sandbox; script-src: https://www.google.com"
+ }
+}