diff --git a/core/src/main/java/google/registry/ui/server/CspFilter.java b/core/src/main/java/google/registry/ui/server/CspFilter.java
new file mode 100644
index 00000000000..8d85d87330a
--- /dev/null
+++ b/core/src/main/java/google/registry/ui/server/CspFilter.java
@@ -0,0 +1,41 @@
+// Copyright 2026 The Nomulus Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package google.registry.ui.server;
+
+import jakarta.servlet.Filter;
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.ServletRequest;
+import jakarta.servlet.ServletResponse;
+import jakarta.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/** Filter to inject security headers, including CSP, for defense-in-depth. */
+public class CspFilter implements Filter {
+
+ private static final String CSP_POLICY =
+ "default-src 'self'; object-src 'none'; base-uri 'self';";
+
+ @Override
+ public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
+ throws IOException, ServletException {
+ if (response instanceof HttpServletResponse httpResponse) {
+ httpResponse.setHeader("Content-Security-Policy", CSP_POLICY);
+ httpResponse.setHeader("X-Content-Type-Options", "nosniff");
+ httpResponse.setHeader("X-Frame-Options", "DENY");
+ }
+ chain.doFilter(request, response);
+ }
+}
diff --git a/core/src/test/java/google/registry/ui/server/CspFilterTest.java b/core/src/test/java/google/registry/ui/server/CspFilterTest.java
new file mode 100644
index 00000000000..47cdfbfcad6
--- /dev/null
+++ b/core/src/test/java/google/registry/ui/server/CspFilterTest.java
@@ -0,0 +1,44 @@
+// Copyright 2026 The Nomulus Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package google.registry.ui.server;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.junit.jupiter.api.Test;
+
+/** Unit tests for {@link CspFilter}. */
+class CspFilterTest {
+
+ @Test
+ void testDoFilter_setsHeaders() throws Exception {
+ CspFilter filter = new CspFilter();
+ HttpServletRequest request = mock(HttpServletRequest.class);
+ HttpServletResponse response = mock(HttpServletResponse.class);
+ FilterChain chain = mock(FilterChain.class);
+
+ filter.doFilter(request, response, chain);
+
+ verify(response)
+ .setHeader(
+ "Content-Security-Policy", "default-src 'self'; object-src 'none'; base-uri 'self';");
+ verify(response).setHeader("X-Content-Type-Options", "nosniff");
+ verify(response).setHeader("X-Frame-Options", "DENY");
+ verify(chain).doFilter(request, response);
+ }
+}
diff --git a/jetty/src/main/jetty-base/start.d/ee10-servlets.ini b/jetty/src/main/jetty-base/start.d/ee10-servlets.ini
new file mode 100644
index 00000000000..6d834c99a57
--- /dev/null
+++ b/jetty/src/main/jetty-base/start.d/ee10-servlets.ini
@@ -0,0 +1,4 @@
+# ---------------------------------------
+# Module: ee10-servlets
+# ---------------------------------------
+--module=ee10-servlets
diff --git a/jetty/src/main/jetty-base/webapps/console/WEB-INF/web.xml b/jetty/src/main/jetty-base/webapps/console/WEB-INF/web.xml
index aaa3eed9483..d09e08dac22 100644
--- a/jetty/src/main/jetty-base/webapps/console/WEB-INF/web.xml
+++ b/jetty/src/main/jetty-base/webapps/console/WEB-INF/web.xml
@@ -3,6 +3,22 @@
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
version="5.0">
+
+ CspHeaderFilter
+ org.eclipse.jetty.ee10.servlets.HeaderFilter
+
+ headerConfig
+
+ set Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; object-src 'none'; base-uri 'self';
+
+
+
+
+
+ CspHeaderFilter
+ /*
+
+
default-no-cache
org.eclipse.jetty.ee10.servlet.DefaultServlet
diff --git a/jetty/src/main/webapp/WEB-INF/web.xml b/jetty/src/main/webapp/WEB-INF/web.xml
index e6ba35badcb..af4bfe6ead5 100644
--- a/jetty/src/main/webapp/WEB-INF/web.xml
+++ b/jetty/src/main/webapp/WEB-INF/web.xml
@@ -2,6 +2,17 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
version="6.0">
+
+
+ CspFilter
+ google.registry.ui.server.CspFilter
+
+
+
+ CspFilter
+ /*
+
+