Chrome 18 Dev/Canary has just been released, and content_security_policy
will be needed in the manifest for certain extensions.
I'm trying to get a CSP working for inline scripting, but I don't know if I'm doing something wrong or if this is a Chrome 18 bug.
manifest.json:
{
"name": "CSP Test",
"version": "1.0",
"manifest_version": 2,
"options_page": "test.html",
"content_security_policy": "default-src 'unsafe-inline'"
}
test.html:
<html><head>
<script type="text/javascript">
alert("hello");
</script>
</head></html>
In Chrome 18, this unpacked extension fails to load, displaying an error:
If I change 'unsafe-inline'
to 'self'
, the extension loads fine, but alert()
does not work, and the option page's console contains an error:
Refused to execute inline script because of Content-Security-Policy.
In Chrome 16, using 'unsafe-inline'
lets the extension load fine and alert()
works, too. However, in Chrome 16, replacing 'unsafe-inline'
with 'foo'
lets the extension load, but of course does not let alert()
work, so perhaps Chrome 18 is stricter than 16, but...
Is default-src 'unsafe-inline'
actually invalid, or is this a bug? What CSP value can I use to make alert()
work in Chrome 18?
Based on the accepted answer below, inline scripts no longer work in extensions in Chrome 18. alert()
will need to be placed in its own JavaScript file.
For recent versions of Chrome (46+), the previously accepted answer is no longer true. unsafe-inline
still has no effect (in the manifest and in meta
header tags), but per the documentation, you can use the technique described here to relax the restriction.
Hash usage for
<script>
elements
The
script-src
directive lets developers whitelist a particular inline script by specifying its hash as an allowed source of script.
Usage is straightforward. The server computes the hash of a particular script block’s contents, and includes the base64 encoding of that value in the
Content-Security-Policy
header:
Content-Security-Policy: default-src 'self';
script-src 'self' https://example.com 'sha256-base64 encoded hash'
Consider the following:
manifest.json:
{
"manifest_version": 2,
"name": "csp test",
"version": "1.0.0",
"minimum_chrome_version": "46",
"content_security_policy": "script-src 'self' 'sha256-WOdSzz11/3cpqOdrm89LBL2UPwEU9EhbDtMy2OciEhs='",
"background": {
"page": "background.html"
}
}
background.html:
<!DOCTYPE html>
<html>
<head></head>
<body>
<script>alert('foo');</script>
</body>
</html>
I also tested putting the applicable directive in a meta
tag instead of the manifest. While the CSP indicated in the console message did include the content of the tag, it would not execute the inline script (in Chrome 53).
new background.html:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'sha256-WOdSzz11/3cpqOdrm89LBL2UPwEU9EhbDtMy2OciEhs='">
</head>
<body>
<script>alert('foo');</script>
</body>
</html>
Here are two methods for generating the hashes:
import hashlib
import base64
import sys
def hash(s):
hash = hashlib.sha256(s.encode()).digest()
encoded = base64.b64encode(hash)
return encoded
contents = sys.stdin.read()
print(hash(contents))
var sjcl = require('sjcl');
// Generate base64-encoded SHA256 for given string.
function hash(s) {
var hashed = sjcl.hash.sha256.hash(s);
return sjcl.codec.base64.fromBits(hashed);
}
Make sure when hashing the inline scripts that the whole contents of the script tag are included (including all leading/trailing whitespace). If you want to incorporate this into your builds, you can use something like cheerio to get the relevant sections. Generically, for any html
, you can do:
var $ = cheerio.load(html);
var csp_hashes = $('script')
.map((i, el) => hash($(el).text())
.toArray()
.map(h => `'sha256-${h}'`)
.join(' ');
var content_security_policy = `script-src 'self' 'unsafe-eval' ${csp_hashes}; object-src 'self'`;
This is the method used in hash-csp, a gulp plugin for generating hashes.