express-legacy-csp
Version:
Downgrade content-security-policy version and fidelity to support the requesting browser
468 lines (409 loc) • 14.7 kB
JavaScript
const express = require('express');
const expect = require('unexpected')
.clone()
.use(require('unexpected-express'));
const expressLegacyCsp = require('../');
let userAgentString;
let inputHeaderName;
let expectedOutputHeaderName;
beforeEach(() => {
userAgentString = undefined;
inputHeaderName = 'Content-Security-Policy';
expectedOutputHeaderName = 'Content-Security-Policy';
});
expect.addAssertion(
'<string|array> to come out as <string|array|undefined>',
(expect, subject, value) => {
return expect(
express()
.use(expressLegacyCsp())
.use((req, res, next) => {
if (Array.isArray(subject)) {
subject.forEach(headerValue =>
res.append('Content-Security-Policy', headerValue)
);
} else {
res.setHeader(inputHeaderName, subject);
}
res.status(200).end();
}),
'to yield exchange',
{
request: { headers: { 'User-Agent': userAgentString } },
response: { headers: { [expectedOutputHeaderName]: value } }
}
);
}
);
expect.addAssertion('<string|array> to be left intact', (expect, subject) =>
expect(subject, 'to come out as', subject)
);
describe('with an empty policy', function() {
// Safari 7
beforeEach(() => {
userAgentString =
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.75.14 (KHTML, like Gecko) Version/7.0.3 Safari/7046A194A';
});
it('should output an empty policy', function() {
return expect('', 'to be left intact');
});
});
describe('with a browser that caniuse-db has data about in a version that is older than all the explicitly mentioned ones', function() {
// Chrome 1
beforeEach(() => {
userAgentString =
'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/525.19 (KHTML, like Gecko) Chrome/1.0.154.36 Safari/525.19';
});
it('should assume that CSP is not supported and strip the header', function() {
return expect(
'script-src somewhere.com/with/a/path',
'to come out as',
undefined
);
});
});
describe('with a policy that has a trailing semicolon', function() {
// Safari 7
beforeEach(() => {
userAgentString =
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.75.14 (KHTML, like Gecko) Version/7.0.3 Safari/7046A194A';
});
it('should remove the semicolon when reserializing', function() {
return expect('foo;', 'to come out as', 'foo');
});
});
describe('with a policy that has a leading semicolon', function() {
// Safari 7
beforeEach(() => {
userAgentString =
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.75.14 (KHTML, like Gecko) Version/7.0.3 Safari/7046A194A';
});
it('should remove the semicolon when reserializing', function() {
return expect(';foo', 'to come out as', 'foo');
});
});
describe('with multiple directives', function() {
// Safari 7
beforeEach(() => {
userAgentString =
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.75.14 (KHTML, like Gecko) Version/7.0.3 Safari/7046A194A';
});
it('should rejoin the directives with semicolon followed by a space', function() {
return expect('foo;bar', 'to come out as', 'foo; bar');
});
});
describe('with multiple CSP headers', function() {
// Safari 7
beforeEach(() => {
userAgentString =
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.75.14 (KHTML, like Gecko) Version/7.0.3 Safari/7046A194A';
});
it('should process all headers', function() {
return expect(
['script-src somewhere.com/with/a/path', "script-src 'nonce-foo'"],
'to come out as',
['script-src somewhere.com', "script-src 'unsafe-inline'"]
);
});
});
describe('with multiple comma-separated policies in a single header', function() {
// Safari 7
beforeEach(() => {
userAgentString =
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.75.14 (KHTML, like Gecko) Version/7.0.3 Safari/7046A194A';
});
it('should process each contained policy individually and reassemble them', function() {
return expect(
"script-src somewhere.com/with/a/path, script-src 'sha256-XeYlw2NVzOfB1UCIJqCyGr+0n7bA4fFslFpvKu84IAw='",
'to come out as',
"script-src somewhere.com, script-src 'unsafe-inline'"
);
});
});
describe('with a "report only" CSP header', function() {
describe('in a browser that does not require the "base" header name to be changed', function() {
// Safari 7
beforeEach(() => {
inputHeaderName = expectedOutputHeaderName =
'Content-Security-Policy-Report-Only';
userAgentString =
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.75.14 (KHTML, like Gecko) Version/7.0.3 Safari/7046A194A';
});
it('should process the header', function() {
return expect(
'script-src somewhere.com/with/a/path',
'to come out as',
'script-src somewhere.com'
);
});
});
describe('in a browser that does require the "base" header name to be changed', function() {
// Safari 6
beforeEach(() => {
inputHeaderName = expectedOutputHeaderName =
'Content-Security-Policy-Report-Only';
expectedOutputHeaderName = 'X-Webkit-CSP-Report-Only';
userAgentString =
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/536.26.17 (KHTML, like Gecko) Version/6.0.2 Safari/536.26.17';
});
it('should process the header', function() {
return expect(
'script-src somewhere.com/with/a/path',
'to come out as',
'script-src somewhere.com'
);
});
});
});
describe('in Chrome 28 on Android 4.4.2', function() {
beforeEach(() => {
userAgentString =
'Mozilla/5.0 (Linux; Android 4.4.2: sv-se; SAMSUNG SM-C115 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Version/1.6 Chrome/28.0.1500.94 Mobile Safari/537.36';
});
it('should strip the Content-Security-Policy header', () => {
return expect(
"script-src somewhere.com/with/a/path 'strict-dynamic'",
'to come out as',
undefined
);
});
});
describe('in Safari 5', function() {
beforeEach(() => {
userAgentString =
'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; de-at) AppleWebKit/533.21.1 (KHTML, like Gecko) Version/5.0.5 Safari/533.21.1';
});
it('should remove the Content-Security-Policy header', () => {
return expect(
"script-src somewhere.com/with/a/path 'strict-dynamic'",
'to come out as',
undefined
);
});
});
describe('in Safari 6', function() {
beforeEach(() => {
userAgentString =
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/536.26.17 (KHTML, like Gecko) Version/6.0.2 Safari/536.26.17';
expectedOutputHeaderName = 'X-Webkit-CSP';
});
it('should downgrade to CSP 1 and switch to the X-Webkit-CSP header', () => {
return expect(
"script-src somewhere.com/with/a/path 'strict-dynamic'",
'to come out as',
"script-src somewhere.com 'unsafe-inline'"
);
});
});
describe('in Safari 7', function() {
beforeEach(() => {
userAgentString =
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.75.14 (KHTML, like Gecko) Version/7.0.3 Safari/7046A194A';
});
it('should downgrade to CSP 1', () => {
return expect(
"script-src somewhere.com/with/a/path 'strict-dynamic'",
'to come out as',
"script-src somewhere.com 'unsafe-inline'"
);
});
});
describe('in Safari 8', function() {
beforeEach(() => {
userAgentString =
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_4) AppleWebKit/600.7.12 (KHTML, like Gecko) Version/8.0.7 Safari/600.7.12';
});
it('should strip the path from a source expression without a scheme', () => {
return expect(
'script-src somewhere.com/with/a/path',
'to come out as',
'script-src somewhere.com'
);
});
it('should strip the path from a source expression with a scheme', () => {
return expect(
'script-src https://somewhere.com/with/a/path',
'to come out as',
'script-src https://somewhere.com'
);
});
it('should dedupe after stripping the path', () => {
return expect(
'script-src somewhere.com/with/a/path somewhere.com/with/another/path',
'to come out as',
'script-src somewhere.com'
);
});
});
describe('in Safari 10', function() {
beforeEach(() => {
userAgentString =
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_1) AppleWebKit/602.2.14 (KHTML, like Gecko) Version/10.0.1 Safari/602.2.14';
});
it('should downgrade to CSP 2', () => {
return expect(
"script-src somewhere.com/with/a/path 'strict-dynamic'",
'to come out as',
"script-src somewhere.com/with/a/path 'unsafe-inline'"
);
});
});
describe('in Mobile Safari 9.0 (iOS 9)', function() {
beforeEach(() => {
userAgentString =
'Mozilla/5.0 (iPad; CPU OS 9_3_5 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13G36 Safari/601.1';
});
it('should downgrade to CSP 1', () => {
return expect(
"script-src somewhere.com/with/a/path 'strict-dynamic'",
'to come out as',
"script-src somewhere.com 'unsafe-inline'"
);
});
});
describe('in Firefox 51', function() {
beforeEach(() => {
userAgentString =
'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:51.0) Gecko/20100101 Firefox/51.0';
});
it('should not have CSP level 2 constructs downgraded, even though caniusedb says "a #7"', function() {
return expect('script-src somewhere.com/with/a/path', 'to be left intact');
});
});
describe('in Edge 13', function() {
beforeEach(() => {
userAgentString =
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Safari/537.36 Edge/13.10586';
});
it('should have CSP level 2 constructs downgraded', function() {
return expect(
"script-src somewhere.com/with/a/path 'sha256-XeYlw2NVzOfB1UCIJqCyGr+0n7bA4fFslFpvKu84IAw='",
'to come out as',
"script-src somewhere.com 'unsafe-inline'"
);
});
});
describe('in Edge 79', function() {
beforeEach(() => {
userAgentString =
'Mozilla/5.0 (Windows NT 10.0; ARM; RM-1141) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36 Edge/79.14977';
});
it('should leave CSP level 2 constructs intact', function() {
return expect(
"script-src somewhere.com/with/a/path 'sha256-XeYlw2NVzOfB1UCIJqCyGr+0n7bA4fFslFpvKu84IAw='",
'to be left intact'
);
});
});
describe('in IE10', function() {
beforeEach(() => {
userAgentString =
'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)';
expectedOutputHeaderName = 'X-Content-Security-Policy';
});
it('should strip the path from a source expression without a scheme', () => {
return expect(
'script-src somewhere.com/with/a/path',
'to come out as',
'script-src somewhere.com'
);
});
});
describe('in a CSP2 compliant browser', () => {
beforeEach(() => {
// Chrome 55
userAgentString =
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36';
});
it('should remove CSP3 constructs', () => {
return expect(
"script-src 'unsafe-hashed-attributes'",
'to come out as',
"script-src 'unsafe-inline'"
);
});
it('should keep CSP2 constructs', () => {
return expect('script-src somewhere.com/with/a/path', 'to be left intact');
});
});
describe('in a CSP1 compliant browser', () => {
beforeEach(() => {
// Chrome 14
userAgentString =
'Mozilla/5.0 (Windows NT 6.0; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.835.2 Safari/535.1';
});
it('should remove CSP3 and CSP2 constructs', () => {
return expect(
"script-src 'unsafe-hashed-attributes' somewhere.com/with/a/path somewhere.com/with/another/path",
'to come out as',
"script-src somewhere.com 'unsafe-inline'"
);
});
});
describe('in a browser that does not support CSP', function() {
beforeEach(() => {
// Chrome 13
userAgentString =
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_3) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.220 Safari/535.1';
});
it('should remove CSP3 and CSP2 constructs', () => {
return expect(
"script-src 'unsafe-hashed-attributes' somewhere.com/with/a/path somewhere.com/with/another/path",
'to come out as',
undefined
);
});
});
describe('in an unknown browser', function() {
beforeEach(() => {
userAgentString = 'Sunshine, lollipops and rainbows';
});
it('should preserve the CSP as-is', function() {
return expect("script-src 'strict-dynamic'", 'to be left intact');
});
});
it('should not crash when the parsed version number is not accepted by the semver module due to leading zeroes', function() {
userAgentString =
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_3) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/999.01.782.220 Safari/535.1';
return expect(
"script-src 'strict-dynamic'",
'to come out as',
"script-src 'unsafe-inline'"
);
});
describe('with CSP2 directives', function() {
describe('in CSP2 capable browsers', function() {
it('should keep the directives in Safari 10.1', function() {
userAgentString =
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/603.3.8 (KHTML, like Gecko) Version/10.1.2 Safari/603.3.8';
return expect(
"frame-ancestors 'none'; child-src 'self'; script-src 'self'",
'to be left intact'
);
});
});
describe('in CSP1 capable browsers', function() {
it('should drop the directives in Safari 9.1', function() {
userAgentString =
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/601.7.8 (KHTML, like Gecko) Version/9.1.3 Safari/601.7.8';
return expect(
"frame-ancestors 'none'; child-src 'self'; script-src 'self'",
'to come out as',
"script-src 'self'"
);
});
});
describe('with CSP3 directives', function() {
describe('in CSP2 capable browsers', function() {
it('should drop the directives in Safari 10.1', function() {
userAgentString =
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/603.3.8 (KHTML, like Gecko) Version/10.1.2 Safari/603.3.8';
return expect(
"manifest-src 'self'; worker-src; report-to https://example.com; script-src 'self'",
'to come out as',
"script-src 'self'"
);
});
});
});
});