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'
Example
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>
Result:
Further investigation
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>
Result:
Appendix: Generating the hashes
Here are two methods for generating the hashes:
- Python (pass JS to stdin, pipe it somewhere else):
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))
- In JS, using the Stanford Javascript Crypto Library:
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.