Skip to content

Commit

Permalink
Added support for rewriting Content-Security-Policy-Report-Only
Browse files Browse the repository at this point in the history
Fixes #3.
  • Loading branch information
papandreou committed Feb 22, 2017
1 parent 0bc3473 commit 75fe006
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 32 deletions.
55 changes: 29 additions & 26 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,36 +112,39 @@ module.exports = (config = {}) => {
const agent = useragent.lookup(req.headers['user-agent']);
const oldWriteHead = res.writeHead;
res.writeHead = (...args) => {
let existingCspHeaderValues = res.getHeader('Content-Security-Policy');
['Content-Security-Policy', 'Content-Security-Policy-Report-Only'].forEach(headerName => {
let existingCspHeaderValues = res.getHeader(headerName);

if (existingCspHeaderValues) {
if (!Array.isArray(existingCspHeaderValues)) {
existingCspHeaderValues = [ existingCspHeaderValues ];
}
let targetCspLevel;
let newHeaderName;
if (existingCspHeaderValues) {
if (!Array.isArray(existingCspHeaderValues)) {
existingCspHeaderValues = [ existingCspHeaderValues ];
}
let reportOnlySuffix = /-Report-Only$/.test(headerName) ? '-Report-Only' : '';
let targetCspLevel;
let newHeaderName;

if (agent.family === 'IE' && agent.satisfies('11 || 10')) {
newHeaderName = 'X-Content-Security-Policy';
targetCspLevel = 1;
} else if (agent.family === 'Safari' && agent.satisfies('6.1 || 6')) {
targetCspLevel = 1;
newHeaderName = 'X-Webkit-CSP';
} else if (agent.family === 'Firefox' && agent.satisfies('>= 45')) {
targetCspLevel = 2;
} else {
targetCspLevel = lookupTargetCspLevelInCanIUseDb(agent.family, agent.major, agent.minor);
}
if (agent.family === 'IE' && agent.satisfies('11 || 10')) {
newHeaderName = 'X-Content-Security-Policy' + reportOnlySuffix;
targetCspLevel = 1;
} else if (agent.family === 'Safari' && agent.satisfies('6.1 || 6')) {
targetCspLevel = 1;
newHeaderName = 'X-Webkit-CSP' + reportOnlySuffix;
} else if (agent.family === 'Firefox' && agent.satisfies('>= 45')) {
targetCspLevel = 2;
} else {
targetCspLevel = lookupTargetCspLevelInCanIUseDb(agent.family, agent.major, agent.minor);
}

if (targetCspLevel === 0) {
res.removeHeader('Content-Security-Policy');
} else if (targetCspLevel > 0) {
res.removeHeader('Content-Security-Policy');
existingCspHeaderValues.forEach(existingCspHeaderValue => {
res.append(newHeaderName || 'Content-Security-Policy', downgradeAndSerializeCsp(existingCspHeaderValue, targetCspLevel));
});
if (targetCspLevel === 0) {
res.removeHeader(headerName);
} else if (targetCspLevel > 0) {
res.removeHeader(headerName);
existingCspHeaderValues.forEach(existingCspHeaderValue => {
res.append(newHeaderName || headerName, downgradeAndSerializeCsp(existingCspHeaderValue, targetCspLevel));
});
}
}
}
});
return oldWriteHead.call(res, ...args);
};
next();
Expand Down
42 changes: 36 additions & 6 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ const expect = require('unexpected').clone()
const expressLegacyCsp = require('../');

let userAgentString;
let headerName;
let inputHeaderName;
let expectedOutputHeaderName;
beforeEach(() => {
userAgentString = undefined;
headerName = 'Content-Security-Policy';
inputHeaderName = 'Content-Security-Policy';
expectedOutputHeaderName = 'Content-Security-Policy';
});

expect.addAssertion('<string|array> to come out as <string|array|undefined>', (expect, subject, value) => {
Expand All @@ -18,13 +20,13 @@ expect.addAssertion('<string|array> to come out as <string|array|undefined>', (e
if (Array.isArray(subject)) {
subject.forEach(headerValue => res.append('Content-Security-Policy', headerValue));
} else {
res.setHeader('Content-Security-Policy', subject);
res.setHeader(inputHeaderName, subject);
}
res.status(200).end();
}),
'to yield exchange', {
request: { headers: { 'User-Agent': userAgentString } },
response: { headers: { [headerName]: value } }
response: { headers: { [expectedOutputHeaderName]: value } }
}
);
});
Expand All @@ -34,6 +36,7 @@ describe('with multiple CSP headers', 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 process all headers', function () {
return expect([
'script-src somewhere.com/with/a/path',
Expand All @@ -45,6 +48,33 @@ describe('with multiple CSP headers', function () {
});
});

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 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';
Expand All @@ -68,7 +98,7 @@ describe('in Safari 7', function () {
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';
headerName = 'X-Webkit-CSP';
expectedOutputHeaderName = 'X-Webkit-CSP';
});

it('should downgrade to CSP 1 and switch to the X-Webkit-CSP header', () => {
Expand Down Expand Up @@ -127,7 +157,7 @@ describe('in Edge 13', function () {
describe('in IE10', function () {
beforeEach(() => {
userAgentString = 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)';
headerName = 'X-Content-Security-Policy';
expectedOutputHeaderName = 'X-Content-Security-Policy';
});

it('should strip the path from a source expression without a scheme', () => {
Expand Down

0 comments on commit 75fe006

Please sign in to comment.