diff --git a/API.md b/API.md index fc9b3860b..6141acfb8 100755 --- a/API.md +++ b/API.md @@ -3345,13 +3345,17 @@ following options: otherwise this field is ignored. If `rule` is `'allow-from'` but `source` is unset, the rule will be automatically changed to `'sameorigin'`. -- `xss` - boolean that controls the 'X-XSS-PROTECTION' header for Internet Explorer. Defaults to - `true` which sets the header to equal `'1; mode=block'`. - - Note: this setting can create a security vulnerability in versions of Internet Exploere below - 8, as well as unpatched versions of IE8. See [here](https://hackademix.net/2009/11/21/ies-xss-filter-creates-xss-vulnerabilities/) - and [here](https://technet.microsoft.com/library/security/ms10-002) for more information. If - you actively support old versions of IE, it may be wise to explicitly set this flag to - `false`. +- `xss` - controls the 'X-XSS-Protection' header, where: + + - `'disable'` - the header will be set to `'0'`. This is the default value. + - `'enable'` - the header will be set to `'1; mode=block'`. + - `false` - the header will be omitted. + + Note: when enabled, this setting can create a security vulnerabilities in versions of Internet Explorer + below 8, unpatched versions of IE8, and browsers that employ an XSS filter/auditor. See + [here](https://hackademix.net/2009/11/21/ies-xss-filter-creates-xss-vulnerabilities/), + [here](https://technet.microsoft.com/library/security/ms10-002), and + [here](https://blog.innerht.ml/the-misunderstood-x-xss-protection/) for more information. - `noOpen` - boolean controlling the 'X-Download-Options' header for Internet Explorer, preventing downloads from executing in your context. Defaults to `true` setting the header to `'noopen'`. diff --git a/lib/config.js b/lib/config.js index 94ca0586f..b56a5c08f 100755 --- a/lib/config.js +++ b/lib/config.js @@ -193,7 +193,7 @@ internals.routeBase = Validate.object({ }) ]) .default('deny'), - xss: Validate.boolean().default(true), + xss: Validate.valid('enabled', 'disabled', false).default('disabled'), noOpen: Validate.boolean().default(true), noSniff: Validate.boolean().default(true), referrer: Validate.alternatives([ diff --git a/lib/security.js b/lib/security.js index cd0063861..3b243f27e 100755 --- a/lib/security.js +++ b/lib/security.js @@ -65,9 +65,12 @@ exports.headers = function (response) { response._header('x-frame-options', security._xframe, { override: false }); } - if (security.xss) { + if (security.xss === 'enabled') { response._header('x-xss-protection', '1; mode=block', { override: false }); } + else if (security.xss === 'disabled') { + response._header('x-xss-protection', '0', { override: false }); + } if (security.noOpen) { response._header('x-download-options', 'noopen', { override: false }); diff --git a/test/headers.js b/test/headers.js index c6a447649..d04dc9074 100755 --- a/test/headers.js +++ b/test/headers.js @@ -213,7 +213,7 @@ describe('Headers', () => { expect(res.result).to.equal('Test'); expect(res.headers['strict-transport-security']).to.equal('max-age=15768000'); expect(res.headers['x-frame-options']).to.equal('DENY'); - expect(res.headers['x-xss-protection']).to.equal('1; mode=block'); + expect(res.headers['x-xss-protection']).to.equal('0'); expect(res.headers['x-download-options']).to.equal('noopen'); expect(res.headers['x-content-type-options']).to.equal('nosniff'); }); @@ -243,7 +243,7 @@ describe('Headers', () => { expect(res.result).to.equal('Test'); expect(res.headers['strict-transport-security']).to.not.exist(); expect(res.headers['x-frame-options']).to.equal('DENY'); - expect(res.headers['x-xss-protection']).to.equal('1; mode=block'); + expect(res.headers['x-xss-protection']).to.equal('0'); expect(res.headers['x-download-options']).to.equal('noopen'); expect(res.headers['x-content-type-options']).to.equal('nosniff'); }); @@ -335,7 +335,7 @@ describe('Headers', () => { expect(res.result).to.equal('Test'); expect(res.headers['x-frame-options']).to.not.exist(); expect(res.headers['strict-transport-security']).to.equal('max-age=15768000'); - expect(res.headers['x-xss-protection']).to.equal('1; mode=block'); + expect(res.headers['x-xss-protection']).to.equal('0'); expect(res.headers['x-download-options']).to.equal('noopen'); expect(res.headers['x-content-type-options']).to.equal('nosniff'); }); @@ -418,6 +418,36 @@ describe('Headers', () => { expect(res.headers['x-content-type-options']).to.not.exist(); }); + it('sets the x-xss-protection header when security.xss is enabled', async () => { + + const server = Hapi.server({ routes: { security: { xss: 'enabled' } } }); + server.route({ method: 'GET', path: '/', handler: () => 'Test' }); + + const res = await server.inject({ url: '/' }); + expect(res.result).to.exist(); + expect(res.result).to.equal('Test'); + expect(res.headers['x-xss-protection']).to.equal('1; mode=block'); + expect(res.headers['strict-transport-security']).to.equal('max-age=15768000'); + expect(res.headers['x-frame-options']).to.equal('DENY'); + expect(res.headers['x-download-options']).to.equal('noopen'); + expect(res.headers['x-content-type-options']).to.equal('nosniff'); + }); + + it('sets the x-xss-protection header when security.xss is disabled', async () => { + + const server = Hapi.server({ routes: { security: { xss: 'disabled' } } }); + server.route({ method: 'GET', path: '/', handler: () => 'Test' }); + + const res = await server.inject({ url: '/' }); + expect(res.result).to.exist(); + expect(res.result).to.equal('Test'); + expect(res.headers['x-xss-protection']).to.equal('0'); + expect(res.headers['strict-transport-security']).to.equal('max-age=15768000'); + expect(res.headers['x-frame-options']).to.equal('DENY'); + expect(res.headers['x-download-options']).to.equal('noopen'); + expect(res.headers['x-content-type-options']).to.equal('nosniff'); + }); + it('does not set the x-xss-protection header when security.xss is false', async () => { const server = Hapi.server({ routes: { security: { xss: false } } });