Skip to content

Commit 653da19

Browse files
authored
Implement CSP for the registrar console (#3129)
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
1 parent 4df734d commit 653da19

5 files changed

Lines changed: 118 additions & 0 deletions

File tree

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Copyright 2026 The Nomulus Authors. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package google.registry.ui.server;
16+
17+
import jakarta.servlet.Filter;
18+
import jakarta.servlet.FilterChain;
19+
import jakarta.servlet.ServletException;
20+
import jakarta.servlet.ServletRequest;
21+
import jakarta.servlet.ServletResponse;
22+
import jakarta.servlet.http.HttpServletResponse;
23+
import java.io.IOException;
24+
25+
/** Filter to inject security headers, including CSP, for defense-in-depth. */
26+
public class CspFilter implements Filter {
27+
28+
private static final String CSP_POLICY =
29+
"default-src 'self'; object-src 'none'; base-uri 'self';";
30+
31+
@Override
32+
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
33+
throws IOException, ServletException {
34+
if (response instanceof HttpServletResponse httpResponse) {
35+
httpResponse.setHeader("Content-Security-Policy", CSP_POLICY);
36+
httpResponse.setHeader("X-Content-Type-Options", "nosniff");
37+
httpResponse.setHeader("X-Frame-Options", "DENY");
38+
}
39+
chain.doFilter(request, response);
40+
}
41+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// Copyright 2026 The Nomulus Authors. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package google.registry.ui.server;
16+
17+
import static org.mockito.Mockito.mock;
18+
import static org.mockito.Mockito.verify;
19+
20+
import jakarta.servlet.FilterChain;
21+
import jakarta.servlet.http.HttpServletRequest;
22+
import jakarta.servlet.http.HttpServletResponse;
23+
import org.junit.jupiter.api.Test;
24+
25+
/** Unit tests for {@link CspFilter}. */
26+
class CspFilterTest {
27+
28+
@Test
29+
void testDoFilter_setsHeaders() throws Exception {
30+
CspFilter filter = new CspFilter();
31+
HttpServletRequest request = mock(HttpServletRequest.class);
32+
HttpServletResponse response = mock(HttpServletResponse.class);
33+
FilterChain chain = mock(FilterChain.class);
34+
35+
filter.doFilter(request, response, chain);
36+
37+
verify(response)
38+
.setHeader(
39+
"Content-Security-Policy", "default-src 'self'; object-src 'none'; base-uri 'self';");
40+
verify(response).setHeader("X-Content-Type-Options", "nosniff");
41+
verify(response).setHeader("X-Frame-Options", "DENY");
42+
verify(chain).doFilter(request, response);
43+
}
44+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# ---------------------------------------
2+
# Module: ee10-servlets
3+
# ---------------------------------------
4+
--module=ee10-servlets

jetty/src/main/jetty-base/webapps/console/WEB-INF/web.xml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,24 @@
33
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
44
version="5.0">
55

6+
<filter>
7+
<filter-name>CspHeaderFilter</filter-name>
8+
<filter-class>org.eclipse.jetty.ee10.servlets.HeaderFilter</filter-class>
9+
<init-param>
10+
<param-name>headerConfig</param-name>
11+
<param-value>
12+
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';
13+
set X-Content-Type-Options: nosniff
14+
set X-Frame-Options: DENY
15+
</param-value>
16+
</init-param>
17+
</filter>
18+
19+
<filter-mapping>
20+
<filter-name>CspHeaderFilter</filter-name>
21+
<url-pattern>/*</url-pattern>
22+
</filter-mapping>
23+
624
<servlet>
725
<servlet-name>default-no-cache</servlet-name>
826
<servlet-class>org.eclipse.jetty.ee10.servlet.DefaultServlet</servlet-class>

jetty/src/main/webapp/WEB-INF/web.xml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,17 @@
22
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
33
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
44
version="6.0">
5+
<!-- Filters -->
6+
<filter>
7+
<filter-name>CspFilter</filter-name>
8+
<filter-class>google.registry.ui.server.CspFilter</filter-class>
9+
</filter>
10+
11+
<filter-mapping>
12+
<filter-name>CspFilter</filter-name>
13+
<url-pattern>/*</url-pattern>
14+
</filter-mapping>
15+
516
<!-- Servlets -->
617

718
<!-- Servlet for injected frontend actions -->

0 commit comments

Comments
 (0)