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

tkobayas pushed a commit to branch 10.2.x
in repository https://gitbox.apache.org/repos/asf/incubator-kie-drools.git


The following commit(s) were added to refs/heads/10.2.x by this push:
     new 1f686f32d08 improve protobuf session validation with deserialization 
(#6822)
1f686f32d08 is described below

commit 1f686f32d08809188714a00b1fef5bd8fe4748ac
Author: Toshiya Kobayashi <[email protected]>
AuthorDate: Thu Jul 23 22:26:20 2026 +0900

    improve protobuf session validation with deserialization (#6822)
    
    Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
---
 .../base/common/DroolsObjectInputStream.java       |   2 +-
 .../SerializablePlaceholderResolverStrategy.java   |   4 +
 .../core/util/DeserializationFilterHelper.java     | 150 ++++++
 .../org/drools/core/util/KeyStoreConstants.java    |   5 +
 .../marshaller/JPAPlaceholderResolverStrategy.java |  17 +-
 .../DeserializationFilterTestSupport.java          |  40 ++
 .../session/JpaPersistentStatefulSessionTest.java  |  17 +-
 .../map/impl/JpaBasedPersistenceTest.java          |   5 +-
 .../map/impl/MapBasedPersistenceTest.java          |  11 +-
 .../session/JpaPersistentStatefulSessionTest.java  |   3 +
 .../integrationtests/TimerAndCalendarTest.java     |   7 +-
 drools-serialization-protobuf/pom.xml              |   7 +-
 .../serialization/protobuf/PersisterHelper.java    |  40 +-
 .../JavaSerializableResolverStrategy.java          |   6 +-
 .../protobuf/DeserializationFilterTestSupport.java |  40 ++
 .../serialization/protobuf/MarshallerTest.java     |  14 +
 .../serialization/protobuf/MarshallingTest.java    |  28 +
 .../protobuf/MarshallingValidationCheckTest.java   | 566 +++++++++++++++++++++
 .../drools/serialization/protobuf/QueryTest.java   |  11 +
 .../protobuf/SerializationHelper.java              |  21 +
 .../protobuf/TruthMaintenanceTest.java             |  16 +
 .../serialization/protobuf/UnmarshallingTest.java  |  12 +
 .../testdata/enhancement/DeserializationProbe.java |  33 ++
 .../test/java/testdata/enhancement/EnumProbe.java  |  29 ++
 .../java/testdata/enhancement/StaticInitProbe.java |  34 ++
 .../src/test/resources/logback-test.xml            |  31 ++
 .../integrationtests/BackwardChainingTest.java     |  21 +
 .../compiler/integrationtests/CepEspTest.java      |  15 +
 .../IncrementalCompilationTest.java                |  13 +
 .../regression/EventDeserializationInPastTest.java |  11 +
 .../LogicalInsertionsSerializationTest.java        |  12 +
 .../factmodel/traits/LogicalTraitTest.java         |  86 ++++
 .../traits/TraitMarshallingValidationTest.java     | 133 +++++
 33 files changed, 1412 insertions(+), 28 deletions(-)

diff --git 
a/drools-base/src/main/java/org/drools/base/common/DroolsObjectInputStream.java 
b/drools-base/src/main/java/org/drools/base/common/DroolsObjectInputStream.java
index 9eb8ba090a1..0a4149bbf75 100644
--- 
a/drools-base/src/main/java/org/drools/base/common/DroolsObjectInputStream.java
+++ 
b/drools-base/src/main/java/org/drools/base/common/DroolsObjectInputStream.java
@@ -82,7 +82,7 @@ public class DroolsObjectInputStream extends ObjectInputStream
     }
 
     protected Class resolveClass(String className) throws 
ClassNotFoundException {
-        return ClassUtils.getClassFromName( className, true, this.classLoader 
);
+        return ClassUtils.getClassFromName( className, false, this.classLoader 
);
     }
 
     protected Class< ? > resolveClass(ObjectStreamClass desc) throws 
IOException,
diff --git 
a/drools-core/src/main/java/org/drools/core/marshalling/SerializablePlaceholderResolverStrategy.java
 
b/drools-core/src/main/java/org/drools/core/marshalling/SerializablePlaceholderResolverStrategy.java
index 13ae30b6d46..2c4642bf3a5 100644
--- 
a/drools-core/src/main/java/org/drools/core/marshalling/SerializablePlaceholderResolverStrategy.java
+++ 
b/drools-core/src/main/java/org/drools/core/marshalling/SerializablePlaceholderResolverStrategy.java
@@ -24,6 +24,7 @@ import java.io.ObjectOutputStream;
 import java.util.ArrayList;
 import java.util.List;
 
+import org.drools.core.util.DeserializationFilterHelper;
 import org.kie.api.marshalling.ObjectMarshallingStrategy;
 import org.kie.api.marshalling.ObjectMarshallingStrategyAcceptor;
 
@@ -92,6 +93,9 @@ public class SerializablePlaceholderResolverStrategy
         @SuppressWarnings("unchecked")
         public void read(ObjectInputStream ois) throws IOException,
                                                ClassNotFoundException {
+            if (DeserializationFilterHelper.isDeserializationFilterEnabled()) {
+                
ois.setObjectInputFilter(DeserializationFilterHelper.createDeserializationFilter());
+            }
             this.data = (List<Object>) ois.readObject();
         }
 
diff --git 
a/drools-core/src/main/java/org/drools/core/util/DeserializationFilterHelper.java
 
b/drools-core/src/main/java/org/drools/core/util/DeserializationFilterHelper.java
new file mode 100644
index 00000000000..29ce1da123a
--- /dev/null
+++ 
b/drools-core/src/main/java/org/drools/core/util/DeserializationFilterHelper.java
@@ -0,0 +1,150 @@
+/*
+ * 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.drools.core.util;
+
+import java.io.ObjectInputFilter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class DeserializationFilterHelper {
+
+    private static final Logger logger = 
LoggerFactory.getLogger(DeserializationFilterHelper.class);
+
+    private static final List<String> DEFAULT_ALLOWED_PREFIXES = List.of(
+            "java.lang.", "java.util.", "java.math.", "java.time.");
+
+    private static final Set<String> DEFAULT_ALLOWED_CLASSES = Set.of(
+            "org.drools.base.reteoo.InitialFactImpl",
+            "org.drools.core.util.LinkedListEntry",
+            "org.drools.tms.SimpleMode",
+            "org.drools.tms.DefeasibleMode",
+            "org.drools.traits.core.factmodel.AbstractTriple",
+            "org.drools.traits.core.factmodel.BitMaskKey",
+            "org.drools.traits.core.factmodel.ExternalizableLinkedHashMap",
+            "org.drools.traits.core.factmodel.Key",
+            "org.drools.traits.core.factmodel.NullTraitType",
+            "org.drools.traits.core.factmodel.ThingProxyImplPlaceHolder",
+            "org.drools.traits.core.factmodel.TraitFieldDefaultValue",
+            "org.drools.traits.core.factmodel.TraitFieldImpl",
+            
"org.drools.traits.core.factmodel.TraitFieldImpl$DefaultValueHierarchy",
+            "org.drools.traits.core.factmodel.TraitFieldImpl$TypeComparator",
+            "org.drools.traits.core.factmodel.TraitFieldTMSImpl",
+            "org.drools.traits.core.factmodel.TraitProxyImpl",
+            
"org.drools.traits.core.factmodel.TraitRegistryImpl$CachingHierarcyEncoderImpl",
+            "org.drools.traits.core.factmodel.TraitTypeMapImpl",
+            "org.drools.traits.core.factmodel.TripleBasedBean",
+            "org.drools.traits.core.factmodel.TripleBasedStruct",
+            "org.drools.traits.core.factmodel.TripleFactoryImpl",
+            "org.drools.traits.core.factmodel.TripleImpl",
+            "org.drools.traits.core.factmodel.TripleStore",
+            "org.drools.traits.core.factmodel.TypeCache",
+            "org.drools.traits.core.factmodel.TypeHierarchy",
+            "org.drools.traits.core.factmodel.TypeWrapper",
+            "org.drools.traits.core.util.AbstractBitwiseHierarchyImpl",
+            
"org.drools.traits.core.util.AbstractBitwiseHierarchyImpl$HierCodeComparator",
+            "org.drools.traits.core.util.AbstractCodedHierarchyImpl",
+            "org.drools.traits.core.util.CodedHierarchyImpl",
+            "org.drools.traits.core.util.HierarchyEncoderImpl",
+            "org.drools.traits.core.util.HierarchyEncoderImpl$ImmutableBitSet",
+            "org.drools.traits.core.util.HierNode",
+            "org.drools.util.bitmask.EmptyButLastBitMask",
+            "org.drools.util.bitmask.LongBitMask",
+            "org.drools.util.bitmask.SingleLongBitMask");
+
+    private DeserializationFilterHelper() {
+    }
+
+    public static boolean isClassAllowed(String className) {
+        if (DEFAULT_ALLOWED_CLASSES.contains(className)) {
+            return true;
+        }
+        for (String prefix : DEFAULT_ALLOWED_PREFIXES) {
+            if (className.startsWith(prefix)) {
+                return true;
+            }
+        }
+        for (String prefix : getUserAllowedPrefixes()) {
+            if (className.startsWith(prefix)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public static boolean isDeserializationFilterEnabled() {
+        return Boolean.parseBoolean(
+                System.getProperty(KeyStoreConstants.PROP_ENABLE_DESER_FILTER, 
"true"));
+    }
+
+    public static ObjectInputFilter createDeserializationFilter() {
+        return filterInfo -> {
+            Class<?> clazz = filterInfo.serialClass();
+            if (clazz == null) {
+                return ObjectInputFilter.Status.UNDECIDED;
+            }
+            if (clazz.isPrimitive()) {
+                return ObjectInputFilter.Status.ALLOWED;
+            }
+            if (clazz.isArray()) {
+                Class<?> componentType = clazz.getComponentType();
+                while (componentType.isArray()) {
+                    componentType = componentType.getComponentType();
+                }
+                if (componentType.isPrimitive()) {
+                    return ObjectInputFilter.Status.ALLOWED;
+                }
+                clazz = componentType;
+            }
+            String className = clazz.getName();
+            if (isClassAllowed(className)) {
+                return ObjectInputFilter.Status.ALLOWED;
+            }
+            logger.warn("Deserialization of class '{}' was rejected by the 
ObjectInputFilter. "
+                    + "If this is a legitimate fact class, add it to the 
allowlist via "
+                    + "-D{}=<pattern>",
+                    className, 
KeyStoreConstants.PROP_ALLOWED_DESER_CLASS_PATTERNS);
+            return ObjectInputFilter.Status.REJECTED;
+        };
+    }
+
+    private static List<String> getUserAllowedPrefixes() {
+        String patterns = 
System.getProperty(KeyStoreConstants.PROP_ALLOWED_DESER_CLASS_PATTERNS, "");
+        if (patterns.isEmpty()) {
+            return List.of();
+        }
+        List<String> prefixes = new ArrayList<>();
+        for (String pattern : patterns.split(";")) {
+            String trimmed = pattern.trim();
+            if (!trimmed.isEmpty()) {
+                if (trimmed.endsWith(".*")) {
+                    prefixes.add(trimmed.substring(0, trimmed.length() - 1));
+                } else if (trimmed.endsWith("*")) {
+                    prefixes.add(trimmed.substring(0, trimmed.length() - 1));
+                } else {
+                    prefixes.add(trimmed);
+                }
+            }
+        }
+        return prefixes;
+    }
+}
diff --git 
a/drools-core/src/main/java/org/drools/core/util/KeyStoreConstants.java 
b/drools-core/src/main/java/org/drools/core/util/KeyStoreConstants.java
index 2786b1bb8db..de02a3aaa31 100644
--- a/drools-core/src/main/java/org/drools/core/util/KeyStoreConstants.java
+++ b/drools-core/src/main/java/org/drools/core/util/KeyStoreConstants.java
@@ -46,4 +46,9 @@ public class KeyStoreConstants {
 
     // true if you allow verifying with old sign algorithm "MD5withRSA"
     public static final String PROP_VERIFY_OLD_SIGN = 
"drools.serialization.verify.old.sign";
+
+    // true (default) to enable ObjectInputFilter on deserialization
+    public static final String PROP_ENABLE_DESER_FILTER = 
"drools.serialization.enableDeserializationFilter";
+    // semicolon-separated package patterns to allow during deserialization 
(e.g. "com.mycompany.*;org.example.*")
+    public static final String PROP_ALLOWED_DESER_CLASS_PATTERNS = 
"drools.serialization.allowedDeserializationClassPatterns";
 }
diff --git 
a/drools-persistence/drools-persistence-jpa/src/main/java/org/drools/persistence/jpa/marshaller/JPAPlaceholderResolverStrategy.java
 
b/drools-persistence/drools-persistence-jpa/src/main/java/org/drools/persistence/jpa/marshaller/JPAPlaceholderResolverStrategy.java
index 0d0bc492c02..7b07aaa5402 100644
--- 
a/drools-persistence/drools-persistence-jpa/src/main/java/org/drools/persistence/jpa/marshaller/JPAPlaceholderResolverStrategy.java
+++ 
b/drools-persistence/drools-persistence-jpa/src/main/java/org/drools/persistence/jpa/marshaller/JPAPlaceholderResolverStrategy.java
@@ -33,6 +33,7 @@ import jakarta.persistence.Persistence;
 import jakarta.persistence.metamodel.EntityType;
 import jakarta.persistence.metamodel.Metamodel;
 import org.drools.base.common.DroolsObjectInputStream;
+import org.drools.core.util.DeserializationFilterHelper;
 import org.drools.persistence.api.TransactionAware;
 import org.drools.persistence.api.TransactionManager;
 import org.drools.serialization.protobuf.ProtobufProcessMarshallerWriteContext;
@@ -122,11 +123,7 @@ public class JPAPlaceholderResolverStrategy implements 
ObjectMarshallingStrategy
     }
 
     public Object read(ObjectInputStream is) throws IOException, 
ClassNotFoundException {
-        String canonicalName = is.readUTF();
-        Object id = is.readObject();
-
-        EntityManager em = getEntityManager();
-        return em.find(Class.forName(canonicalName), id);
+        return readEntity(is, null);
     }
 
     public byte[] marshal(Context context,
@@ -172,11 +169,19 @@ public class JPAPlaceholderResolverStrategy implements 
ObjectMarshallingStrategy
         }
 
         DroolsObjectInputStream is = new DroolsObjectInputStream( new 
ByteArrayInputStream( object ), clToUse );
+        return readEntity(is, clToUse);
+    }
+
+    private Object readEntity(ObjectInputStream is, ClassLoader classloader) 
throws IOException, ClassNotFoundException {
+        if (DeserializationFilterHelper.isDeserializationFilterEnabled()) {
+            
is.setObjectInputFilter(DeserializationFilterHelper.createDeserializationFilter());
+        }
         String canonicalName = is.readUTF();
         Object id = is.readObject();
 
         EntityManager em = getEntityManager();
-        return em.find(Class.forName(canonicalName, true, 
(clToUse==null?this.getClass().getClassLoader():clToUse)), id);
+        ClassLoader cl = classloader != null ? classloader : 
this.getClass().getClassLoader();
+        return em.find(Class.forName(canonicalName, true, cl), id);
     }
     
     public Context createContext() {
diff --git 
a/drools-persistence/drools-persistence-jpa/src/test/java/org/drools/persistence/DeserializationFilterTestSupport.java
 
b/drools-persistence/drools-persistence-jpa/src/test/java/org/drools/persistence/DeserializationFilterTestSupport.java
new file mode 100644
index 00000000000..b88aed3451c
--- /dev/null
+++ 
b/drools-persistence/drools-persistence-jpa/src/test/java/org/drools/persistence/DeserializationFilterTestSupport.java
@@ -0,0 +1,40 @@
+/*
+ * 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.drools.persistence;
+
+import org.drools.core.util.KeyStoreConstants;
+
+public class DeserializationFilterTestSupport {
+
+    private String savedAllowedPatterns;
+
+    public void setUp(String... patterns) {
+        savedAllowedPatterns = 
System.getProperty(KeyStoreConstants.PROP_ALLOWED_DESER_CLASS_PATTERNS);
+        System.setProperty(KeyStoreConstants.PROP_ALLOWED_DESER_CLASS_PATTERNS,
+                String.join(";", patterns));
+    }
+
+    public void tearDown() {
+        if (savedAllowedPatterns != null) {
+            
System.setProperty(KeyStoreConstants.PROP_ALLOWED_DESER_CLASS_PATTERNS, 
savedAllowedPatterns);
+        } else {
+            
System.clearProperty(KeyStoreConstants.PROP_ALLOWED_DESER_CLASS_PATTERNS);
+        }
+    }
+}
diff --git 
a/drools-persistence/drools-persistence-jpa/src/test/java/org/drools/persistence/kie/persistence/session/JpaPersistentStatefulSessionTest.java
 
b/drools-persistence/drools-persistence-jpa/src/test/java/org/drools/persistence/kie/persistence/session/JpaPersistentStatefulSessionTest.java
index 554cac69f03..de4bd0cc0a2 100644
--- 
a/drools-persistence/drools-persistence-jpa/src/test/java/org/drools/persistence/kie/persistence/session/JpaPersistentStatefulSessionTest.java
+++ 
b/drools-persistence/drools-persistence-jpa/src/test/java/org/drools/persistence/kie/persistence/session/JpaPersistentStatefulSessionTest.java
@@ -74,25 +74,28 @@ public class JpaPersistentStatefulSessionTest {
     private static Logger logger = 
LoggerFactory.getLogger(JpaPersistentStatefulSessionTest.class);
     private Map<String, Object> context;
     private Environment env;
-    
+    private final org.drools.persistence.DeserializationFilterTestSupport 
filterSupport = new org.drools.persistence.DeserializationFilterTestSupport();
+
     public static Stream<String> parameters() {
        return Stream.of(OPTIMISTIC_LOCKING, PESSIMISTIC_LOCKING);
-    };
-    
+    }
+
     @BeforeEach
     public void setUp() throws Exception {
+        filterSupport.setUp("org.drools.mvel.compiler.Person");
         context = 
DroolsPersistenceUtil.setupWithPoolingDataSource(DROOLS_PERSISTENCE_UNIT_NAME);
         env = createEnvironment(context);
     }
-    
+
     private void setUpLocking(String locking) {
-        if(PESSIMISTIC_LOCKING.equals(locking)) { 
+        if(PESSIMISTIC_LOCKING.equals(locking)) {
             env.set(EnvironmentName.USE_PESSIMISTIC_LOCKING, true);
         }
     }
-        
+
     @AfterEach
     public void tearDown() throws Exception {
+        filterSupport.tearDown();
         DroolsPersistenceUtil.cleanUp(context);
     }
 
@@ -360,6 +363,8 @@ public class JpaPersistentStatefulSessionTest {
     public void testSharedReferences(String locking) {
        setUpLocking(locking);
         KieServices ks = KieServices.Factory.get();
+        KieFileSystem kfs = ks.newKieFileSystem();
+        ks.newKieBuilder(kfs).buildAll();
         KieBase kbase = 
ks.newKieContainer(ks.getRepository().getDefaultReleaseId()).getKieBase();
         KieSession ksession = ks.getStoreServices().newKieSession( kbase, 
null, env );
 
diff --git 
a/drools-persistence/drools-persistence-jpa/src/test/java/org/drools/persistence/map/impl/JpaBasedPersistenceTest.java
 
b/drools-persistence/drools-persistence-jpa/src/test/java/org/drools/persistence/map/impl/JpaBasedPersistenceTest.java
index da9879391f3..579679b36c4 100644
--- 
a/drools-persistence/drools-persistence-jpa/src/test/java/org/drools/persistence/map/impl/JpaBasedPersistenceTest.java
+++ 
b/drools-persistence/drools-persistence-jpa/src/test/java/org/drools/persistence/map/impl/JpaBasedPersistenceTest.java
@@ -46,11 +46,12 @@ import static 
org.kie.api.runtime.EnvironmentName.USE_PESSIMISTIC_LOCKING;
 public class JpaBasedPersistenceTest extends MapPersistenceTest {
 
     private static Logger logger = 
LoggerFactory.getLogger(JPAPlaceholderResolverStrategy.class);
-    
+
     private Map<String, Object> context;
     private EntityManagerFactory emf;
     private JtaTransactionManager txm;
     private boolean useTransactions = false;
+    private final org.drools.persistence.DeserializationFilterTestSupport 
filterSupport = new org.drools.persistence.DeserializationFilterTestSupport();
     
     public static Stream<String> parameters() {
         return Stream.of(OPTIMISTIC_LOCKING, PESSIMISTIC_LOCKING);
@@ -59,6 +60,7 @@ public class JpaBasedPersistenceTest extends 
MapPersistenceTest {
     
     @BeforeEach
     public void setUp() throws Exception {
+        filterSupport.setUp("org.drools.persistence.map.impl.Buddy");
         context = 
DroolsPersistenceUtil.setupWithPoolingDataSource(DROOLS_PERSISTENCE_UNIT_NAME);
         emf = (EntityManagerFactory) context.get(ENTITY_MANAGER_FACTORY);
         
@@ -74,6 +76,7 @@ public class JpaBasedPersistenceTest extends 
MapPersistenceTest {
     
     @AfterEach
     public void tearDown() throws Exception {
+        filterSupport.tearDown();
         DroolsPersistenceUtil.cleanUp(context);
     }
     
diff --git 
a/drools-persistence/drools-persistence-jpa/src/test/java/org/drools/persistence/map/impl/MapBasedPersistenceTest.java
 
b/drools-persistence/drools-persistence-jpa/src/test/java/org/drools/persistence/map/impl/MapBasedPersistenceTest.java
index 107a63bee29..a0dd2d871f4 100644
--- 
a/drools-persistence/drools-persistence-jpa/src/test/java/org/drools/persistence/map/impl/MapBasedPersistenceTest.java
+++ 
b/drools-persistence/drools-persistence-jpa/src/test/java/org/drools/persistence/map/impl/MapBasedPersistenceTest.java
@@ -39,17 +39,24 @@ import org.kie.api.runtime.EnvironmentName;
 import org.kie.api.runtime.KieSession;
 
 public class MapBasedPersistenceTest extends MapPersistenceTest{
-    
+
     private SimpleKnowledgeSessionStorage storage;
-    
+    private final org.drools.persistence.DeserializationFilterTestSupport 
filterSupport = new org.drools.persistence.DeserializationFilterTestSupport();
+
     public static Stream<String> parameters() {
         return Stream.of("not relevant");
     };
 
     @BeforeEach
     public void createStorage(){
+        filterSupport.setUp("org.drools.persistence.map.impl.Buddy");
         storage = new SimpleKnowledgeSessionStorage();
     }
+
+    @org.junit.jupiter.api.AfterEach
+    public void clearDeserializationFilter() {
+        filterSupport.tearDown();
+    }
     
     @Override
     protected KieSession createSession(String locking, KieBase kbase) {
diff --git 
a/drools-persistence/drools-persistence-jpa/src/test/java/org/drools/persistence/session/JpaPersistentStatefulSessionTest.java
 
b/drools-persistence/drools-persistence-jpa/src/test/java/org/drools/persistence/session/JpaPersistentStatefulSessionTest.java
index f092a2fed72..e1afb525be2 100644
--- 
a/drools-persistence/drools-persistence-jpa/src/test/java/org/drools/persistence/session/JpaPersistentStatefulSessionTest.java
+++ 
b/drools-persistence/drools-persistence-jpa/src/test/java/org/drools/persistence/session/JpaPersistentStatefulSessionTest.java
@@ -72,6 +72,7 @@ public class JpaPersistentStatefulSessionTest {
 
     private Map<String, Object> context;
     private Environment env;
+    private final org.drools.persistence.DeserializationFilterTestSupport 
filterSupport = new org.drools.persistence.DeserializationFilterTestSupport();
 
     public static Stream<String> parameters() {
        return Stream.of(OPTIMISTIC_LOCKING, PESSIMISTIC_LOCKING);
@@ -79,6 +80,7 @@ public class JpaPersistentStatefulSessionTest {
 
     @BeforeEach
     public void setUp() throws Exception {
+        filterSupport.setUp("org.drools.mvel.compiler.Person");
         context = 
DroolsPersistenceUtil.setupWithPoolingDataSource(DROOLS_PERSISTENCE_UNIT_NAME);
         env = createEnvironment(context);
     }
@@ -92,6 +94,7 @@ public class JpaPersistentStatefulSessionTest {
     
     @AfterEach
     public void tearDown() throws Exception {
+        filterSupport.tearDown();
         DroolsPersistenceUtil.cleanUp(context);
     }
 
diff --git 
a/drools-persistence/drools-persistence-jpa/src/test/java/org/drools/persistence/timer/integrationtests/TimerAndCalendarTest.java
 
b/drools-persistence/drools-persistence-jpa/src/test/java/org/drools/persistence/timer/integrationtests/TimerAndCalendarTest.java
index 2223b958fa8..f675a463d9a 100644
--- 
a/drools-persistence/drools-persistence-jpa/src/test/java/org/drools/persistence/timer/integrationtests/TimerAndCalendarTest.java
+++ 
b/drools-persistence/drools-persistence-jpa/src/test/java/org/drools/persistence/timer/integrationtests/TimerAndCalendarTest.java
@@ -67,20 +67,23 @@ import static 
org.drools.persistence.util.DroolsPersistenceUtil.createEnvironmen
 import static 
org.drools.persistence.util.DroolsPersistenceUtil.setupWithPoolingDataSource;
 
 public class TimerAndCalendarTest {
-    
+
     private Map<String, Object> context;
+    private final org.drools.persistence.DeserializationFilterTestSupport 
filterSupport = new org.drools.persistence.DeserializationFilterTestSupport();
 
     public static Stream<String> parameters() {
        return Stream.of(OPTIMISTIC_LOCKING, PESSIMISTIC_LOCKING);
     };
-    
+
     @BeforeEach
     public void before() throws Exception {
+        filterSupport.setUp("org.drools.test.TestEvent");
         context = setupWithPoolingDataSource(DROOLS_PERSISTENCE_UNIT_NAME);
     }
 
     @AfterEach
     public void after() throws Exception {
+        filterSupport.tearDown();
         cleanUp(context);
     }
 
diff --git a/drools-serialization-protobuf/pom.xml 
b/drools-serialization-protobuf/pom.xml
index 055dc2eb439..e730cd86fb9 100644
--- a/drools-serialization-protobuf/pom.xml
+++ b/drools-serialization-protobuf/pom.xml
@@ -104,6 +104,11 @@
             <artifactId>mockito-core</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>ch.qos.logback</groupId>
+            <artifactId>logback-classic</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
     <profiles>
@@ -148,4 +153,4 @@
         </profile>
     </profiles>
 
-</project>
\ No newline at end of file
+</project>
diff --git 
a/drools-serialization-protobuf/src/main/java/org/drools/serialization/protobuf/PersisterHelper.java
 
b/drools-serialization-protobuf/src/main/java/org/drools/serialization/protobuf/PersisterHelper.java
index 976004373b0..689987cdf3a 100644
--- 
a/drools-serialization-protobuf/src/main/java/org/drools/serialization/protobuf/PersisterHelper.java
+++ 
b/drools-serialization-protobuf/src/main/java/org/drools/serialization/protobuf/PersisterHelper.java
@@ -38,6 +38,8 @@ import 
org.drools.tms.beliefsystem.simple.BeliefSystemLogicalCallback;
 import org.drools.base.common.DroolsObjectInputStream;
 import org.drools.base.common.DroolsObjectOutputStream;
 import org.drools.core.common.WorkingMemoryAction;
+import org.drools.core.util.DeserializationFilterHelper;
+import org.drools.core.util.KeyStoreConstants;
 import org.drools.base.factmodel.traits.TraitFactory;
 import org.drools.core.impl.InternalRuleBase;
 import org.drools.core.impl.WorkingMemoryReteExpireAction;
@@ -169,7 +171,8 @@ public class PersisterHelper extends MarshallingHelper {
         }
 
         byte[] buff = payload.toByteArray();
-        sign( _header, buff );
+        byte[] headerFieldsBytes = _header.build().toByteArray();
+        sign( _header, buildSignedData( buff, headerFieldsBytes ) );
         _header.setPayload( ByteString.copyFrom( buff ) );
 
         context.write( _header.build().toByteArray() );
@@ -233,13 +236,15 @@ public class PersisterHelper extends MarshallingHelper {
     }
     
     private static ProtobufMessages.Header loadStrategiesCheckSignature( 
MarshallerReaderContext context, ProtobufMessages.Header _header) throws 
ClassNotFoundException, IOException {
-        loadStrategiesIndex( context, _header );
-
         byte[] sessionbuff = _header.getPayload().toByteArray();
+        ProtobufMessages.Header headerFields = _header.toBuilder()
+                .clearSignature()
+                .clearPayload()
+                .build();
+        checkSignature( _header, buildSignedData( sessionbuff, 
headerFields.toByteArray() ) );
+
+        loadStrategiesIndex( context, _header );
 
-        // should we check version as well here?
-        checkSignature( _header, sessionbuff );
-        
         return _header;
     }
 
@@ -284,7 +289,7 @@ public class PersisterHelper extends MarshallingHelper {
                     classLoader = 
context.getKnowledgeBase().getRootClassLoader();
                 }
                 if ( classLoader instanceof ProjectClassLoader ) {
-                   readRuntimeDefinedClasses( _header, (ProjectClassLoader) 
classLoader );
+                   readRuntimeDefinedClasses( _header, (ProjectClassLoader) 
classLoader, context.getKnowledgeBase() );
                 }
                 ctx.read( new DroolsObjectInputStream( 
_entry.getData().newInput(), classLoader) );
             }
@@ -292,10 +297,22 @@ public class PersisterHelper extends MarshallingHelper {
     }
 
     public static void readRuntimeDefinedClasses( Header _header,
-                                                  ProjectClassLoader pcl ) 
throws IOException, ClassNotFoundException {
+                                                  ProjectClassLoader pcl,
+                                                  InternalRuleBase kBase ) 
throws IOException, ClassNotFoundException {
         if ( _header.getRuntimeClassDefinitionsCount() > 0 ) {
+            TraitFactory traitFactory = kBase != null ? 
RuntimeComponentFactory.get().getTraitFactory(kBase) : null;
+            if ( traitFactory == null ) {
+                throw new RuntimeException( "RuntimeClassDef entries found but 
no TraitFactory is available. Deserialization aborted." );
+            }
             for ( ProtobufMessages.RuntimeClassDef def : 
_header.getRuntimeClassDefinitionsList() ) {
                 String resourceName = def.getClassFqName();
+                String className = resourceName.replace('/', 
'.').replace(".class", "");
+                if ( !DeserializationFilterHelper.isClassAllowed( className ) 
) {
+                    throw new RuntimeException( "RuntimeClassDef '" + 
resourceName
+                            + "' is not in the deserialization allowlist. "
+                            + "If this is a legitimate trait class, add it via 
-D"
+                            + 
KeyStoreConstants.PROP_ALLOWED_DESER_CLASS_PATTERNS + "=<pattern>" );
+                }
                 byte[] byteCode = def.getClassDef().toByteArray();
                 if ( ! pcl.getStore().containsKey( resourceName ) ) {
                     pcl.getStore().put(resourceName, byteCode);
@@ -340,6 +357,13 @@ public class PersisterHelper extends MarshallingHelper {
         }
     }
     
+    private static byte[] buildSignedData(byte[] payloadBytes, byte[] 
headerFieldsBytes) {
+        byte[] result = new byte[payloadBytes.length + 
headerFieldsBytes.length];
+        System.arraycopy(payloadBytes, 0, result, 0, payloadBytes.length);
+        System.arraycopy(headerFieldsBytes, 0, result, payloadBytes.length, 
headerFieldsBytes.length);
+        return result;
+    }
+
     public static ExtensionRegistry buildRegistry( MarshallerReaderContext 
context, ProcessMarshaller processMarshaller ) {
         ExtensionRegistry registry = ExtensionRegistry.newInstance();
         if( processMarshaller != null ) {
diff --git 
a/drools-serialization-protobuf/src/main/java/org/drools/serialization/protobuf/marshalling/JavaSerializableResolverStrategy.java
 
b/drools-serialization-protobuf/src/main/java/org/drools/serialization/protobuf/marshalling/JavaSerializableResolverStrategy.java
index 5bb6badc1fa..cd43bc268b9 100644
--- 
a/drools-serialization-protobuf/src/main/java/org/drools/serialization/protobuf/marshalling/JavaSerializableResolverStrategy.java
+++ 
b/drools-serialization-protobuf/src/main/java/org/drools/serialization/protobuf/marshalling/JavaSerializableResolverStrategy.java
@@ -25,6 +25,7 @@ import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
 import java.io.ObjectStreamClass;
 
+import org.drools.core.util.DeserializationFilterHelper;
 import org.kie.api.marshalling.ObjectMarshallingStrategy;
 import org.kie.api.marshalling.ObjectMarshallingStrategyAcceptor;
 
@@ -72,9 +73,12 @@ public class JavaSerializableResolverStrategy
             is = new ObjectInputStream(bs) {
                 @Override
                 protected Class<?> resolveClass(ObjectStreamClass desc) throws 
ClassNotFoundException {
-                    return Class.forName(desc.getName(), true, classloader);
+                    return Class.forName(desc.getName(), false, classloader);
                 }
             };
+            if (DeserializationFilterHelper.isDeserializationFilterEnabled()) {
+                
is.setObjectInputFilter(DeserializationFilterHelper.createDeserializationFilter());
+            }
             return read(is);
         } catch (Exception e) {
             throw new RuntimeException(e);
diff --git 
a/drools-serialization-protobuf/src/test/java/org/drools/serialization/protobuf/DeserializationFilterTestSupport.java
 
b/drools-serialization-protobuf/src/test/java/org/drools/serialization/protobuf/DeserializationFilterTestSupport.java
new file mode 100644
index 00000000000..63eefa3cf0f
--- /dev/null
+++ 
b/drools-serialization-protobuf/src/test/java/org/drools/serialization/protobuf/DeserializationFilterTestSupport.java
@@ -0,0 +1,40 @@
+/*
+ * 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.drools.serialization.protobuf;
+
+import org.drools.core.util.KeyStoreConstants;
+
+public class DeserializationFilterTestSupport {
+
+    private String savedAllowedPatterns;
+
+    public void setUp(String... patterns) {
+        savedAllowedPatterns = 
System.getProperty(KeyStoreConstants.PROP_ALLOWED_DESER_CLASS_PATTERNS);
+        System.setProperty(KeyStoreConstants.PROP_ALLOWED_DESER_CLASS_PATTERNS,
+                String.join(";", patterns));
+    }
+
+    public void tearDown() {
+        if (savedAllowedPatterns != null) {
+            
System.setProperty(KeyStoreConstants.PROP_ALLOWED_DESER_CLASS_PATTERNS, 
savedAllowedPatterns);
+        } else {
+            
System.clearProperty(KeyStoreConstants.PROP_ALLOWED_DESER_CLASS_PATTERNS);
+        }
+    }
+}
diff --git 
a/drools-serialization-protobuf/src/test/java/org/drools/serialization/protobuf/MarshallerTest.java
 
b/drools-serialization-protobuf/src/test/java/org/drools/serialization/protobuf/MarshallerTest.java
index b83a8637c2d..31873bac1a6 100644
--- 
a/drools-serialization-protobuf/src/test/java/org/drools/serialization/protobuf/MarshallerTest.java
+++ 
b/drools-serialization-protobuf/src/test/java/org/drools/serialization/protobuf/MarshallerTest.java
@@ -47,6 +47,7 @@ import static org.assertj.core.api.Assertions.assertThat;
 public class MarshallerTest {
 
     private Environment env = EnvironmentFactory.newEnvironment();
+    private final DeserializationFilterTestSupport filterSupport = new 
DeserializationFilterTestSupport();
 
     public static Stream<ObjectMarshallingStrategy> parameters() {
         return Stream.of(new 
JavaSerializableResolverStrategy(ClassObjectMarshallingStrategyAcceptor.DEFAULT),
@@ -57,6 +58,19 @@ public class MarshallerTest {
         this.env.set( EnvironmentName.OBJECT_MARSHALLING_STRATEGIES, new 
ObjectMarshallingStrategy[]{ strategy } );
     }
 
+    @org.junit.jupiter.api.BeforeEach
+    public void setUpDeserializationFilter() {
+        filterSupport.setUp("org.drools.mvel.compiler.Person",
+                "org.drools.serialization.protobuf.MarshallerTest$LongFact",
+                "org.drools.serialization.protobuf.MarshallerTest$LongFacts");
+    }
+
+    @org.junit.jupiter.api.AfterEach
+    public void clearDeserializationFilter() {
+        filterSupport.tearDown();
+    }
+
+
     @ParameterizedTest(name = "{0}")
     @MethodSource("parameters")
     public void testAgendaDoNotSerializeObject(ObjectMarshallingStrategy 
strategy) throws Exception {
diff --git 
a/drools-serialization-protobuf/src/test/java/org/drools/serialization/protobuf/MarshallingTest.java
 
b/drools-serialization-protobuf/src/test/java/org/drools/serialization/protobuf/MarshallingTest.java
index b66f1e5bdb9..8b5b3dfd90f 100644
--- 
a/drools-serialization-protobuf/src/test/java/org/drools/serialization/protobuf/MarshallingTest.java
+++ 
b/drools-serialization-protobuf/src/test/java/org/drools/serialization/protobuf/MarshallingTest.java
@@ -111,6 +111,34 @@ import static 
org.drools.serialization.protobuf.SerializationHelper.getSerialise
 
 public class MarshallingTest extends CommonTestMethodBase {
 
+    private final DeserializationFilterTestSupport filterSupport = new 
DeserializationFilterTestSupport();
+
+    @org.junit.jupiter.api.BeforeEach
+    public void setUpDeserializationFilter() {
+        filterSupport.setUp("org.drools.mvel.compiler.Address",
+                "org.drools.mvel.compiler.Alarm",
+                "org.drools.mvel.compiler.Cell",
+                "org.drools.mvel.compiler.Cheese",
+                "org.drools.mvel.compiler.CheeseEqual",
+                "org.drools.mvel.compiler.FactA",
+                "org.drools.mvel.compiler.FactB",
+                "org.drools.mvel.compiler.FactC",
+                "org.drools.mvel.compiler.Message",
+                "org.drools.mvel.compiler.Person",
+                "org.drools.mvel.compiler.Primitives",
+                "org.drools.mvel.compiler.Sensor",
+                "org.drools.serialization.protobuf.MarshallingTest$A",
+                "org.drools.serialization.protobuf.MarshallingTest$B",
+                "org.drools.serialization.protobuf.MarshallingTest$C",
+                "defaultpkg.Employee",
+                "defaultpkg.Person");
+    }
+
+    @org.junit.jupiter.api.AfterEach
+    public void clearDeserializationFilter() {
+        filterSupport.tearDown();
+    }
+
     @Test
     public void testSerializable() throws Exception {
         Collection<KiePackage>  kpkgs = 
loadKnowledgePackages("test_Serializable.drl" );
diff --git 
a/drools-serialization-protobuf/src/test/java/org/drools/serialization/protobuf/MarshallingValidationCheckTest.java
 
b/drools-serialization-protobuf/src/test/java/org/drools/serialization/protobuf/MarshallingValidationCheckTest.java
new file mode 100644
index 00000000000..98e7943f8b3
--- /dev/null
+++ 
b/drools-serialization-protobuf/src/test/java/org/drools/serialization/protobuf/MarshallingValidationCheckTest.java
@@ -0,0 +1,566 @@
+/*
+ * 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.drools.serialization.protobuf;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.net.URL;
+
+import com.google.protobuf.ByteString;
+import org.drools.core.impl.EnvironmentFactory;
+import org.drools.core.marshalling.ClassObjectMarshallingStrategyAcceptor;
+import org.drools.core.marshalling.SerializablePlaceholderResolverStrategy;
+import org.drools.core.util.KeyStoreConstants;
+import org.drools.core.util.KeyStoreHelper;
+import org.drools.kiesession.rulebase.InternalKnowledgeBase;
+import 
org.drools.serialization.protobuf.marshalling.JavaSerializableResolverStrategy;
+import org.drools.wiring.api.classloader.ProjectClassLoader;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+import org.kie.api.KieBase;
+import org.kie.api.io.ResourceType;
+import org.kie.api.marshalling.ObjectMarshallingStrategy;
+import org.kie.api.runtime.Environment;
+import org.kie.api.runtime.EnvironmentName;
+import org.kie.api.runtime.KieSession;
+import org.kie.internal.marshalling.MarshallerFactory;
+import org.kie.internal.utils.KieHelper;
+import testdata.enhancement.DeserializationProbe;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+public class MarshallingValidationCheckTest {
+
+    private final DeserializationFilterTestSupport filterSupport = new 
DeserializationFilterTestSupport();
+
+    @org.junit.jupiter.api.BeforeEach
+    void setUp() {
+        filterSupport.setUp();
+    }
+
+    @AfterEach
+    void tearDown() {
+        filterSupport.tearDown();
+        System.clearProperty(KeyStoreConstants.PROP_ENABLE_DESER_FILTER);
+        System.clearProperty(KeyStoreConstants.PROP_SIGN);
+        System.clearProperty(KeyStoreConstants.PROP_PVT_KS_URL);
+        System.clearProperty(KeyStoreConstants.PROP_PVT_KS_PWD);
+        System.clearProperty(KeyStoreConstants.PROP_PVT_ALIAS);
+        System.clearProperty(KeyStoreConstants.PROP_PVT_PWD);
+        System.clearProperty(KeyStoreConstants.PROP_PUB_KS_URL);
+        System.clearProperty(KeyStoreConstants.PROP_PUB_KS_PWD);
+        KeyStoreHelper.reInit();
+        DeserializationProbe.deserialized = false;
+    }
+
+    @Test
+    void testDeserializationFilterRejectsUnexpectedClasses() throws Exception {
+        byte[] probeBytes = serializeObject(new DeserializationProbe());
+
+        JavaSerializableResolverStrategy strategy =
+                new 
JavaSerializableResolverStrategy(ClassObjectMarshallingStrategyAcceptor.DEFAULT);
+
+        assertThatThrownBy(() ->
+                strategy.unmarshal(null, null, probeBytes, 
getClass().getClassLoader()))
+                .as("Deserialization of non-whitelisted class should be 
rejected by the ObjectInputFilter")
+                .isInstanceOf(RuntimeException.class);
+
+        assertThat(DeserializationProbe.deserialized)
+                .as("DeserializationProbe.readObject() should NOT have been 
invoked")
+                .isFalse();
+    }
+
+    @Test
+    void testDeserializationFilterRejectsEnum() throws Exception {
+        byte[] enumBytes = 
serializeObject(testdata.enhancement.EnumProbe.INSTANCE);
+
+        JavaSerializableResolverStrategy strategy =
+                new 
JavaSerializableResolverStrategy(ClassObjectMarshallingStrategyAcceptor.DEFAULT);
+
+        testdata.enhancement.EnumProbe.initialized = false;
+
+        assertThatThrownBy(() ->
+                strategy.unmarshal(null, null, enumBytes, 
getClass().getClassLoader()))
+                .as("Deserialization of non-whitelisted enum should be 
rejected — "
+                        + "enums can execute code in static initializers and 
constructors")
+                .isInstanceOf(RuntimeException.class);
+
+        assertThat(testdata.enhancement.EnumProbe.initialized)
+                .as("EnumProbe static initializer should NOT have been 
invoked")
+                .isFalse();
+    }
+
+    @Test
+    void testTamperedFactHandleDeserialization() throws Exception {
+        String drl =
+                "rule R1 when\n" +
+                "    String()\n" +
+                "then\n" +
+                "end\n";
+
+        ObjectMarshallingStrategy[] strategies = new 
ObjectMarshallingStrategy[]{
+                new 
JavaSerializableResolverStrategy(ClassObjectMarshallingStrategyAcceptor.DEFAULT)
+        };
+
+        Environment env = EnvironmentFactory.newEnvironment();
+        env.set(EnvironmentName.OBJECT_MARSHALLING_STRATEGIES, strategies);
+
+        KieBase kbase = new KieHelper().addContent(drl, 
ResourceType.DRL).build();
+        KieSession ksession = kbase.newKieSession(null, env);
+        ksession.insert("test-fact");
+        ksession.fireAllRules();
+
+        ProtobufMarshaller marshaller = (ProtobufMarshaller) 
MarshallerFactory.newMarshaller(kbase, strategies);
+
+        byte[] serializedSession;
+        try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
+            marshaller.marshall(bos, ksession, 
ksession.getSessionClock().getCurrentTime());
+            serializedSession = bos.toByteArray();
+        }
+        ksession.dispose();
+
+        byte[] probeBytes = serializeObject(new DeserializationProbe());
+
+        byte[] protobufBytes = extractProtobufBytes(serializedSession);
+        ProtobufMessages.Header header = 
ProtobufMessages.Header.parseFrom(protobufBytes);
+        ProtobufMessages.KnowledgeSession session =
+                
ProtobufMessages.KnowledgeSession.parseFrom(header.getPayload());
+
+        ProtobufMessages.KnowledgeSession.Builder sessionBuilder = 
session.toBuilder();
+        boolean replaced = false;
+        for (int epIdx = 0; epIdx < 
session.getRuleData().getEntryPointCount(); epIdx++) {
+            ProtobufMessages.EntryPoint ep = 
session.getRuleData().getEntryPoint(epIdx);
+            for (int fhIdx = 0; fhIdx < ep.getHandleCount(); fhIdx++) {
+                ProtobufMessages.FactHandle fh = ep.getHandle(fhIdx);
+                if (fh.hasObject()) {
+                    ProtobufMessages.FactHandle tamperedFh = fh.toBuilder()
+                            .setObject(ByteString.copyFrom(probeBytes))
+                            .build();
+                    ProtobufMessages.EntryPoint tamperedEp = ep.toBuilder()
+                            .setHandle(fhIdx, tamperedFh)
+                            .build();
+                    
sessionBuilder.setRuleData(session.getRuleData().toBuilder()
+                            .setEntryPoint(epIdx, tamperedEp)
+                            .build());
+                    replaced = true;
+                    break;
+                }
+            }
+            if (replaced) {
+                break;
+            }
+        }
+
+        assertThat(replaced)
+                .as("Should have found a fact handle to tamper with")
+                .isTrue();
+
+        ProtobufMessages.Header tamperedHeader = header.toBuilder()
+                
.setPayload(ByteString.copyFrom(sessionBuilder.build().toByteArray()))
+                .build();
+        byte[] tamperedSession = 
wrapInObjectStream(tamperedHeader.toByteArray());
+
+        assertThatThrownBy(() ->
+                marshaller.unmarshall(new 
ByteArrayInputStream(tamperedSession)))
+                .as("Unmarshalling a tampered payload with a non-whitelisted 
class should fail")
+                .isInstanceOf(RuntimeException.class);
+
+        assertThat(DeserializationProbe.deserialized)
+                .as("DeserializationProbe.readObject() should NOT have been 
invoked during unmarshalling "
+                        + "of a tampered payload — the deserialization filter 
should block it")
+                .isFalse();
+    }
+
+    @Test
+    void testDeserializationAllowsJdkTypes() throws Exception {
+        String drl =
+                "rule R1 when\n" +
+                "    String()\n" +
+                "then\n" +
+                "end\n";
+
+        ObjectMarshallingStrategy[] strategies = new 
ObjectMarshallingStrategy[]{
+                new 
JavaSerializableResolverStrategy(ClassObjectMarshallingStrategyAcceptor.DEFAULT)
+        };
+
+        Environment env = EnvironmentFactory.newEnvironment();
+        env.set(EnvironmentName.OBJECT_MARSHALLING_STRATEGIES, strategies);
+
+        KieBase kbase = new KieHelper().addContent(drl, 
ResourceType.DRL).build();
+        KieSession ksession = kbase.newKieSession(null, env);
+        ksession.insert("hello");
+        ksession.fireAllRules();
+
+        ProtobufMarshaller marshaller = (ProtobufMarshaller) 
MarshallerFactory.newMarshaller(kbase, strategies);
+
+        byte[] serializedSession;
+        try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
+            marshaller.marshall(bos, ksession, 
ksession.getSessionClock().getCurrentTime());
+            serializedSession = bos.toByteArray();
+        }
+        ksession.dispose();
+
+        KieSession restored = marshaller.unmarshall(new 
ByteArrayInputStream(serializedSession));
+        assertThat(restored.getObjects().iterator().next()).isEqualTo("hello");
+        restored.dispose();
+    }
+
+    @Test
+    void testDeserializationAllowsConfiguredPatterns() throws Exception {
+        System.setProperty(KeyStoreConstants.PROP_ALLOWED_DESER_CLASS_PATTERNS,
+                "testdata.enhancement.*");
+
+        byte[] probeBytes = serializeObject(new DeserializationProbe());
+
+        JavaSerializableResolverStrategy strategy =
+                new 
JavaSerializableResolverStrategy(ClassObjectMarshallingStrategyAcceptor.DEFAULT);
+
+        Object result = strategy.unmarshal(null, null, probeBytes, 
getClass().getClassLoader());
+        assertThat(result).isInstanceOf(DeserializationProbe.class);
+        assertThat(DeserializationProbe.deserialized).isTrue();
+    }
+
+    @Test
+    void testDeserializationFilterOptOut() throws Exception {
+        System.setProperty(KeyStoreConstants.PROP_ENABLE_DESER_FILTER, 
"false");
+
+        byte[] probeBytes = serializeObject(new DeserializationProbe());
+
+        JavaSerializableResolverStrategy strategy =
+                new 
JavaSerializableResolverStrategy(ClassObjectMarshallingStrategyAcceptor.DEFAULT);
+
+        Object result = strategy.unmarshal(null, null, probeBytes, 
getClass().getClassLoader());
+        assertThat(result).isInstanceOf(DeserializationProbe.class);
+        assertThat(DeserializationProbe.deserialized).isTrue();
+    }
+
+    @Test
+    void testSignatureMustCoverRuntimeClassDefinitions() throws Exception {
+        setPrivateKeyProperties();
+        setPublicKeyProperties();
+
+        String drl =
+                "rule R1 when\n" +
+                "    String()\n" +
+                "then\n" +
+                "end\n";
+
+        KieBase kbase = new KieHelper().addContent(drl, 
ResourceType.DRL).build();
+
+        ObjectMarshallingStrategy[] strategies = new 
ObjectMarshallingStrategy[]{
+                new 
SerializablePlaceholderResolverStrategy(ClassObjectMarshallingStrategyAcceptor.DEFAULT)
+        };
+        Environment env = EnvironmentFactory.newEnvironment();
+        env.set(EnvironmentName.OBJECT_MARSHALLING_STRATEGIES, strategies);
+
+        KieSession ksession = kbase.newKieSession(null, env);
+        ksession.insert("test");
+        ksession.fireAllRules();
+
+        ProtobufMarshaller marshaller = (ProtobufMarshaller) 
MarshallerFactory.newMarshaller(kbase, strategies);
+
+        byte[] serializedSession;
+        try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
+            marshaller.marshall(bos, ksession, 
ksession.getSessionClock().getCurrentTime());
+            serializedSession = bos.toByteArray();
+        }
+        ksession.dispose();
+
+        byte[] protobufBytes = extractProtobufBytes(serializedSession);
+        ProtobufMessages.Header originalHeader = 
ProtobufMessages.Header.parseFrom(protobufBytes);
+
+        byte[] fakeBytecode = new byte[]{(byte) 0xCA, (byte) 0xFE, (byte) 
0xBA, (byte) 0xBE};
+
+        ProtobufMessages.Header tamperedHeader = originalHeader.toBuilder()
+                
.addRuntimeClassDefinitions(ProtobufMessages.RuntimeClassDef.newBuilder()
+                        .setClassFqName("com/example/Payload")
+                        .setClassDef(ByteString.copyFrom(fakeBytecode))
+                        .build())
+                .build();
+
+        byte[] tamperedSession = 
wrapInObjectStream(tamperedHeader.toByteArray());
+
+        assertThatThrownBy(() ->
+                marshaller.unmarshall(new 
ByteArrayInputStream(tamperedSession)))
+                .as("Signature check should detect that RuntimeClassDef 
entries were "
+                        + "tampered — currently the signature only covers the 
payload, "
+                        + "not the full Header")
+                .isInstanceOf(RuntimeException.class)
+                .hasMessageContaining("Signature");
+    }
+
+    @Test
+    void testSignatureCheckedBeforeBytecodeLoading() throws Exception {
+        setPrivateKeyProperties();
+        setPublicKeyProperties();
+
+        String drl =
+                "rule R1 when\n" +
+                "    String()\n" +
+                "then\n" +
+                "end\n";
+
+        KieBase kbase = new KieHelper().addContent(drl, 
ResourceType.DRL).build();
+
+        ObjectMarshallingStrategy[] strategies = new 
ObjectMarshallingStrategy[]{
+                new 
SerializablePlaceholderResolverStrategy(ClassObjectMarshallingStrategyAcceptor.DEFAULT)
+        };
+        Environment env = EnvironmentFactory.newEnvironment();
+        env.set(EnvironmentName.OBJECT_MARSHALLING_STRATEGIES, strategies);
+
+        KieSession ksession = kbase.newKieSession(null, env);
+        ksession.insert("test");
+        ksession.fireAllRules();
+
+        ProtobufMarshaller marshaller = (ProtobufMarshaller) 
MarshallerFactory.newMarshaller(kbase, strategies);
+
+        byte[] serializedSession;
+        try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
+            marshaller.marshall(bos, ksession, 
ksession.getSessionClock().getCurrentTime());
+            serializedSession = bos.toByteArray();
+        }
+        ksession.dispose();
+
+        ProjectClassLoader pcl = (ProjectClassLoader) ((InternalKnowledgeBase) 
kbase).getRootClassLoader();
+        pcl.storeClass("dummy.Placeholder", "dummy/Placeholder.class", new 
byte[0]);
+
+        byte[] protobufBytes = extractProtobufBytes(serializedSession);
+        ProtobufMessages.Header originalHeader = 
ProtobufMessages.Header.parseFrom(protobufBytes);
+
+        String injectedClassName = "com/example/Payload";
+        byte[] fakeBytecode = new byte[]{(byte) 0xCA, (byte) 0xFE, (byte) 
0xBA, (byte) 0xBE};
+
+        ProtobufMessages.Header tamperedHeader = originalHeader.toBuilder()
+                
.addRuntimeClassDefinitions(ProtobufMessages.RuntimeClassDef.newBuilder()
+                        .setClassFqName(injectedClassName)
+                        .setClassDef(ByteString.copyFrom(fakeBytecode))
+                        .build())
+                .build();
+
+        byte[] tamperedSession = 
wrapInObjectStream(tamperedHeader.toByteArray());
+
+        assertThatThrownBy(() ->
+                marshaller.unmarshall(new 
ByteArrayInputStream(tamperedSession)))
+                .as("Signature check should reject the tampered header")
+                .isInstanceOf(RuntimeException.class)
+                .hasMessageContaining("Signature");
+
+        assertThat(pcl.getStore().containsKey(injectedClassName))
+                .as("Bytecode for injected class '%s' must NOT be in the 
classloader "
+                        + "— checkSignature() must run before 
loadStrategiesIndex() so "
+                        + "that a rejected payload never loads bytecode",
+                        injectedClassName)
+                .isFalse();
+    }
+
+    @Test
+    void testUnsignedSessionRejectsInjectedRuntimeClassDef() throws Exception {
+        String drl =
+                "rule R1 when\n" +
+                "    String()\n" +
+                "then\n" +
+                "end\n";
+
+        KieBase kbase = new KieHelper().addContent(drl, 
ResourceType.DRL).build();
+
+        ObjectMarshallingStrategy[] strategies = new 
ObjectMarshallingStrategy[]{
+                new 
SerializablePlaceholderResolverStrategy(ClassObjectMarshallingStrategyAcceptor.DEFAULT)
+        };
+        Environment env = EnvironmentFactory.newEnvironment();
+        env.set(EnvironmentName.OBJECT_MARSHALLING_STRATEGIES, strategies);
+
+        KieSession ksession = kbase.newKieSession(null, env);
+        ksession.insert("test");
+        ksession.fireAllRules();
+
+        ProtobufMarshaller marshaller = (ProtobufMarshaller) 
MarshallerFactory.newMarshaller(kbase, strategies);
+
+        byte[] serializedSession;
+        try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
+            marshaller.marshall(bos, ksession, 
ksession.getSessionClock().getCurrentTime());
+            serializedSession = bos.toByteArray();
+        }
+        ksession.dispose();
+
+        ProjectClassLoader pcl = (ProjectClassLoader) ((InternalKnowledgeBase) 
kbase).getRootClassLoader();
+        pcl.storeClass("dummy.Placeholder", "dummy/Placeholder.class", new 
byte[0]);
+
+        byte[] protobufBytes = extractProtobufBytes(serializedSession);
+        ProtobufMessages.Header originalHeader = 
ProtobufMessages.Header.parseFrom(protobufBytes);
+
+        String injectedClassName = "example/Payload";
+        byte[] fakeBytecode = new byte[]{(byte) 0xCA, (byte) 0xFE, (byte) 
0xBA, (byte) 0xBE};
+
+        ProtobufMessages.Header tamperedHeader = originalHeader.toBuilder()
+                
.addRuntimeClassDefinitions(ProtobufMessages.RuntimeClassDef.newBuilder()
+                        .setClassFqName(injectedClassName)
+                        .setClassDef(ByteString.copyFrom(fakeBytecode))
+                        .build())
+                .build();
+
+        byte[] tamperedSession = 
wrapInObjectStream(tamperedHeader.toByteArray());
+
+        assertThatThrownBy(() ->
+                marshaller.unmarshall(new 
ByteArrayInputStream(tamperedSession)))
+                .as("Unmarshalling with injected RuntimeClassDef should fail "
+                        + "— without drools-traits on the classpath, 
TraitFactory is null "
+                        + "and readRuntimeDefinedClasses() rejects all 
RuntimeClassDef entries")
+                .isInstanceOf(RuntimeException.class)
+                .hasMessageContaining("no TraitFactory is available");
+
+        assertThat(pcl.getStore().containsKey(injectedClassName))
+                .as("Bytecode for injected class '%s' must NOT be in the 
classloader store",
+                        injectedClassName)
+                .isFalse();
+    }
+
+    @Test
+    void testReadRuntimeDefinedClassesRejectsNonTraitClasses() throws 
Exception {
+        String injectedClassName = "org/drools/example/Payload";
+        byte[] fakeBytecode = new byte[]{(byte) 0xCA, (byte) 0xFE, (byte) 
0xBA, (byte) 0xBE};
+
+        ProtobufMessages.Header header = ProtobufMessages.Header.newBuilder()
+                
.addRuntimeClassDefinitions(ProtobufMessages.RuntimeClassDef.newBuilder()
+                        .setClassFqName(injectedClassName)
+                        .setClassDef(ByteString.copyFrom(fakeBytecode))
+                        .build())
+                .build();
+
+        java.util.Map<String, byte[]> store = new java.util.HashMap<>();
+        ProjectClassLoader pcl = ProjectClassLoader.createProjectClassLoader(
+                getClass().getClassLoader(), store);
+
+        assertThatThrownBy(() ->
+                PersisterHelper.readRuntimeDefinedClasses(header, pcl, null))
+                .as("readRuntimeDefinedClasses() should reject when no 
TraitFactory is available")
+                .isInstanceOf(RuntimeException.class)
+                .hasMessageContaining("no TraitFactory is available");
+
+        assertThat(store.containsKey(injectedClassName))
+                .as("Rejected class must not be in the classloader store")
+                .isFalse();
+    }
+
+    private void setPrivateKeyProperties() {
+        URL serverKeyStoreURL = 
getClass().getResource("droolsServer.keystore");
+        System.setProperty(KeyStoreConstants.PROP_SIGN, "true");
+        System.setProperty(KeyStoreConstants.PROP_PVT_KS_URL, 
serverKeyStoreURL.toExternalForm());
+        System.setProperty(KeyStoreConstants.PROP_PVT_KS_PWD, "serverpwd");
+        System.setProperty(KeyStoreConstants.PROP_PVT_ALIAS, "droolsKey");
+        System.setProperty(KeyStoreConstants.PROP_PVT_PWD, "keypwd");
+        KeyStoreHelper.reInit();
+    }
+
+    private void setPublicKeyProperties() {
+        URL clientKeyStoreURL = 
getClass().getResource("droolsClient.keystore");
+        System.setProperty(KeyStoreConstants.PROP_SIGN, "true");
+        System.setProperty(KeyStoreConstants.PROP_PUB_KS_URL, 
clientKeyStoreURL.toExternalForm());
+        System.setProperty(KeyStoreConstants.PROP_PUB_KS_PWD, "clientpwd");
+        KeyStoreHelper.reInit();
+    }
+
+    @Test
+    void testDeserializationFilterBlocksBeforeStaticInitializer() throws 
Exception {
+        byte[] probeBytes = serializeObject(new 
testdata.enhancement.StaticInitProbe());
+
+        // Clear the marker set by the serialization above (loading the class 
runs its static init)
+        
System.clearProperty(testdata.enhancement.StaticInitProbe.MARKER_PROPERTY);
+
+        // Use a child-first classloader so Class.forName triggers a fresh 
class load
+        // (the JVM only runs static initializers once per classloader)
+        ClassLoader childFirstCL = new 
ChildFirstClassLoader(getClass().getClassLoader());
+
+        JavaSerializableResolverStrategy strategy =
+                new 
JavaSerializableResolverStrategy(ClassObjectMarshallingStrategyAcceptor.DEFAULT);
+
+        assertThatThrownBy(() ->
+                strategy.unmarshal(null, null, probeBytes, childFirstCL))
+                .as("Deserialization of non-whitelisted class should be 
rejected")
+                .isInstanceOf(RuntimeException.class);
+
+        
assertThat(System.getProperty(testdata.enhancement.StaticInitProbe.MARKER_PROPERTY))
+                .as("Static initializer should NOT have run — the filter 
should reject "
+                        + "the class before Class.forName initializes it")
+                .isNull();
+    }
+
+    private static class ChildFirstClassLoader extends ClassLoader {
+        private final ClassLoader parent;
+
+        ChildFirstClassLoader(ClassLoader parent) {
+            super(parent);
+            this.parent = parent;
+        }
+
+        @Override
+        protected Class<?> loadClass(String name, boolean resolve) throws 
ClassNotFoundException {
+            if (name.startsWith("testdata.enhancement.")) {
+                Class<?> loaded = findLoadedClass(name);
+                if (loaded != null) {
+                    return loaded;
+                }
+                String resourceName = name.replace('.', '/') + ".class";
+                try (java.io.InputStream is = 
parent.getResourceAsStream(resourceName)) {
+                    if (is == null) {
+                        throw new ClassNotFoundException(name);
+                    }
+                    byte[] bytes = is.readAllBytes();
+                    return defineClass(name, bytes, 0, bytes.length);
+                } catch (IOException e) {
+                    throw new ClassNotFoundException(name, e);
+                }
+            }
+            return super.loadClass(name, resolve);
+        }
+    }
+
+    private static byte[] serializeObject(Object obj) throws IOException {
+        try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
+             ObjectOutputStream oos = new ObjectOutputStream(bos)) {
+            oos.writeObject(obj);
+            oos.flush();
+            return bos.toByteArray();
+        }
+    }
+
+    private static byte[] extractProtobufBytes(byte[] objectStreamBytes) 
throws IOException {
+        try (ObjectInputStream ois = new ObjectInputStream(new 
ByteArrayInputStream(objectStreamBytes))) {
+            ByteArrayOutputStream result = new ByteArrayOutputStream();
+            byte[] buf = new byte[4096];
+            int read;
+            while ((read = ois.read(buf)) != -1) {
+                result.write(buf, 0, read);
+            }
+            return result.toByteArray();
+        }
+    }
+
+    private static byte[] wrapInObjectStream(byte[] protobufBytes) throws 
IOException {
+        try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
+             ObjectOutputStream oos = new ObjectOutputStream(bos)) {
+            oos.write(protobufBytes);
+            oos.flush();
+            return bos.toByteArray();
+        }
+    }
+}
diff --git 
a/drools-serialization-protobuf/src/test/java/org/drools/serialization/protobuf/QueryTest.java
 
b/drools-serialization-protobuf/src/test/java/org/drools/serialization/protobuf/QueryTest.java
index cea98f0db83..37e577e8e51 100644
--- 
a/drools-serialization-protobuf/src/test/java/org/drools/serialization/protobuf/QueryTest.java
+++ 
b/drools-serialization-protobuf/src/test/java/org/drools/serialization/protobuf/QueryTest.java
@@ -42,6 +42,17 @@ import static org.assertj.core.api.Assertions.assertThat;
 
 public class QueryTest extends CommonTestMethodBase {
 
+    private final DeserializationFilterTestSupport filterSupport = new 
DeserializationFilterTestSupport();
+
+    @org.junit.jupiter.api.BeforeEach
+    public void setUpDeserializationFilter() {
+        filterSupport.setUp("org.drools.mvel.compiler.Cheese");
+    }
+
+    @org.junit.jupiter.api.AfterEach
+    public void clearDeserializationFilter() {
+        filterSupport.tearDown();
+    }
 
     private static QueryResults getQueryResults(KieSession session, String 
queryName, Object... arguments ) throws Exception {
         QueryResultsImpl results = (QueryResultsImpl) session.getQueryResults( 
queryName, arguments );
diff --git 
a/drools-serialization-protobuf/src/test/java/org/drools/serialization/protobuf/SerializationHelper.java
 
b/drools-serialization-protobuf/src/test/java/org/drools/serialization/protobuf/SerializationHelper.java
index 694882c0df4..b52edc15fbb 100644
--- 
a/drools-serialization-protobuf/src/test/java/org/drools/serialization/protobuf/SerializationHelper.java
+++ 
b/drools-serialization-protobuf/src/test/java/org/drools/serialization/protobuf/SerializationHelper.java
@@ -93,4 +93,25 @@ public class SerializationHelper {
 
         return readSessionResult;
     }
+
+    public static byte[] serializeStatefulKnowledgeSession(final KieSession 
ksession) throws Exception {
+        final KieBase kbase = ksession.getKieBase();
+        final ProtobufMarshaller marshaller = (ProtobufMarshaller) 
MarshallerFactory.newMarshaller(kbase,
+                (ObjectMarshallingStrategy[]) 
ksession.getEnvironment().get(EnvironmentName.OBJECT_MARSHALLING_STRATEGIES));
+        ksession.getEnvironment().set(EnvironmentName.GLOBALS, 
ksession.getGlobals());
+
+        try (final ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
+            marshaller.marshall(bos, ksession, 
ksession.getSessionClock().getCurrentTime());
+            return bos.toByteArray();
+        }
+    }
+
+    public static StatefulKnowledgeSession 
deserializeStatefulKnowledgeSession(final byte[] serializedSession,
+                                                                               
final KieBase kbase) throws Exception {
+        final ProtobufMarshaller marshaller = (ProtobufMarshaller) 
MarshallerFactory.newMarshaller(kbase);
+
+        try (final ByteArrayInputStream bais = new 
ByteArrayInputStream(serializedSession)) {
+            return marshaller.unmarshall(bais, null, null);
+        }
+    }
 }
diff --git 
a/drools-serialization-protobuf/src/test/java/org/drools/serialization/protobuf/TruthMaintenanceTest.java
 
b/drools-serialization-protobuf/src/test/java/org/drools/serialization/protobuf/TruthMaintenanceTest.java
index fe16a6792c1..74ad801315f 100644
--- 
a/drools-serialization-protobuf/src/test/java/org/drools/serialization/protobuf/TruthMaintenanceTest.java
+++ 
b/drools-serialization-protobuf/src/test/java/org/drools/serialization/protobuf/TruthMaintenanceTest.java
@@ -57,6 +57,22 @@ import static 
org.drools.serialization.protobuf.SerializationHelper.getSerialise
 
 public class TruthMaintenanceTest extends CommonTestMethodBase {
 
+    private final DeserializationFilterTestSupport filterSupport = new 
DeserializationFilterTestSupport();
+
+    @org.junit.jupiter.api.BeforeEach
+    public void setUpDeserializationFilter() {
+        filterSupport.setUp("org.drools.mvel.compiler.Address",
+                "org.drools.mvel.compiler.Alarm",
+                "org.drools.mvel.compiler.Cheese",
+                "org.drools.mvel.compiler.Person",
+                "org.drools.mvel.compiler.Sensor");
+    }
+
+    @org.junit.jupiter.api.AfterEach
+    public void clearDeserializationFilter() {
+        filterSupport.tearDown();
+    }
+
     @Test
     public void testLogicalInsertionsDynamicRule() throws Exception {
         KnowledgeBuilder kbuilder = 
KnowledgeBuilderFactory.newKnowledgeBuilder();
diff --git 
a/drools-serialization-protobuf/src/test/java/org/drools/serialization/protobuf/UnmarshallingTest.java
 
b/drools-serialization-protobuf/src/test/java/org/drools/serialization/protobuf/UnmarshallingTest.java
index 52594eac9a9..686fc4a32ee 100644
--- 
a/drools-serialization-protobuf/src/test/java/org/drools/serialization/protobuf/UnmarshallingTest.java
+++ 
b/drools-serialization-protobuf/src/test/java/org/drools/serialization/protobuf/UnmarshallingTest.java
@@ -44,6 +44,18 @@ import static org.assertj.core.api.Assertions.fail;
 
 public class UnmarshallingTest {
 
+    private final DeserializationFilterTestSupport filterSupport = new 
DeserializationFilterTestSupport();
+
+    @org.junit.jupiter.api.BeforeEach
+    public void setUpDeserializationFilter() {
+        
filterSupport.setUp("org.drools.serialization.protobuf.UnmarshallingTest$Ben");
+    }
+
+    @org.junit.jupiter.api.AfterEach
+    public void clearDeserializationFilter() {
+        filterSupport.tearDown();
+    }
+
     @Test
     public void testMarshallWithNot() throws Exception {
         String whenBenNotVilgaxRule =
diff --git 
a/drools-serialization-protobuf/src/test/java/testdata/enhancement/DeserializationProbe.java
 
b/drools-serialization-protobuf/src/test/java/testdata/enhancement/DeserializationProbe.java
new file mode 100644
index 00000000000..101ab303142
--- /dev/null
+++ 
b/drools-serialization-protobuf/src/test/java/testdata/enhancement/DeserializationProbe.java
@@ -0,0 +1,33 @@
+/*
+ * 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 testdata.enhancement;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.Serializable;
+
+public class DeserializationProbe implements Serializable {
+    private static final long serialVersionUID = 1L;
+    public static volatile boolean deserialized = false;
+
+    private void readObject(ObjectInputStream in) throws IOException, 
ClassNotFoundException {
+        in.defaultReadObject();
+        deserialized = true;
+    }
+}
diff --git 
a/drools-serialization-protobuf/src/test/java/testdata/enhancement/EnumProbe.java
 
b/drools-serialization-protobuf/src/test/java/testdata/enhancement/EnumProbe.java
new file mode 100644
index 00000000000..a9437daa47e
--- /dev/null
+++ 
b/drools-serialization-protobuf/src/test/java/testdata/enhancement/EnumProbe.java
@@ -0,0 +1,29 @@
+/*
+ * 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 testdata.enhancement;
+
+public enum EnumProbe {
+    INSTANCE;
+
+    public static volatile boolean initialized = false;
+
+    static {
+        initialized = true;
+    }
+}
diff --git 
a/drools-serialization-protobuf/src/test/java/testdata/enhancement/StaticInitProbe.java
 
b/drools-serialization-protobuf/src/test/java/testdata/enhancement/StaticInitProbe.java
new file mode 100644
index 00000000000..53c2567d5a1
--- /dev/null
+++ 
b/drools-serialization-protobuf/src/test/java/testdata/enhancement/StaticInitProbe.java
@@ -0,0 +1,34 @@
+/*
+ * 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 testdata.enhancement;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+public class StaticInitProbe implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    public static final String MARKER_PROPERTY = 
"drools.test.staticInitProbe.initialized";
+
+    static {
+        System.setProperty(MARKER_PROPERTY, "true");
+    }
+}
diff --git a/drools-serialization-protobuf/src/test/resources/logback-test.xml 
b/drools-serialization-protobuf/src/test/resources/logback-test.xml
new file mode 100644
index 00000000000..fb13562b853
--- /dev/null
+++ b/drools-serialization-protobuf/src/test/resources/logback-test.xml
@@ -0,0 +1,31 @@
+<?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.
+
+-->
+<configuration>
+    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+        <encoder>
+            <pattern>%-5level %logger{36} - %msg%n</pattern>
+        </encoder>
+    </appender>
+    <root level="WARN">
+        <appender-ref ref="STDOUT"/>
+    </root>
+</configuration>
diff --git 
a/drools-test-coverage/test-compiler-integration/src/test/java/org/drools/compiler/integrationtests/BackwardChainingTest.java
 
b/drools-test-coverage/test-compiler-integration/src/test/java/org/drools/compiler/integrationtests/BackwardChainingTest.java
index 15feda07449..8672ec5c22a 100644
--- 
a/drools-test-coverage/test-compiler-integration/src/test/java/org/drools/compiler/integrationtests/BackwardChainingTest.java
+++ 
b/drools-test-coverage/test-compiler-integration/src/test/java/org/drools/compiler/integrationtests/BackwardChainingTest.java
@@ -88,6 +88,27 @@ import static org.kie.api.runtime.rule.Variable.v;
 
 public class BackwardChainingTest extends AbstractBackwardChainingTest {
 
+    @org.junit.jupiter.api.BeforeEach
+    public void setUpDeserializationFilter() {
+        
System.setProperty(org.drools.core.util.KeyStoreConstants.PROP_ALLOWED_DESER_CLASS_PATTERNS,
+                
"org.drools.compiler.integrationtests.BackwardChainingTest$Man;" +
+                
"org.drools.compiler.integrationtests.BackwardChainingTest$Woman;" +
+                
"org.drools.compiler.integrationtests.BackwardChainingTest$Parent;" +
+                "org.drools.compiler.test.Door;" +
+                "org.drools.compiler.test.Edible;" +
+                "org.drools.compiler.test.Here;" +
+                "org.drools.compiler.test.Location;" +
+                "org.drools.compiler.test.Room;" +
+                "org.drools.compiler.test.TastesYucky;" +
+                "org.drools.testcoverage.common.model.Person;" +
+                "org.drools.testcoverage.common.model.Address");
+    }
+
+    @org.junit.jupiter.api.AfterEach
+    public void clearDeserializationFilter() {
+        
System.clearProperty(org.drools.core.util.KeyStoreConstants.PROP_ALLOWED_DESER_CLASS_PATTERNS);
+    }
+
     public static Stream<KieBaseTestConfiguration> parameters() {
         return 
TestParametersUtil2.getKieBaseCloudConfigurations(true).stream();
     }
diff --git 
a/drools-test-coverage/test-compiler-integration/src/test/java/org/drools/compiler/integrationtests/CepEspTest.java
 
b/drools-test-coverage/test-compiler-integration/src/test/java/org/drools/compiler/integrationtests/CepEspTest.java
index ed56a00f5ca..1aea08d4f5f 100644
--- 
a/drools-test-coverage/test-compiler-integration/src/test/java/org/drools/compiler/integrationtests/CepEspTest.java
+++ 
b/drools-test-coverage/test-compiler-integration/src/test/java/org/drools/compiler/integrationtests/CepEspTest.java
@@ -108,6 +108,21 @@ import static org.mockito.Mockito.verify;
 
 public class CepEspTest extends AbstractCepEspTest {
 
+    @org.junit.jupiter.api.BeforeEach
+    public void setUpDeserializationFilter() {
+        
System.setProperty(org.drools.core.util.KeyStoreConstants.PROP_ALLOWED_DESER_CLASS_PATTERNS,
+                "org.drools.compiler.integrationtests.CepEspTest$EventA;" +
+                "org.drools.compiler.integrationtests.CepEspTest$SuperClass;" +
+                "org.drools.compiler.integrationtests.CepEspTest$SubClass;" +
+                "org.drools.testcoverage.common.model.OrderEvent;" +
+                "org.drools.testcoverage.common.model.StockTick");
+    }
+
+    @org.junit.jupiter.api.AfterEach
+    public void clearDeserializationFilter() {
+        
System.clearProperty(org.drools.core.util.KeyStoreConstants.PROP_ALLOWED_DESER_CLASS_PATTERNS);
+    }
+
     public static Stream<KieBaseTestConfiguration> parameters() {
         return 
TestParametersUtil2.getKieBaseStreamConfigurations(true).stream();
     }
diff --git 
a/drools-test-coverage/test-compiler-integration/src/test/java/org/drools/compiler/integrationtests/incrementalcompilation/IncrementalCompilationTest.java
 
b/drools-test-coverage/test-compiler-integration/src/test/java/org/drools/compiler/integrationtests/incrementalcompilation/IncrementalCompilationTest.java
index 172f455a702..e907f6b0e2d 100644
--- 
a/drools-test-coverage/test-compiler-integration/src/test/java/org/drools/compiler/integrationtests/incrementalcompilation/IncrementalCompilationTest.java
+++ 
b/drools-test-coverage/test-compiler-integration/src/test/java/org/drools/compiler/integrationtests/incrementalcompilation/IncrementalCompilationTest.java
@@ -101,6 +101,19 @@ import static 
org.drools.core.util.DroolsTestUtil.rulestoMap;
 
 public class IncrementalCompilationTest {
 
+    @org.junit.jupiter.api.BeforeEach
+    public void setUpDeserializationFilter() {
+        
System.setProperty(org.drools.core.util.KeyStoreConstants.PROP_ALLOWED_DESER_CLASS_PATTERNS,
+                "org.drools.testcoverage.common.model.Message;" +
+                "org.drools.testcoverage.common.model.Person;" +
+                "org.drools.testcoverage.common.model.Address");
+    }
+
+    @org.junit.jupiter.api.AfterEach
+    public void clearDeserializationFilter() {
+        
System.clearProperty(org.drools.core.util.KeyStoreConstants.PROP_ALLOWED_DESER_CLASS_PATTERNS);
+    }
+
     public static Stream<KieBaseTestConfiguration> parameters() {
         return 
TestParametersUtil2.getKieBaseCloudConfigurations(true).stream();
     }
diff --git 
a/drools-test-coverage/test-suite/src/test/java/org/drools/testcoverage/regression/EventDeserializationInPastTest.java
 
b/drools-test-coverage/test-suite/src/test/java/org/drools/testcoverage/regression/EventDeserializationInPastTest.java
index bd11e5bceaf..4ad6a25567f 100644
--- 
a/drools-test-coverage/test-suite/src/test/java/org/drools/testcoverage/regression/EventDeserializationInPastTest.java
+++ 
b/drools-test-coverage/test-suite/src/test/java/org/drools/testcoverage/regression/EventDeserializationInPastTest.java
@@ -45,6 +45,17 @@ import static org.assertj.core.api.Assertions.fail;
  */
 public class EventDeserializationInPastTest {
 
+    @org.junit.jupiter.api.BeforeEach
+    public void setUpDeserializationFilter() {
+        
System.setProperty(org.drools.core.util.KeyStoreConstants.PROP_ALLOWED_DESER_CLASS_PATTERNS,
+                
"org.drools.testcoverage.regression.EventDeserializationInPastTest$Event1");
+    }
+
+    @org.junit.jupiter.api.AfterEach
+    public void clearDeserializationFilter() {
+        
System.clearProperty(org.drools.core.util.KeyStoreConstants.PROP_ALLOWED_DESER_CLASS_PATTERNS);
+    }
+
     @Test
     public void testSerializationWithEventInPastBZ1205666() {
         // DROOLS-749
diff --git 
a/drools-test-coverage/test-suite/src/test/java/org/drools/testcoverage/regression/LogicalInsertionsSerializationTest.java
 
b/drools-test-coverage/test-suite/src/test/java/org/drools/testcoverage/regression/LogicalInsertionsSerializationTest.java
index a676607881f..7e22c487e91 100644
--- 
a/drools-test-coverage/test-suite/src/test/java/org/drools/testcoverage/regression/LogicalInsertionsSerializationTest.java
+++ 
b/drools-test-coverage/test-suite/src/test/java/org/drools/testcoverage/regression/LogicalInsertionsSerializationTest.java
@@ -50,6 +50,18 @@ public class LogicalInsertionsSerializationTest extends 
KieSessionTest {
     @TempDir
     public File name;
 
+    @org.junit.jupiter.api.BeforeEach
+    public void setUpDeserializationFilter() {
+        
System.setProperty(org.drools.core.util.KeyStoreConstants.PROP_ALLOWED_DESER_CLASS_PATTERNS,
+                "org.drools.testcoverage.regression.Person;" +
+                "org.drools.testcoverage.regression.Employee");
+    }
+
+    @org.junit.jupiter.api.AfterEach
+    public void clearDeserializationFilter() {
+        
System.clearProperty(org.drools.core.util.KeyStoreConstants.PROP_ALLOWED_DESER_CLASS_PATTERNS);
+    }
+
     public static Stream<Arguments> parameters() {
         return 
TestParametersUtil2.getKieBaseAndStatefulKieSessionConfigurations().stream();
     }
diff --git 
a/drools-traits/src/test/java/org/drools/traits/compiler/factmodel/traits/LogicalTraitTest.java
 
b/drools-traits/src/test/java/org/drools/traits/compiler/factmodel/traits/LogicalTraitTest.java
index 365a818d2f2..096d99dd565 100644
--- 
a/drools-traits/src/test/java/org/drools/traits/compiler/factmodel/traits/LogicalTraitTest.java
+++ 
b/drools-traits/src/test/java/org/drools/traits/compiler/factmodel/traits/LogicalTraitTest.java
@@ -60,6 +60,21 @@ public class LogicalTraitTest extends CommonTraitTest {
 
     private static final Logger LOGGER = 
LoggerFactory.getLogger(LogicalTraitTest.class);
 
+    @org.junit.jupiter.api.BeforeEach
+    public void setUpDeserializationFilter() {
+        // Prefixes used because trait proxy class names are generated at 
runtime from DRL-declared
+        // types (e.g. org.drools.test.X.org.drools.test.Y_Proxy). Exact names 
are not practical
+        // as each test method declares different trait/core combinations.
+        
System.setProperty(org.drools.core.util.KeyStoreConstants.PROP_ALLOWED_DESER_CLASS_PATTERNS,
+                "org.drools.test.*;" +
+                "org.drools.factmodel.traits.*");
+    }
+
+    @org.junit.jupiter.api.AfterEach
+    public void clearDeserializationFilter() {
+        
System.clearProperty(org.drools.core.util.KeyStoreConstants.PROP_ALLOWED_DESER_CLASS_PATTERNS);
+    }
+
     public static Stream<VirtualPropertyMode> parameters() {
         return Stream.of(VirtualPropertyMode.MAP, VirtualPropertyMode.TRIPLES);
     }
@@ -1096,6 +1111,77 @@ public class LogicalTraitTest extends CommonTraitTest {
         }
     }
 
+    @ParameterizedTest()
+    @MethodSource("parameters")
+    public void testSerialWithFreshKieBase(VirtualPropertyMode mode) throws 
Exception {
+
+        String drl = "package org.drools.test; \n" +
+                     "import org.drools.base.factmodel.traits.*; \n" +
+                     "import org.drools.base.factmodel.traits.Trait; \n" +
+                     "" +
+                     "global java.util.List list; \n" +
+                     "" +
+                     "declare trait X end \n" +
+                     "declare trait Z end \n" +
+                     "" +
+                     "declare Y \n" +
+                     "@Traitable( ) \n" +
+                     "end \n" +
+                     "" +
+                     "rule Don \n" +
+                     "when \n" +
+                     "then \n" +
+                     "  Y y = new Y( ); \n" +
+                     "  don( y, X.class ); \n" +
+                     "  don( y, Z.class ); \n" +
+                     "end \n" +
+                     "" +
+                     "rule CheckTraits \n" +
+                     "when \n" +
+                     "  String( this == \"go\" ) \n" +
+                     "  $x : X() \n" +
+                     "  $z : Z() \n" +
+                     "then \n" +
+                     "  list.add( \"ok\" ); \n" +
+                     "end \n" +
+                     "";
+
+        KieBase kbase = loadKnowledgeBaseFromString( drl );
+        TraitFactoryImpl.setMode(mode, (InternalRuleBase) kbase);
+
+        KieSession ks = kbase.newKieSession();
+        List list = new ArrayList();
+        ks.setGlobal( "list", list );
+
+        ks.fireAllRules();
+
+        // Verify facts exist before serialization: 1 Y + 1 X proxy + 1 Z 
proxy = 3
+        assertThat(ks.getObjects()).hasSize(3);
+
+        byte[] serialized = 
SerializationHelper.serializeStatefulKnowledgeSession( ks );
+
+        ks.dispose();
+
+        // Build a fresh KieBase from the same DRL to simulate JVM restart
+        KieBase freshKbase = loadKnowledgeBaseFromString( drl );
+        TraitFactoryImpl.setMode(mode, (InternalRuleBase) freshKbase);
+
+        KieSession deserialized = 
SerializationHelper.deserializeStatefulKnowledgeSession( serialized, freshKbase 
);
+
+        // Verify facts survived serialization/deserialization
+        assertThat(deserialized.getObjects()).hasSize(3);
+
+        List freshList = new ArrayList();
+        deserialized.setGlobal( "list", freshList );
+
+        deserialized.insert( "go" );
+        deserialized.fireAllRules();
+
+        assertThat(freshList).containsExactly("ok");
+
+        deserialized.dispose();
+    }
+
     @ParameterizedTest()
     @MethodSource("parameters")
     public void testTraitMismatchTypes(VirtualPropertyMode mode)
diff --git 
a/drools-traits/src/test/java/org/drools/traits/compiler/factmodel/traits/TraitMarshallingValidationTest.java
 
b/drools-traits/src/test/java/org/drools/traits/compiler/factmodel/traits/TraitMarshallingValidationTest.java
new file mode 100644
index 00000000000..f41f215ed54
--- /dev/null
+++ 
b/drools-traits/src/test/java/org/drools/traits/compiler/factmodel/traits/TraitMarshallingValidationTest.java
@@ -0,0 +1,133 @@
+/*
+ * 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.drools.traits.compiler.factmodel.traits;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+
+import com.google.protobuf.ByteString;
+import org.drools.core.impl.EnvironmentFactory;
+import org.drools.core.marshalling.ClassObjectMarshallingStrategyAcceptor;
+import org.drools.core.marshalling.SerializablePlaceholderResolverStrategy;
+import org.drools.kiesession.rulebase.InternalKnowledgeBase;
+import org.drools.serialization.protobuf.ProtobufMarshaller;
+import org.drools.serialization.protobuf.ProtobufMessages;
+import org.drools.wiring.api.classloader.ProjectClassLoader;
+import org.junit.jupiter.api.Test;
+import org.kie.api.KieBase;
+import org.kie.api.io.ResourceType;
+import org.kie.api.marshalling.ObjectMarshallingStrategy;
+import org.kie.api.runtime.Environment;
+import org.kie.api.runtime.EnvironmentName;
+import org.kie.api.runtime.KieSession;
+import org.kie.internal.marshalling.MarshallerFactory;
+import org.kie.internal.utils.KieHelper;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+public class TraitMarshallingValidationTest {
+
+    @Test
+    void testInjectedRuntimeClassDefLandsInStoreWhenTraitsPresent() throws 
Exception {
+        String drl =
+                "package org.drools.test;\n" +
+                "declare MyFact\n" +
+                "    value : String\n" +
+                "end\n" +
+                "rule R1 when\n" +
+                "    String()\n" +
+                "then\n" +
+                "end\n";
+
+        KieBase kbase = new KieHelper().addContent(drl, 
ResourceType.DRL).build();
+
+        ObjectMarshallingStrategy[] strategies = new 
ObjectMarshallingStrategy[]{
+                new 
SerializablePlaceholderResolverStrategy(ClassObjectMarshallingStrategyAcceptor.DEFAULT)
+        };
+        Environment env = EnvironmentFactory.newEnvironment();
+        env.set(EnvironmentName.OBJECT_MARSHALLING_STRATEGIES, strategies);
+
+        KieSession ksession = kbase.newKieSession(null, env);
+        ksession.insert("test");
+        ksession.fireAllRules();
+
+        ProtobufMarshaller marshaller = (ProtobufMarshaller) 
MarshallerFactory.newMarshaller(kbase, strategies);
+
+        byte[] serializedSession;
+        try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
+            marshaller.marshall(bos, ksession, 
ksession.getSessionClock().getCurrentTime());
+            serializedSession = bos.toByteArray();
+        }
+        ksession.dispose();
+
+        ProjectClassLoader pcl = (ProjectClassLoader) ((InternalKnowledgeBase) 
kbase).getRootClassLoader();
+
+        byte[] protobufBytes = extractProtobufBytes(serializedSession);
+        ProtobufMessages.Header originalHeader = 
ProtobufMessages.Header.parseFrom(protobufBytes);
+
+        String injectedClassName = "example/Payload.class";
+        byte[] fakeBytecode = new byte[]{(byte) 0xCA, (byte) 0xFE, (byte) 
0xBA, (byte) 0xBE};
+
+        ProtobufMessages.Header tamperedHeader = originalHeader.toBuilder()
+                
.addRuntimeClassDefinitions(ProtobufMessages.RuntimeClassDef.newBuilder()
+                        .setClassFqName(injectedClassName)
+                        .setClassDef(ByteString.copyFrom(fakeBytecode))
+                        .build())
+                .build();
+
+        byte[] tamperedSession = 
wrapInObjectStream(tamperedHeader.toByteArray());
+
+        assertThatThrownBy(() ->
+                marshaller.unmarshall(new 
ByteArrayInputStream(tamperedSession)))
+                .as("With drools-traits on the classpath, TraitFactory is 
non-null, "
+                        + "but readRuntimeDefinedClasses() should still reject 
"
+                        + "class names that don't match the trait proxy naming 
pattern")
+                .isInstanceOf(RuntimeException.class);
+
+        assertThat(pcl.getStore() != null && 
pcl.getStore().containsKey(injectedClassName))
+                .as("Injected bytecode for '%s' should NOT be in the 
classloader store",
+                        injectedClassName)
+                .isFalse();
+    }
+
+    private static byte[] extractProtobufBytes(byte[] objectStreamBytes) 
throws IOException {
+        try (ObjectInputStream ois = new ObjectInputStream(new 
ByteArrayInputStream(objectStreamBytes))) {
+            ByteArrayOutputStream result = new ByteArrayOutputStream();
+            byte[] buf = new byte[4096];
+            int read;
+            while ((read = ois.read(buf)) != -1) {
+                result.write(buf, 0, read);
+            }
+            return result.toByteArray();
+        }
+    }
+
+    private static byte[] wrapInObjectStream(byte[] protobufBytes) throws 
IOException {
+        try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
+             ObjectOutputStream oos = new ObjectOutputStream(bos)) {
+            oos.write(protobufBytes);
+            oos.flush();
+            return bos.toByteArray();
+        }
+    }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]


Reply via email to