Merge pull request #905 from apache/dependabot/maven/org.apache.maven.plugins-maven-failsafe-plugin-3.2.5

Bump org.apache.maven.plugins:maven-failsafe-plugin from 3.0.0-M6 to 3.2.5
diff --git a/.github/workflows/scorecards-analysis.yaml b/.github/workflows/scorecards-analysis.yaml
index 67faabc..6867c45 100644
--- a/.github/workflows/scorecards-analysis.yaml
+++ b/.github/workflows/scorecards-analysis.yaml
@@ -57,7 +57,7 @@
           publish_results: true
 
       - name: "Upload artifact"
-        uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3    # 4.3.1
+        uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808    # 4.3.3
         with:
           name: SARIF file
           path: results.sarif
diff --git a/core/pom.xml b/core/pom.xml
index 3fcfaaf..8165d19 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -230,6 +230,15 @@
             <groupId>org.apache.commons</groupId>
             <artifactId>commons-text</artifactId>
         </dependency>
+
+        <!-- Optional used in com.opensymphony.xwork2.util.ProxyUtil to detect if object is HibernateProxy -->
+        <dependency>
+            <groupId>org.hibernate</groupId>
+            <artifactId>hibernate-core</artifactId>
+            <version>5.6.15.Final</version>
+            <optional>true</optional>
+        </dependency>
+
         <dependency>
             <groupId>org.springframework</groupId>
             <artifactId>spring-test</artifactId>
diff --git a/core/src/main/java/com/opensymphony/xwork2/XWorkTestCase.java b/core/src/main/java/com/opensymphony/xwork2/XWorkTestCase.java
index 88790fc..8a4695e 100644
--- a/core/src/main/java/com/opensymphony/xwork2/XWorkTestCase.java
+++ b/core/src/main/java/com/opensymphony/xwork2/XWorkTestCase.java
@@ -36,6 +36,8 @@
 import java.util.Locale;
 import java.util.Map;
 
