Repository: traefik/traefik (language: go)

Issue:
Content-Security-Policy-Report-Only
### *Feature request*

Add support for Content-Security-Policy-Report-Only. We can use the standard CSP header with 'contentSecurityPolicy' but it would be nice to be able to instead use it in Report-Only mode. This would help many users to develop their CSP using web-tools before deploying it into their 'production' or (in my case as a home user) homelabs. 

### What did you expect to see?

contentSecurityPolicyReportOnly in available header middlewares. 

### Reference
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only


Discussion / hints:
While we wait for this, note that you can use a custom response header:

```yaml
customResponseHeaders:
  Content-Security-Policy-Report-Only: >-
    connect-src self;
    default-src none;
    font-src self;
    img-src self;
    script-src self;
    style-src self inline-unsafe; 
```
I think it is time to give this request a bump

While it should be possible to use a customResponseHeader to express this, it causes issues when trying to express a configuration as 'infrastructure as code'.

It is very easy to switch between "contentSecurityPolicy" and "Content-Security-Policy-Report-Only" using an environment variable also having to switch between a builtin header definition and a customResponseHeader is not so easy, with the result not being as easy to follow when the config is revisited by another person.

As an example, my definition within a docker-compose file currently looks like this 

`     - "traefik.http.middlewares.security-headers.headers.${DOCKER_TRAEFIK_CSP_CMD}${DOCKER_TRAEFIK_CSP_LINE1};${DOCKER_TRAEFIK_CSP_LINEEND}"`

With a growing number of 'LINEx' variables being added as the CSP becomes more of an essay than a line entry. To support both header types all I would need to do is change the value of DOCKER_TRAEFIK_CSP_CMD to switch between them. Instead, I have to be creative and include more of the traefik label within the environment variable, which makes things harder to read. 


Fix patch:
diff --git a/docs/content/middlewares/http/headers.md b/docs/content/middlewares/http/headers.md
index d0cb636724..afeb6891f2 100644
--- a/docs/content/middlewares/http/headers.md
+++ b/docs/content/middlewares/http/headers.md
@@ -394,6 +394,10 @@ This overrides the `BrowserXssFilter` option.
 
 The `contentSecurityPolicy` option allows the `Content-Security-Policy` header value to be set with a custom value.
 
+### `contentSecurityPolicyReportOnly`
+
+The `contentSecurityPolicyReportOnly` option allows the `Content-Security-Policy-Report-Only` header value to be set with a custom value.
+
 ### `publicKey`
 
 The `publicKey` implements HPKP to prevent MITM attacks with forged certificates.
diff --git a/docs/content/reference/dynamic-configuration/docker-labels.yml b/docs/content/reference/dynamic-configuration/docker-labels.yml
index 3199888948..a3e65baa2c 100644
--- a/docs/content/reference/dynamic-configuration/docker-labels.yml
+++ b/docs/content/reference/dynamic-configuration/docker-labels.yml
@@ -55,6 +55,7 @@
 - "traefik.http.middlewares.middleware12.headers.allowedhosts=foobar, foobar"
 - "traefik.http.middlewares.middleware12.headers.browserxssfilter=true"
 - "traefik.http.middlewares.middleware12.headers.contentsecuritypolicy=foobar"
+- "traefik.http.middlewares.middleware12.headers.contentsecuritypolicyreportonly=foobar"
 - "traefik.http.middlewares.middleware12.headers.contenttypenosniff=true"
 - "traefik.http.middlewares.middleware12.headers.custombrowserxssvalue=foobar"
 - "traefik.http.middlewares.middleware12.headers.customframeoptionsvalue=foobar"
diff --git a/docs/content/reference/dynamic-configuration/file.toml b/docs/content/reference/dynamic-configuration/file.toml
index a42f9ba17d..cd0ffde605 100644
--- a/docs/content/reference/dynamic-configuration/file.toml
+++ b/docs/content/reference/dynamic-configuration/file.toml
@@ -198,6 +198,7 @@
         browserXssFilter = true
         customBrowserXSSValue = "foobar"
         contentSecurityPolicy = "foobar"
+        contentSecurityPolicyReportOnly = "foobar"
         publicKey = "foobar"
         referrerPolicy = "foobar"
         permissionsPolicy = "foobar"
