1 // Package secureheader adds some HTTP header fields widely
2 // considered to improve safety of HTTP requests. These fields
3 // are documented as follows:
5 // Strict Transport Security: https://tools.ietf.org/html/rfc6797
6 // Frame Options: https://tools.ietf.org/html/draft-ietf-websec-x-frame-options-00
7 // Cross Site Scripting: http://msdn.microsoft.com/en-us/library/dd565647%28v=vs.85%29.aspx
8 // Content Type Options: http://msdn.microsoft.com/en-us/library/ie/gg622941%28v=vs.85%29.aspx
10 // The easiest way to use this package:
12 // http.ListenAndServe(addr, secureheader.DefaultConfig)
14 // DefaultConfig is initialized with conservative (safer and more
15 // restrictive) behavior. If you want to change that, set its
16 // fields to different values before calling ListenAndServe. See
17 // the example code below.
19 // This package was inspired by Twitter's secureheaders Ruby
20 // library. See https://github.com/twitter/secureheaders.
23 // TODO(kr): figure out how to add this one:
24 // Content Security Policy: https://dvcs.w3.org/hg/content-security-policy/raw-file/tip/csp-specification.dev.html
25 // See https://github.com/kr/secureheader/issues/1.
34 // DefaultConfig is initialized with conservative (safer and more
35 // restrictive) behavior.
36 var DefaultConfig = &Config{
38 HTTPSUseForwardedProto: ShouldUseForwardedProto(),
40 PermitClearLoopback: false,
42 ContentTypeOptions: true,
45 HSTSMaxAge: 300 * 24 * time.Hour,
46 HSTSIncludeSubdomains: true,
49 FrameOptionsPolicy: Deny,
52 XSSProtectionBlock: false,
56 // If true, redirects any request with scheme http to the
57 // equivalent https URL.
59 HTTPSUseForwardedProto bool
61 // Allow cleartext (non-HTTPS) HTTP connections to a loopback
62 // address, even if HTTPSRedirect is true.
63 PermitClearLoopback bool
65 // If true, sets X-Content-Type-Options to "nosniff".
66 ContentTypeOptions bool
68 // If true, sets the HTTP Strict Transport Security header
69 // field, which instructs browsers to send future requests
70 // over HTTPS, even if the URL uses the unencrypted http
73 HSTSMaxAge time.Duration
74 HSTSIncludeSubdomains bool
76 // If true, sets X-Frame-Options, to control when the request
77 // should be displayed inside an HTML frame.
79 FrameOptionsPolicy FramePolicy
81 // If true, sets X-XSS-Protection to "1", optionally with
82 // "mode=block". See the official documentation, linked above,
83 // for the meaning of these values.
85 XSSProtectionBlock bool
87 // Used by ServeHTTP, after setting any extra headers, to
88 // reply to the request. Next is typically nil, in which case
89 // http.DefaultServeMux is used instead.
93 // ServeHTTP sets header fields on w according to the options in
94 // c, then either replies directly or runs c.Next to reply.
95 // Typically c.Next is nil, in which case http.DefaultServeMux is
97 func (c *Config) ServeHTTP(w http.ResponseWriter, r *http.Request) {
98 if c.HTTPSRedirect && !c.isHTTPS(r) && !c.okloopback(r) {
102 http.Redirect(w, r, url.String(), http.StatusMovedPermanently)
105 if c.ContentTypeOptions {
106 w.Header().Set("X-Content-Type-Options", "nosniff")
108 if c.HSTS && c.isHTTPS(r) {
109 v := "max-age=" + strconv.FormatInt(int64(c.HSTSMaxAge/time.Second), 10)
110 if c.HSTSIncludeSubdomains {
111 v += "; includeSubDomains"
113 w.Header().Set("Strict-Transport-Security", v)
116 w.Header().Set("X-Frame-Options", string(c.FrameOptionsPolicy))
120 if c.XSSProtectionBlock {
123 w.Header().Set("X-XSS-Protection", v)
127 next = http.DefaultServeMux
132 // Given that r is cleartext (not HTTPS), okloopback returns
133 // whether r is on a permitted loopback connection.
134 func (c *Config) okloopback(r *http.Request) bool {
135 return c.PermitClearLoopback && isLoopback(r)
138 func (c *Config) isHTTPS(r *http.Request) bool {
139 if c.HTTPSUseForwardedProto {
140 return r.Header.Get("X-Forwarded-Proto") == "https"
145 // FramePolicy tells the browser under what circumstances to allow
146 // the response to be displayed inside an HTML frame. There are
149 // Deny do not permit display in a frame
150 // SameOrigin permit display in a frame from the same origin
151 // AllowFrom(url) permit display in a frame from the given url
152 type FramePolicy string
155 Deny FramePolicy = "DENY"
156 SameOrigin FramePolicy = "SAMEORIGIN"
159 // AllowFrom returns a FramePolicy specifying that the requested
160 // resource should be included in a frame from only the given url.
161 func AllowFrom(url string) FramePolicy {
162 return FramePolicy("ALLOW-FROM: " + url)
165 // ShouldUseForwardedProto returns whether to trust the
166 // X-Forwarded-Proto header field.
167 // DefaultConfig.HTTPSUseForwardedProto is initialized to this
170 // This value depends on the particular environment where the
171 // package is built. It is currently true iff build constraint
172 // "heroku" is satisfied.
173 func ShouldUseForwardedProto() bool {
174 return defaultUseForwardedProto
177 func isLoopback(r *http.Request) bool {
178 a, err := net.ResolveTCPAddr("tcp", r.RemoteAddr)
179 return err == nil && a.IP.IsLoopback()