+import static java.util.Collections.singletonMap;
+
 /**
  * Base JUnit TestCase to extend for XWork specific JUnit tests. Uses
  * the generic test setup for logic.
@@ -56,9 +58,7 @@
     @Override
     protected void setUp() throws Exception {
         configurationManager = XWorkTestCaseHelper.setUp();
-        configuration = configurationManager.getConfiguration();
-        container = configuration.getContainer();
-        actionProxyFactory = container.getInstance(ActionProxyFactory.class);
+        reloadConfiguration(configurationManager);
     }
 
     @Override
@@ -66,13 +66,17 @@
         XWorkTestCaseHelper.tearDown(configurationManager);
     }
 
-    protected void loadConfigurationProviders(ConfigurationProvider... providers) {
-        configurationManager = XWorkTestCaseHelper.loadConfigurationProviders(configurationManager, providers);
+    private void reloadConfiguration(ConfigurationManager configurationManager) {
         configuration = configurationManager.getConfiguration();
         container = configuration.getContainer();
         actionProxyFactory = container.getInstance(ActionProxyFactory.class);
     }
 
+    protected void loadConfigurationProviders(ConfigurationProvider... providers) {
+        configurationManager = XWorkTestCaseHelper.loadConfigurationProviders(configurationManager, providers);
+        reloadConfiguration(configurationManager);
+    }
+
     protected void loadButSet(Map<String, ?> properties) {
         loadConfigurationProviders(new StubConfigurationProvider() {
             @Override
@@ -115,4 +119,25 @@
             .getContextMap();
     }
 
+    protected void setStrutsConstant(String constant, String value) {
+        setStrutsConstant(singletonMap(constant, value));
+    }
+
+    protected void setStrutsConstant(final Map<String, String> overwritePropeties) {
+        configurationManager.addContainerProvider(new StubConfigurationProvider() {
+            @Override
+            public void register(ContainerBuilder builder, LocatableProperties props) throws ConfigurationException {
+                for (Map.Entry<String, String> stringStringEntry : overwritePropeties.entrySet()) {
+                    props.setProperty(stringStringEntry.getKey(), stringStringEntry.getValue(), null);
+                }
+            }
+
+            @Override
+            public void destroy() {
+            }
+        });
+
+        configurationManager.reload();
+        reloadConfiguration(configurationManager);
+    }
 }
diff --git a/core/src/main/java/com/opensymphony/xwork2/config/entities/PackageConfig.java b/core/src/main/java/com/opensymphony/xwork2/config/entities/PackageConfig.java
index 9174e65..eafff83 100644
--- a/core/src/main/java/com/opensymphony/xwork2/config/entities/PackageConfig.java
+++ b/core/src/main/java/com/opensymphony/xwork2/config/entities/PackageConfig.java
@@ -47,6 +47,7 @@
     protected String name;
     protected String namespace = "";
     protected boolean isAbstract = false;
+    protected boolean isFinal = false; // a final package is unextendable
     protected boolean needsRefresh;
     protected boolean strictMethodInvocation = true;
 
@@ -69,6 +70,7 @@
         this.name = orig.name;
         this.namespace = orig.namespace;
         this.isAbstract = orig.isAbstract;
+        this.isFinal = orig.isFinal;
         this.needsRefresh = orig.needsRefresh;
         this.actionConfigs = new LinkedHashMap<>(orig.actionConfigs);
         this.globalResultConfigs = new LinkedHashMap<>(orig.globalResultConfigs);
@@ -85,6 +87,10 @@
         return isAbstract;
     }
 
+    public boolean isFinal() {
+        return isFinal;
+    }
+
     public Map<String, ActionConfig> getActionConfigs() {
         return actionConfigs;
     }
@@ -360,6 +366,7 @@
         PackageConfig that = (PackageConfig) o;
 
         if (isAbstract != that.isAbstract) return false;
+        if (isFinal != that.isFinal) return false;
         if (needsRefresh != that.needsRefresh) return false;
         if (strictMethodInvocation != that.strictMethodInvocation) return false;
         if (actionConfigs != null ? !actionConfigs.equals(that.actionConfigs) : that.actionConfigs != null)
@@ -404,6 +411,7 @@
         result = 31 * result + name.hashCode();
         result = 31 * result + (namespace != null ? namespace.hashCode() : 0);
         result = 31 * result + (isAbstract ? 1 : 0);
+        result = 31 * result + (isFinal ? 1 : 0);
         result = 31 * result + (needsRefresh ? 1 : 0);
         result = 31 * result + (strictMethodInvocation ? 1 : 0);
         return result;
@@ -453,6 +461,11 @@
             return this;
         }
 
+        public Builder isFinal(boolean isFinal) {
+            target.isFinal = isFinal;
+            return this;
+        }
+
         public Builder defaultInterceptorRef(String name) {
             target.defaultInterceptorRef = name;
             return this;
diff --git a/core/src/main/java/com/opensymphony/xwork2/config/impl/DefaultConfiguration.java b/core/src/main/java/com/opensymphony/xwork2/config/impl/DefaultConfiguration.java
index 7bf0e7c..7c725e1 100644
--- a/core/src/main/java/com/opensymphony/xwork2/config/impl/DefaultConfiguration.java
+++ b/core/src/main/java/com/opensymphony/xwork2/config/impl/DefaultConfiguration.java
@@ -120,6 +120,7 @@
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
 import java.util.TreeMap;
 import java.util.TreeSet;
@@ -459,9 +460,12 @@
         boolean appendNamedParameters = Boolean.parseBoolean(
                 container.getInstance(String.class, StrutsConstants.STRUTS_MATCHER_APPEND_NAMED_PARAMETERS)
         );
+        boolean fallbackToEmptyNamespace = Boolean.parseBoolean(
+                Optional.ofNullable(container.getInstance(String.class, StrutsConstants.STRUTS_ACTION_CONFIG_FALLBACK_TO_EMPTY_NAMESPACE)).orElse("true")
+        );
 
         return new RuntimeConfigurationImpl(Collections.unmodifiableMap(namespaceActionConfigs),
-                Collections.unmodifiableMap(namespaceConfigs), matcher, appendNamedParameters);
+                Collections.unmodifiableMap(namespaceConfigs), matcher, appendNamedParameters, fallbackToEmptyNamespace);
     }
 
     private void setDefaultResults(Map<String, ResultConfig> results, PackageConfig packageContext) {
@@ -536,14 +540,17 @@
         private final Map<String, ActionConfigMatcher> namespaceActionConfigMatchers;
         private final NamespaceMatcher namespaceMatcher;
         private final Map<String, String> namespaceConfigs;
+        private final boolean fallbackToEmptyNamespace;
 
         public RuntimeConfigurationImpl(Map<String, Map<String, ActionConfig>> namespaceActionConfigs,
                                         Map<String, String> namespaceConfigs,
                                         PatternMatcher<int[]> matcher,
-                                        boolean appendNamedParameters)
+                                        boolean appendNamedParameters,
+                                        boolean fallbackToEmptyNamespace)
         {
             this.namespaceActionConfigs = namespaceActionConfigs;
             this.namespaceConfigs = namespaceConfigs;
+            this.fallbackToEmptyNamespace = fallbackToEmptyNamespace;
 
             this.namespaceActionConfigMatchers = new LinkedHashMap<>();
             this.namespaceMatcher = new NamespaceMatcher(matcher, namespaceActionConfigs.keySet(), appendNamedParameters);
@@ -583,14 +590,17 @@
             }
 
             // fail over to empty namespace
-            if (config == null && StringUtils.isNotBlank(namespace)) {
+            if (config == null && shouldFallbackToEmptyNamespace(namespace)) {
                 config = findActionConfigInNamespace("", name);
             }
 
-
             return config;
         }
 
+        private boolean shouldFallbackToEmptyNamespace(String namespace) {
+            return StringUtils.isNotBlank(namespace) && ("/".equals(namespace) || fallbackToEmptyNamespace);
+        }
+
         private ActionConfig findActionConfigInNamespace(String namespace, String name) {
             ActionConfig config = null;
             if (namespace == null) {
diff --git a/core/src/main/java/com/opensymphony/xwork2/config/providers/XmlDocConfigurationProvider.java b/core/src/main/java/com/opensymphony/xwork2/config/providers/XmlDocConfigurationProvider.java
index cad52fb..6de2024 100644
--- a/core/src/main/java/com/opensymphony/xwork2/config/providers/XmlDocConfigurationProvider.java
+++ b/core/src/main/java/com/opensymphony/xwork2/config/providers/XmlDocConfigurationProvider.java
@@ -603,8 +603,8 @@
      */
     protected PackageConfig.Builder buildPackageContext(Element packageElement) {
         String parent = packageElement.getAttribute("extends");
-        String abstractVal = packageElement.getAttribute("abstract");
-        boolean isAbstract = parseBoolean(abstractVal);
+        boolean isAbstract = parseBoolean(packageElement.getAttribute("abstract"));
+        boolean isFinal = parseBoolean(packageElement.getAttribute("final"));
         String name = defaultString(packageElement.getAttribute("name"));
         String namespace = defaultString(packageElement.getAttribute("namespace"));
 
@@ -617,6 +617,7 @@
         PackageConfig.Builder cfg = new PackageConfig.Builder(name)
                 .namespace(namespace)
                 .isAbstract(isAbstract)
+                .isFinal(isFinal)
                 .strictMethodInvocation(strictDMI)
                 .location(DomHelper.getLocationObject(packageElement));
 
@@ -627,17 +628,23 @@
         // has parents, let's look it up
         List<PackageConfig> parents = new ArrayList<>();
         for (String parentPackageName : ConfigurationUtil.buildParentListFromString(parent)) {
-            if (configuration.getPackageConfigNames().contains(parentPackageName)) {
-                parents.add(configuration.getPackageConfig(parentPackageName));
-            } else if (declaredPackages.containsKey(parentPackageName)) {
-                if (configuration.getPackageConfig(parentPackageName) == null) {
-                    addPackage(declaredPackages.get(parentPackageName));
+            boolean isParentPackageConfigDefined = false;
+            if (configuration.getPackageConfigNames().contains(parentPackageName)) { // parent package already added to configuration
+                isParentPackageConfigDefined = true;
+            } else if (declaredPackages.containsKey(parentPackageName)) { // parent package declared but yet added to configuration
+                addPackage(declaredPackages.get(parentPackageName));
+                isParentPackageConfigDefined = true;
+            }
+
+            if (isParentPackageConfigDefined) {
+                PackageConfig parentPackageConfig = configuration.getPackageConfig(parentPackageName);
+                if (parentPackageConfig.isFinal()) {
+                    throw new ConfigurationException("Parent package is final and unextendable: " + parentPackageName);
                 }
-                parents.add(configuration.getPackageConfig(parentPackageName));
+                parents.add(parentPackageConfig);
             } else {
                 throw new ConfigurationException("Parent package is not defined: " + parentPackageName);
             }
-
         }
 
         if (parents.isEmpty()) {
diff --git a/core/src/main/java/com/opensymphony/xwork2/ognl/DefaultOgnlCacheFactory.java b/core/src/main/java/com/opensymphony/xwork2/ognl/DefaultOgnlCacheFactory.java
index 29dc7fc..e503f49 100644
--- a/core/src/main/java/com/opensymphony/xwork2/ognl/DefaultOgnlCacheFactory.java
+++ b/core/src/main/java/com/opensymphony/xwork2/ognl/DefaultOgnlCacheFactory.java
@@ -32,6 +32,7 @@
 
     private CacheType defaultCacheType;
     private int cacheMaxSize;
+    private final int initialCapacity;
 
     /**
      * @deprecated since 6.4.0, use {@link #DefaultOgnlCacheFactory(int, CacheType)}
@@ -42,13 +43,18 @@
     }
 
     public DefaultOgnlCacheFactory(int cacheMaxSize, CacheType defaultCacheType) {
+        this(cacheMaxSize, defaultCacheType, DEFAULT_INIT_CAPACITY);
+    }
+
+    public DefaultOgnlCacheFactory(int cacheMaxSize, CacheType defaultCacheType, int initialCapacity) {
         this.cacheMaxSize = cacheMaxSize;
         this.defaultCacheType = defaultCacheType;
+        this.initialCapacity = initialCapacity;
     }
 
     @Override
     public OgnlCache<Key, Value> buildOgnlCache() {
-        return buildOgnlCache(getCacheMaxSize(), DEFAULT_INIT_CAPACITY, DEFAULT_LOAD_FACTOR, defaultCacheType);
+        return buildOgnlCache(getCacheMaxSize(), initialCapacity, DEFAULT_LOAD_FACTOR, defaultCacheType);
     }
 
     @Override
diff --git a/core/src/main/java/com/opensymphony/xwork2/ognl/SecurityMemberAccess.java b/core/src/main/java/com/opensymphony/xwork2/ognl/SecurityMemberAccess.java
index 510a65c..43ae992 100644
--- a/core/src/main/java/com/opensymphony/xwork2/ognl/SecurityMemberAccess.java
+++ b/core/src/main/java/com/opensymphony/xwork2/ognl/SecurityMemberAccess.java
@@ -31,7 +31,6 @@
 import java.lang.reflect.AccessibleObject;
 import java.lang.reflect.Field;
 import java.lang.reflect.Member;
-import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
 import java.util.Arrays;
 import java.util.HashSet;
@@ -87,6 +86,7 @@
     private boolean enforceAllowlistEnabled = false;
     private Set<Class<?>> allowlistClasses = emptySet();
     private Set<String> allowlistPackageNames = emptySet();
+    private boolean disallowProxyObjectAccess = false;
     private boolean disallowProxyMemberAccess = false;
     private boolean disallowDefaultPackageAccess = false;
 
@@ -160,6 +160,11 @@
             }
         }
 
+        if (!checkProxyObjectAccess(target)) {
+            LOG.warn("Access to proxy is blocked! Target [{}], proxy class [{}]", target, target.getClass().getName());
+            return false;
+        }
+
         if (!checkProxyMemberAccess(target, member)) {
             LOG.warn("Access to proxy is blocked! Member class [{}] of target [{}], member [{}]", member.getDeclaringClass(), target, member);
             return false;
@@ -286,7 +291,14 @@
     }
 
     /**
-     * @return {@code true} if member access is allowed
+     * @return {@code true} if proxy object access is allowed
+     */
+    protected boolean checkProxyObjectAccess(Object target) {
+        return !(disallowProxyObjectAccess && ProxyUtil.isProxy(target));
+    }
+
+    /**
+     * @return {@code true} if proxy member access is allowed
      */
     protected boolean checkProxyMemberAccess(Object target, Member member) {
         return !(disallowProxyMemberAccess && ProxyUtil.isProxyMember(member, target));
@@ -300,10 +312,6 @@
      * @return {@code true} if member access is allowed
      */
     protected boolean checkStaticMethodAccess(Member member) {
-        if (checkEnumAccess(member)) {
-            LOG.trace("Exempting Enum#values from static method check: class [{}]", member.getDeclaringClass());
-            return true;
-        }
         return member instanceof Field || !isStatic(member);
     }
 
@@ -334,17 +342,6 @@
         return Modifier.isPublic(member.getModifiers());
     }
 
-    /**
-     * @return {@code true} if member access is allowed
-     */
-    protected boolean checkEnumAccess(Member member) {
-        return member.getDeclaringClass().isEnum()
-                && isStatic(member)
-                && member instanceof Method
-                && member.getName().equals("values")
-                && ((Method) member).getParameterCount() == 0;
-    }
-
     protected boolean isPackageExcluded(Class<?> clazz) {
         return !excludedPackageExemptClasses.contains(clazz.getName()) && (isExcludedPackageNames(clazz) || isExcludedPackageNamePatterns(clazz));
     }
@@ -448,6 +445,11 @@
         this.allowlistPackageNames = toPackageNamesSet(commaDelimitedPackageNames);
     }
 
+    @Inject(value = StrutsConstants.STRUTS_DISALLOW_PROXY_OBJECT_ACCESS, required = false)
+    public void useDisallowProxyObjectAccess(String disallowProxyObjectAccess) {
+        this.disallowProxyObjectAccess = BooleanUtils.toBoolean(disallowProxyObjectAccess);
+    }
+
     @Inject(value = StrutsConstants.STRUTS_DISALLOW_PROXY_MEMBER_ACCESS, required = false)
     public void useDisallowProxyMemberAccess(String disallowProxyMemberAccess) {
         this.disallowProxyMemberAccess = BooleanUtils.toBoolean(disallowProxyMemberAccess);
diff --git a/core/src/main/java/com/opensymphony/xwork2/util/ProxyUtil.java b/core/src/main/java/com/opensymphony/xwork2/util/ProxyUtil.java
index 9b0e7d4..c169af2 100644
--- a/core/src/main/java/com/opensymphony/xwork2/util/ProxyUtil.java
+++ b/core/src/main/java/com/opensymphony/xwork2/util/ProxyUtil.java
@@ -18,13 +18,20 @@
  */
 package com.opensymphony.xwork2.util;
 
+import com.opensymphony.xwork2.ognl.DefaultOgnlCacheFactory;
+import com.opensymphony.xwork2.ognl.OgnlCache;
+import com.opensymphony.xwork2.ognl.OgnlCacheFactory;
 import org.apache.commons.lang3.reflect.ConstructorUtils;
 import org.apache.commons.lang3.reflect.FieldUtils;
 import org.apache.commons.lang3.reflect.MethodUtils;
+import org.hibernate.proxy.HibernateProxy;
 
-import java.lang.reflect.*;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Member;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.Proxy;
 
 /**
  * <code>ProxyUtil</code>
@@ -38,11 +45,13 @@
     private static final String SPRING_SPRINGPROXY_CLASS_NAME = "org.springframework.aop.SpringProxy";
     private static final String SPRING_SINGLETONTARGETSOURCE_CLASS_NAME = "org.springframework.aop.target.SingletonTargetSource";
     private static final String SPRING_TARGETCLASSAWARE_CLASS_NAME = "org.springframework.aop.TargetClassAware";
-
-    private static final Map<Class<?>, Boolean> isProxyCache =
-            new ConcurrentHashMap<>(256);
-    private static final Map<Member, Boolean> isProxyMemberCache =
-            new ConcurrentHashMap<>(256);
+    private static final String HIBERNATE_HIBERNATEPROXY_CLASS_NAME = "org.hibernate.proxy.HibernateProxy";
+    private static final int CACHE_MAX_SIZE = 10000;
+    private static final int CACHE_INITIAL_CAPACITY = 256;
+    private static final OgnlCache<Class<?>, Boolean> isProxyCache = new DefaultOgnlCacheFactory<Class<?>, Boolean>(
+            CACHE_MAX_SIZE, OgnlCacheFactory.CacheType.WTLFU, CACHE_INITIAL_CAPACITY).buildOgnlCache();
+    private static final OgnlCache<Member, Boolean> isProxyMemberCache = new DefaultOgnlCacheFactory<Member, Boolean>(
+            CACHE_MAX_SIZE, OgnlCacheFactory.CacheType.WTLFU, CACHE_INITIAL_CAPACITY).buildOgnlCache();
 
     /**
      * Determine the ultimate target class of the given instance, traversing
@@ -75,7 +84,7 @@
             return flag;
         }
 
-        boolean isProxy = isSpringAopProxy(object);
+        boolean isProxy = isSpringAopProxy(object) || isHibernateProxy(object);
 
         isProxyCache.put(clazz, isProxy);
         return isProxy;
@@ -87,7 +96,7 @@
      * @param object the object to check
      */
     public static boolean isProxyMember(Member member, Object object) {
-        if (!Modifier.isStatic(member.getModifiers()) && !isProxy(object)) {
+        if (!Modifier.isStatic(member.getModifiers()) && !isProxy(object) && !isHibernateProxy(object)) {
             return false;
         }
 
@@ -96,13 +105,41 @@
             return flag;
         }
 
-        boolean isProxyMember = isSpringProxyMember(member);
+        boolean isProxyMember = isSpringProxyMember(member) || isHibernateProxyMember(member);
 
         isProxyMemberCache.put(member, isProxyMember);
         return isProxyMember;
     }
 
     /**
+     * Check whether the given object is a Hibernate proxy.
+     *
+     * @param object the object to check
+     */
+    public static boolean isHibernateProxy(Object object) {
+        try {
+            return HibernateProxy.class.isAssignableFrom(object.getClass());
+        } catch (NoClassDefFoundError ignored) {
+            return false;
+        }
+    }
+
+    /**
+     * Check whether the given member is a member of a Hibernate proxy.
+     *
+     * @param member the member to check
+     */
+    public static boolean isHibernateProxyMember(Member member) {
+        try {
+            Class<?> clazz = ClassLoaderUtil.loadClass(HIBERNATE_HIBERNATEPROXY_CLASS_NAME, ProxyUtil.class);
+            return hasMember(clazz, member);
+        } catch (ClassNotFoundException ignored) {
+        }
+
+        return false;
+    }
+
+    /**
      * Determine the ultimate target class of the given spring bean instance, traversing
      * not only a top-level spring proxy but any number of nested spring proxies as well &mdash;
      * as long as possible without side effects, that is, just for singleton targets.
diff --git a/core/src/main/java/org/apache/struts2/StrutsConstants.java b/core/src/main/java/org/apache/struts2/StrutsConstants.java
index 3d0d1a0..91b8eb2 100644
--- a/core/src/main/java/org/apache/struts2/StrutsConstants.java
+++ b/core/src/main/java/org/apache/struts2/StrutsConstants.java
@@ -230,6 +230,8 @@
     public static final String STRUTS_XWORKCONVERTER = "struts.xworkConverter";
 
     public static final String STRUTS_ALWAYS_SELECT_FULL_NAMESPACE = "struts.mapper.alwaysSelectFullNamespace";
+    /** Fallback to empty namespace when request namespace didn't match any in action configuration */
+    public static final String STRUTS_ACTION_CONFIG_FALLBACK_TO_EMPTY_NAMESPACE = "struts.actionConfig.fallbackToEmptyNamespace";
 
     /** The {@link com.opensymphony.xwork2.LocaleProviderFactory} implementation class */
     public static final String STRUTS_LOCALE_PROVIDER_FACTORY = "struts.localeProviderFactory";
@@ -480,6 +482,7 @@
     public static final String STRUTS_TEXT_PROVIDER_FACTORY = "struts.textProviderFactory";
     public static final String STRUTS_LOCALIZED_TEXT_PROVIDER = "struts.localizedTextProvider";
 
+    public static final String STRUTS_DISALLOW_PROXY_OBJECT_ACCESS = "struts.disallowProxyObjectAccess";
     public static final String STRUTS_DISALLOW_PROXY_MEMBER_ACCESS = "struts.disallowProxyMemberAccess";
     public static final String STRUTS_DISALLOW_DEFAULT_PACKAGE_ACCESS = "struts.disallowDefaultPackageAccess";
 
diff --git a/core/src/main/java/org/apache/struts2/config/StrutsXmlConfigurationProvider.java b/core/src/main/java/org/apache/struts2/config/StrutsXmlConfigurationProvider.java
index 9b62b3d..9c1b394 100644
--- a/core/src/main/java/org/apache/struts2/config/StrutsXmlConfigurationProvider.java
+++ b/core/src/main/java/org/apache/struts2/config/StrutsXmlConfigurationProvider.java
@@ -54,6 +54,7 @@
         put("-//Apache Software Foundation//DTD Struts Configuration 2.3//EN", "struts-2.3.dtd");
         put("-//Apache Software Foundation//DTD Struts Configuration 2.5//EN", "struts-2.5.dtd");
         put("-//Apache Software Foundation//DTD Struts Configuration 6.0//EN", "struts-6.0.dtd");
+        put("-//Apache Software Foundation//DTD Struts Configuration 6.5//EN", "struts-6.5.dtd");
     }});
     private File baseDir = null;
     private final String filename;
diff --git a/core/src/main/java/org/apache/struts2/config/entities/ConstantConfig.java b/core/src/main/java/org/apache/struts2/config/entities/ConstantConfig.java
index 2b85424..11b30c2 100644
--- a/core/src/main/java/org/apache/struts2/config/entities/ConstantConfig.java
+++ b/core/src/main/java/org/apache/struts2/config/entities/ConstantConfig.java
@@ -90,6 +90,7 @@
     private Boolean freemarkerWrapperAltMap;
     private BeanConfig xworkConverter;
     private Boolean mapperAlwaysSelectFullNamespace;
+    private Boolean actionConfigFallbackToEmptyNamespace;
     private BeanConfig localeProviderFactory;
     private String mapperIdParameterName;
     private Boolean ognlAllowStaticFieldAccess;
@@ -145,6 +146,7 @@
     private String strictMethodInvocationMethodRegex;
     private BeanConfig textProviderFactory;
     private BeanConfig localizedTextProvider;
+    private Boolean disallowProxyObjectAccess;
     private Boolean disallowProxyMemberAccess;
     private Integer ognlAutoGrowthCollectionLimit;
     private String staticContentPath;
@@ -225,6 +227,7 @@
         map.put(StrutsConstants.STRUTS_FREEMARKER_WRAPPER_ALT_MAP, Objects.toString(freemarkerWrapperAltMap, null));
         map.put(StrutsConstants.STRUTS_XWORKCONVERTER, beanConfToString(xworkConverter));
         map.put(StrutsConstants.STRUTS_ALWAYS_SELECT_FULL_NAMESPACE, Objects.toString(mapperAlwaysSelectFullNamespace, null));
+        map.put(StrutsConstants.STRUTS_ACTION_CONFIG_FALLBACK_TO_EMPTY_NAMESPACE, Objects.toString(actionConfigFallbackToEmptyNamespace, null));
         map.put(StrutsConstants.STRUTS_LOCALE_PROVIDER_FACTORY, beanConfToString(localeProviderFactory));
         map.put(StrutsConstants.STRUTS_ID_PARAMETER_NAME, mapperIdParameterName);
         map.put(StrutsConstants.STRUTS_ALLOW_STATIC_FIELD_ACCESS, Objects.toString(ognlAllowStaticFieldAccess, null));
@@ -279,6 +282,7 @@
         map.put(StrutsConstants.STRUTS_SMI_METHOD_REGEX, strictMethodInvocationMethodRegex);
         map.put(StrutsConstants.STRUTS_TEXT_PROVIDER_FACTORY, beanConfToString(textProviderFactory));
         map.put(StrutsConstants.STRUTS_LOCALIZED_TEXT_PROVIDER, beanConfToString(localizedTextProvider));
+        map.put(StrutsConstants.STRUTS_DISALLOW_PROXY_OBJECT_ACCESS, Objects.toString(disallowProxyObjectAccess, null));
         map.put(StrutsConstants.STRUTS_DISALLOW_PROXY_MEMBER_ACCESS, Objects.toString(disallowProxyMemberAccess, null));
         map.put(StrutsConstants.STRUTS_OGNL_AUTO_GROWTH_COLLECTION_LIMIT, Objects.toString(ognlAutoGrowthCollectionLimit, null));
         map.put(StrutsConstants.STRUTS_UI_STATIC_CONTENT_PATH, Objects.toString(staticContentPath, StaticContentLoader.DEFAULT_STATIC_CONTENT_PATH));
@@ -812,6 +816,14 @@
         this.mapperAlwaysSelectFullNamespace = mapperAlwaysSelectFullNamespace;
     }
 
