Skip to content

Commit 0e0015a

Browse files
authored
feat: enable reporting for CSP errors (rollbar#903)
1 parent 16afe18 commit 0e0015a

File tree

4 files changed

+120
-2
lines changed

4 files changed

+120
-2
lines changed

examples/csp-errors.html

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8">
5+
<title>Generate CSP error for test automation</title>
6+
<script src="https://example.com/v3/"></script>
7+
</head>
8+
<body>
9+
</body>
10+
</html>

karma.conf.js

+10
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,16 @@ module.exports = function (config) {
5959
'/examples/': '/base/examples/'
6060
},
6161

62+
customHeaders: [
63+
// Allow CSP error testing, but leave enough enabled so that karma
64+
// can still run correctly.
65+
{
66+
match: '\\.html',
67+
name: 'Content-Security-Policy',
68+
value: "default-src 'self' 'unsafe-inline' 'unsafe-eval';"
69+
}
70+
],
71+
6272
reporters: ['progress'],
6373

6474
singleRun: true,

src/browser/telemetry.js

+49-2
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ var defaults = {
1515
log: true,
1616
dom: true,
1717
navigation: true,
18-
connectivity: true
18+
connectivity: true,
19+
contentSecurityPolicy: true,
20+
errorOnContentSecurityPolicy: false
1921
};
2022

2123
function replace(obj, name, replacement, replacements, type) {
@@ -89,7 +91,8 @@ function Instrumenter(options, telemeter, rollbar, _window, _document) {
8991
};
9092
this.eventRemovers = {
9193
dom: [],
92-
connectivity: []
94+
connectivity: [],
95+
contentsecuritypolicy: []
9396
};
9497

9598
this._location = this._window.location;
@@ -117,6 +120,7 @@ Instrumenter.prototype.configure = function(options) {
117120
}
118121
};
119122

123+
// eslint-disable-next-line complexity
120124
Instrumenter.prototype.instrument = function(oldSettings) {
121125
if (this.autoInstrument.network && !(oldSettings && oldSettings.network)) {
122126
this.instrumentNetwork();
@@ -147,6 +151,12 @@ Instrumenter.prototype.instrument = function(oldSettings) {
147151
} else if (!this.autoInstrument.connectivity && oldSettings && oldSettings.connectivity) {
148152
this.deinstrumentConnectivity();
149153
}
154+
155+
if (this.autoInstrument.contentSecurityPolicy && !(oldSettings && oldSettings.contentSecurityPolicy)) {
156+
this.instrumentContentSecurityPolicy();
157+
} else if (!this.autoInstrument.contentSecurityPolicy && oldSettings && oldSettings.contentSecurityPolicy) {
158+
this.deinstrumentContentSecurityPolicy();
159+
}
150160
};
151161

152162
Instrumenter.prototype.deinstrumentNetwork = function() {
@@ -694,6 +704,43 @@ Instrumenter.prototype.instrumentConnectivity = function() {
694704
}
695705
};
696706

707+
Instrumenter.prototype.handleCspEvent = function(cspEvent) {
708+
var message = 'Security Policy Violation: ' +
709+
'blockedURI: ' + cspEvent.blockedURI + ', ' +
710+
'violatedDirective: ' + cspEvent.violatedDirective + ', ' +
711+
'effectiveDirective: ' + cspEvent.effectiveDirective + ', ';
712+
713+
if (cspEvent.sourceFile) {
714+
message += 'location: ' + cspEvent.sourceFile + ', ' +
715+
'line: ' + cspEvent.lineNumber + ', ' +
716+
'col: ' + cspEvent.columnNumber + ', ';
717+
}
718+
719+
message += 'originalPolicy: ' + cspEvent.originalPolicy;
720+
721+
this.telemeter.captureLog(message, 'error');
722+
this.handleCspError(message);
723+
}
724+
725+
Instrumenter.prototype.handleCspError = function(message) {
726+
if (this.autoInstrument.errorOnContentSecurityPolicy) {
727+
this.rollbar.error(message);
728+
}
729+
}
730+
731+
Instrumenter.prototype.deinstrumentContentSecurityPolicy = function() {
732+
if (!('addEventListener' in this._window)) { return; }
733+
734+
this.removeListeners('contentsecuritypolicy');
735+
};
736+
737+
Instrumenter.prototype.instrumentContentSecurityPolicy = function() {
738+
if (!('addEventListener' in this._window)) { return; }
739+
740+
var cspHandler = this.handleCspEvent.bind(this);
741+
this.addListener('contentsecuritypolicy', this._window, 'securitypolicyviolation', null, cspHandler, false);
742+
};
743+
697744
Instrumenter.prototype.addListener = function(section, obj, type, altType, handler, capture) {
698745
if (obj.addEventListener) {
699746
obj.addEventListener(type, handler, capture);

test/browser.rollbar.test.js

+51
Original file line numberDiff line numberDiff line change
@@ -1137,6 +1137,57 @@ describe('options.autoInstrument', function() {
11371137
);
11381138
}
11391139

1140+
describe('options.autoInstrument.contentSecurityPolicy', function() {
1141+
beforeEach(function (done) {
1142+
var options = {
1143+
accessToken: 'POST_CLIENT_ITEM_TOKEN',
1144+
autoInstrument: {
1145+
log: false,
1146+
contentSecurityPolicy: true,
1147+
errorOnContentSecurityPolicy: true
1148+
}
1149+
};
1150+
window.rollbar = new Rollbar(options);
1151+
done();
1152+
});
1153+
1154+
afterEach(function () {
1155+
window.rollbar.configure({ autoInstrument: false, captureUncaught: false });
1156+
});
1157+
1158+
it('should report content security policy errors', function(done) {
1159+
var queue = rollbar.client.notifier.queue;
1160+
var queueStub = sinon.stub(queue, '_makeApiRequest');
1161+
1162+
// Load the HTML page, so errors can be generated.
1163+
document.write(window.__html__['examples/csp-errors.html']);
1164+
1165+
setTimeout(function() {
1166+
try {
1167+
var item = queueStub.getCall(0).args[0];
1168+
var message = item.body.message.body;
1169+
var telemetry = item.body.telemetry[0]
1170+
1171+
expect(message).to.match(/Security Policy Violation/);
1172+
expect(message).to.match(/blockedURI: https:\/\/example.com\/v3\//);
1173+
expect(message).to.match(/violatedDirective: script-src/);
1174+
expect(message).to.match(/originalPolicy: default-src 'self' 'unsafe-inline' 'unsafe-eval';/);
1175+
1176+
expect(telemetry.level).to.eql('error');
1177+
expect(telemetry.type).to.eql('log');
1178+
expect(telemetry.body.message).to.match(/Security Policy Violation/);
1179+
expect(telemetry.body.message).to.match(/blockedURI: https:\/\/example.com\/v3\//);
1180+
expect(telemetry.body.message).to.match(/violatedDirective: script-src/);
1181+
expect(telemetry.body.message).to.match(/originalPolicy: default-src 'self' 'unsafe-inline' 'unsafe-eval';/);
1182+
1183+
done();
1184+
} catch (e) {
1185+
done(e);
1186+
}
1187+
}, 100);
1188+
});
1189+
});
1190+
11401191
it('should add telemetry events when console.log is called', function(done) {
11411192
var server = window.server;
11421193
stubResponse(server);

0 commit comments

Comments
 (0)