diff --git a/docs/content/reference/dynamic-configuration/file.yaml b/docs/content/reference/dynamic-configuration/file.yaml
index 6f675e626f..d0de416ee7 100644
--- a/docs/content/reference/dynamic-configuration/file.yaml
+++ b/docs/content/reference/dynamic-configuration/file.yaml
@@ -242,6 +242,7 @@ http:
         browserXssFilter: true
         customBrowserXSSValue: foobar
         contentSecurityPolicy: foobar
+        contentSecurityPolicyReportOnly: foobar
         publicKey: foobar
         referrerPolicy: foobar
         permissionsPolicy: foobar
diff --git a/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml b/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml
index fa2baaf1ee..761a8f9282 100644
--- a/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml
+++ b/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml
@@ -1309,6 +1309,10 @@ spec:
                     description: ContentSecurityPolicy defines the Content-Security-Policy
                       header value.
                     type: string
+                  contentSecurityPolicyReportOnly:
+                    description: ContentSecurityPolicyReportOnly defines the Content-Security-Policy-Report-Only
+                      header value.
+                    type: string
                   contentTypeNosniff:
                     description: ContentTypeNosniff defines whether to add the X-Content-Type-Options
                       header with the nosniff value.
diff --git a/docs/content/reference/dynamic-configuration/kv-ref.md b/docs/content/reference/dynamic-configuration/kv-ref.md
index cd425b7108..10b9a8d002 100644
--- a/docs/content/reference/dynamic-configuration/kv-ref.md
+++ b/docs/content/reference/dynamic-configuration/kv-ref.md
@@ -71,6 +71,7 @@ THIS FILE MUST NOT BE EDITED BY HAND
 | `traefik/http/middlewares/Middleware12/headers/allowedHosts/1` | `foobar` |
 | `traefik/http/middlewares/Middleware12/headers/browserXssFilter` | `true` |
 | `traefik/http/middlewares/Middleware12/headers/contentSecurityPolicy` | `foobar` |
+| `traefik/http/middlewares/Middleware12/headers/contentSecurityPolicyReportOnly` | `foobar` |
 | `traefik/http/middlewares/Middleware12/headers/contentTypeNosniff` | `true` |
 | `traefik/http/middlewares/Middleware12/headers/customBrowserXSSValue` | `foobar` |
 | `traefik/http/middlewares/Middleware12/headers/customFrameOptionsValue` | `foobar` |
diff --git a/docs/content/reference/dynamic-configuration/traefik.io_middlewares.yaml b/docs/content/reference/dynamic-configuration/traefik.io_middlewares.yaml
index 72b7d69bd1..0d005e64df 100644
--- a/docs/content/reference/dynamic-configuration/traefik.io_middlewares.yaml
+++ b/docs/content/reference/dynamic-configuration/traefik.io_middlewares.yaml
@@ -585,6 +585,10 @@ spec:
                     description: ContentSecurityPolicy defines the Content-Security-Policy
                       header value.
                     type: string
+                  contentSecurityPolicyReportOnly:
+                    description: ContentSecurityPolicyReportOnly defines the Content-Security-Policy-Report-Only
+                      header value.
+                    type: string
                   contentTypeNosniff:
                     description: ContentTypeNosniff defines whether to add the X-Content-Type-Options
                       header with the nosniff value.
diff --git a/integration/fixtures/k8s/01-traefik-crd.yml b/integration/fixtures/k8s/01-traefik-crd.yml
index fa2baaf1ee..761a8f9282 100644
--- a/integration/fixtures/k8s/01-traefik-crd.yml
+++ b/integration/fixtures/k8s/01-traefik-crd.yml
@@ -1309,6 +1309,10 @@ spec:
                     description: ContentSecurityPolicy defines the Content-Security-Policy
                       header value.
                     type: string
+                  contentSecurityPolicyReportOnly:
+                    description: ContentSecurityPolicyReportOnly defines the Content-Security-Policy-Report-Only
+                      header value.
+                    type: string
                   contentTypeNosniff:
                     description: ContentTypeNosniff defines whether to add the X-Content-Type-Options
                       header with the nosniff value.
