From f466e132f6c9ed0961ae6bbbfe04442abdca14bf Mon Sep 17 00:00:00 2001 From: Gus Brodman Date: Tue, 30 Jun 2026 16:02:09 -0400 Subject: [PATCH] Implement CSP for the registrar console Implement a hybrid Content Security Policy (CSP) for the Registrar Console to protect against XSS - The CspFilter injects the proper headers on the Java backend endpoints - Uinsg Jetty's HeaderFilter to inject the header for statically-served frontend assets We need to add the ee10-servlets.ini file for Jetty to have acess to the HeaderFilter class --- .../google/registry/ui/server/CspFilter.java | 41 +++++++++++++++++ .../registry/ui/server/CspFilterTest.java | 44 +++++++++++++++++++ .../main/jetty-base/start.d/ee10-servlets.ini | 4 ++ .../webapps/console/WEB-INF/web.xml | 16 +++++++ jetty/src/main/webapp/WEB-INF/web.xml | 11 +++++ 5 files changed, 116 insertions(+) create mode 100644 core/src/main/java/google/registry/ui/server/CspFilter.java create mode 100644 core/src/test/java/google/registry/ui/server/CspFilterTest.java create mode 100644 jetty/src/main/jetty-base/start.d/ee10-servlets.ini 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 + /* + +