Skip to content

Commit

Permalink
Disable X-XSS-Protection filter, for safety
Browse files Browse the repository at this point in the history
Surprisingly, [`X-XSS-Protection` is safer when disabled][0], and
browsers are dropping support for it as a result. Because it's less safe
to enable the filter, this change sets the default (and only) value to
`0`, instead of `1; mode=block`.

This is a breaking change.

See [issue brokenhandsio#25][1].

[0]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection
[1]: brokenhandsio#25
  • Loading branch information
EvanHahn committed Apr 13, 2022
1 parent 82b5174 commit 114ce16
Show file tree
Hide file tree
Showing 5 changed files with 15 additions and 94 deletions.
44 changes: 5 additions & 39 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ The default factory will add default values to your site for Content-Security-Po
x-content-type-options: nosniff
content-security-policy: default-src 'self'
x-frame-options: DENY
x-xss-protection: 1; mode=block
x-xss-protection: 0
```

***Note:*** You should ensure you set the security headers as the first middleware in your `Middlewares` (i.e., the first middleware to be applied to responses) to make sure the headers get added to all responses.
Expand All @@ -94,7 +94,7 @@ application.middleware.use(securityHeadersFactory.build())
x-content-type-options: nosniff
content-security-policy: default-src 'none'; script-src https://static.brokenhands.io;
x-frame-options: DENY
x-xss-protection: 1; mode=block
x-xss-protection: 0
```

Each different header has its own configuration and options, details of which can be found below.
Expand All @@ -114,7 +114,7 @@ application.middleware.use(securityHeaders.build())
x-content-type-options: nosniff
content-security-policy: default-src 'none'
x-frame-options: DENY
x-xss-protection: 1; mode=block
x-xss-protection: 0
```

# Server Configuration
Expand Down Expand Up @@ -317,44 +317,10 @@ The [above blog post](https://scotthelme.co.uk/content-security-policy-an-introd

## X-XSS-Protection

X-XSS-Protection configures the browser's cross-site scripting filter. The recommended, and default, setting is `.block` which blocks the response if the browser detects an attack. This can be configured with:

```swift
let xssProtectionConfig = XSSProtectionConfiguration(option: .block)

let securityHeadersFactory = SecurityHeadersFactory().with(XSSProtection: xssProtectionConfig)
```

```http
x-xss-protection: 1; mode=block
```

To just enable the protection:

```swift
let xssProtectionConfig = XSSProtectionConfiguration(option: .enable)
```

```http
x-xss-protection: 1
```

To sanitize the page and report the violation:

```swift
let xssProtectionConfig = XSSProtectionConfiguration(option: .report(uri: "https://report-uri.com"))

let securityHeadersFactory = SecurityHeadersFactory().with(XSSProtection: xssProtectionConfig)
```

```http
x-xss-protection: 1; report=https://report-uri.com
```

Or to disable:
X-XSS-Protection configures the browser's cross-site scripting filter. This package configures the header to be disabled, which (surprisingly) offers security benefits. See [this article on MDN for more information](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection).

```swift
let xssProtectionConfig = XSSProtectionConfiguration(option: .disable)
let xssProtectionConfig = XSSProtectionConfiguration()

let securityHeadersFactory = SecurityHeadersFactory().with(XSSProtection: xssProtectionConfig)
```
Expand Down
Original file line number Diff line number Diff line change
@@ -1,30 +1,9 @@
import Vapor

public struct XSSProtectionConfiguration: SecurityHeaderConfiguration {

public enum Options {
case disable
case enable
case block
case report(uri: String)
}

private let option: Options

public init(option: Options) {
self.option = option
}
public init () {}

func setHeader(on response: Response, from request: Request) {
switch option {
case .disable:
response.headers.replaceOrAdd(name: .xssProtection, value: "0")
case .enable:
response.headers.replaceOrAdd(name: .xssProtection, value: "1")
case .block:
response.headers.replaceOrAdd(name: .xssProtection, value: "1; mode=block")
case .report(let uri):
response.headers.replaceOrAdd(name: .xssProtection, value: "1; report=\(uri)")
}
response.headers.replaceOrAdd(name: .xssProtection, value: "0")
}
}
2 changes: 1 addition & 1 deletion Sources/VaporSecurityHeaders/SecurityHeaders.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ public struct SecurityHeaders {
init(contentTypeConfiguration: ContentTypeOptionsConfiguration = ContentTypeOptionsConfiguration(option: .nosniff),
contentSecurityPolicyConfiguration: ContentSecurityPolicyConfiguration = ContentSecurityPolicyConfiguration(value: ContentSecurityPolicy().defaultSrc(sources: CSPKeywords.`self`)),
frameOptionsConfiguration: FrameOptionsConfiguration = FrameOptionsConfiguration(option: .deny),
xssProtectionConfiguration: XSSProtectionConfiguration = XSSProtectionConfiguration(option: .block),
xssProtectionConfiguration: XSSProtectionConfiguration = XSSProtectionConfiguration(),
hstsConfiguration: StrictTransportSecurityConfiguration? = nil,
serverConfiguration: ServerConfiguration? = nil,
contentSecurityPolicyReportOnlyConfiguration: ContentSecurityPolicyReportOnlyConfiguration? = nil,
Expand Down
2 changes: 1 addition & 1 deletion Sources/VaporSecurityHeaders/SecurityHeadersFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ public class SecurityHeadersFactory {
var contentTypeOptions = ContentTypeOptionsConfiguration(option: .nosniff)
var contentSecurityPolicy = ContentSecurityPolicyConfiguration(value: ContentSecurityPolicy().defaultSrc(sources: CSPKeywords.`self`))
var frameOptions = FrameOptionsConfiguration(option: .deny)
var xssProtection = XSSProtectionConfiguration(option: .block)
var xssProtection = XSSProtectionConfiguration()
var hsts: StrictTransportSecurityConfiguration?
var server: ServerConfiguration?
var referrerPolicy: ReferrerPolicyConfiguration?
Expand Down
36 changes: 6 additions & 30 deletions Tests/VaporSecurityHeadersTests/HeaderTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class HeaderTests: XCTestCase {
let expectedXCTOHeaderValue = "nosniff"
let expectedCSPHeaderValue = "default-src 'self'"
let expectedXFOHeaderValue = "DENY"
let expectedXSSProtectionHeaderValue = "1; mode=block"
let expectedXSSProtectionHeaderValue = "0"

let response = try makeTestResponse(for: request, securityHeadersToAdd: SecurityHeadersFactory())

Expand All @@ -51,7 +51,7 @@ class HeaderTests: XCTestCase {
let expectedXCTOHeaderValue = "nosniff"
let expectedCSPHeaderValue = "default-src 'self'"
let expectedXFOHeaderValue = "DENY"
let expectedXSSProtectionHeaderValue = "1; mode=block"
let expectedXSSProtectionHeaderValue = "0"
let expectedHSTSHeaderValue = "max-age=31536000; includeSubDomains; preload"

let response = try makeTestResponse(for: request, securityHeadersToAdd: SecurityHeadersFactory().with(strictTransportSecurity: StrictTransportSecurityConfiguration()))
Expand All @@ -67,7 +67,7 @@ class HeaderTests: XCTestCase {
let expectedXCTOHeaderValue = "nosniff"
let expectedCSPHeaderValue = "default-src 'none'"
let expectedXFOHeaderValue = "DENY"
let expectedXSSProtectionHeaderValue = "1; mode=block"
let expectedXSSProtectionHeaderValue = "0"

let response = try makeTestResponse(for: request, securityHeadersToAdd: SecurityHeadersFactory.api())

Expand All @@ -81,7 +81,7 @@ class HeaderTests: XCTestCase {
let expectedXCTOHeaderValue = "nosniff"
let expectedCSPHeaderValue = "default-src 'none'"
let expectedXFOHeaderValue = "DENY"
let expectedXSSProtectionHeaderValue = "1; mode=block"
let expectedXSSProtectionHeaderValue = "0"
let expectedHSTSHeaderValue = "max-age=31536000; includeSubDomains; preload"

let response = try makeTestResponse(for: request, securityHeadersToAdd: SecurityHeadersFactory.api().with(strictTransportSecurity: StrictTransportSecurityConfiguration()))
Expand Down Expand Up @@ -133,38 +133,14 @@ class HeaderTests: XCTestCase {
XCTAssertEqual("ALLOW-FROM https://test.com", response.headers[.xFrameOptions].first)
}

func testHeaderWithXssProtectionDisable() throws {
let xssProtectionConfig = XSSProtectionConfiguration(option: .disable)
func testHeaderWithXssProtection() throws {
let xssProtectionConfig = XSSProtectionConfiguration()
let factory = SecurityHeadersFactory().with(XSSProtection: xssProtectionConfig)
let response = try makeTestResponse(for: request, securityHeadersToAdd: factory)

XCTAssertEqual("0", response.headers[.xssProtection].first)
}

func testHeaderWithXssProtectionEnable() throws {
let xssProtectionConfig = XSSProtectionConfiguration(option: .enable)
let factory = SecurityHeadersFactory().with(XSSProtection: xssProtectionConfig)
let response = try makeTestResponse(for: request, securityHeadersToAdd: factory)

XCTAssertEqual("1", response.headers[.xssProtection].first)
}

func testHeaderWithXssProtectionBlock() throws {
let xssProtectionConfig = XSSProtectionConfiguration(option: .block)
let factory = SecurityHeadersFactory().with(XSSProtection: xssProtectionConfig)
let response = try makeTestResponse(for: request, securityHeadersToAdd: factory)

XCTAssertEqual("1; mode=block", response.headers[.xssProtection].first)
}

func testHeaderWithXssProtectionReport() throws {
let xssProtectionConfig = XSSProtectionConfiguration(option: .report(uri: "https://test.com"))
let factory = SecurityHeadersFactory().with(XSSProtection: xssProtectionConfig)
let response = try makeTestResponse(for: request, securityHeadersToAdd: factory)

XCTAssertEqual("1; report=https://test.com", response.headers[.xssProtection].first)
}

func testHeaderWithHSTSwithMaxAge() throws {
let hstsConfig = StrictTransportSecurityConfiguration(maxAge: 30)
let factory = SecurityHeadersFactory().with(strictTransportSecurity: hstsConfig)
Expand Down

0 comments on commit 114ce16

Please sign in to comment.