2026-06-28 11:35:54 +00:00
import HTTPTypes
import Hummingbird
/// Stamps a set of security-related HTTP headers onto every response.
///
2026-07-30 06:33:57 +00:00
/// Placed at (or near) the top of the middleware chain, it adds the configured headers to whatever response bubbles back up — the rendered pages, the
/// error page produced by ``NotFoundMiddleware``, and every static file served by `FileMiddleware` — so the browser applies the strict, hardened
/// interpretation of the content instead of its lenient legacy defaults.
2026-06-28 11:35:54 +00:00
///
2026-07-30 06:33:57 +00:00
/// The headers are precomputed once from the ``Configuration`` at initialization and reused for every request, so the per-request cost is a handful of
/// header copies.
2026-06-28 11:35:54 +00:00
public struct SecurityHeadersMiddleware < Context : RequestContext > {
// MARK: Properties
/// The precomputed headers applied to every response.
private let fields : HTTPFields
// MARK: Initializers
/// Creates a security-headers middleware.
2026-07-30 06:33:57 +00:00
/// - Parameter configuration: the headers applied to every response. Defaults to a hardened baseline suitable for a static site, with
/// `Strict-Transport-Security` left off (see ``Configuration``).
2026-06-28 11:35:54 +00:00
public init (
configuration : Configuration = . init ()
) {
self . fields = configuration . fields
}
}
// MARK: - RouterMiddleware
extension SecurityHeadersMiddleware : RouterMiddleware {
// MARK: Functions
2026-07-30 06:33:57 +00:00
/// Passes the request down the chain and stamps the configured security headers onto the response on the way back up.
2026-06-28 11:35:54 +00:00
///
2026-07-30 06:33:57 +00:00
/// Errors that can render themselves (`HTTPResponseError`, like the `HTTPError`s thrown by the controllers) are converted to their response
/// here rather than left to the router: the router converts them above the middleware chain, where the response would escape these headers.
/// Existing values for the same header names are replaced so downstream middleware cannot leave a weaker policy in place.
2026-06-28 11:35:54 +00:00
/// - Parameters:
/// - request: the incoming request.
/// - context: the context the request is resolved against.
/// - next: the next responder in the middleware chain.
/// - Returns: the downstream response with the security headers applied.
2026-07-23 01:04:37 +00:00
/// - Throws: any downstream error that does not render as an HTTP response.
2026-06-28 11:35:54 +00:00
public func handle (
_ request : Request ,
context : Context ,
next : ( Request , Context ) async throws -> Response
) async throws -> Response {
2026-07-23 01:04:37 +00:00
var response : Response
do {
response = try await next (
request ,
context
)
} catch let error as any HTTPResponseError {
response = try error . response (
from : request ,
context : context
)
}
2026-06-28 11:35:54 +00:00
for field in fields {
response . headers [ field . name ] = field . value
}
return response
}
}
// MARK: - Helpers
private extension SecurityHeadersMiddleware . Configuration {
// MARK: Computed
/// The configuration expressed as the headers to apply, omitting any whose value is `nil`.
var fields : HTTPFields {
var fields = HTTPFields ()
fields [. contentSecurityPolicy ] = contentSecurityPolicy
fields [. xContentTypeOptions ] = contentTypeOptions
fields [. frameOptions ] = frameOptions
fields [. referrerPolicy ] = referrerPolicy
fields [. permissionsPolicy ] = permissionsPolicy
fields [. strictTransportSecurity ] = strictTransportSecurity
return fields
}
}
// MARK: - Configuration
extension SecurityHeadersMiddleware {
/// The set of security headers a ``SecurityHeadersMiddleware`` applies.
///
2026-07-30 06:33:57 +00:00
/// Each property maps to a single response header. A `nil` value omits that header entirely, which is how `Strict-Transport-Security` stays
/// disabled by default: it is only safe to send over HTTPS and is "sticky" in browsers, so it must stay off in plain-HTTP development and be switched on
/// (via configuration) only in TLS-terminated production.
2026-06-28 11:35:54 +00:00
public struct Configuration : Sendable {
// MARK: Properties
/// The `Content-Security-Policy` value (controls which sources the browser will load).
public let contentSecurityPolicy : String ?
/// The `X-Content-Type-Options` value (disables MIME sniffing when set to `nosniff`).
public let contentTypeOptions : String ?
/// The `X-Frame-Options` value (controls whether the page may be framed).
public let frameOptions : String ?
/// The `Referrer-Policy` value (controls how much referrer information is shared).
public let referrerPolicy : String ?
/// The `Permissions-Policy` value (gates access to powerful browser features).
public let permissionsPolicy : String ?
/// The `Strict-Transport-Security` value, or `nil` to omit the header (the default).
public let strictTransportSecurity : String ?
// MARK: Initializers
/// Creates a security-headers configuration.
///
2026-07-30 06:33:57 +00:00
/// Every parameter defaults to the hardened baseline defined in `String.Security`, except `strictTransportSecurity`, which defaults
/// to `nil` (omitted). Pass `nil` for any header to drop it from the response.
2026-06-28 11:35:54 +00:00
/// - Parameters:
/// - contentSecurityPolicy: the `Content-Security-Policy` value.
/// - contentTypeOptions: the `X-Content-Type-Options` value.
/// - frameOptions: the `X-Frame-Options` value.
/// - referrerPolicy: the `Referrer-Policy` value.
/// - permissionsPolicy: the `Permissions-Policy` value.
/// - strictTransportSecurity: the `Strict-Transport-Security` value, or `nil` to omit it.
public init (
contentSecurityPolicy : String ? = String . Security . contentSecurityPolicy ,
contentTypeOptions : String ? = String . Security . contentTypeOptions ,
frameOptions : String ? = String . Security . frameOptions ,
referrerPolicy : String ? = String . Security . referrerPolicy ,
permissionsPolicy : String ? = String . Security . permissionsPolicy ,
strictTransportSecurity : String ? = nil
) {
self . contentSecurityPolicy = contentSecurityPolicy
self . contentTypeOptions = contentTypeOptions
self . frameOptions = frameOptions
self . referrerPolicy = referrerPolicy
self . permissionsPolicy = permissionsPolicy
self . strictTransportSecurity = strictTransportSecurity
}
}
}