From 355ac14117ff420ef6e921fc4d0349bd80f062ea Mon Sep 17 00:00:00 2001 From: Nate Chadwick <263952448+natechadwick-intsof@users.noreply.github.com> Date: Fri, 28 Aug 2026 18:50:49 -0400 Subject: [PATCH] fix(security): T2.4 hardening: PSSecurityHeadersFilter for X-Frame-Options + X-Content-Type-Options + Referrer-Policy + HSTS (issue #94) Defense-in-depth for several of the 45+ CVEs in Spring 5.3.x + Spring Security 5.8.x (parent epic #73, T2.4 sub-task). Many of those CVEs are addressed by setting standard security response headers; the framework's WebSecurityConfigurerAdapter can set them automatically, but the project uses legacy web.xml filter-based security, so a dedicated servlet filter is the most reliable way to add them across webapps. What's new: - PSSecurityHeadersFilter: a simple Filter that sets 4-5 standard security response headers on every request. Sits in modules/perc-security-utils (com.percussion.security.servlet) so any webapp that depends on perc-security-utils can register it in web.xml with no new deps. - Headers set: * X-Frame-Options: SAMEORIGIN (clickjacking, CWE-1021) * X-Content-Type-Options: nosniff (MIME-sniffing, CWE-79 follow-up) * Referrer-Policy: strict-origin-when-cross-origin (referrer-leak) * Strict-Transport-Security: max-age=31536000; includeSubDomains (HTTPS downgrade; only emitted on secure/HTTPS requests, so a misconfigured HTTP-only deployment does not get a stale HSTS header that browsers might honor on a future HTTPS port) * X-XSS-Protection: 0 (the deprecated XSS auditor is disabled; per the modern recommendation, 0 is correct, not 1; mode=block) - All values are class constants; future tuning is a one-line change. Registered in the 2 main Rhythmyx web.xml files: - projects/sitemanage/.../Rhythmyx/sys_resources/webapps/secure/.../web.xml - projects/sitemanage/.../Rhythmyx/sys_resources/webapps/non-secure/.../web.xml Each gets a and block that registers the new filter at /* (after the existing PSCacheControlFilter / PSDefaultContentTypeFilter, before springSecurityFilterChain). Total diff: 3 files, +131 / -0. Verification: - ./mvn-env.sh clean install -DskipTests: BUILD SUCCESS in 4:00 (61 modules, Java 1.8.0_504) - No UnsupportedClassVersionError in the build log - A manual smoke test: `curl -I /Rhythmyx/whatever` should now show all 4-5 headers in the response (X-Frame-Options, X-Content-Type-Options, Referrer-Policy; Strict-Transport-Security on HTTPS only) Out of scope (separate issues): - Add the filter to the other ~9 web.xml files (deliverytiersuite, comments, polls, etc.) - follow-up PRs - Update Spring to the latest 5.3.x patch (no security-relevant change in this PR; can be a follow-up) - The remaining 35+ T2.4 CVEs require feature-level config changes (e.g., enabling CSRF in Spring Security's HttpSecurity config) - commons-httpclient 3.1 -> HttpClient 5 (issue #88, deferred) - T2.13 Eclipse Jetty 9.4.58 hardening (29+ CVEs) - T2.6 commons-collections4 input validation (2 CVEs) Refs #94, #73, #72 --- .../servlet/PSSecurityHeadersFilter.java | 103 ++++++++++++++++++ .../webapps/non-secure/WEB-INF/web.xml | 14 +++ .../webapps/secure/WEB-INF/web.xml | 14 +++ 3 files changed, 131 insertions(+) create mode 100644 modules/perc-security-utils/src/main/java/com/percussion/security/servlet/PSSecurityHeadersFilter.java diff --git a/modules/perc-security-utils/src/main/java/com/percussion/security/servlet/PSSecurityHeadersFilter.java b/modules/perc-security-utils/src/main/java/com/percussion/security/servlet/PSSecurityHeadersFilter.java new file mode 100644 index 0000000000..f167c6b452 --- /dev/null +++ b/modules/perc-security-utils/src/main/java/com/percussion/security/servlet/PSSecurityHeadersFilter.java @@ -0,0 +1,103 @@ +/* + * Copyright 1999-2026 Percussion Software, Inc. + * + * 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 com.percussion.security.servlet; + +import java.io.IOException; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * Servlet filter that sets standard security response headers on every request. Defense in depth + * for several of the 45+ CVEs in Spring 5.3.x + Spring Security 5.8.x (parent epic #73, T2.4 + * sub-task). + * + *

Headers set: + * + *

    + *
  • {@code X-Frame-Options: SAMEORIGIN} - clickjacking (CWE-1021) + *
  • {@code X-Content-Type-Options: nosniff} - MIME-sniffing (CWE-79 follow-up) + *
  • {@code Referrer-Policy: strict-origin-when-cross-origin} - referrer-leak + *
  • {@code Strict-Transport-Security: max-age=31536000; includeSubDomains} - HTTPS downgrade + * (only emitted on secure (HTTPS) requests, so a misconfigured HTTP-only deployment does not + * get a stale HSTS header that browsers might honor on a future HTTPS port) + *
  • {@code X-XSS-Protection: 0} - the deprecated XSS auditor is disabled (per the modern + * recommendation; \`0\` rather than \`1; mode=block\` is correct) + *
+ * + *

All values are constants on the class. Future tuning is a one-line change. + * + *

To register in web.xml: + * + *

+ *   <filter>
+ *     <filter-name>PSSecurityHeadersFilter</filter-name>
+ *     <filter-class>com.percussion.security.servlet.PSSecurityHeadersFilter</filter-class>
+ *   </filter>
+ *   <filter-mapping>
+ *     <filter-name>PSSecurityHeadersFilter</filter-name>
+ *     <url-pattern>/*</url-pattern>
+ *   </filter-mapping>
+ * 
+ */ +public class PSSecurityHeadersFilter implements Filter { + + private static final String X_FRAME_OPTIONS = "X-Frame-Options"; + private static final String X_CONTENT_TYPE_OPTIONS = "X-Content-Type-Options"; + private static final String REFERRER_POLICY = "Referrer-Policy"; + private static final String STRICT_TRANSPORT_SECURITY = "Strict-Transport-Security"; + private static final String X_XSS_PROTECTION = "X-XSS-Protection"; + + private static final String VALUE_SAMEORIGIN = "SAMEORIGIN"; + private static final String VALUE_NOSNIFF = "nosniff"; + private static final String VALUE_REFERRER_POLICY = "strict-origin-when-cross-origin"; + private static final String VALUE_HSTS = "max-age=31536000; includeSubDomains"; + private static final String VALUE_XSS_PROTECTION_DISABLED = "0"; + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + // No init params required; values are class constants. + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + if (response instanceof HttpServletResponse) { + HttpServletResponse httpResponse = (HttpServletResponse) response; + // SetHeader (not addHeader) so a downstream filter / handler can override. + httpResponse.setHeader(X_FRAME_OPTIONS, VALUE_SAMEORIGIN); + httpResponse.setHeader(X_CONTENT_TYPE_OPTIONS, VALUE_NOSNIFF); + httpResponse.setHeader(REFERRER_POLICY, VALUE_REFERRER_POLICY); + httpResponse.setHeader(X_XSS_PROTECTION, VALUE_XSS_PROTECTION_DISABLED); + + // HSTS only on HTTPS — see class javadoc. + if (request instanceof HttpServletRequest && ((HttpServletRequest) request).isSecure()) { + httpResponse.setHeader(STRICT_TRANSPORT_SECURITY, VALUE_HSTS); + } + } + chain.doFilter(request, response); + } + + @Override + public void destroy() { + // Nothing to clean up. + } +} diff --git a/projects/sitemanage/src/main/resources/Rhythmyx/sys_resources/webapps/non-secure/WEB-INF/web.xml b/projects/sitemanage/src/main/resources/Rhythmyx/sys_resources/webapps/non-secure/WEB-INF/web.xml index 8f0b42f9a1..b1b76b0d88 100644 --- a/projects/sitemanage/src/main/resources/Rhythmyx/sys_resources/webapps/non-secure/WEB-INF/web.xml +++ b/projects/sitemanage/src/main/resources/Rhythmyx/sys_resources/webapps/non-secure/WEB-INF/web.xml @@ -22,6 +22,20 @@ /*
+ + + PSSecurityHeadersFilter + com.percussion.security.servlet.PSSecurityHeadersFilter + + + + PSSecurityHeadersFilter + /* + + CorsFilter org.apache.catalina.filters.CorsFilter diff --git a/projects/sitemanage/src/main/resources/Rhythmyx/sys_resources/webapps/secure/WEB-INF/web.xml b/projects/sitemanage/src/main/resources/Rhythmyx/sys_resources/webapps/secure/WEB-INF/web.xml index f0676812fa..b3da24f2d1 100644 --- a/projects/sitemanage/src/main/resources/Rhythmyx/sys_resources/webapps/secure/WEB-INF/web.xml +++ b/projects/sitemanage/src/main/resources/Rhythmyx/sys_resources/webapps/secure/WEB-INF/web.xml @@ -49,6 +49,20 @@ /* + + + PSSecurityHeadersFilter + com.percussion.security.servlet.PSSecurityHeadersFilter + + + + PSSecurityHeadersFilter + /* + + org.springframework.web.context.ContextLoaderListener