A proposed library that provides safe defaults (with opt-out) for security-relevant HTTP response headers.
A variety of headers have been added over the years to address common security problems. Many of these headers were specified as opt-in to avoid breaking the web.
Type net/http/ResponseWriter
provides a Header
multimap, and a WriteHeaders()
method that commits the header map
to the underlying channel.
When writing new web applications, web application authors should have a way to opt-out of secure defaults based on application requirements instead of having to opt-in.
Security scanners (1, 2, 3) seem to have converged on a set of headers that most sites should use barring application-specific needs.
Among the ones that have secure default values which are not the actual default value are:
- Frame-Options & X-Frame-Options specifies interactions between frames and affects click-jacking
- Referrer-Policy affects leakage of sensitive information via URL parameters included in the referrer header.
- Strict-Transport-Security addresses leakage of sensitive information and MITM attacks via HTTPS->HTTP downgrade attacks.
- X-Content-Type-Options addresses polyglot attacks by forbidding content-type sniffing.
Our API does not deal with opt-out security measures or those for which we cannot identify a secure default.
-
CORS's Access-Control-* headers.
-
Content-Security-Policy and related headers allow opting into a suite of checks on code and binary content provenance.
Building a strict content-security policy requires a lot of application specific knowledge, and there is no safe default compatible with current client-side best practices.
-
Public-Key-Pins similarly is opt-in but has no safe default since it requires detailed knowledge of keys related to the hostname.
-
X-XSS-Protection controls browser XSS heuristics. It defaults to "1" (filter) so its default is secure but it can be configured to allow collecting telemetry.
-
Expect-CT has a secure default but is meant for web-host operators, not an application level decision.
-
Expect-Staple has a secure default but requires coordination with certificate providers so is not an application level decision.
The API below allows creation of a set of headers with safe defaults.
import "net/http"
import "net/http/sec" // Proposed package name for new code
func handle(rw http.ResponseWriter) {
// Creates headerset with safe defaults.
secheaders := sec.SecHeaders{}
// Writes headers to secheaders or panics if body
// content has been written
secheaders.AddTo(rw)
}
To override a default, assign the appropriately named field. The package defines named constants for common header values.
secheaders := sec.SecHeaders{
FrameOptions: sec.FrameOptionsSameOrigin
}
SecHeaders's fields are all CamelCasified header names, and each fields' zero value is the safe default.
RFC2616 says of multiple headers with the same name:
It MUST be possible to combine the multiple header fields into one "field-name: field-value" pair without changing the semantics of the message, by appending each subsequent field-value to the first, each separated by a comma.
rw.Header().Add('X-Frame-Options', 'SameOrigin')
...
secheaders.AddTo(rw) // Adds default value Deny
should not cause an invalid value of SameOrigin,Deny
that might unintentionally
strip both levels of protection from the document.
ResponseWriter
exposes the
internal map from header names to value slices
so AddTo
could detect duplicates and override or decline to set but that would not address
.AddTo
before .Header().Add(...)
.
Ideally WriteHeader
would contain a blacklist of headers whose values are known not to be
comma separated lists and do something reasonable when there are duplicate values.
This check could store the failure and later communicate failure via ResponseWriter::Write
since that method has an error result.
The documentation for ResponseWriter::Write
says
// If the Header // does not contain a Content-Type line, Write adds a Content-Type set // to the result of passing the initial 512 bytes of written data to // DetectContentType.
It would be ideal if this resulted in an error when X-Content-Type-Options
is not permissive
since server-side content-type guessing of proxied content is no less vulnerable to polyglot attacks
than client-side content-type guessing.
Since the set of security relevant headers seems to grow over time, an application author might be concerned that the application might break when a new header is introduced in user-agents.
Application end-to-end tests should catch these issues, but if this is a concern, the API could be extended to provide constructor functions like
secheaders := sec.SecureDefaultsAsOf2017()
Should the API change to add SecureDefaultsAsOf2018()
older dates could be
flagged by code-quality and security-auditing tools and/or a Deprecated:
section added to the docs.
Violation reports allow production engineers to keep an eye out for emerging threats, but when a security policy is applied on the client, server-side logging isn't sufficient.
Several headers provide telemetry on policy violations provide a way to specify a URI to which report violations can be POSTed, and provide a mode in which violations are reported but do not affect user-agent behavior.
Content-Security-Policy
Expect-CT
Expect-Staple
Public-Key-Pins
X-XSS-Protection
(Chrome only)
Given a URI for centralized report collection we could craft a default value with a restrictive policy in report-only mode for headers:
Not collecting telemetry is an insecure default. NIST says
Because performing incident response effectively is a complex undertaking, establishing a successful incident response capability requires substantial planning and resources. Continually monitoring for attacks is essential. Establishing clear procedures for prioritizing the handling of incidents is critical, as is implementing effective methods of collecting, analyzing, and reporting data.