This is an automated email from the ASF dual-hosted git repository.

lukaszlenart pushed a commit to branch WW-5626-cleanup
in repository https://gitbox.apache.org/repos/asf/struts.git

commit 2a0bcc5f572b5ef6d7d894fbd6529ee5edaa3dd9
Author: Lukasz Lenart <[email protected]>
AuthorDate: Mon May 4 12:50:26 2026 +0200

    WW-5626 defensively skip non-String JSON keys in authorization filter
    
    The (String) cast in filterUnauthorizedKeysRecursive threw 
ClassCastException
    for any custom JSONReader producing non-String keys. Replace with an 
instanceof
    pattern that debug-logs and skips entries whose key cannot be converted to a
    parameter path.
---
 .../org/apache/struts2/json/JSONInterceptor.java   |  9 +++++++-
 .../apache/struts2/json/JSONInterceptorTest.java   | 26 ++++++++++++++++++++++
 2 files changed, 34 insertions(+), 1 deletion(-)

diff --git 
a/plugins/json/src/main/java/org/apache/struts2/json/JSONInterceptor.java 
b/plugins/json/src/main/java/org/apache/struts2/json/JSONInterceptor.java
index a0279f2f0..d0acfd4ce 100644
--- a/plugins/json/src/main/java/org/apache/struts2/json/JSONInterceptor.java
+++ b/plugins/json/src/main/java/org/apache/struts2/json/JSONInterceptor.java
@@ -215,7 +215,14 @@ public class JSONInterceptor extends AbstractInterceptor {
         Iterator<Map.Entry> it = json.entrySet().iterator();
         while (it.hasNext()) {
             Map.Entry entry = it.next();
-            String key = (String) entry.getKey();
+            if (!(entry.getKey() instanceof String key)) {
+                // Defensive: a custom JSONReader could produce non-String 
keys. Skip — we cannot
+                // construct a parameter path for authorization, and 
JSONPopulator wouldn't bind
+                // these to bean properties anyway.
+                LOG.debug("Skipping JSON entry with non-String key [{}] of 
type [{}] under prefix [{}]",
+                        entry.getKey(), entry.getKey() == null ? "null" : 
entry.getKey().getClass().getName(), prefix);
+                continue;
+            }
             String fullPath = prefix.isEmpty() ? key : prefix + "." + key;
 
             if (!parameterAuthorizer.isAuthorized(fullPath, target, action)) {
diff --git 
a/plugins/json/src/test/java/org/apache/struts2/json/JSONInterceptorTest.java 
b/plugins/json/src/test/java/org/apache/struts2/json/JSONInterceptorTest.java
index 2203f6f29..ece2f1272 100644
--- 
a/plugins/json/src/test/java/org/apache/struts2/json/JSONInterceptorTest.java
+++ 
b/plugins/json/src/test/java/org/apache/struts2/json/JSONInterceptorTest.java
@@ -583,6 +583,32 @@ public class JSONInterceptorTest extends StrutsTestCase {
         assertNull(action.getBar());
     }
 
+    public void testNonStringKeysAreSkippedByAuthorizationFilter() throws 
Exception {
+        // Simulate a custom JSON reader producing a Map with a non-String key.
+        // The authorizer should skip the entry rather than throw 
ClassCastException.
+        JSONInterceptor interceptor = new JSONInterceptor();
+        JSONUtil jsonUtil = new JSONUtil();
+        jsonUtil.setReader(new StrutsJSONReader());
+        jsonUtil.setWriter(new StrutsJSONWriter());
+        interceptor.setJsonUtil(jsonUtil);
+        interceptor.setParameterAuthorizer((parameterName, target, action) -> 
true);
+
+        java.util.Map<Object, Object> mixedKeyMap = new 
java.util.LinkedHashMap<>();
+        mixedKeyMap.put("validKey", "ok");
+        mixedKeyMap.put(42, "shouldBeSkipped"); // Integer key, not String
+
+        java.lang.reflect.Method method = 
JSONInterceptor.class.getDeclaredMethod(
+                "filterUnauthorizedKeys", java.util.Map.class, Object.class, 
Object.class);
+        method.setAccessible(true);
+
+        // Should not throw ClassCastException
+        method.invoke(interceptor, mixedKeyMap, new TestAction(), new 
TestAction());
+
+        // The non-String key entry should still be present (skipped, not 
removed)
+        assertTrue("non-String-key entry should remain (skipped, not 
removed)", mixedKeyMap.containsKey(42));
+        assertTrue("String-key entry should remain", 
mixedKeyMap.containsKey("validKey"));
+    }
+
     public void testParameterAuthorizerAllowsAllWhenPermissive() throws 
Exception {
         // Same JSON body, but authorizer allows all
         this.request.setContent("{\"foo\":\"value1\", 
\"bar\":\"value2\"}".getBytes());

Reply via email to