diff --git a/pkg/config/dynamic/fixtures/sample.toml b/pkg/config/dynamic/fixtures/sample.toml
index f6ab56cf15..c096381aa3 100644
--- a/pkg/config/dynamic/fixtures/sample.toml
+++ b/pkg/config/dynamic/fixtures/sample.toml
@@ -330,6 +330,7 @@
         browserXssFilter = true
         customBrowserXSSValue = "foobar"
         contentSecurityPolicy = "foobar"
+        contentSecurityPolicyReportOnly = "foobar"
         publicKey = "foobar"
         referrerPolicy = "foobar"
         isDevelopment = true
diff --git a/pkg/config/dynamic/middlewares.go b/pkg/config/dynamic/middlewares.go
index 568c7f46b6..4042ed3ebf 100644
--- a/pkg/config/dynamic/middlewares.go
+++ b/pkg/config/dynamic/middlewares.go
@@ -313,6 +313,8 @@ type Headers struct {
 	CustomBrowserXSSValue string `json:"customBrowserXSSValue,omitempty" toml:"customBrowserXSSValue,omitempty" yaml:"customBrowserXSSValue,omitempty"`
 	// ContentSecurityPolicy defines the Content-Security-Policy header value.
 	ContentSecurityPolicy string `json:"contentSecurityPolicy,omitempty" toml:"contentSecurityPolicy,omitempty" yaml:"contentSecurityPolicy,omitempty"`
+	// ContentSecurityPolicyReportOnly defines the Content-Security-Policy-Report-Only header value.
+	ContentSecurityPolicyReportOnly string `json:"contentSecurityPolicyReportOnly,omitempty" toml:"contentSecurityPolicyReportOnly,omitempty" yaml:"contentSecurityPolicyReportOnly,omitempty"`
 	// PublicKey is the public key that implements HPKP to prevent MITM attacks with forged certificates.
 	PublicKey string `json:"publicKey,omitempty" toml:"publicKey,omitempty" yaml:"publicKey,omitempty"`
 	// ReferrerPolicy defines the Referrer-Policy header value.
@@ -376,6 +378,7 @@ func (h *Headers) HasSecureHeadersDefined() bool {
 		h.BrowserXSSFilter ||
 		h.CustomBrowserXSSValue != "" ||
 		h.ContentSecurityPolicy != "" ||
+		h.ContentSecurityPolicyReportOnly != "" ||
 		h.PublicKey != "" ||
 		h.ReferrerPolicy != "" ||
 		(h.FeaturePolicy != nil && *h.FeaturePolicy != "") ||
diff --git a/pkg/middlewares/headers/secure.go b/pkg/middlewares/headers/secure.go
index 1766e63563..9769627d1c 100644
--- a/pkg/middlewares/headers/secure.go
+++ b/pkg/middlewares/headers/secure.go
@@ -17,24 +17,25 @@ type secureHeader struct {
 // newSecure constructs a new secure instance with supplied options.
 func newSecure(next http.Handler, cfg dynamic.Headers, contextKey string) *secureHeader {
 	opt := secure.Options{
-		BrowserXssFilter:        cfg.BrowserXSSFilter,
-		ContentTypeNosniff:      cfg.ContentTypeNosniff,
-		ForceSTSHeader:          cfg.ForceSTSHeader,
-		FrameDeny:               cfg.FrameDeny,
-		IsDevelopment:           cfg.IsDevelopment,
-		STSIncludeSubdomains:    cfg.STSIncludeSubdomains,
-		STSPreload:              cfg.STSPreload,
-		ContentSecurityPolicy:   cfg.ContentSecurityPolicy,
-		CustomBrowserXssValue:   cfg.CustomBrowserXSSValue,
-		CustomFrameOptionsValue: cfg.CustomFrameOptionsValue,
-		PublicKey:               cfg.PublicKey,
-		ReferrerPolicy:          cfg.ReferrerPolicy,
-		AllowedHosts:            cfg.AllowedHosts,
-		HostsProxyHeaders:       cfg.HostsProxyHeaders,
-		SSLProxyHeaders:         cfg.SSLProxyHeaders,
-		STSSeconds:              cfg.STSSeconds,
-		PermissionsPolicy:       cfg.PermissionsPolicy,
-		SecureContextKey:        contextKey,
+		BrowserXssFilter:                cfg.BrowserXSSFilter,
+		ContentTypeNosniff:              cfg.ContentTypeNosniff,
+		ForceSTSHeader:                  cfg.ForceSTSHeader,
+		FrameDeny:                       cfg.FrameDeny,
+		IsDevelopment:                   cfg.IsDevelopment,
+		STSIncludeSubdomains:            cfg.STSIncludeSubdomains,
+		STSPreload:                      cfg.STSPreload,
+		ContentSecurityPolicy:           cfg.ContentSecurityPolicy,
+		ContentSecurityPolicyReportOnly: cfg.ContentSecurityPolicyReportOnly,
+		CustomBrowserXssValue:           cfg.CustomBrowserXSSValue,
+		CustomFrameOptionsValue:         cfg.CustomFrameOptionsValue,
+		PublicKey:                       cfg.PublicKey,
+		ReferrerPolicy:                  cfg.ReferrerPolicy,
+		AllowedHosts:                    cfg.AllowedHosts,
+		HostsProxyHeaders:               cfg.HostsProxyHeaders,
+		SSLProxyHeaders:                 cfg.SSLProxyHeaders,
+		STSSeconds:                      cfg.STSSeconds,
+		PermissionsPolicy:               cfg.PermissionsPolicy,
+		SecureContextKey:                contextKey,
 	}
 
 	return &secureHeader{
diff --git a/webui/src/components/_commons/PanelMiddlewares.vue b/webui/src/components/_commons/PanelMiddlewares.vue
index 98eff915a3..4921a0b549 100644
--- a/webui/src/components/_commons/PanelMiddlewares.vue
+++ b/webui/src/components/_commons/PanelMiddlewares.vue
@@ -817,6 +817,22 @@
               </div>
             </div>
           </q-card-section>
+          <!-- EXTRA FIELDS FROM MIDDLEWARES - [headers] - contentSecurityPolicyReportOnly -->
+          <q-card-section v-if="middleware.headers">
+            <div class="row items-start no-wrap">
+              <div class="col">
+                <div class="text-subtitle2">
+                  Content Security Policy (Report Only)
+                </div>
+                <q-chip
+                  dense
+                  class="app-chip app-chip-green"
+                >
+                  {{ exData(middleware).contentSecurityPolicyReportOnly }}
+                </q-chip>
+              </div>
+            </div>
+          </q-card-section>
           <!-- EXTRA FIELDS FROM MIDDLEWARES - [headers] - publicKey -->
           <q-card-section v-if="middleware.headers">
             <div class="row items-start no-wrap">


Test patch:
diff --git a/pkg/config/label/label_test.go b/pkg/config/label/label_test.go
index e1c1688201..c9989e1e3f 100644
--- a/pkg/config/label/label_test.go
+++ b/pkg/config/label/label_test.go
@@ -63,6 +63,7 @@ func TestDecodeConfiguration(t *testing.T) {
 		"traefik.http.middlewares.Middleware8.headers.addvaryheader":                               "true",
 		"traefik.http.middlewares.Middleware8.headers.browserxssfilter":                            "true",
 		"traefik.http.middlewares.Middleware8.headers.contentsecuritypolicy":                       "foobar",
+		"traefik.http.middlewares.Middleware8.headers.contentsecuritypolicyreportonly":             "foobar",
 		"traefik.http.middlewares.Middleware8.headers.contenttypenosniff":                          "true",
 		"traefik.http.middlewares.Middleware8.headers.custombrowserxssvalue":                       "foobar",
 		"traefik.http.middlewares.Middleware8.headers.customframeoptionsvalue":                     "foobar",
@@ -611,22 +612,23 @@ func TestDecodeConfiguration(t *testing.T) {
 							"name0": "foobar",
 							"name1": "foobar",
 						},
-						SSLForceHost:            Bool(true),
-						STSSeconds:              42,
-						STSIncludeSubdomains:    true,
-						STSPreload:              true,
-						ForceSTSHeader:          true,
-						FrameDeny:               true,
-						CustomFrameOptionsValue: "foobar",
-						ContentTypeNosniff:      true,
-						BrowserXSSFilter:        true,
-						CustomBrowserXSSValue:   "foobar",
-						ContentSecurityPolicy:   "foobar",
-						PublicKey:               "foobar",
-						ReferrerPolicy:          "foobar",
-						FeaturePolicy:           String("foobar"),
-						PermissionsPolicy:       "foobar",
-						IsDevelopment:           true,
+						SSLForceHost:                    Bool(true),
+						STSSeconds:                      42,
+						STSIncludeSubdomains:            true,
+						STSPreload:                      true,
+						ForceSTSHeader:                  true,
+						FrameDeny:                       true,
+						CustomFrameOptionsValue:         "foobar",
+						ContentTypeNosniff:              true,
+						BrowserXSSFilter:                true,
+						CustomBrowserXSSValue:           "foobar",
+						ContentSecurityPolicy:           "foobar",
+						ContentSecurityPolicyReportOnly: "foobar",
+						PublicKey:                       "foobar",
+						ReferrerPolicy:                  "foobar",
+						FeaturePolicy:                   String("foobar"),
+						PermissionsPolicy:               "foobar",
+						IsDevelopment:                   true,
 					},
 				},
 				"Middleware9": {
@@ -1134,22 +1136,23 @@ func TestEncodeConfiguration(t *testing.T) {
 							"name0": "foobar",
 							"name1": "foobar",
 						},
-						SSLForceHost:            Bool(true),
-						STSSeconds:              42,
-						STSIncludeSubdomains:    true,
-						STSPreload:              true,
-						ForceSTSHeader:          true,
-						FrameDeny:               true,
-						CustomFrameOptionsValue: "foobar",
-						ContentTypeNosniff:      true,
-						BrowserXSSFilter:        true,
-						CustomBrowserXSSValue:   "foobar",
-						ContentSecurityPolicy:   "foobar",
-						PublicKey:               "foobar",
-						ReferrerPolicy:          "foobar",
-						FeaturePolicy:           String("foobar"),
-						PermissionsPolicy:       "foobar",
-						IsDevelopment:           true,
+						SSLForceHost:                    Bool(true),
+						STSSeconds:                      42,
+						STSIncludeSubdomains:            true,
+						STSPreload:                      true,
+						ForceSTSHeader:                  true,
+						FrameDeny:                       true,
+						CustomFrameOptionsValue:         "foobar",
+						ContentTypeNosniff:              true,
+						BrowserXSSFilter:                true,
+						CustomBrowserXSSValue:           "foobar",
+						ContentSecurityPolicy:           "foobar",
+						ContentSecurityPolicyReportOnly: "foobar",
+						PublicKey:                       "foobar",
+						ReferrerPolicy:                  "foobar",
+						FeaturePolicy:                   String("foobar"),
+						PermissionsPolicy:               "foobar",
+						IsDevelopment:                   true,
 					},
 				},
 				"Middleware9": {
@@ -1299,6 +1302,7 @@ func TestEncodeConfiguration(t *testing.T) {
 		"traefik.HTTP.Middlewares.Middleware8.Headers.AllowedHosts":                                "foobar, fiibar",
 		"traefik.HTTP.Middlewares.Middleware8.Headers.BrowserXSSFilter":                            "true",
 		"traefik.HTTP.Middlewares.Middleware8.Headers.ContentSecurityPolicy":                       "foobar",
+		"traefik.HTTP.Middlewares.Middleware8.Headers.ContentSecurityPolicyReportOnly":             "foobar",
 		"traefik.HTTP.Middlewares.Middleware8.Headers.ContentTypeNosniff":                          "true",
 		"traefik.HTTP.Middlewares.Middleware8.Headers.CustomBrowserXSSValue":                       "foobar",
 		"traefik.HTTP.Middlewares.Middleware8.Headers.CustomFrameOptionsValue":                     "foobar",
diff --git a/pkg/provider/kv/kv_test.go b/pkg/provider/kv/kv_test.go
index 4388710313..679bb258ff 100644
--- a/pkg/provider/kv/kv_test.go
+++ b/pkg/provider/kv/kv_test.go
@@ -139,6 +139,7 @@ func Test_buildConfiguration(t *testing.T) {
 		"traefik/http/middlewares/Middleware09/headers/accessControlExposeHeaders/0":                 "foobar",
 		"traefik/http/middlewares/Middleware09/headers/accessControlExposeHeaders/1":                 "foobar",
 		"traefik/http/middlewares/Middleware09/headers/contentSecurityPolicy":                        "foobar",
+		"traefik/http/middlewares/Middleware09/headers/contentSecurityPolicyReportOnly":              "foobar",
 		"traefik/http/middlewares/Middleware09/headers/publicKey":                                    "foobar",
 		"traefik/http/middlewares/Middleware09/headers/customRequestHeaders/name0":                   "foobar",
 		"traefik/http/middlewares/Middleware09/headers/customRequestHeaders/name1":                   "foobar",
@@ -601,22 +602,23 @@ func Test_buildConfiguration(t *testing.T) {
 							"name1": "foobar",
 							"name0": "foobar",
 						},
-						SSLForceHost:            Bool(true),
-						STSSeconds:              42,
-						STSIncludeSubdomains:    true,
-						STSPreload:              true,
-						ForceSTSHeader:          true,
-						FrameDeny:               true,
-						CustomFrameOptionsValue: "foobar",
-						ContentTypeNosniff:      true,
-						BrowserXSSFilter:        true,
-						CustomBrowserXSSValue:   "foobar",
-						ContentSecurityPolicy:   "foobar",
-						PublicKey:               "foobar",
-						ReferrerPolicy:          "foobar",
-						FeaturePolicy:           String("foobar"),
-						PermissionsPolicy:       "foobar",
-						IsDevelopment:           true,
+						SSLForceHost:                    Bool(true),
+						STSSeconds:                      42,
+						STSIncludeSubdomains:            true,
+						STSPreload:                      true,
+						ForceSTSHeader:                  true,
+						FrameDeny:                       true,
+						CustomFrameOptionsValue:         "foobar",
+						ContentTypeNosniff:              true,
+						BrowserXSSFilter:                true,
+						CustomBrowserXSSValue:           "foobar",
+						ContentSecurityPolicy:           "foobar",
+						ContentSecurityPolicyReportOnly: "foobar",
+						PublicKey:                       "foobar",
+						ReferrerPolicy:                  "foobar",
+						FeaturePolicy:                   String("foobar"),
+						PermissionsPolicy:               "foobar",
+						IsDevelopment:                   true,
 					},
 				},
 				"Middleware17": {
diff --git a/pkg/redactor/redactor_config_test.go b/pkg/redactor/redactor_config_test.go
index ed498721f1..ee653b28af 100644
--- a/pkg/redactor/redactor_config_test.go
+++ b/pkg/redactor/redactor_config_test.go
@@ -214,6 +214,7 @@ func init() {
 					BrowserXSSFilter:                  true,
 					CustomBrowserXSSValue:             "foo",
 					ContentSecurityPolicy:             "foo",
+					ContentSecurityPolicyReportOnly:   "foo",
 					PublicKey:                         "foo",
 					ReferrerPolicy:                    "foo",
 					PermissionsPolicy:                 "foo",
diff --git a/pkg/redactor/testdata/anonymized-dynamic-config.json b/pkg/redactor/testdata/anonymized-dynamic-config.json
index b4afd7aa1b..ed3c07c860 100644
--- a/pkg/redactor/testdata/anonymized-dynamic-config.json
+++ b/pkg/redactor/testdata/anonymized-dynamic-config.json
@@ -170,6 +170,7 @@
           "browserXssFilter": true,
           "customBrowserXSSValue": "xxxx",
           "contentSecurityPolicy": "xxxx",
+          "contentSecurityPolicyReportOnly": "xxxx",
           "publicKey": "xxxx",
           "referrerPolicy": "foo",
           "permissionsPolicy": "foo",
diff --git a/pkg/redactor/testdata/secured-dynamic-config.json b/pkg/redactor/testdata/secured-dynamic-config.json
index 8ff3d07895..75c70ae25e 100644
--- a/pkg/redactor/testdata/secured-dynamic-config.json
+++ b/pkg/redactor/testdata/secured-dynamic-config.json
@@ -173,6 +173,7 @@
           "browserXssFilter": true,
           "customBrowserXSSValue": "foo",
           "contentSecurityPolicy": "foo",
+          "contentSecurityPolicyReportOnly": "foo",
           "publicKey": "foo",
           "referrerPolicy": "foo",
           "permissionsPolicy": "foo",