CVE-2024-2383: Improper Restriction of Rendered UI Layers or Frames (CWE-1021)
value: A clickjacking vulnerability exists in zenml-io/zenml versions up to and including 0.55.5 due to the application's failure to set appropriate X-Frame-Options or Content-Security-Policy HTTP headers. This vulnerability allows an attacker to embed the application UI within an iframe on a malicious page, potentially leading to unauthorized actions by tricking users into interacting with the interface under the attacker's control. The issue was addressed in version 0.56.3.
CWE description: The web application does not restrict or incorrectly restricts frame objects or UI layers that belong to another application or domain, which can lead to user confusion about which interface the user is interacting with.
Published: 2024-06-06T19:15Z
Repository: https://github.com/zenml-io/zenml
Language: Python
Files changed: docker/base.Dockerfile, docker/zenml-server-dev.Dockerfile, docker/zenml-server-hf-spaces.Dockerfile, docs/book/deploying-zenml/zenml-self-hosted/deploy-with-docker.md, pyproject.toml, src/zenml/config/server_config.py, src/zenml/constants.py, src/zenml/zen_server/deploy/docker/docker_zen_server.py, src/zenml/zen_server/deploy/helm/templates/_environment.tpl, src/zenml/zen_server/deploy/helm/values.yaml, src/zenml/zen_server/deploy/local/local_zen_server.py, src/zenml/zen_server/utils.py, src/zenml/zen_server/zen_server_api.py
Commit date: 2024-04-06 17:32:36 +0200
Commit message: Add security headers to the ZenML server (#2583)

* Add security headers to the ZenML server

* Make secure servers configurable and add sane default values

* Add secure headers options to helm chart and documentation

* Disable uvicorn's server headers

---------

Co-authored-by: Alex Strick van Linschoten <strickvl@users.noreply.github.com>
Diff: diff --git a/docker/base.Dockerfile b/docker/base.Dockerfile
index da53fbe8c6..6fdd6acaf7 100644
--- a/docker/base.Dockerfile
+++ b/docker/base.Dockerfile
@@ -35,7 +35,7 @@ RUN groupadd --gid $USER_GID $USERNAME \
     && useradd --uid $USER_UID --gid $USER_GID -m $USERNAME
 
 RUN mkdir -p /zenml/.zenconfig/local_stores/default_zen_store && chown -R $USER_UID:$USER_GID /zenml
 ENV PATH="$PATH:/home/$USERNAME/.local/bin"
 
-ENTRYPOINT ["uvicorn", "zenml.zen_server.zen_server_api:app",  "--log-level", "debug"]
+ENTRYPOINT ["uvicorn", "zenml.zen_server.zen_server_api:app", "--log-level", "debug", "--no-server-header"]
 CMD ["--port", "8080", "--host",  "0.0.0.0"]
diff --git a/docker/zenml-server-dev.Dockerfile b/docker/zenml-server-dev.Dockerfile
index 2627285385..37193f8ecd 100644
--- a/docker/zenml-server-dev.Dockerfile
+++ b/docker/zenml-server-dev.Dockerfile
@@ -44,7 +44,7 @@ ENV PATH="$PATH:/home/$USERNAME/.local/bin"
 
 ENV ZENML_CONFIG_PATH=/zenml/.zenconfig \
     ZENML_DEBUG=true \
     ZENML_ANALYTICS_OPT_IN=false
 
-ENTRYPOINT ["uvicorn", "zenml.zen_server.zen_server_api:app",  "--log-level", "debug"]
+ENTRYPOINT ["uvicorn", "zenml.zen_server.zen_server_api:app", "--log-level", "debug", "--no-server-header"]
 CMD ["--port", "8080", "--host",  "0.0.0.0"]
diff --git a/docker/zenml-server-hf-spaces.Dockerfile b/docker/zenml-server-hf-spaces.Dockerfile
index 305589948b..886e8aa688 100644
--- a/docker/zenml-server-hf-spaces.Dockerfile
+++ b/docker/zenml-server-hf-spaces.Dockerfile
@@ -57,7 +57,7 @@ ENV ZENML_SERVER_DEPLOYMENT_TYPE="hf_spaces"
 # ENV ZENML_SECRETS_STORE_VAULT_ADDR=""
 # ENV ZENML_SECRETS_STORE_VAULT_TOKEN=""
 # ENV ZENML_SECRETS_STORE_VAULT_NAMESPACE=""
 # ENV ZENML_SECRETS_STORE_MAX_VERSIONS=""
 
-ENTRYPOINT ["uvicorn", "zenml.zen_server.zen_server_api:app",  "--log-level", "debug"]
+ENTRYPOINT ["uvicorn", "zenml.zen_server.zen_server_api:app", "--log-level", "debug", "--no-server-header"]
 CMD ["--port", "8080", "--host",  "0.0.0.0"]
diff --git a/docs/book/deploying-zenml/zenml-self-hosted/deploy-with-docker.md b/docs/book/deploying-zenml/zenml-self-hosted/deploy-with-docker.md
index d9103dfecf..04b471fe62 100644
--- a/docs/book/deploying-zenml/zenml-self-hosted/deploy-with-docker.md
+++ b/docs/book/deploying-zenml/zenml-self-hosted/deploy-with-docker.md
@@ -246,10 +246,28 @@ These configuration options are not required for most use cases, but can be usef
 
     ```shell
     openssl rand -hex 32
     ```
 
+The environment variables starting with **ZENML\_SERVER\_SECURE*\_HEADERS\_** can be used to enable, disable or set custom values for security headers in the ZenML server's HTTP responses. The following values can be set for any of the supported secure headers configuration options:
+
+* `enabled`, `on`, `true` or `yes` - enables the secure header with the default value.
+* `disabled`, `off`, `false`, `none` or `no` - disables the secure header entirely, so that it is not set in the ZenML server's HTTP responses.
+* any other value - sets the secure header to the specified value.  
+
+The following secure headers environment variables are supported:
+
+* **ZENML\_SERVER\_SECURE*\_HEADERS\_SERVER**: The `Server` HTTP header value used to identify the server. The default value is the ZenML server ID.
+* **ZENML\_SERVER\_SECURE\_HEADERS\_HSTS**: The `Strict-Transport-Security` HTTP header value. The default value is `max-age=63072000; includeSubDomains`.
+* **ZENML\_SERVER\_SECURE\_HEADERS\_XFO**: The `X-Frame-Options` HTTP header value. The default value is `SAMEORIGIN`.
+* **ZENML\_SERVER\_SECURE\_HEADERS\_XXP**: The `X-XSS-Protection` HTTP header value. The default value is `0`. NOTE: this header is deprecated and should not be customized anymore. The `Content-Security-Policy` header should be used instead.
+* **ZENML\_SERVER\_SECURE\_HEADERS\_CONTENT**: The `X-Content-Type-Options` HTTP header value. The default value is `nosniff`.
+* **ZENML\_SERVER\_SECURE\_HEADERS\_CSP**: The `Content-Security-Policy` HTTP header value. This is by default set to a strict CSP policy that only allows content from the origins required by the ZenML dashboard. NOTE: customizing this header is discouraged, as it may cause the ZenML dashboard to malfunction.
+* **ZENML\_SERVER\_SECURE\_HEADERS\_REFERRER**: The `Referrer-Policy` HTTP header value. The default value is `no-referrer-when-downgrade`.
+* **ZENML\_SERVER\_SECURE\_HEADERS\_CACHE**: The `Cache-Control` HTTP header value. The default value is `no-store, no-cache, must-revalidate`.
+* **ZENML\_SERVER\_SECURE\_HEADERS\_PERMISSIONS**: The `Permissions-Policy` HTTP header value. The default value is `accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()`.
+
 ## Run the ZenML server with Docker
 
 As previously mentioned, the ZenML server container image uses sensible defaults for most configuration options. This means that you can simply run the container with Docker without any additional configuration and it will work out of the box for most use cases:
 
 ```bash
diff --git a/pyproject.toml b/pyproject.toml
index 3e3b5018e9..6afd590a49 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -71,10 +71,11 @@ python-multipart = { version = "~0.0.9", optional = true }
 pyjwt = { extras = ["crypto"], version = "2.7.*", optional = true }
 fastapi-utils = { version = "~0.2.1", optional = true }
 orjson = { version = "~3.10.0", optional = true }
 Jinja2 = { version = "*", optional = true }
 ipinfo = { version = ">=4.4.3", optional = true }
+secure = { version = "~0.3.0", optional = true }
 
 # Optional dependencies for project templates
 copier = { version = ">=8.1.0", optional = true }
 pyyaml-include = {version = "<2.0", optional = true}
 jinja2-time = { version = "^0.2.0", optional = true }
@@ -179,10 +180,11 @@ server = [
     "pyjwt",
     "fastapi-utils",
     "orjson",
     "Jinja2",
     "ipinfo",
+    "secure",
 ]
 templates = ["copier", "jinja2-time", "ruff", "pyyaml-include"]
 terraform = ["python-terraform"]
 secrets-aws = ["boto3"]
 secrets-gcp = ["google-cloud-secret-manager"]
diff --git a/src/zenml/config/server_config.py b/src/zenml/config/server_config.py
index 2fef2890e0..7a3224e9b5 100644
--- a/src/zenml/config/server_config.py
+++ b/src/zenml/config/server_config.py
@@ -14,11 +14,11 @@
 """Functionality to support ZenML GlobalConfiguration."""
 
 import json
 import os
 from secrets import token_hex
-from typing import Any, Dict, List, Optional
+from typing import Any, Dict, List, Optional, Union
 from uuid import UUID
 
 from pydantic import BaseModel, Field, SecretStr, root_validator
 
 from zenml.constants import (
@@ -28,10 +28,18 @@ from zenml.constants import (
     DEFAULT_ZENML_SERVER_DEVICE_AUTH_TIMEOUT,
     DEFAULT_ZENML_SERVER_LOGIN_RATE_LIMIT_DAY,
     DEFAULT_ZENML_SERVER_LOGIN_RATE_LIMIT_MINUTE,
     DEFAULT_ZENML_SERVER_MAX_DEVICE_AUTH_ATTEMPTS,
     DEFAULT_ZENML_SERVER_PIPELINE_RUN_AUTH_WINDOW,
+    DEFAULT_ZENML_SERVER_SECURE_HEADERS_CACHE,
+    DEFAULT_ZENML_SERVER_SECURE_HEADERS_CONTENT,
+    DEFAULT_ZENML_SERVER_SECURE_HEADERS_CSP,
+    DEFAULT_ZENML_SERVER_SECURE_HEADERS_HSTS,
+    DEFAULT_ZENML_SERVER_SECURE_HEADERS_PERMISSIONS,
+    DEFAULT_ZENML_SERVER_SECURE_HEADERS_REFERRER,
+    DEFAULT_ZENML_SERVER_SECURE_HEADERS_XFO,
+    DEFAULT_ZENML_SERVER_SECURE_HEADERS_XXP,
     ENV_ZENML_SERVER_PREFIX,
 )
 from zenml.enums import AuthScheme
 from zenml.logger import get_logger
 from zenml.models import ServerDeploymentType
@@ -126,10 +134,80 @@ class ServerConfiguration(BaseModel):
         pipeline_run_auth_window: The default time window in minutes for which
             a pipeline run action is allowed to authenticate with the ZenML
             server.
         login_rate_limit_minute: The number of login attempts allowed per minute.
         login_rate_limit_day: The number of login attempts allowed per day.
+        secure_headers_server: Custom value to be set in the `Server` HTTP
+            header to identify the server. If not specified, or if set to one of
+            the reserved values `enabled`, `yes`, `true`, `on`, the `Server`
+            header will be set to the default value (ZenML server ID). If set to
+            one of the reserved values `disabled`, `no`, `none`, `false`, `off`
+            or to an empty string, the `Server` header will not be included in
+            responses.
+        secure_headers_hsts: The server header value to be set in the HTTP
+            header `Strict-Transport-Security`. If not specified, or if set to
+            one of the reserved values `enabled`, `yes`, `true`, `on`, the
+            `Strict-Transport-Security` header will be set to the default value
+            (`max-age=63072000; includeSubdomains`). If set to one of
+            the reserved values `disabled`, `no`, `none`, `false`, `off` or to
+            an empty string, the `Strict-Transport-Security` header will not be
+            included in responses.
+        secure_headers_xfo: The server header value to be set in the HTTP
+            header `X-Frame-Options`. If not specified, or if set to one of the
+            reserved values `enabled`, `yes`, `true`, `on`, the `X-Frame-Options`
+            header will be set to the default value (`SAMEORIGIN`). If set to
+            one of the reserved values `disabled`, `no`, `none`, `false`, `off`
+            or to an empty string, the `X-Frame-Options` header will not be
+            included in responses.
+        secure_headers_xxp: The server header value to be set in the HTTP
+            header `X-XSS-Protection`. If not specified, or if set to one of the
+            reserved values `enabled`, `yes`, `true`, `on`, the `X-XSS-Protection`
+            header will be set to the default value (`0`). If set to one of the
+            reserved values `disabled`, `no`, `none`, `false`, `off` or
+            to an empty string, the `X-XSS-Protection` header will not be
+            included in responses. NOTE: this header is deprecated and should
+            always be set to `0`. The `Content-Security-Policy` header should be
+            used instead.
+        secure_headers_content: The server header value to be set in the HTTP
+            header `X-Content-Type-Options`. If not specified, or if set to one
+            of the reserved values `enabled`, `yes`, `true`, `on`, the
+            `X-Content-Type-Options` header will be set to the default value
+            (`nosniff`). If set to one of the reserved values `disabled`, `no`,
+            `none`, `false`, `off` or to an empty string, the
+            `X-Content-Type-Options` header will not be included in responses.
+        secure_headers_csp: The server header value to be set in the HTTP
+            header `Content-Security-Policy`. If not specified, or if set to one
+            of the reserved values `enabled`, `yes`, `true`, `on`, the
+            `Content-Security-Policy` header will be set to a default value
+            that is compatible with the ZenML dashboard. If set to one of the
+            reserved values `disabled`, `no`, `none`, `false`, `off` or to an
+            empty string, the `Content-Security-Policy` header will not be
+            included in responses.
+        secure_headers_referrer: The server header value to be set in the HTTP
+            header `Referrer-Policy`. If not specified, or if set to one of the
+            reserved values `enabled`, `yes`, `true`, `on`, the `Referrer-Policy`
+            header will be set to the default value
+            (`no-referrer-when-downgrade`). If set to one of the reserved values
+            `disabled`, `no`, `none`, `false`, `off` or to an empty string, the
+            `Referrer-Policy` header will not be included in responses.
+        secure_headers_cache: The server header value to be set in the HTTP
+            header `Cache-Control`. If not specified, or if set to one of the
+            reserved values `enabled`, `yes`, `true`, `on`, the `Cache-Control`
+            header will be set to the default value
+            (`no-store, no-cache, must-revalidate`). If set to one of the
+            reserved values `disabled`, `no`, `none`, `false`, `off` or to an
+            empty string, the `Cache-Control` header will not be included in
+            responses.
+        secure_headers_permissions: The server header value to be set in the
+            HTTP header `Permissions-Policy`. If not specified, or if set to one
+            of the reserved values `enabled`, `yes`, `true`, `on`, the
+            `Permissions-Policy` header will be set to the default value
+            (`accelerometer=(), camera=(), geolocation=(), gyroscope=(),
+              magnetometer=(), microphone=(), payment=(), usb=()`). If set to
+            one of the reserved values `disabled`, `no`, `none`, `false`, `off`
+            or to an empty string, the `Permissions-Policy` header will not be
+            included in responses.
     """
 
     deployment_type: ServerDeploymentType = ServerDeploymentType.OTHER
     base_url: str = ""
     root_url_path: str = ""
@@ -169,10 +247,36 @@ class ServerConfiguration(BaseModel):
 
     rate_limit_enabled: bool = False
     login_rate_limit_minute: int = DEFAULT_ZENML_SERVER_LOGIN_RATE_LIMIT_MINUTE
     login_rate_limit_day: int = DEFAULT_ZENML_SERVER_LOGIN_RATE_LIMIT_DAY
 
+    secure_headers_server: Union[bool, str] = True
+    secure_headers_hsts: Union[bool, str] = (
+        DEFAULT_ZENML_SERVER_SECURE_HEADERS_HSTS
+    )
+    secure_headers_xfo: Union[bool, str] = (
+        DEFAULT_ZENML_SERVER_SECURE_HEADERS_XFO
+    )
+    secure_headers_xxp: Union[bool, str] = (
+        DEFAULT_ZENML_SERVER_SECURE_HEADERS_XXP
+    )
+    secure_headers_content: Union[bool, str] = (
+        DEFAULT_ZENML_SERVER_SECURE_HEADERS_CONTENT
+    )
+    secure_headers_csp: Union[bool, str] = (
+        DEFAULT_ZENML_SERVER_SECURE_HEADERS_CSP
+    )
+    secure_headers_referrer: Union[bool, str] = (
+        DEFAULT_ZENML_SERVER_SECURE_HEADERS_REFERRER
+    )
+    secure_headers_cache: Union[bool, str] = (
+        DEFAULT_ZENML_SERVER_SECURE_HEADERS_CACHE
+    )
+    secure_headers_permissions: Union[bool, str] = (
+        DEFAULT_ZENML_SERVER_SECURE_HEADERS_PERMISSIONS
+    )
+
     _deployment_id: Optional[UUID] = None
 
     @root_validator(pre=True)
     def _validate_config(cls, values: Dict[str, Any]) -> Dict[str, Any]:
         """Validate the server configuration.
@@ -219,10 +323,20 @@ class ServerConfiguration(BaseModel):
             except json.JSONDecodeError as e:
                 raise ValueError(
                     f"The server metadata is not a valid JSON string: {e}"
                 )
 
+        # if one of the secure headers options is set to a boolean value, set
+        # the corresponding value
+        for k, v in values.copy().items():
+            if k.startswith("secure_headers_") and isinstance(v, str):
+                if v.lower() in ["disabled", "no", "none", "false", "off", ""]:
+                    values[k] = False
+                if v.lower() in ["enabled", "yes", "true", "on"]:
+                    # Revert to the default value if the header is enabled
+                    del values[k]
+
         return values
 
     @property
     def deployment_id(self) -> UUID:
         """Get the ZenML server deployment ID.
diff --git a/src/zenml/constants.py b/src/zenml/constants.py
index febf57ac3c..21f6cfe9ad 100644
--- a/src/zenml/constants.py
+++ b/src/zenml/constants.py
@@ -234,10 +234,38 @@ DEFAULT_HTTP_TIMEOUT = 30
 ZENML_API_KEY_PREFIX = "ZENKEY_"
 DEFAULT_ZENML_SERVER_PIPELINE_RUN_AUTH_WINDOW = 60 * 48  # 48 hours
 DEFAULT_ZENML_SERVER_LOGIN_RATE_LIMIT_MINUTE = 5
 DEFAULT_ZENML_SERVER_LOGIN_RATE_LIMIT_DAY = 1000
 
+DEFAULT_ZENML_SERVER_SECURE_HEADERS_HSTS = (
+    "max-age=63072000; includeSubdomains"
+)
+DEFAULT_ZENML_SERVER_SECURE_HEADERS_XFO = "SAMEORIGIN"
+DEFAULT_ZENML_SERVER_SECURE_HEADERS_XXP = "0"
+DEFAULT_ZENML_SERVER_SECURE_HEADERS_CONTENT = "nosniff"
+DEFAULT_ZENML_SERVER_SECURE_HEADERS_CSP = (
+    "default-src 'none'; "
+    "script-src 'self' 'unsafe-inline' 'unsafe-eval'; "
+    "connect-src 'self' https://sdkdocs.zenml.io https://hubapi.zenml.io; "
+    "img-src 'self' data: https://public-flavor-logos.s3.eu-central-1.amazonaws.com; "
+    "style-src 'self' 'unsafe-inline'; "
+    "base-uri 'self'; "
+    "form-action 'self'; "
+    "font-src 'self';"
+    "frame-src https://zenml.hellonext.co https://sdkdocs.zenml.io "
+)
+DEFAULT_ZENML_SERVER_SECURE_HEADERS_REFERRER = "no-referrer-when-downgrade"
+DEFAULT_ZENML_SERVER_SECURE_HEADERS_CACHE = (
+    "no-store, no-cache, must-revalidate"
+)
+DEFAULT_ZENML_SERVER_SECURE_HEADERS_PERMISSIONS = (
+    "accelerometer=(), autoplay=(), camera=(), encrypted-media=(), "
+    "geolocation=(), gyroscope=(), magnetometer=(), microphone=(), midi=(), "
+    "payment=(), sync-xhr=(), usb=()"
+)
+DEFAULT_ZENML_SERVER_SECURE_HEADERS_REPORT_TO = "default"
+
 # Configurations to decide which resources report their usage and check for
 # entitlement in the case of a cloud deployment. Expected Format is this:
 # ENV_ZENML_REPORTABLE_RESOURCES='["Foo", "bar"]'
 REPORTABLE_RESOURCES: List[str] = handle_json_env_var(
     ENV_ZENML_SERVER_REPORTABLE_RESOURCES,
diff --git a/src/zenml/zen_server/deploy/docker/docker_zen_server.py b/src/zenml/zen_server/deploy/docker/docker_zen_server.py
index 58c0216583..fdaad7c470 100644
--- a/src/zenml/zen_server/deploy/docker/docker_zen_server.py
+++ b/src/zenml/zen_server/deploy/docker/docker_zen_server.py
@@ -203,8 +203,9 @@ class DockerZenServer(ContainerService):
             uvicorn.run(
                 ZEN_SERVER_ENTRYPOINT,
                 host="0.0.0.0",  # nosec
                 port=self.endpoint.config.port or 8000,
                 log_level="info",
+                server_header=False,
             )
         except KeyboardInterrupt:
             logger.info("ZenML Server stopped. Resuming normal execution.")
diff --git a/src/zenml/zen_server/deploy/helm/templates/_environment.tpl b/src/zenml/zen_server/deploy/helm/templates/_environment.tpl
index 27a870e5fd..956e3621c2 100644
--- a/src/zenml/zen_server/deploy/helm/templates/_environment.tpl
+++ b/src/zenml/zen_server/deploy/helm/templates/_environment.tpl
@@ -86,10 +86,13 @@ root_url_path: {{ .ZenML.rootUrlPath | quote }}
 base_url: {{ .ZenML.baseURL | quote }}
 {{- end }}
 {{- if .ZenML.auth.rbacImplementationSource }}
 rbac_implementation_source: {{ .ZenML.auth.rbacImplementationSource | quote }}
 {{- end }}
+{{- range $key, $value := .ZenML.secure_headers }}
+secure_headers_{{ $key }}: {{ $value | quote }}
+{{- end }}
 {{- end }}
 
 
 {{/*
 Server configuration environment variables (non-secret values).
diff --git a/src/zenml/zen_server/deploy/helm/values.yaml b/src/zenml/zen_server/deploy/helm/values.yaml
index 0098ab64a9..f43cb6a104 100644
--- a/src/zenml/zen_server/deploy/helm/values.yaml
+++ b/src/zenml/zen_server/deploy/helm/values.yaml
@@ -785,10 +785,53 @@ zenml:
       # secrets store implementation expects an `aws_access_key_id` option, you
       # should set the `ZENML_SECRETS_STORE_AWS_ACCESS_KEY_ID` environment
       # variable in the `zenml.secretEnvironment` variable.
       class_path: my.custom.secrets.store.MyCustomSecretsStore
 
+  # The ZenML server's secure headers configuration. This can be used to
+  # enable, disable or set custom values for security headers in the ZenML
+  # server's HTTP responses. The following values can be set for any of the
+  # supported secure headers configuration options:
+  #
+  # - `enabled`, `on`, `true` or `yes` - enables the secure header with the
+  # default value.
+  # - `disabled`, `off`, `false`, `none` or `no` - disables the secure header
+  # entirely, so that it is not set in the ZenML server's HTTP responses.
+  # - any other value - sets the secure header to the specified value.  
+  secure_headers:
+    # The `Server` HTTP header value used to identify the server. The default
+    # value is the ZenML server ID.
+    server: enabled
+    # The `Strict-Transport-Security` HTTP header value. The default value is
+    # `max-age=63072000; includeSubDomains`.
+    hsts: enabled
+    # The `X-Frame-Options` HTTP header value. The default value is `SAMEORIGIN`.
+    xfo: enabled
+    # The `X-XSS-Protection` HTTP header value. The default value is `0`.
+    # NOTE: this header is deprecated and should not be customized anymore. The
+    # `Content-Security-Policy` header should be used instead.
+    xxp: enabled
+    # The `X-Content-Type-Options` HTTP header value. The default value is
+    # `nosniff`.
+    content: enabled
+    # The `Content-Security-Policy` HTTP header value. This is by default set
+    # to a strict CSP policy that only allows content from the origins required
+    # by the ZenML dashboard.
+    # NOTE: customizing this header is discouraged, as it may cause the ZenML
+    # dashboard to malfunction.
+    csp: enabled
+    # The `Referrer-Policy` HTTP header value. The default value is
+    # `no-referrer-when-downgrade`.
+    referrer: enabled
+    # The `Cache-Control` HTTP header value. The default value is
+    # `no-store, no-cache, must-revalidate`.
+    cache: enabled
+    # The `Permissions-Policy` HTTP header value. The default value is
+    # `accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()`.
+    permissions: enabled
+
+
   # Extra environment variables to set in the ZenML server container.
   environment: {}
 
   # Extra environment variables to set in the ZenML server container that
   # should be kept secret. These will be set as Kubernetes secrets and
diff --git a/src/zenml/zen_server/deploy/local/local_zen_server.py b/src/zenml/zen_server/deploy/local/local_zen_server.py
index 8f5041d9de..60bd0ff0bd 100644
--- a/src/zenml/zen_server/deploy/local/local_zen_server.py
+++ b/src/zenml/zen_server/deploy/local/local_zen_server.py
@@ -226,8 +226,9 @@ class LocalZenServer(LocalDaemonService):
             uvicorn.run(
                 ZEN_SERVER_ENTRYPOINT,
                 host=self.endpoint.config.ip_address,
                 port=self.endpoint.config.port or 8000,
                 log_level="info",
+                server_header=False,
             )
         except KeyboardInterrupt:
             logger.info("ZenML Server stopped. Resuming normal execution.")
diff --git a/src/zenml/zen_server/utils.py b/src/zenml/zen_server/utils.py
index 2d6d9af132..2953621a1e 100644
--- a/src/zenml/zen_server/utils.py
+++ b/src/zenml/zen_server/utils.py
@@ -25,10 +25,11 @@ from typing import (
     TypeVar,
     cast,
 )
 from urllib.parse import urlparse
 
+import secure
 from pydantic import BaseModel, ValidationError
 
 from zenml.config.global_config import GlobalConfiguration
 from zenml.config.server_config import ServerConfiguration
 from zenml.constants import (
@@ -57,10 +58,11 @@ logger = get_logger(__name__)
 _zen_store: Optional["SqlZenStore"] = None
 _rbac: Optional[RBACInterface] = None
 _feature_gate: Optional[FeatureGateInterface] = None
 _workload_manager: Optional[WorkloadManagerInterface] = None
 _plugin_flavor_registry: Optional[PluginFlavorRegistry] = None
+_secure_headers: Optional[secure.Secure] = None
 
 
 def zen_store() -> "SqlZenStore":
     """Initialize the ZenML Store.
 
@@ -212,10 +214,108 @@ def initialize_zen_store() -> None:
 
     global _zen_store
     _zen_store = zen_store_
 
 
+def secure_headers() -> secure.Secure:
+    """Return the secure headers component.
+
+    Returns:
+        The secure headers component.
+
+    Raises:
+        RuntimeError: If the secure headers component is not initialized.
+    """
+    global _secure_headers
+    if _secure_headers is None:
+        raise RuntimeError("Secure headers component not initialized")
+    return _secure_headers
+
+
+def initialize_secure_headers() -> None:
+    """Initialize the secure headers component."""
+    global _secure_headers
+
+    config = server_config()
+
+    # For each of the secure headers supported by the `secure` library, we
+    # check if the corresponding configuration is set in the server
+    # configuration:
+    #
+    # - if set to `True`, we use the default value for the header
+    # - if set to a string, we use the string as the value for the header
+    # - if set to `False`, we don't set the header
+
+    server: Optional[secure.Server] = None
+    if config.secure_headers_server:
+        server = secure.Server()
+        if isinstance(config.secure_headers_server, str):
+            server.set(config.secure_headers_server)
+        else:
+            server.set(str(config.deployment_id))
+
+    hsts: Optional[secure.StrictTransportSecurity] = None
+    if config.secure_headers_hsts:
+        hsts = secure.StrictTransportSecurity()
+        if isinstance(config.secure_headers_hsts, str):
+            hsts.set(config.secure_headers_hsts)
+
+    xfo: Optional[secure.XFrameOptions] = None
+    if config.secure_headers_xfo:
+        xfo = secure.XFrameOptions()
+        if isinstance(config.secure_headers_xfo, str):
+            xfo.set(config.secure_headers_xfo)
+
+    xxp: Optional[secure.XXSSProtection] = None
+    if config.secure_headers_xxp:
+        xxp = secure.XXSSProtection()
+        if isinstance(config.secure_headers_xxp, str):
+            xxp.set(config.secure_headers_xxp)
+
+    csp: Optional[secure.ContentSecurityPolicy] = None
+    if config.secure_headers_csp:
+        csp = secure.ContentSecurityPolicy()
+        if isinstance(config.secure_headers_csp, str):
+            csp.set(config.secure_headers_csp)
+
+    content: Optional[secure.XContentTypeOptions] = None
+    if config.secure_headers_content:
+        content = secure.XContentTypeOptions()
+        if isinstance(config.secure_headers_content, str):
+            content.set(config.secure_headers_content)
+
+    referrer: Optional[secure.ReferrerPolicy] = None
+    if config.secure_headers_referrer:
+        referrer = secure.ReferrerPolicy()
+        if isinstance(config.secure_headers_referrer, str):
+            referrer.set(config.secure_headers_referrer)
+
+    cache: Optional[secure.CacheControl] = None
+    if config.secure_headers_cache:
+        cache = secure.CacheControl()
+        if isinstance(config.secure_headers_cache, str):
+            cache.set(config.secure_headers_cache)
+
+    permissions: Optional[secure.PermissionsPolicy] = None
+    if config.secure_headers_permissions:
+        permissions = secure.PermissionsPolicy()
+        if isinstance(config.secure_headers_permissions, str):
+            permissions.value = config.secure_headers_permissions
+
+    _secure_headers = secure.Secure(
+        server=server,
+        hsts=hsts,
+        xfo=xfo,
+        xxp=xxp,
+        csp=csp,
+        content=content,
+        referrer=referrer,
+        cache=cache,
+        permissions=permissions,
+    )
+
+
 _server_config: Optional[ServerConfiguration] = None
 
 
 def server_config() -> ServerConfiguration:
     """Returns the ZenML Server configuration.
diff --git a/src/zenml/zen_server/zen_server_api.py b/src/zenml/zen_server/zen_server_api.py
index b5f01f940d..892dbc37e7 100644
--- a/src/zenml/zen_server/zen_server_api.py
+++ b/src/zenml/zen_server/zen_server_api.py
@@ -64,12 +64,14 @@ from zenml.zen_server.routers import (
 )
 from zenml.zen_server.utils import (
     initialize_feature_gate,
     initialize_plugins,
     initialize_rbac,
+    initialize_secure_headers,
     initialize_workload_manager,
     initialize_zen_store,
+    secure_headers,
     server_config,
 )
 
 DASHBOARD_DIRECTORY = "dashboard"
 
@@ -119,10 +121,26 @@ app.add_middleware(
     allow_methods=["*"],
     allow_headers=["*"],
 )
 
 
+@app.middleware("http")
+async def set_secure_headers(request: Request, call_next: Any) -> Any:
+    """Middleware to set secure headers.
+
+    Args:
+        request: The incoming request.
+        call_next: The next function to be called.
+
+    Returns:
+        The response with secure headers set.
+    """
+    response = await call_next(request)
+    secure_headers().framework.fastapi(response)
+    return response
+
+
 @app.middleware("http")
 async def infer_source_context(request: Request, call_next: Any) -> Any:
     """A middleware to track the source of an event.
 
     It extracts the source context from the header of incoming requests
@@ -161,10 +179,11 @@ def initialize() -> None:
     initialize_zen_store()
     initialize_rbac()
     initialize_feature_gate()
     initialize_workload_manager()
     initialize_plugins()
+    initialize_secure_headers()
 
 
 app.mount(
     "/static",
     StaticFiles(

Vulnerable code: ENTRYPOINT ["uvicorn", "zenml.zen_server.zen_server_api:app",  "--log-level", "debug"]
ENTRYPOINT ["uvicorn", "zenml.zen_server.zen_server_api:app",  "--log-level", "debug"]
ENTRYPOINT ["uvicorn", "zenml.zen_server.zen_server_api:app",  "--log-level", "debug"]
from typing import Any, Dict, List, Optional
Fixed code: ENTRYPOINT ["uvicorn", "zenml.zen_server.zen_server_api:app", "--log-level", "debug", "--no-server-header"]
ENTRYPOINT ["uvicorn", "zenml.zen_server.zen_server_api:app", "--log-level", "debug", "--no-server-header"]
ENTRYPOINT ["uvicorn", "zenml.zen_server.zen_server_api:app", "--log-level", "debug", "--no-server-header"]
The environment variables starting with **ZENML\_SERVER\_SECURE*\_HEADERS\_** can be used to enable, disable or set custom values for security headers in the ZenML server's HTTP responses. The following values can be set for any of the supported secure headers configuration options:

* `enabled`, `on`, `true` or `yes` - enables the secure header with the default value.
* `disabled`, `off`, `false`, `none` or `no` - disables the secure header entirely, so that it is not set in the ZenML server's HTTP responses.
* any other value - sets the secure header to the specified value.  

The following secure headers environment variables are supported:

* **ZENML\_SERVER\_SECURE*\_HEADERS\_SERVER**: The `Server` HTTP header value used to identify the server. The default value is the ZenML server ID.
* **ZENML\_SERVER\_SECURE\_HEADERS\_HSTS**: The `Strict-Transport-Security` HTTP header value. The default value is `max-age=63072000; includeSubDomains`.
* **ZENML\_SERVER\_SECURE\_HEADERS\_XFO**: The `X-Frame-Options` HTTP header value. The default value is `SAMEORIGIN`.
* **ZENML\_SERVER\_SECURE\_HEADERS\_XXP**: The `X-XSS-Protection` HTTP header value. The default value is `0`. NOTE: this header is deprecated and should not be customized anymore. The `Content-Security-Policy` header should be used instead.
* **ZENML\_SERVER\_SECURE\_HEADERS\_CONTENT**: The `X-Content-Type-Options` HTTP header value. The default value is `nosniff`.
* **ZENML\_SERVER\_SECURE\_HEADERS\_CSP**: The `Content-Security-Policy` HTTP header value. This is by default set to a strict CSP policy that only allows content from the origins required by the ZenML dashboard. NOTE: customizing this header is discouraged, as it may cause the ZenML dashboard to malfunction.
* **ZENML\_SERVER\_SECURE\_HEADERS\_REFERRER**: The `Referrer-Policy` HTTP header value. The default value is `no-referrer-when-downgrade`.
* **ZENML\_SERVER\_SECURE\_HEADERS\_CACHE**: The `Cache-Control` HTTP header value. The default value is `no-store, no-cache, must-revalidate`.
* **ZENML\_SERVER\_SECURE\_HEADERS\_PERMISSIONS**: The `Permissions-Policy` HTTP header value. The default value is `accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()`.

secure = { version = "~0.3.0", optional = true }
    "secure",
from typing import Any, Dict, List, Optional, Union
    DEFAULT_ZENML_SERVER_SECURE_HEADERS_CACHE,
    DEFAULT_ZENML_SERVER_SECURE_HEADERS_CONTENT,
    DEFAULT_ZENML_SERVER_SECURE_HEADERS_CSP,
    DEFAULT_ZENML_SERVER_SECURE_HEADERS_HSTS,
    DEFAULT_ZENML_SERVER_SECURE_HEADERS_PERMISSIONS,
    DEFAULT_ZENML_SERVER_SECURE_HEADERS_REFERRER,
    DEFAULT_ZENML_SERVER_SECURE_HEADERS_XFO,
    DEFAULT_ZENML_SERVER_SECURE_HEADERS_XXP,
        secure_headers_server: Custom value to be set in the `Server` HTTP
            header to identify the server. If not specified, or if set to one of
            the reserved values `enabled`, `yes`, `true`, `on`, the `Server`
            header will be set to the default value (ZenML server ID). If set to
            one of the reserved values `disabled`, `no`, `none`, `false`, `off`
            or to an empty string, the `Server` header will not be included in
            responses.
        secure_headers_hsts: The server header value to be set in the HTTP
            header `Strict-Transport-Security`. If not specified, or if set to
            one of the reserved values `enabled`, `yes`, `true`, `on`, the
            `Strict-Transport-Security` header will be set to the default value
            (`max-age=63072000; includeSubdomains`). If set to one of
            the reserved values `disabled`, `no`, `none`, `false`, `off` or to
            an empty string, the `Strict-Transport-Security` header will not be
            included in responses.
        secure_headers_xfo: The server header value to be set in the HTTP
            header `X-Frame-Options`. If not specified, or if set to one of the
            reserved values `enabled`, `yes`, `true`, `on`, the `X-Frame-Options`
            header will be set to the default value (`SAMEORIGIN`). If set to
            one of the reserved values `disabled`, `no`, `none`, `false`, `off`
            or to an empty string, the `X-Frame-Options` header will not be
            included in responses.
        secure_headers_xxp: The server header value to be set in the HTTP
            header `X-XSS-Protection`. If not specified, or if set to one of the
            reserved values `enabled`, `yes`, `true`, `on`, the `X-XSS-Protection`
            header will be set to the default value (`0`). If set to one of the
            reserved values `disabled`, `no`, `none`, `false`, `off` or
            to an empty string, the `X-XSS-Protection` header will not be
            included in responses. NOTE: this header is deprecated and should
            always be set to `0`. The `Content-Security-Policy` header should be
            used instead.
        secure_headers_content: The server header value to be set in the HTTP
            header `X-Content-Type-Options`. If not specified, or if set to one
            of the reserved values `enabled`, `yes`, `true`, `on`, the
            `X-Content-Type-Options` header will be set to the default value
            (`nosniff`). If set to one of the reserved values `disabled`, `no`,
            `none`, `false`, `off` or to an empty string, the
            `X-Content-Type-Options` header will not be included in responses.
        secure_headers_csp: The server header value to be set in the HTTP
            header `Content-Security-Policy`. If not specified, or if set to one
            of the reserved values `enabled`, `yes`, `true`, `on`, the
            `Content-Security-Policy` header will be set to a default value
            that is compatible with the ZenML dashboard. If set to one of the
            reserved values `disabled`, `no`, `none`, `false`, `off` or to an
            empty string, the `Content-Security-Policy` header will not be
            included in responses.
        secure_headers_referrer: The server header value to be set in the HTTP
            header `Referrer-Policy`. If not specified, or if set to one of the
            reserved values `enabled`, `yes`, `true`, `on`, the `Referrer-Policy`
            header will be set to the default value
            (`no-referrer-when-downgrade`). If set to one of the reserved values
            `disabled`, `no`, `none`, `false`, `off` or to an empty string, the
            `Referrer-Policy` header will not be included in responses.
        secure_headers_cache: The server header value to be set in the HTTP
            header `Cache-Control`. If not specified, or if set to one of the
            reserved values `enabled`, `yes`, `true`, `on`, the `Cache-Control`
            header will be set to the default value
            (`no-store, no-cache, must-revalidate`). If set to one of the
            reserved values `disabled`, `no`, `none`, `false`, `off` or to an
            empty string, the `Cache-Control` header will not be included in
            responses.
        secure_headers_permissions: The server header value to be set in the
            HTTP header `Permissions-Policy`. If not specified, or if set to one
            of the reserved values `enabled`, `yes`, `true`, `on`, the
            `Permissions-Policy` header will be set to the default value
            (`accelerometer=(), camera=(), geolocation=(), gyroscope=(),
              magnetometer=(), microphone=(), payment=(), usb=()`). If set to
            one of the reserved values `disabled`, `no`, `none`, `false`, `off`
            or to an empty string, the `Permissions-Policy` header will not be
            included in responses.
    secure_headers_server: Union[bool, str] = True
    secure_headers_hsts: Union[bool, str] = (
        DEFAULT_ZENML_SERVER_SECURE_HEADERS_HSTS
    )
    secure_headers_xfo: Union[bool, str] = (
        DEFAULT_ZENML_SERVER_SECURE_HEADERS_XFO
    )
    secure_headers_xxp: Union[bool, str] = (
        DEFAULT_ZENML_SERVER_SECURE_HEADERS_XXP
    )
    secure_headers_content: Union[bool, str] = (
        DEFAULT_ZENML_SERVER_SECURE_HEADERS_CONTENT
    )
    secure_headers_csp: Union[bool, str] = (
        DEFAULT_ZENML_SERVER_SECURE_HEADERS_CSP
    )
    secure_headers_referrer: Union[bool, str] = (
        DEFAULT_ZENML_SERVER_SECURE_HEADERS_REFERRER
    )
    secure_headers_cache: Union[bool, str] = (
        DEFAULT_ZENML_SERVER_SECURE_HEADERS_CACHE
    )
    secure_headers_permissions: Union[bool, str] = (
        DEFAULT_ZENML_SERVER_SECURE_HEADERS_PERMISSIONS
    )

        # if one of the secure headers options is set to a boolean value, set
        # the corresponding value
        for k, v in values.copy().items():
            if k.startswith("secure_headers_") and isinstance(v, str):
                if v.lower() in ["disabled", "no", "none", "false", "off", ""]:
                    values[k] = False
                if v.lower() in ["enabled", "yes", "true", "on"]:
                    # Revert to the default value if the header is enabled
                    del values[k]

DEFAULT_ZENML_SERVER_SECURE_HEADERS_HSTS = (
    "max-age=63072000; includeSubdomains"
)
DEFAULT_ZENML_SERVER_SECURE_HEADERS_XFO = "SAMEORIGIN"
DEFAULT_ZENML_SERVER_SECURE_HEADERS_XXP = "0"
DEFAULT_ZENML_SERVER_SECURE_HEADERS_CONTENT = "nosniff"
DEFAULT_ZENML_SERVER_SECURE_HEADERS_CSP = (
    "default-src 'none'; "
    "script-src 'self' 'unsafe-inline' 'unsafe-eval'; "
    "connect-src 'self' https://sdkdocs.zenml.io https://hubapi.zenml.io; "
    "img-src 'self' data: https://public-flavor-logos.s3.eu-central-1.amazonaws.com; "
    "style-src 'self' 'unsafe-inline'; "
    "base-uri 'self'; "
    "form-action 'self'; "
    "font-src 'self';"
    "frame-src https://zenml.hellonext.co https://sdkdocs.zenml.io "
)
DEFAULT_ZENML_SERVER_SECURE_HEADERS_REFERRER = "no-referrer-when-downgrade"
DEFAULT_ZENML_SERVER_SECURE_HEADERS_CACHE = (
    "no-store, no-cache, must-revalidate"
)
DEFAULT_ZENML_SERVER_SECURE_HEADERS_PERMISSIONS = (
    "accelerometer=(), autoplay=(), camera=(), encrypted-media=(), "
    "geolocation=(), gyroscope=(), magnetometer=(), microphone=(), midi=(), "
    "payment=(), sync-xhr=(), usb=()"
)
DEFAULT_ZENML_SERVER_SECURE_HEADERS_REPORT_TO = "default"

                server_header=False,
{{- range $key, $value := .ZenML.secure_headers }}
secure_headers_{{ $key }}: {{ $value | quote }}
{{- end }}
  # The ZenML server's secure headers configuration. This can be used to
  # enable, disable or set custom values for security headers in the ZenML
  # server's HTTP responses. The following values can be set for any of the
  # supported secure headers configuration options:
  #
  # - `enabled`, `on`, `true` or `yes` - enables the secure header with the
  # default value.
  # - `disabled`, `off`, `false`, `none` or `no` - disables the secure header
  # entirely, so that it is not set in the ZenML server's HTTP responses.
  # - any other value - sets the secure header to the specified value.  
  secure_headers:
    # The `Server` HTTP header value used to identify the server. The default
    # value is the ZenML server ID.
    server: enabled
    # The `Strict-Transport-Security` HTTP header value. The default value is
    # `max-age=63072000; includeSubDomains`.
    hsts: enabled
    # The `X-Frame-Options` HTTP header value. The default value is `SAMEORIGIN`.
    xfo: enabled
    # The `X-XSS-Protection` HTTP header value. The default value is `0`.
    # NOTE: this header is deprecated and should not be customized anymore. The
    # `Content-Security-Policy` header should be used instead.
    xxp: enabled
    # The `X-Content-Type-Options` HTTP header value. The default value is
    # `nosniff`.
    content: enabled
    # The `Content-Security-Policy` HTTP header value. This is by default set
    # to a strict CSP policy that only allows content from the origins required
    # by the ZenML dashboard.
    # NOTE: customizing this header is discouraged, as it may cause the ZenML
    # dashboard to malfunction.
    csp: enabled
    # The `Referrer-Policy` HTTP header value. The default value is
    # `no-referrer-when-downgrade`.
    referrer: enabled
    # The `Cache-Control` HTTP header value. The default value is
    # `no-store, no-cache, must-revalidate`.
    cache: enabled
    # The `Permissions-Policy` HTTP header value. The default value is
    # `accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()`.
    permissions: enabled


                server_header=False,
import secure
_secure_headers: Optional[secure.Secure] = None
def secure_headers() -> secure.Secure:
    """Return the secure headers component.

    Returns:
        The secure headers component.

    Raises:
        RuntimeError: If the secure headers component is not initialized.
    """
    global _secure_headers
    if _secure_headers is None:
        raise RuntimeError("Secure headers component not initialized")
    return _secure_headers


def initialize_secure_headers() -> None:
    """Initialize the secure headers component."""
    global _secure_headers

    config = server_config()

    # For each of the secure headers supported by the `secure` library, we
    # check if the corresponding configuration is set in the server
    # configuration:
    #
    # - if set to `True`, we use the default value for the header
    # - if set to a string, we use the string as the value for the header
    # - if set to `False`, we don't set the header

    server: Optional[secure.Server] = None
    if config.secure_headers_server:
        server = secure.Server()
        if isinstance(config.secure_headers_server, str):
            server.set(config.secure_headers_server)
        else:
            server.set(str(config.deployment_id))

    hsts: Optional[secure.StrictTransportSecurity] = None
    if config.secure_headers_hsts:
        hsts = secure.StrictTransportSecurity()
        if isinstance(config.secure_headers_hsts, str):
            hsts.set(config.secure_headers_hsts)

    xfo: Optional[secure.XFrameOptions] = None
    if config.secure_headers_xfo:
        xfo = secure.XFrameOptions()
        if isinstance(config.secure_headers_xfo, str):
            xfo.set(config.secure_headers_xfo)

    xxp: Optional[secure.XXSSProtection] = None
    if config.secure_headers_xxp:
        xxp = secure.XXSSProtection()
        if isinstance(config.secure_headers_xxp, str):
            xxp.set(config.secure_headers_xxp)

    csp: Optional[secure.ContentSecurityPolicy] = None
    if config.secure_headers_csp:
        csp = secure.ContentSecurityPolicy()
        if isinstance(config.secure_headers_csp, str):
            csp.set(config.secure_headers_csp)

    content: Optional[secure.XContentTypeOptions] = None
    if config.secure_headers_content:
        content = secure.XContentTypeOptions()
        if isinstance(config.secure_headers_content, str):
            content.set(config.secure_headers_content)

    referrer: Optional[secure.ReferrerPolicy] = None
    if config.secure_headers_referrer:
        referrer = secure.ReferrerPolicy()
        if isinstance(config.secure_headers_referrer, str):
            referrer.set(config.secure_headers_referrer)

    cache: Optional[secure.CacheControl] = None
    if config.secure_headers_cache:
        cache = secure.CacheControl()
        if isinstance(config.secure_headers_cache, str):
            cache.set(config.secure_headers_cache)

    permissions: Optional[secure.PermissionsPolicy] = None
    if config.secure_headers_permissions:
        permissions = secure.PermissionsPolicy()
        if isinstance(config.secure_headers_permissions, str):
            permissions.value = config.secure_headers_permissions

    _secure_headers = secure.Secure(
        server=server,
        hsts=hsts,
        xfo=xfo,
        xxp=xxp,
        csp=csp,
        content=content,
        referrer=referrer,
        cache=cache,
        permissions=permissions,
    )


    initialize_secure_headers,
    secure_headers,
@app.middleware("http")
async def set_secure_headers(request: Request, call_next: Any) -> Any:
    """Middleware to set secure headers.

    Args:
        request: The incoming request.
        call_next: The next function to be called.

    Returns:
        The response with secure headers set.
    """
    response = await call_next(request)
    secure_headers().framework.fastapi(response)
    return response


    initialize_secure_headers()