Repository: google/go-safeweb (go)

Issue:
Add Trusted Types policy to CSP plugin
The CSP plugin (#74 ) should contain a policy that can enable Trusted Types.

I will add this after #93 has been merged.

Pull request:
Add Trusted Types policy to CSP plugin
An example server presenting the protection will follow up.
Fixes #99

Patch:
diff --git a/safehttp/plugins/csp/csp.go b/safehttp/plugins/csp/csp.go
index 2a99fc1..bfdc360 100644
--- a/safehttp/plugins/csp/csp.go
+++ b/safehttp/plugins/csp/csp.go
@@ -15,9 +15,10 @@
 // Package csp provides a safehttp.Interceptor which applies Content-Security Policies
 // to responses.
 //
-// Two default policies are provided:
+// Three default policies are provided:
 //  - A strict nonce based CSP
 //  - A framing policy which sets frame-ancestors to 'self'
+//  - A Trusted Types policy which makes usage of dangerous web API functions secure by default
 package csp
 
 import (
@@ -166,6 +167,31 @@ func (f FramingPolicy) Serialize(nonce string) string {
 	return b.String()
 }
 
+// TrustedTypesPolicy policy can be used to create a new CSP which makes
+// dangerous web API functions secure by default.
+//
+// See https://web.dev/trusted-types for more info.
+type TrustedTypesPolicy struct {
+	// ReportURI controls the report-uri directive. If ReportUri is empty, no report-uri
+	// directive will be set.
+	ReportURI string
+}
+
+// Serialize serializes this policy for use in a Content-Security-Policy header
+// or in a Content-Security-Policy-Report-Only header. A nonce will be provided
+// to Serialize which can be used in 'nonce-{random-nonce}' values in directives.
+func (t TrustedTypesPolicy) Serialize(nonce string) string {
+	var b strings.Builder
+	b.WriteString("require-trusted-types-for 'script'")
+
+	if t.ReportURI != "" {
+		b.WriteString("; report-uri ")
+		b.WriteString(t.ReportURI)
+	}
+
+	return b.String()
+}
+
 // Interceptor intercepts requests and applies CSP policies.
 type Interceptor struct {
 	// TODO: move it to a safehttp.InterceptorConfig implementation to enable
@@ -181,13 +207,14 @@ type Interceptor struct {
 
 var _ safehttp.Interceptor = Interceptor{}
 
-// Default creates a new CSP interceptor with a strict nonce-based policy and a
-// framing policy, both in enforcement mode.
+// Default creates a new CSP interceptor with a strict nonce-based policy, a framing policy
+// and a TrustedTypes policy. All in enforcement mode.
 func Default(reportURI string) Interceptor {
 	return Interceptor{
 		Enforce: []Policy{
 			StrictPolicy{ReportURI: reportURI},
 			FramingPolicy{ReportURI: reportURI},
+			TrustedTypesPolicy{ReportURI: reportURI},
 		},
 	}
 }


Test patch:
diff --git a/safehttp/plugins/csp/csp_test.go b/safehttp/plugins/csp/csp_test.go
index 44100a7..7272b31 100644
--- a/safehttp/plugins/csp/csp_test.go
+++ b/safehttp/plugins/csp/csp_test.go
@@ -96,6 +96,16 @@ func TestSerialize(t *testing.T) {
 			policy:     FramingPolicy{ReportURI: "httsp://example.com/collector"},
 			wantString: "frame-ancestors 'self'; report-uri httsp://example.com/collector",
 		},
+		{
+			name:       "TrustedTypesCSP",
+			policy:     TrustedTypesPolicy{},
+			wantString: "require-trusted-types-for 'script'",
+		},
+		{
+			name:       "TrustedTypesCSP with report-uri",
+			policy:     TrustedTypesPolicy{ReportURI: "httsp://example.com/collector"},
+			wantString: "require-trusted-types-for 'script'; report-uri httsp://example.com/collector",
+		},
 	}
 
 	for _, tt := range tests {
@@ -128,6 +138,7 @@ func TestBefore(t *testing.T) {
 			wantEnforcePolicy: []string{
 				"object-src 'none'; script-src 'unsafe-inline' 'nonce-KSkpKSkpKSkpKSkpKSkpKSkpKSk=' 'strict-dynamic' https: http:; base-uri 'none'",
 				"frame-ancestors 'self'",
+				"require-trusted-types-for 'script'",
 			},
 			wantNonce: "KSkpKSkpKSkpKSkpKSkpKSkpKSk=",
 		},
@@ -137,6 +148,7 @@ func TestBefore(t *testing.T) {
 			wantEnforcePolicy: []string{
 				"object-src 'none'; script-src 'unsafe-inline' 'nonce-KSkpKSkpKSkpKSkpKSkpKSkpKSk=' 'strict-dynamic' https: http:; base-uri 'none'; report-uri https://example.com/collector",
 				"frame-ancestors 'self'; report-uri https://example.com/collector",
+				"require-trusted-types-for 'script'; report-uri https://example.com/collector",
 			},
 			wantNonce: "KSkpKSkpKSkpKSkpKSkpKSkpKSk=",
 		},
diff --git a/tests/integration/csp/csp_test.go b/tests/integration/csp/csp_test.go
index 0f3dc7e..22024e1 100644
--- a/tests/integration/csp/csp_test.go
+++ b/tests/integration/csp/csp_test.go
@@ -72,7 +72,8 @@ func TestServeMuxInstallCSP(t *testing.T) {
 		"Content-Type": {"text/html; charset=utf-8"},
 		"Content-Security-Policy": {
 			"object-src 'none'; script-src 'unsafe-inline' 'nonce-" + nonce + "' 'strict-dynamic' https: http:; base-uri 'none'",
-			"frame-ancestors 'self'"},
+			"frame-ancestors 'self'",
+			"require-trusted-types-for 'script'"},
 	}
 	if diff := cmp.Diff(wantHeaders, map[string][]string(rr.Header())); diff != "" {
 		t.Errorf("rr.Header(): mismatch (-want +got):\n%s", diff)