Skip to content

Commit 1881ca4

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

File tree

3 files changed

+39
-7
lines changed

3 files changed

+39
-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: 12 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 updates of the
59+
// content security policy by only reporting 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,11 @@ 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.HeaderContentSecurityPolicyReportOnly, config.ContentSecurityPolicy)
120+
} else {
121+
res.Header().Set(echo.HeaderContentSecurityPolicy, config.ContentSecurityPolicy)
122+
}
112123
}
113124
return next(c)
114125
}

middleware/secure_test.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,24 @@ 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+
assert.Equal(t, "", rec.Header().Get(echo.HeaderContentSecurityPolicyReportOnly))
46+
47+
// Custom with CSPReportOnly flag
48+
req.Header.Set(echo.HeaderXForwardedProto, "https")
49+
rec = httptest.NewRecorder()
50+
c = e.NewContext(req, rec)
51+
SecureWithConfig(SecureConfig{
52+
XSSProtection: "",
53+
ContentTypeNosniff: "",
54+
XFrameOptions: "",
55+
HSTSMaxAge: 3600,
56+
ContentSecurityPolicy: "default-src 'self'",
57+
CSPReportOnly: true,
58+
})(h)(c)
59+
assert.Equal(t, "", rec.Header().Get(echo.HeaderXXSSProtection))
60+
assert.Equal(t, "", rec.Header().Get(echo.HeaderXContentTypeOptions))
61+
assert.Equal(t, "", rec.Header().Get(echo.HeaderXFrameOptions))
62+
assert.Equal(t, "max-age=3600; includeSubdomains", rec.Header().Get(echo.HeaderStrictTransportSecurity))
63+
assert.Equal(t, "default-src 'self'", rec.Header().Get(echo.HeaderContentSecurityPolicyReportOnly))
64+
assert.Equal(t, "", rec.Header().Get(echo.HeaderContentSecurityPolicy))
4565
}

0 commit comments

Comments
 (0)