diff --git a/docs/client/packages/browser-policy.html b/docs/client/packages/browser-policy.html index 8499e891c9..94c90a4616 100644 --- a/docs/client/packages/browser-policy.html +++ b/docs/client/packages/browser-policy.html @@ -169,6 +169,12 @@ sites can frame your site, while `BrowserPolicy.content.allowFrameOrigin` allows you to control which sites can be loaded inside frames on your site. +Adding `browser-policy-content` to your app also tells certain +browsers to avoid sniffing content types away from the declared type +(for example, interpreting a text file as JavaScript), using the +[X-Content-Type-Options](http://msdn.microsoft.com/en-us/library/ie/gg622941(v=vs.85).aspx) +header. To re-enable content sniffing, you can call +`BrowserPolicy.content.allowContentTypeSniffing()`. {{/markdown}} diff --git a/packages/browser-policy-common/browser-policy-common.js b/packages/browser-policy-common/browser-policy-common.js index 9dd18f8556..f4c2afd4ec 100644 --- a/packages/browser-policy-common/browser-policy-common.js +++ b/packages/browser-policy-common/browser-policy-common.js @@ -19,9 +19,29 @@ WebApp.connectHandlers.use(function (req, res, next) { BrowserPolicy.framing._constructXFrameOptions(); var csp = BrowserPolicy.content && BrowserPolicy.content._constructCsp(); - if (xFrameOptions) + if (xFrameOptions) { res.setHeader("X-Frame-Options", xFrameOptions); - if (csp) + } + if (csp) { res.setHeader("Content-Security-Policy", csp); + } + next(); +}); + +// We use `rawConnectHandlers` to set X-Content-Type-Options on all +// requests, including static files. +// XXX We should probably use `rawConnectHandlers` for X-Frame-Options +// and Content-Security-Policy too, but let's make sure that doesn't +// break anything first (e.g. the OAuth popup flow won't work well with +// a CSP that disallows inline scripts). +WebApp.rawConnectHandlers.use(function (req, res, next) { + if (BrowserPolicy._runningTest()) + return next(); + + var contentTypeOptions = BrowserPolicy.content && + BrowserPolicy.content._xContentTypeOptions(); + if (contentTypeOptions) { + res.setHeader("X-Content-Type-Options", contentTypeOptions); + } next(); }); diff --git a/packages/browser-policy-content/browser-policy-content.js b/packages/browser-policy-content/browser-policy-content.js index 569bf9c4d0..8550504e91 100644 --- a/packages/browser-policy-content/browser-policy-content.js +++ b/packages/browser-policy-content/browser-policy-content.js @@ -1,7 +1,8 @@ // By adding this package, you get the following default policy: // No eval or other string-to-code, and content can only be loaded from the // same origin as the app (except for XHRs and websocket connections, which can -// go to any origin). +// go to any origin). Browsers will also be told not to sniff content types +// away from declared content types (X-Content-Type-Options: nosniff). // // Apps should call BrowserPolicy.content.disallowInlineScripts() if they are // not using any inline script tags and are willing to accept an extra round @@ -32,6 +33,8 @@ // allowAllContentSameOrigin() // disallowAllContent() // +// You can allow content type sniffing by calling +// `BrowserPolicy.content.allowContentTypeSniffing()`. var cspSrcs; var cachedCsp; // Avoid constructing the header out of cspSrcs when possible. @@ -44,6 +47,9 @@ var keywords = { none: "'none'" }; +// If false, we set the X-Content-Type-Options header to 'nosniff'. +var contentSniffingAllowed = false; + BrowserPolicy.content = {}; var parseCsp = function (csp) { @@ -134,6 +140,9 @@ var setWebAppInlineScripts = function (value) { }; _.extend(BrowserPolicy.content, { + allowContentTypeSniffing: function () { + contentSniffingAllowed = true; + }, // Exported for tests and browser-policy-common. _constructCsp: function () { if (! cspSrcs || _.isEmpty(cspSrcs)) @@ -220,6 +229,12 @@ _.extend(BrowserPolicy.content, { "default-src": [] }; setWebAppInlineScripts(false); + }, + + _xContentTypeOptions: function () { + if (! contentSniffingAllowed) { + return "nosniff"; + } } }); diff --git a/packages/browser-policy/browser-policy-test.js b/packages/browser-policy/browser-policy-test.js index 48f999c9ba..2f5bcb604c 100644 --- a/packages/browser-policy/browser-policy-test.js +++ b/packages/browser-policy/browser-policy-test.js @@ -151,3 +151,9 @@ Tinytest.add("browser-policy - x-frame-options", function (test) { BrowserPolicy.framing.restrictToOrigin("bar.com"); }); }); + +Tinytest.add("browser-policy - X-Content-Type-Options", function (test) { + test.equal(BrowserPolicy.content._xContentTypeOptions(), "nosniff"); + BrowserPolicy.content.allowContentTypeSniffing(); + test.equal(BrowserPolicy.content._xContentTypeOptions(), undefined); +});