+    public Boolean getActionConfigFallbackToEmptyNamespace() {
+        return actionConfigFallbackToEmptyNamespace;
+    }
+
+    public void setActionConfigFallbackToEmptyNamespace(Boolean actionConfigFallbackToEmptyNamespace) {
+        this.actionConfigFallbackToEmptyNamespace = actionConfigFallbackToEmptyNamespace;
+    }
+
     public BeanConfig getLocaleProviderFactory() {
         return localeProviderFactory;
     }
@@ -1360,6 +1372,14 @@
         this.localizedTextProvider = new BeanConfig(clazz, clazz.getName());
     }
 
+    public Boolean getDisallowProxyObjectAccess() {
+        return disallowProxyObjectAccess;
+    }
+
+    public void setDisallowProxyObjectAccess(Boolean disallowProxyObjectAccess) {
+        this.disallowProxyObjectAccess = disallowProxyObjectAccess;
+    }
+
     public Boolean getDisallowProxyMemberAccess() {
         return disallowProxyMemberAccess;
     }
diff --git a/core/src/main/java/org/apache/struts2/dispatcher/Dispatcher.java b/core/src/main/java/org/apache/struts2/dispatcher/Dispatcher.java
index 5bad0b4..a1f9094 100644
--- a/core/src/main/java/org/apache/struts2/dispatcher/Dispatcher.java
+++ b/core/src/main/java/org/apache/struts2/dispatcher/Dispatcher.java
@@ -78,6 +78,7 @@
 import javax.servlet.http.HttpServletResponse;
 import java.io.File;
 import java.io.IOException;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.HashSet;
 import java.util.List;
@@ -88,6 +89,10 @@
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.regex.Pattern;
 
+import static java.util.Collections.emptyList;
+import static java.util.Collections.unmodifiableList;
+import static java.util.stream.Collectors.toList;
+
 /**
  * A utility class the actual dispatcher delegates most of its tasks to. Each instance
  * of the primary dispatcher holds an instance of this dispatcher to be shared for
@@ -162,6 +167,10 @@
      */
     private Pattern multipartValidationPattern = Pattern.compile(MULTIPART_FORM_DATA_REGEX);
 
+    private String actionExcludedPatternsStr;
+    private String actionExcludedPatternsSeparator = ",";
+    private List<Pattern> actionExcludedPatterns;
+
     /**
      * Provide list of default configuration files.
      */
