Skip to content

Commit cffb271

Browse files
author
Kumar Harsh
committed
feat(secure): support Content-Security-Policy-Report-Only header
Closes #1283
1 parent be919e8 commit cffb271

File tree

3 files changed

+36
-7
lines changed

3 files changed

+36
-7
lines changed

echo.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -212,12 +212,13 @@ const (
212212
HeaderAccessControlMaxAge = "Access-Control-Max-Age"
213213

214214
// Security
215-
HeaderStrictTransportSecurity = "Strict-Transport-Security"
216-
HeaderXContentTypeOptions = "X-Content-Type-Options"
217-
HeaderXXSSProtection = "X-XSS-Protection"
218-
HeaderXFrameOptions = "X-Frame-Options"
219-
HeaderContentSecurityPolicy = "Content-Security-Policy"
220-
HeaderXCSRFToken = "X-CSRF-Token"
215+
HeaderStrictTransportSecurity = "Strict-Transport-Security"
216+
HeaderXContentTypeOptions = "X-Content-Type-Options"
217+
HeaderXXSSProtection = "X-XSS-Protection"
218+
HeaderXFrameOptions = "X-Frame-Options"
219+
HeaderContentSecurityPolicy = "Content-Security-Policy"
220+
HeaderContentSecurityPolicyReportOnly = "Content-Security-Policy-Report-Only"
221+
HeaderXCSRFToken = "X-CSRF-Token"
221222
)
222223

223224
const (

middleware/secure.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,13 @@ type (
5353
// trusted web page context.
5454
// Optional. Default value "".
5555
ContentSecurityPolicy string `yaml:"content_security_policy"`
56+
57+
// CSPReportOnly would use the `Content-Security-Policy-Report-Only` header instead
58+
// of the `Content-Security-Policy` header. This allows iterative update of the
59+
// content security policy by only reports the violations that would
60+
// have occurred instead of blocking the resource.
61+
// Optional. Default value false.
62+
CSPReportOnly bool `yaml:"csp_report_only"`
5663
}
5764
)
5865

@@ -108,7 +115,10 @@ func SecureWithConfig(config SecureConfig) echo.MiddlewareFunc {
108115
res.Header().Set(echo.HeaderStrictTransportSecurity, fmt.Sprintf("max-age=%d%s", config.HSTSMaxAge, subdomains))
109116
}
110117
if config.ContentSecurityPolicy != "" {
111-
res.Header().Set(echo.HeaderContentSecurityPolicy, config.ContentSecurityPolicy)
118+
if !config.CSPReportOnly {
119+
res.Header().Set(echo.HeaderContentSecurityPolicy, config.ContentSecurityPolicy)
120+
}
121+
res.Header().Set(echo.HeaderContentSecurityPolicyReportOnly, config.ContentSecurityPolicy)
112122
}
113123
return next(c)
114124
}

middleware/secure_test.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,22 @@ func TestSecure(t *testing.T) {
4242
assert.Equal(t, "", rec.Header().Get(echo.HeaderXFrameOptions))
4343
assert.Equal(t, "max-age=3600; includeSubdomains", rec.Header().Get(echo.HeaderStrictTransportSecurity))
4444
assert.Equal(t, "default-src 'self'", rec.Header().Get(echo.HeaderContentSecurityPolicy))
45+
46+
// Custom with CSPReportOnly flag
47+
req.Header.Set(echo.HeaderXForwardedProto, "https")
48+
rec = httptest.NewRecorder()
49+
c = e.NewContext(req, rec)
50+
SecureWithConfig(SecureConfig{
51+
XSSProtection: "",
52+
ContentTypeNosniff: "",
53+
XFrameOptions: "",
54+
HSTSMaxAge: 3600,
55+
ContentSecurityPolicy: "default-src 'self'",
56+
CSPReportOnly: true,
57+
})(h)(c)
58+
assert.Equal(t, "", rec.Header().Get(echo.HeaderXXSSProtection))
59+
assert.Equal(t, "", rec.Header().Get(echo.HeaderXContentTypeOptions))
60+
assert.Equal(t, "", rec.Header().Get(echo.HeaderXFrameOptions))
61+
assert.Equal(t, "max-age=3600; includeSubdomains", rec.Header().Get(echo.HeaderStrictTransportSecurity))
62+
assert.Equal(t, "default-src 'self'", rec.Header().Get(echo.HeaderContentSecurityPolicyReportOnly))
4563
}

0 commit comments

Comments
 (0)