@@ -340,6 +349,33 @@
         this.multipartValidationPattern = Pattern.compile(multipartValidationRegex);
     }
 
+    @Inject(value = StrutsConstants.STRUTS_ACTION_EXCLUDE_PATTERN_SEPARATOR, required = false)
+    public void setActionExcludedPatternsSeparator(String separator) {
+        this.actionExcludedPatternsSeparator = separator;
+    }
+
+    @Inject(value = StrutsConstants.STRUTS_ACTION_EXCLUDE_PATTERN, required = false)
+    public void setActionExcludedPatterns(String excludedPatterns) {
+        this.actionExcludedPatternsStr = excludedPatterns;
+    }
+
+    public List<Pattern> getActionExcludedPatterns() {
+        if (actionExcludedPatterns == null) {
+            initActionExcludedPatterns();
+        }
+        return actionExcludedPatterns;
+    }
+
+    private void initActionExcludedPatterns() {
+        if (actionExcludedPatternsStr == null || actionExcludedPatternsStr.trim().isEmpty()) {
+            actionExcludedPatterns = emptyList();
+            return;
+        }
+        actionExcludedPatterns = unmodifiableList(
+                Arrays.stream(actionExcludedPatternsStr.split(actionExcludedPatternsSeparator))
+                        .map(String::trim).map(Pattern::compile).collect(toList()));
+    }
+
     @Inject
     public void setValueStackFactory(ValueStackFactory valueStackFactory) {
         this.valueStackFactory = valueStackFactory;
diff --git a/core/src/main/java/org/apache/struts2/dispatcher/InitOperations.java b/core/src/main/java/org/apache/struts2/dispatcher/InitOperations.java
index 367aeba..f5cf21a 100644
--- a/core/src/main/java/org/apache/struts2/dispatcher/InitOperations.java
+++ b/core/src/main/java/org/apache/struts2/dispatcher/InitOperations.java
@@ -19,10 +19,7 @@
 package org.apache.struts2.dispatcher;
 
 import com.opensymphony.xwork2.ActionContext;
-import org.apache.struts2.StrutsConstants;
 
-import java.util.ArrayList;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
@@ -100,27 +97,11 @@
      * @param dispatcher The dispatcher to check for exclude pattern configuration
      * @return a List of Patterns for request to exclude if apply, or <tt>null</tt>
      * @see org.apache.struts2.StrutsConstants#STRUTS_ACTION_EXCLUDE_PATTERN
+     * @deprecated since 6.4.0, use {@link Dispatcher#getActionExcludedPatterns()} instead.
      */
+    @Deprecated
     public List<Pattern> buildExcludedPatternsList(Dispatcher dispatcher) {
-        String excludePatterns = dispatcher.getContainer().getInstance(String.class, StrutsConstants.STRUTS_ACTION_EXCLUDE_PATTERN);
-        String separator = dispatcher.getContainer().getInstance(String.class, StrutsConstants.STRUTS_ACTION_EXCLUDE_PATTERN_SEPARATOR);
-        if (separator == null) {
-            separator = ",";
-        }
-        return buildExcludedPatternsList(excludePatterns, separator);
-    }
-
-    private List<Pattern> buildExcludedPatternsList(String patterns, String separator) {
-        if (null != patterns && patterns.trim().length() != 0) {
-            List<Pattern> list = new ArrayList<>();
-            String[] tokens = patterns.split(separator);
-            for (String token : tokens) {
-                list.add(Pattern.compile(token.trim()));
-            }
-            return Collections.unmodifiableList(list);
-        } else {
-            return null;
-        }
+        return dispatcher.getActionExcludedPatterns();
     }
 
 }
diff --git a/core/src/main/java/org/apache/struts2/dispatcher/PrepareOperations.java b/core/src/main/java/org/apache/struts2/dispatcher/PrepareOperations.java
index 6888c5b..ab43235 100644
--- a/core/src/main/java/org/apache/struts2/dispatcher/PrepareOperations.java
+++ b/core/src/main/java/org/apache/struts2/dispatcher/PrepareOperations.java
@@ -223,21 +223,23 @@
      * Check whether the request matches a list of exclude patterns.
      *
      * @param request          The request to check patterns against
-     * @param excludedPatterns list of patterns for exclusion
-     *
      * @return <tt>true</tt> if the request URI matches one of the given patterns
      */
+    public boolean isUrlExcluded(HttpServletRequest request) {
+        String uri = RequestUtils.getUri(request);
+        return dispatcher.getActionExcludedPatterns().stream().anyMatch(pattern -> pattern.matcher(uri).matches());
+    }
+
+    /**
+     * @deprecated since 6.4.0, use {@link #isUrlExcluded(HttpServletRequest)} instead.
+     */
+    @Deprecated
     public boolean isUrlExcluded(HttpServletRequest request, List<Pattern> excludedPatterns) {
         if (excludedPatterns == null) {
             return false;
         }
         String uri = RequestUtils.getUri(request);
-        for (Pattern pattern : excludedPatterns) {
-            if (pattern.matcher(uri).matches()) {
-                return true;
-            }
-        }
-        return false;
+        return excludedPatterns.stream().anyMatch(pattern -> pattern.matcher(uri).matches());
     }
 
     /**
diff --git a/core/src/main/java/org/apache/struts2/dispatcher/filter/StrutsPrepareAndExecuteFilter.java b/core/src/main/java/org/apache/struts2/dispatcher/filter/StrutsPrepareAndExecuteFilter.java
index e91a1b9..bce6ec4 100644
--- a/core/src/main/java/org/apache/struts2/dispatcher/filter/StrutsPrepareAndExecuteFilter.java
+++ b/core/src/main/java/org/apache/struts2/dispatcher/filter/StrutsPrepareAndExecuteFilter.java
@@ -50,6 +50,12 @@
 
     protected PrepareOperations prepare;
     protected ExecuteOperations execute;
+
+    /**
+     * @deprecated since 6.4.0, use {@link Dispatcher#getActionExcludedPatterns} or
+     * {@link PrepareOperations#isUrlExcluded(HttpServletRequest)} instead.
+     */
+    @Deprecated
     protected List<Pattern> excludedPatterns;
 
     public void init(FilterConfig filterConfig) throws ServletException {
@@ -62,7 +68,7 @@
 
             prepare = createPrepareOperations(dispatcher);
             execute = createExecuteOperations(dispatcher);
-            // Note: Currently, excluded patterns are not refreshed following an XWork config reload
+
             this.excludedPatterns = init.buildExcludedPatternsList(dispatcher);
 
             postInit(dispatcher, filterConfig);
@@ -121,7 +127,7 @@
         try {
             prepare.trackRecursion(request);
             String uri = RequestUtils.getUri(request);
-            if (prepare.isUrlExcluded(request, excludedPatterns)) {
+            if (prepare.isUrlExcluded(request)) {
                 LOG.trace("Request: {} is excluded from handling by Struts, passing request to other filters", uri);
                 chain.doFilter(request, response);
             } else {
diff --git a/core/src/main/java/org/apache/struts2/dispatcher/filter/StrutsPrepareFilter.java b/core/src/main/java/org/apache/struts2/dispatcher/filter/StrutsPrepareFilter.java
index 81dffb6..475a139 100644
--- a/core/src/main/java/org/apache/struts2/dispatcher/filter/StrutsPrepareFilter.java
+++ b/core/src/main/java/org/apache/struts2/dispatcher/filter/StrutsPrepareFilter.java
@@ -43,6 +43,12 @@
     protected static final String REQUEST_EXCLUDED_FROM_ACTION_MAPPING = StrutsPrepareFilter.class.getName() + ".REQUEST_EXCLUDED_FROM_ACTION_MAPPING";
 
     protected PrepareOperations prepare;
+
+    /**
+     * @deprecated since 6.4.0, use {@link Dispatcher#getActionExcludedPatterns} or
+     * {@link PrepareOperations#isUrlExcluded(HttpServletRequest)} instead.
+     */
+    @Deprecated
     protected List<Pattern> excludedPatterns;
 
     public void init(FilterConfig filterConfig) throws ServletException {
@@ -53,7 +59,7 @@
             dispatcher = init.initDispatcher(config);
 
             prepare = createPrepareOperations(dispatcher);
-            // Note: Currently, excluded patterns are not refreshed following an XWork config reload
+
             this.excludedPatterns = init.buildExcludedPatternsList(dispatcher);
 
             postInit(dispatcher, filterConfig);
@@ -102,7 +108,7 @@
         boolean didWrap = false;
         try {
             prepare.trackRecursion(request);
-            if (prepare.isUrlExcluded(request, excludedPatterns)) {
+            if (prepare.isUrlExcluded(request)) {
                 request.setAttribute(REQUEST_EXCLUDED_FROM_ACTION_MAPPING, true);
             } else {
                 request.setAttribute(REQUEST_EXCLUDED_FROM_ACTION_MAPPING, false);
diff --git a/core/src/main/resources/org/apache/struts2/default.properties b/core/src/main/resources/org/apache/struts2/default.properties
index 96c5459..8b7226a 100644
--- a/core/src/main/resources/org/apache/struts2/default.properties
+++ b/core/src/main/resources/org/apache/struts2/default.properties
@@ -215,6 +215,9 @@
 ### Whether to always select the namespace to be everything before the last slash or not
 struts.mapper.alwaysSelectFullNamespace=false
 
+### Whether to fallback to empty namespace when request namespace does not match any in configuration
+struts.actionConfig.fallbackToEmptyNamespace=true
+
 ### Whether to allow static field access in OGNL expressions or not
 struts.ognl.allowStaticFieldAccess=true
 
diff --git a/core/src/main/resources/struts-6.5.dtd b/core/src/main/resources/struts-6.5.dtd
new file mode 100644
index 0000000..6c2ef0d
--- /dev/null
+++ b/core/src/main/resources/struts-6.5.dtd
@@ -0,0 +1,158 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+-->
+<!-- START SNIPPET: strutsDtd -->
+
+<!--
+   Struts configuration DTD.
+   Use the following DOCTYPE
+
+   <!DOCTYPE struts PUBLIC
+    "-//Apache Software Foundation//DTD Struts Configuration 6.5//EN"
+    "https://struts.apache.org/dtds/struts-6.5.dtd">
+-->
+
+<!ELEMENT struts ((package|include|bean|constant)*,bean-selection?, unknown-handler-stack?)>
+<!ATTLIST struts
+    order CDATA #IMPLIED
+>
+
+<!ELEMENT package (result-types?, interceptors?, default-interceptor-ref?, default-action-ref?, default-class-ref?, global-results?, global-allowed-methods?, global-exception-mappings?, action*)>
+<!ATTLIST package
+    name CDATA #REQUIRED
+    extends CDATA #IMPLIED
+    namespace CDATA #IMPLIED
+    abstract CDATA #IMPLIED
+    final CDATA #IMPLIED
+    strict-method-invocation (true|false) "true"
+>
+
+<!ELEMENT result-types (result-type+)>
+
+<!ELEMENT result-type (param*)>
+<!ATTLIST result-type
+    name CDATA #REQUIRED
+    class CDATA #REQUIRED
+    default (true|false) "false"
+>
+
+<!ELEMENT interceptors (interceptor|interceptor-stack)+>
+
+<!ELEMENT interceptor (param*)>
+<!ATTLIST interceptor
+    name CDATA #REQUIRED
+    class CDATA #REQUIRED
+>
+
+<!ELEMENT interceptor-stack (interceptor-ref*)>
+<!ATTLIST interceptor-stack
+    name CDATA #REQUIRED
+>
+
+<!ELEMENT interceptor-ref (param*)>
+<!ATTLIST interceptor-ref
+    name CDATA #REQUIRED
+>
+
+<!ELEMENT default-interceptor-ref (#PCDATA)>
+<!ATTLIST default-interceptor-ref
+    name CDATA #REQUIRED
+>
+
+<!ELEMENT default-action-ref (#PCDATA)>
+<!ATTLIST default-action-ref
+    name CDATA #REQUIRED
+>
+
+<!ELEMENT default-class-ref (#PCDATA)>
+<!ATTLIST default-class-ref
+    class CDATA #REQUIRED
+>
+
+<!ELEMENT global-results (result+)>
+
+<!ELEMENT global-allowed-methods (#PCDATA)>
+
+<!ELEMENT global-exception-mappings (exception-mapping+)>
+
+<!ELEMENT action ((param|result|interceptor-ref|exception-mapping)*,allowed-methods?)>
+<!ATTLIST action
+    name CDATA #REQUIRED
+    class CDATA #IMPLIED
+    method CDATA #IMPLIED
+    converter CDATA #IMPLIED
+>
+
+<!ELEMENT param (#PCDATA)>
+<!ATTLIST param
+    name CDATA #REQUIRED
+>
+
+<!ELEMENT result (#PCDATA|param)*>
+<!ATTLIST result
+    name CDATA #IMPLIED
+    type CDATA #IMPLIED
+>
+
+<!ELEMENT exception-mapping (#PCDATA|param)*>
+<!ATTLIST exception-mapping
+    name CDATA #IMPLIED
+    exception CDATA #REQUIRED
+    result CDATA #REQUIRED
+>
+
+<!ELEMENT allowed-methods (#PCDATA)>
+
+<!ELEMENT include (#PCDATA)>
+<!ATTLIST include
+    file CDATA #REQUIRED
+>
+
+<!ELEMENT bean (#PCDATA)>
+<!ATTLIST bean
+    type CDATA #IMPLIED
+    name CDATA #IMPLIED
+    class CDATA #REQUIRED
+    scope CDATA #IMPLIED
+    static CDATA #IMPLIED
+    optional CDATA #IMPLIED
+>
+
+<!ELEMENT bean-selection (#PCDATA)>
+<!ATTLIST bean-selection
+    name CDATA #IMPLIED
+    class CDATA #IMPLIED
+>
+
+<!ELEMENT constant (#PCDATA)>
+<!ATTLIST constant
+    name CDATA #REQUIRED
+    value CDATA #REQUIRED
+>
+
+<!ELEMENT unknown-handler-stack (unknown-handler-ref*)>
+<!ELEMENT unknown-handler-ref (#PCDATA)>
+<!ATTLIST unknown-handler-ref
+    name CDATA #REQUIRED
+>
+
+<!-- END SNIPPET: strutsDtd -->
+
diff --git a/core/src/main/resources/struts-excluded-classes.xml b/core/src/main/resources/struts-excluded-classes.xml
index f3f4f3f..58b89aa 100644
--- a/core/src/main/resources/struts-excluded-classes.xml
+++ b/core/src/main/resources/struts-excluded-classes.xml
@@ -92,6 +92,7 @@
                 org.apache.catalina.core,
                 org.apache.commons.beanutils,
                 org.apache.commons.collections,
+                org.apache.jasper,
                 org.apache.struts2.ognl,
                 org.apache.tomcat,
                 org.apache.velocity,
@@ -124,6 +125,7 @@
                 org.apache.catalina.core,
                 org.apache.commons.beanutils,
                 org.apache.commons.collections,
+                org.apache.jasper,
                 org.apache.struts2.ognl,
                 org.apache.tomcat,
                 org.apache.velocity,
diff --git a/core/src/test/java/com/opensymphony/xwork2/config/ConfigurationTest.java b/core/src/test/java/com/opensymphony/xwork2/config/ConfigurationTest.java
index b52b9d4..520f8c2 100644
--- a/core/src/test/java/com/opensymphony/xwork2/config/ConfigurationTest.java
+++ b/core/src/test/java/com/opensymphony/xwork2/config/ConfigurationTest.java
@@ -31,6 +31,7 @@
 import com.opensymphony.xwork2.mock.MockInterceptor;
 import com.opensymphony.xwork2.test.StubConfigurationProvider;
 import com.opensymphony.xwork2.util.location.LocatableProperties;
+import org.apache.struts2.StrutsConstants;
 import org.apache.struts2.config.StrutsXmlConfigurationProvider;
 import org.apache.struts2.dispatcher.HttpParameters;
 
@@ -239,6 +240,41 @@
         mockContainerProvider.verify();
     }
 
+    public void testGetActionConfigFallbackToEmptyNamespaceWhenNamespaceDontMatchAndEmptyNamespaceFallbackIsEnabled() {
+        // struts.actionConfig.fallbackToEmptyNamespace default to true, so it is enabled
+        RuntimeConfiguration configuration = configurationManager.getConfiguration().getRuntimeConfiguration();
+
+        // check namespace that doesn't match fallback to empty namespace
+        ActionConfig actionConfig = configuration.getActionConfig("/something/that/is/not/in/the/namespace/config", "LazyFoo");
+        assertEquals("default", actionConfig.getPackageName()); // fallback to empty namespace (package name is default)
+        assertEquals("LazyFoo", actionConfig.getName());
+
+        // check non-empty namespace and name in config still matches
+        assertNotNull(configuration.getActionConfig("includeTest", "Foo"));
+
+        // check root namespace and name in config still matches
+        actionConfig = configuration.getActionConfig("/", "LazyFoo");
+        assertEquals("default", actionConfig.getPackageName());
+        assertEquals("LazyFoo", actionConfig.getName());
+    }
+
+    public void testGetActionConfigReturnNullWhenNamespaceDontMatchAndEmptyNamespaceFallbackIsDisabled() {
+        // set the struts.actionConfig.fallbackToEmptyNamespace to false and reload the configuration
+        setStrutsConstant(StrutsConstants.STRUTS_ACTION_CONFIG_FALLBACK_TO_EMPTY_NAMESPACE, "false");
+        RuntimeConfiguration configuration = configurationManager.getConfiguration().getRuntimeConfiguration();
+
+        // check namespace that doesn't match NOT fallback to empty namespace and return null
+        assertNull(configuration.getActionConfig("/something/that/is/not/in/the/namespace/config", "LazyFoo"));
+
+        // check non-empty namespace and name in config still matches
+        assertNotNull(configuration.getActionConfig("includeTest", "Foo"));
+
+        // check root namespace and name in config still matches
+        ActionConfig actionConfig = configuration.getActionConfig("/", "LazyFoo");
+        assertEquals("default", actionConfig.getPackageName());
+        assertEquals("LazyFoo", actionConfig.getName());
+    }
+
     public void testInitForPackageProviders() {
 
         loadConfigurationProviders(new StubConfigurationProvider() {
diff --git a/core/src/test/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProviderPackagesTest.java b/core/src/test/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProviderPackagesTest.java
index b39baec..ed8910b 100644
--- a/core/src/test/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProviderPackagesTest.java
+++ b/core/src/test/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProviderPackagesTest.java
@@ -36,10 +36,9 @@
 public class XmlConfigurationProviderPackagesTest extends ConfigurationTestBase {
 
     public void testBadInheritance() throws ConfigurationException {
-        final String filename = "com/opensymphony/xwork2/config/providers/xwork-test-bad-inheritance.xml";
         ConfigurationProvider provider = null;
         try {
-	    	provider = buildConfigurationProvider(filename);
+	    	provider = buildConfigurationProvider(getXmlConfigFilePath("xwork-test-bad-inheritance.xml"));
 	    	fail("Should have thrown a ConfigurationException");
 	        provider.init(configuration);
 	        provider.loadPackages();
@@ -49,8 +48,7 @@
     }
 
     public void testBasicPackages() throws ConfigurationException {
-        final String filename = "com/opensymphony/xwork2/config/providers/xwork-test-basic-packages.xml";
-        ConfigurationProvider provider = buildConfigurationProvider(filename);
+        ConfigurationProvider provider = buildConfigurationProvider(getXmlConfigFilePath("xwork-test-basic-packages.xml"));
         provider.init(configuration);
         provider.loadPackages();
 
@@ -70,8 +68,7 @@
     }
 
     public void testDefaultPackage() throws ConfigurationException {
-        final String filename = "com/opensymphony/xwork2/config/providers/xwork-test-default-package.xml";
-        ConfigurationProvider provider = buildConfigurationProvider(filename);
+        ConfigurationProvider provider = buildConfigurationProvider(getXmlConfigFilePath("xwork-test-default-package.xml"));
         provider.init(configuration);
         provider.loadPackages();
 
@@ -84,8 +81,7 @@
     }
 
     public void testPackageInheritance() throws ConfigurationException {
-        final String filename = "com/opensymphony/xwork2/config/providers/xwork-test-package-inheritance.xml";
-        ConfigurationProvider provider = buildConfigurationProvider(filename);
+        ConfigurationProvider provider = buildConfigurationProvider(getXmlConfigFilePath("xwork-test-package-inheritance.xml"));
 
         provider.init(configuration);
         provider.loadPackages();
@@ -111,7 +107,7 @@
         assertTrue(multipleParents.contains(defaultPackage));
         assertTrue(multipleParents.contains(abstractPackage));
         assertTrue(multipleParents.contains(singlePackage));
-        
+
         PackageConfig parentBelow = configuration.getPackageConfig("testParentBelow");
         assertEquals(1, parentBelow.getParents().size());
         List<PackageConfig> parentBelowParents = parentBelow.getParents();
@@ -129,7 +125,7 @@
         assertNull(runtimeConfiguration.getActionConfig("/single", "abstract"));
         assertNotNull(runtimeConfiguration.getActionConfig("/single", "single"));
         assertNull(runtimeConfiguration.getActionConfig("/single", "multiple"));
-        
+
         assertNotNull(runtimeConfiguration.getActionConfig("/parentBelow", "default"));
         assertNotNull(runtimeConfiguration.getActionConfig("/parentBelow", "abstract"));
         assertNotNull(runtimeConfiguration.getActionConfig("/parentBelow", "single"));
@@ -138,13 +134,57 @@
 
     }
 
+    public void testPackageWithFinalAttributeLoads() throws ConfigurationException {
+        ConfigurationProvider provider = buildConfigurationProvider(getXmlConfigFilePath("xwork-test-package-final.xml"));
+
+        provider.init(configuration);
+        provider.loadPackages();
+
+        // test expectations
+        assertEquals(3, configuration.getPackageConfigs().size());
+        PackageConfig defaultPackage = configuration.getPackageConfig("default");
+        assertNotNull(defaultPackage);
+        assertEquals("default", defaultPackage.getName());
+
+        // final package extends default
+        PackageConfig finalPackage = configuration.getPackageConfig("finalPackage");
+        assertNotNull(finalPackage);
+        assertEquals("finalPackage", finalPackage.getName());
+        assertEquals(1, finalPackage.getParents().size());
+        assertEquals(defaultPackage, finalPackage.getParents().get(0));
+
+        // normal package extends default
+        PackageConfig normalPackage = configuration.getPackageConfig("normalPackage");
+        assertNotNull(normalPackage);
+        assertEquals("normalPackage", normalPackage.getName());
+        assertEquals(1, normalPackage.getParents().size());
+        assertEquals(defaultPackage, normalPackage.getParents().get(0));
+
+        configurationManager.addContainerProvider(provider);
+        configurationManager.reload();
+
+        RuntimeConfiguration runtimeConfiguration = configurationManager.getConfiguration().getRuntimeConfiguration();
+        assertNotNull(runtimeConfiguration.getActionConfig("/final", "default"));
+        assertNotNull(runtimeConfiguration.getActionConfig("/final", "actionFinal"));
+
+        assertNotNull(runtimeConfiguration.getActionConfig("/normal", "default"));
+        assertNotNull(runtimeConfiguration.getActionConfig("/normal", "actionNormal"));
+    }
+
+    public void testExtendsFinalPackageThrowsConfigurationException() throws ConfigurationException {
+        try {
+            buildConfigurationProvider(getXmlConfigFilePath("xwork-test-package-extends-final.xml"));
+        } catch (ConfigurationException e) {
+            assertEquals("Parent package is final and unextendable: parentLevelTwo", e.getMessage());
+        }
+    }
+
     public void testDefaultClassRef() throws ConfigurationException {
-    	final String filename = "com/opensymphony/xwork2/config/providers/xwork-test-defaultclassref-package.xml";
         final String hasDefaultClassRefPkgName = "hasDefaultClassRef";
         final String noDefaultClassRefPkgName = "noDefaultClassRef";
         final String testDefaultClassRef = "com.opensymphony.xwork2.ActionSupport";
 
-    	ConfigurationProvider provider = buildConfigurationProvider(filename);
+        ConfigurationProvider provider = buildConfigurationProvider(getXmlConfigFilePath("xwork-test-defaultclassref-package.xml"));
         provider.init(configuration);
 
         // setup our expectations
@@ -157,4 +197,8 @@
         assertEquals(expectedDefaultClassRefPackage, configuration.getPackageConfig(hasDefaultClassRefPkgName));
         assertEquals(expectedNoDefaultClassRefPackage, configuration.getPackageConfig(noDefaultClassRefPkgName));
     }
+
+    private String getXmlConfigFilePath(String fileName) {
+        return "com/opensymphony/xwork2/config/providers/" + fileName;
+    }
 }
diff --git a/core/src/test/java/com/opensymphony/xwork2/ognl/OgnlValueStackTest.java b/core/src/test/java/com/opensymphony/xwork2/ognl/OgnlValueStackTest.java
index 3bdfd67..7fb560c 100644
--- a/core/src/test/java/com/opensymphony/xwork2/ognl/OgnlValueStackTest.java
+++ b/core/src/test/java/com/opensymphony/xwork2/ognl/OgnlValueStackTest.java
@@ -437,12 +437,12 @@
     }
 
     /**
-     * Allow access Enums without enabling access to static methods
+     * Enum methods should also be banned alongside static methods
      */
     public void testEnum() throws Exception {
-        assertEquals("ONE", vs.findValue("@com.opensymphony.xwork2.ognl.MyNumbers@values()[0]", String.class));
-        assertEquals("TWO", vs.findValue("@com.opensymphony.xwork2.ognl.MyNumbers@values()[1]", String.class));
-        assertEquals("THREE", vs.findValue("@com.opensymphony.xwork2.ognl.MyNumbers@values()[2]", String.class));
+        assertNull("ONE", vs.findValue("@com.opensymphony.xwork2.ognl.MyNumbers@values()[0]", String.class));
+        assertNull("TWO", vs.findValue("@com.opensymphony.xwork2.ognl.MyNumbers@values()[1]", String.class));
+        assertNull("THREE", vs.findValue("@com.opensymphony.xwork2.ognl.MyNumbers@values()[2]", String.class));
     }
 
     public void testStaticMethodDisallow() {
diff --git a/core/src/test/java/com/opensymphony/xwork2/ognl/SecurityMemberAccessTest.java b/core/src/test/java/com/opensymphony/xwork2/ognl/SecurityMemberAccessTest.java
index 03bad82..381b7d0 100644
--- a/core/src/test/java/com/opensymphony/xwork2/ognl/SecurityMemberAccessTest.java
+++ b/core/src/test/java/com/opensymphony/xwork2/ognl/SecurityMemberAccessTest.java
@@ -413,7 +413,7 @@
         boolean actual = sma.isAccessible(context, MyValues.class, values, null);
 
         // then
-        assertTrue("Access to enums is blocked!", actual);
+        assertFalse("Access to enums is allowed!", actual);
     }
 
     @Test
diff --git a/core/src/test/java/org/apache/struts2/dispatcher/DispatcherTest.java b/core/src/test/java/org/apache/struts2/dispatcher/DispatcherTest.java
index 8884667..54b2a96 100644
--- a/core/src/test/java/org/apache/struts2/dispatcher/DispatcherTest.java
+++ b/core/src/test/java/org/apache/struts2/dispatcher/DispatcherTest.java
@@ -54,10 +54,12 @@
 import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
+import java.util.regex.Pattern;
 
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static java.util.Collections.emptyMap;
 import static java.util.Collections.singletonMap;
+import static org.assertj.core.api.Assertions.assertThat;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -588,6 +590,30 @@
         assertEquals(Locale.CANADA_FRENCH, dispatcher.getLocale(request));
     }
 
+    @Test
+    public void testExcludePatterns() {
+        initDispatcher(singletonMap(StrutsConstants.STRUTS_ACTION_EXCLUDE_PATTERN, "/ns1/.*\\.json,/ns2/.*\\.json"));
+
+        assertThat(dispatcher.getActionExcludedPatterns()).extracting(Pattern::toString).containsOnly(
+                "/ns1/.*\\.json",
+                "/ns2/.*\\.json"
+        );
+    }
+
+    @Test
+    public void testExcludePatternsUsingCustomSeparator() {
+        Map<String, String> props = new HashMap<>();
+        props.put(StrutsConstants.STRUTS_ACTION_EXCLUDE_PATTERN, "/ns1/[a-z]{1,10}.json///ns2/[a-z]{1,10}.json");
+        props.put(StrutsConstants.STRUTS_ACTION_EXCLUDE_PATTERN_SEPARATOR, "//");
+
+        initDispatcher(props);
+
+        assertThat(dispatcher.getActionExcludedPatterns()).extracting(Pattern::toString).containsOnly(
+                "/ns1/[a-z]{1,10}.json",
+                "/ns2/[a-z]{1,10}.json"
+        );
+    }
+
     public static Dispatcher spyDispatcherWithConfigurationManager(Dispatcher dispatcher, ConfigurationManager configurationManager) {
         Dispatcher spiedDispatcher = spy(dispatcher);
         doReturn(configurationManager).when(spiedDispatcher).createConfigurationManager(any());
diff --git a/core/src/test/java/org/apache/struts2/dispatcher/InitOperationsTest.java b/core/src/test/java/org/apache/struts2/dispatcher/InitOperationsTest.java
deleted file mode 100644
index aa2aaea..0000000
--- a/core/src/test/java/org/apache/struts2/dispatcher/InitOperationsTest.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you 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 org.apache.struts2.dispatcher;
-
-import com.opensymphony.xwork2.config.ConfigurationException;
-import com.opensymphony.xwork2.inject.ContainerBuilder;
-import com.opensymphony.xwork2.util.location.LocatableProperties;
-import org.apache.struts2.StrutsConstants;
-import org.apache.struts2.StrutsInternalTestCase;
-import org.apache.struts2.config.PropertiesConfigurationProvider;
-
-import java.util.List;
-import java.util.regex.Pattern;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-public class InitOperationsTest extends StrutsInternalTestCase {
-
-    public void testExcludePatterns() {
-        // given
-        loadConfigurationProviders(new PropertiesConfigurationProvider() {
-            @Override
-            public void register(ContainerBuilder builder, LocatableProperties props) throws ConfigurationException {
-                props.setProperty(StrutsConstants.STRUTS_ACTION_EXCLUDE_PATTERN, "/ns1/.*\\.json,/ns2/.*\\.json");
-            }
-        });
-
-        Dispatcher mockDispatcher = mock(Dispatcher.class);
-        when(mockDispatcher.getContainer()).thenReturn(container);
-
-        // when
-        InitOperations init = new InitOperations();
-        List<Pattern> patterns = init.buildExcludedPatternsList(mockDispatcher);
-
-        // then
-        assertThat(patterns).extracting(Pattern::toString).containsOnly(
-            "/ns1/.*\\.json",
-            "/ns2/.*\\.json"
-        );
-    }
-
-    public void testExcludePatternsUsingCustomSeparator() {
-        // given
-        loadConfigurationProviders(new PropertiesConfigurationProvider() {
-            @Override
-            public void register(ContainerBuilder builder, LocatableProperties props) throws ConfigurationException {
-                props.setProperty(StrutsConstants.STRUTS_ACTION_EXCLUDE_PATTERN, "/ns1/[a-z]{1,10}.json///ns2/[a-z]{1,10}.json");
-                props.setProperty(StrutsConstants.STRUTS_ACTION_EXCLUDE_PATTERN_SEPARATOR, "//");
-            }
-        });
-
-        Dispatcher mockDispatcher = mock(Dispatcher.class);
-        when(mockDispatcher.getContainer()).thenReturn(container);
-
-        // when
-        InitOperations init = new InitOperations();
-
-        String separator = container.getInstance(String.class, StrutsConstants.STRUTS_ACTION_EXCLUDE_PATTERN_SEPARATOR);
-        List<Pattern> patterns = init.buildExcludedPatternsList(mockDispatcher);
-
-        // then
-        assertThat(separator).isNotBlank().isEqualTo("//");
-        assertThat(patterns).extracting(Pattern::toString).containsOnly(
-            "/ns1/[a-z]{1,10}.json",
-            "/ns2/[a-z]{1,10}.json"
-        );
-    }
-}
diff --git a/core/src/test/java/org/apache/struts2/dispatcher/StrutsPrepareAndExecuteFilterIntegrationTest.java b/core/src/test/java/org/apache/struts2/dispatcher/StrutsPrepareAndExecuteFilterIntegrationTest.java
index eb87235..c7d5257 100644
--- a/core/src/test/java/org/apache/struts2/dispatcher/StrutsPrepareAndExecuteFilterIntegrationTest.java
+++ b/core/src/test/java/org/apache/struts2/dispatcher/StrutsPrepareAndExecuteFilterIntegrationTest.java
@@ -26,13 +26,10 @@
 import org.springframework.mock.web.MockHttpServletRequest;
 import org.springframework.mock.web.MockHttpServletResponse;
 
-import javax.servlet.FilterConfig;
 import javax.servlet.ServletException;
 import javax.servlet.ServletRequest;
 import javax.servlet.ServletResponse;
 import java.io.IOException;
-import java.util.ArrayList;
-import java.util.regex.Pattern;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
@@ -127,6 +124,7 @@
         MockHttpServletRequest request = new MockHttpServletRequest();
         MockHttpServletResponse response = new MockHttpServletResponse();
         MockFilterConfig filterConfig = new MockFilterConfig();
+        filterConfig.addInitParameter("struts.action.excludePattern", ".*hello.*");
         MockFilterChain filterChain = new MockFilterChain() {
             @Override
             public void doFilter(ServletRequest req, ServletResponse res) {
@@ -135,14 +133,7 @@
         };
 
         request.setRequestURI("/hello.action");
-        StrutsPrepareAndExecuteFilter filter = new StrutsPrepareAndExecuteFilter() {
-            @Override
-            public void init( FilterConfig filterConfig ) throws ServletException {
-                super.init(filterConfig);
-                excludedPatterns = new ArrayList<>();
-                excludedPatterns.add(Pattern.compile(".*hello.*"));
-            }
-        };
+        StrutsPrepareAndExecuteFilter filter = new StrutsPrepareAndExecuteFilter();
         filter.init(filterConfig);
         filter.doFilter(request, response, filterChain);
         assertEquals(200, response.getStatus());
diff --git a/core/src/test/java/org/apache/struts2/ognl/OgnlSetPossiblePropertyTest.java b/core/src/test/java/org/apache/struts2/ognl/OgnlSetPossiblePropertyTest.java
new file mode 100644
index 0000000..546d2fd
--- /dev/null
+++ b/core/src/test/java/org/apache/struts2/ognl/OgnlSetPossiblePropertyTest.java
@@ -0,0 +1,240 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.struts2.ognl;
+
+import com.opensymphony.xwork2.XWorkTestCase;
+import com.opensymphony.xwork2.ognl.OgnlValueStack;
+import com.opensymphony.xwork2.util.ValueStackFactory;
+import ognl.OgnlRuntime;
+import org.apache.struts2.StrutsConstants;
+
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.Assert.assertNotEquals;
+
+public class OgnlSetPossiblePropertyTest extends XWorkTestCase {
+    private OgnlValueStack vs;
+
+    public <T> T setUpClass(Class<T> holderClass) throws Exception {
+        Map<String, String> properties = new HashMap<>();
+        properties.put(StrutsConstants.STRUTS_EXCLUDED_CLASSES, holderClass.getName() + "$ExcludedField");
+        loadButSet(properties);
+        vs = (OgnlValueStack) container.getInstance(ValueStackFactory.class).createValueStack();
+
+        T nonExcludedHolder = holderClass.getDeclaredConstructor().newInstance();
+        vs.push(nonExcludedHolder);
+
+        return nonExcludedHolder;
+    }
+
+    public void testSetFieldValueDontAssignWhenHolderClassAndFieldClassHaveOnlyPublicFields() throws Exception {
+        /* Case: to test setFieldValue without having set method
+         *
+         *  NonExcludedHolder class
+         *   - field: public
+         *  ExcludeField class
+         *   - field: public
+         */
+        HolderWithPublicField holder = setUpClass(HolderWithPublicField.class);
+        vs.setValue("excludedField.excludedFieldString", "EXPLOITED");
+
+        assertNotEquals("EXPLOITED", holder.excludedField.excludedFieldString);
+    }
+
+    public void testSetMethodValueDontAssignWhenHolderAndFieldClassWithPublicMethodsAndPrivateFields() throws Exception {
+        /* Case: to test setMethodValue, so to make fields as private
+         *
+         *  NonExcludedHolder class
+         *   - field: private
+         *   - method: public
+         *  ExcludeField class
+         *   - field: private
+         *   - method: public
+         */
+        HolderWithPublicMethod holder = setUpClass(HolderWithPublicMethod.class);
+        vs.setValue("excludedField.excludedFieldString", "EXPLOITED");
+
+        assertNotEquals("EXPLOITED", holder.excludedField.excludedFieldString);
+    }
+
+    public void testSetFieldValueDontAssignWhenHolderClassWithGetMethodAndFieldClassWithPublicField() throws Exception {
+        /* Case: to test setFieldValue when holder get method is public and field class set method is private so fallback to set field
+         *
+         *  NonExcludedHolder class
+         *   - field: private
+         *   - method: public
+         *  ExcludeField class
+         *   - field: public
+         *   - method: private
+         */
+        HolderWhoseFieldWithPrivateMethod holder = setUpClass(HolderWhoseFieldWithPrivateMethod.class);
+        vs.setValue("excludedField.excludedFieldString", "EXPLOITED");
+
+        assertNotEquals("EXPLOITED", holder.excludedField.excludedFieldString);
+    }
+
+    public void testSetMethodValueDontAssignWhenHolderClassWithGetMethodAndFieldClassWithPublicMethod() throws Exception {
+        /* Case: to test setMethodValue when holder get method is public and field class field is private so only call to set method
+         *
+         *  NonExcludedHolder class
+         *   - field: private
+         *   - method: public
+         *  ExcludeField class
+         *   - field: private
+         *   - method: public
+         */
+        HolderWhoseFieldWithPublicMethod holder = setUpClass(HolderWhoseFieldWithPublicMethod.class);
+        vs.setValue("excludedField.excludedFieldString", "EXPLOITED");
+
+        assertNotEquals("EXPLOITED", holder.excludedField.excludedFieldString);
+    }
+
+    public void testWriteMethodValueDontAssignWhenWriteMethodIsNotAccessible() throws Exception {
+        /* Case: to test invoke method from getWriteMethod when holder get method is public and field class field / set method is private so fallback to write method
+         *
+         *  NonExcludedHolder class
+         *   - field: private
+         *   - method: public
+         *  ExcludeField class
+         *   - field: private
+         *   - set method: private
+         *   - write method: public
+         */
+        HolderWhoseFieldWithPublicWriteMethod holder = setUpClass(HolderWhoseFieldWithPublicWriteMethod.class);
+        Method writeMethod = OgnlRuntime.getWriteMethod(HolderWhoseFieldWithPublicWriteMethod.ExcludedField.class, "excludedFieldString");
+        vs.setValue("excludedField.excludedFieldString", "EXPLOITED");
+
+        assertEquals("setexcludedfieldstring", writeMethod.getName());
+        assertNotEquals("EXPLOITED", holder.excludedField.excludedFieldString);
+    }
+
+    public void testWriteMethodValueDontAssignWhenPublicSetterDifferentFieldName() throws Exception {
+        /* Case: to test invoke method from getWriteMethod when holder get method is public and field class field / set method is of different name
+         *
+         *  NonExcludedHolder class
+         *   - field: private
+         *   - method: public
+         *  ExcludeField class
+         *   - field: private
+         *   - set method: public (but not matching with field name)
+         */
+        HolderWhoseFieldWithPublicSetterDifferentFieldName holder = setUpClass(HolderWhoseFieldWithPublicSetterDifferentFieldName.class);
+        vs.setValue("excludedField.excludedFieldString", "EXPLOITED");
+
+        assertNotEquals("EXPLOITED", holder.excludedField.excludedFieldStringInternal);
+    }
+
+
+    public static class HolderWithPublicField {
+        public ExcludedField excludedField = new ExcludedField();
+
+        public static class ExcludedField {
+            public String excludedFieldString = "defaultValue";
+        }
+    }
+
+    public static class HolderWithPublicMethod {
+        private ExcludedField excludedField = new ExcludedField();
+
+        public ExcludedField getExcludedField() {
+            return excludedField;
+        }
+
+        public static class ExcludedField {
+            private String excludedFieldString = "defaultValue";
+
+            public void setExcludedFieldString(String value) {
+                this.excludedFieldString = value;
+            }
+        }
+    }
+
+    public static class HolderWhoseFieldWithPrivateMethod {
+        private ExcludedField excludedField = new ExcludedField();
+
+
+        public ExcludedField getExcludedField() {
+            return excludedField;
+        }
+
+        public static class ExcludedField {
+            public String excludedFieldString = "defaultValue";
+
+            private void setExcludedFieldString(String value) {
+                this.excludedFieldString = value;
+            }
+        }
+    }
+
+    public static class HolderWhoseFieldWithPublicMethod {
+        private ExcludedField excludedField = new ExcludedField();
+
+
+        public ExcludedField getExcludedField() {
+            return excludedField;
+        }
+
+        public static class ExcludedField {
+            public String excludedFieldString = "defaultValue";
+
+            private void setExcludedFieldString(String value) {
+                this.excludedFieldString = value;
+            }
+        }
+    }
+
+    public static class HolderWhoseFieldWithPublicWriteMethod {
+        private ExcludedField excludedField = new ExcludedField();
+
+
+        public ExcludedField getExcludedField() {
+            return excludedField;
+        }
+
+        public static class ExcludedField {
+            private String excludedFieldString = "defaultValue";
+
+            private void setExcludedFieldString(String value) {
+                this.excludedFieldString = value;
+            }
+
+            public void setexcludedfieldstring(String value) {
+                this.excludedFieldString = value;
+            }
+        }
+    }
+
+    public static class HolderWhoseFieldWithPublicSetterDifferentFieldName {
+        private ExcludedField excludedField = new ExcludedField();
+
+        public ExcludedField getExcludedField() {
+            return excludedField;
+        }
+
+        public static class ExcludedField {
+            private String excludedFieldStringInternal = "defaultValue";
+
+            public void setExcludedFieldString(String value) {
+                this.excludedFieldStringInternal = value;
+            }
+        }
+    }
+}
diff --git a/core/src/test/java/org/apache/struts2/views/jsp/ui/DebugTagTest.java b/core/src/test/java/org/apache/struts2/views/jsp/ui/DebugTagTest.java
index 7f4b545..b7db515 100644
--- a/core/src/test/java/org/apache/struts2/views/jsp/ui/DebugTagTest.java
+++ b/core/src/test/java/org/apache/struts2/views/jsp/ui/DebugTagTest.java
@@ -217,23 +217,9 @@
     /**
      * Overwrite the Struts Constant and reload container
      */
-    private void setStrutsConstant(final Map<String, String> overwritePropeties) {
-        configurationManager.addContainerProvider(new StubConfigurationProvider() {
-            @Override
-            public boolean needsReload() {
-                return true;
-            }
-
-            @Override
-            public void register(ContainerBuilder builder, LocatableProperties props) throws ConfigurationException {
-                for (Map.Entry<String, String> stringStringEntry : overwritePropeties.entrySet()) {
-                    props.setProperty(stringStringEntry.getKey(), stringStringEntry.getValue(), null);
-                }
-            }
-        });
-
-        configurationManager.reload();
-        container = configurationManager.getConfiguration().getContainer();
+    @Override
+    protected void setStrutsConstant(final Map<String, String> overwritePropeties) {
+        super.setStrutsConstant(overwritePropeties);
         stack.getActionContext().withContainer(container);
     }
-}
\ No newline at end of file
+}
diff --git a/core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-package-extends-final.xml b/core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-package-extends-final.xml
new file mode 100644
index 0000000..4e42422
--- /dev/null
+++ b/core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-package-extends-final.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+-->
+<!DOCTYPE struts PUBLIC
+        "-//Apache Software Foundation//DTD Struts Configuration 6.5//EN"
+        "struts-6.5.dtd">
+<struts>
+    <package name="default" namespace="/default">
+        <action name="default" class="com.opensymphony.xwork2.ActionSupport"/>
+    </package>
+
+    <package name="parentLevelTwo" namespace="/parent2" final="true">
+        <action name="levelTwo" class="com.opensymphony.xwork2.ActionSupport"/>
+    </package>
+
+    <package name="child" namespace="/child" extends="default,parentLevelTwo">
+        <action name="single" class="com.opensymphony.xwork2.ActionSupport"/>
+    </package>
+</struts>
diff --git a/core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-package-final.xml b/core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-package-final.xml
new file mode 100644
index 0000000..2a49a15
--- /dev/null
+++ b/core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-package-final.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+-->
+<!DOCTYPE struts PUBLIC
+        "-//Apache Software Foundation//DTD Struts Configuration 6.5//EN"
+        "struts-6.5.dtd">
+<struts>
+    <package name="default" namespace="/default">
+        <action name="default" class="com.opensymphony.xwork2.ActionSupport"/>
+    </package>
+
+    <package name="finalPackage" namespace="/final" extends="default" final="true">
+        <action name="actionFinal" class="com.opensymphony.xwork2.ActionSupport"/>
+    </package>
+
+    <package name="normalPackage" namespace="/normal" extends="default">
+        <action name="actionNormal" class="com.opensymphony.xwork2.ActionSupport"/>
+    </package>
+</struts>
diff --git a/plugins/spring/src/test/java/com/opensymphony/xwork2/ognl/SecurityMemberAccessProxyTest.java b/plugins/spring/src/test/java/com/opensymphony/xwork2/ognl/SecurityMemberAccessProxyTest.java
index 4d8046d..3838ca9 100644
--- a/plugins/spring/src/test/java/com/opensymphony/xwork2/ognl/SecurityMemberAccessProxyTest.java
+++ b/plugins/spring/src/test/java/com/opensymphony/xwork2/ognl/SecurityMemberAccessProxyTest.java
@@ -29,6 +29,11 @@
 
 public class SecurityMemberAccessProxyTest extends XWorkTestCase {
     private Map<String, Object> context;
+    private ActionProxy proxy;
+    private Map<String, Member> members;
+    private final SecurityMemberAccess sma = new SecurityMemberAccess(true);
+    private final String PROXY_MEMBER_METHOD = "isExposeProxy";
+    private final String TEST_SUB_BEAN_CLASS_METHOD = "setIssueId";
 
     @Override
     public void setUp() throws Exception {
@@ -39,30 +44,51 @@
         XmlConfigurationProvider provider = new StrutsXmlConfigurationProvider("com/opensymphony/xwork2/spring/actionContext-xwork.xml");
         container.inject(provider);
         loadConfigurationProviders(provider);
+
+        // Setup proxy object
+        setupProxy();
     }
 
     public void testProxyAccessIsBlocked() throws Exception {
-        ActionProxy proxy = actionProxyFactory.createActionProxy(null,
-            "chaintoAOPedTestSubBeanAction", null, context);
+        members.values().forEach(member -> {
+            // When disallowProxyObjectAccess is set to true, and disallowProxyMemberAccess is set to false, the proxy access is blocked
+            sma.useDisallowProxyObjectAccess(Boolean.TRUE.toString());
+            sma.useDisallowProxyMemberAccess(Boolean.FALSE.toString());
+            assertFalse(sma.isAccessible(context, proxy.getAction(), member, ""));
 
-        SecurityMemberAccess sma = new SecurityMemberAccess(true);
+            // When disallowProxyObjectAccess is set to true, and disallowProxyMemberAccess is set to true, the proxy access is blocked
+            sma.useDisallowProxyObjectAccess(Boolean.TRUE.toString());
+            sma.useDisallowProxyMemberAccess(Boolean.TRUE.toString());
+            assertFalse(sma.isAccessible(context, proxy.getAction(), member, ""));
+        });
+
+        // When disallowProxyObjectAccess is set to false, and disallowProxyMemberAccess is set to true, the proxy member access is blocked
+        sma.useDisallowProxyObjectAccess(Boolean.FALSE.toString());
         sma.useDisallowProxyMemberAccess(Boolean.TRUE.toString());
-
-        Member member = proxy.getAction().getClass().getMethod("isExposeProxy");
-
-        boolean accessible = sma.isAccessible(context, proxy.getAction(), member, "");
-        assertFalse(accessible);
+        assertFalse(sma.isAccessible(context, proxy.getAction(), members.get(PROXY_MEMBER_METHOD), ""));
     }
 
     public void testProxyAccessIsAccessible() throws Exception {
-        ActionProxy proxy = actionProxyFactory.createActionProxy(null,
-            "chaintoAOPedTestSubBeanAction", null, context);
+        members.values().forEach(member -> {
+            // When disallowProxyObjectAccess is set to false, and disallowProxyMemberAccess is set to false, the proxy access is allowed
+            sma.useDisallowProxyObjectAccess(Boolean.FALSE.toString());
+            sma.useDisallowProxyMemberAccess(Boolean.FALSE.toString());
+            assertTrue(sma.isAccessible(context, proxy.getAction(), member, ""));
+        });
 
-        SecurityMemberAccess sma = new SecurityMemberAccess(true);
+        // When disallowProxyObjectAccess is set to false, and disallowProxyMemberAccess is set to true, the original class member access is allowed
+        sma.useDisallowProxyObjectAccess(Boolean.FALSE.toString());
+        sma.useDisallowProxyMemberAccess(Boolean.TRUE.toString());
+        assertTrue(sma.isAccessible(context, proxy.getAction(), members.get(TEST_SUB_BEAN_CLASS_METHOD), ""));
+    }
 
-        Member member = proxy.getAction().getClass().getMethod("isExposeProxy");
+    private void setupProxy() throws NoSuchMethodException {
+        proxy = actionProxyFactory.createActionProxy(null, "chaintoAOPedTestSubBeanAction", null, context);
 
-        boolean accessible = sma.isAccessible(context, proxy.getAction(), member, "");
-        assertTrue(accessible);
+        members = new HashMap<>();
+        // method is proxy member
+        members.put(PROXY_MEMBER_METHOD, proxy.getAction().getClass().getMethod(PROXY_MEMBER_METHOD));
+        // method is not proxy member but from POJO class
+        members.put(TEST_SUB_BEAN_CLASS_METHOD, proxy.getAction().getClass().getMethod(TEST_SUB_BEAN_CLASS_METHOD, String.class));
     }
 }
diff --git a/plugins/tiles/pom.xml b/plugins/tiles/pom.xml
index 618bc6a..208e50f 100644
--- a/plugins/tiles/pom.xml
+++ b/plugins/tiles/pom.xml
@@ -40,7 +40,7 @@
                     <plugin>
                         <groupId>org.codehaus.mojo</groupId>
                         <artifactId>exec-maven-plugin</artifactId>
-                        <version>3.1.0</version>
+                        <version>3.2.0</version>
                         <executions>
                           <execution>
                             <phase>compile</phase>
diff --git a/pom.xml b/pom.xml
index 34490d5..4ad0d73 100644
--- a/pom.xml
+++ b/pom.xml
@@ -109,10 +109,10 @@
         <maven.compiler.target>1.8</maven.compiler.target>
 
         <!-- dependency versions in alphanumeric order -->
-        <asm.version>9.6</asm.version>
+        <asm.version>9.7</asm.version>
         <jackson.version>2.16.1</jackson.version>
         <log4j2.version>2.23.1</log4j2.version>
-        <ognl.version>3.3.4</ognl.version>
+        <ognl.version>3.3.5</ognl.version>
         <slf4j.version>2.0.12</slf4j.version>
         <spring.platformVersion>5.3.31</spring.platformVersion>
         <tiles.version>3.0.8</tiles.version>
@@ -487,7 +487,7 @@
             <plugin>
                 <groupId>org.codehaus.mojo</groupId>
                 <artifactId>versions-maven-plugin</artifactId>
-                <version>2.16.1</version>
+                <version>2.16.2</version>
                 <reportSets>
                     <reportSet>
                         <reports>
@@ -978,7 +978,7 @@
             <dependency>
                 <groupId>org.assertj</groupId>
                 <artifactId>assertj-core</artifactId>
-                <version>3.25.2</version>
+                <version>3.25.3</version>
                 <scope>test</scope>
             </dependency>