Goal: Implement zero-trust security with explicit type allowlisting
Timeline: 2-3 weeks
Security improvement: Maximum - only explicitly allowed types can be processed
This phase implements the highest level of security by migrating to allowlist mode, where only explicitly allowed types can be processed.
Based on Phase 2 analysis, create allowlist configurations:
1 2public class XStreamAllowlistConfig { public static void configureAllowlist(XStream xstream) { // Core application types (from Phase 2 analysis) xstream.allowTypes(new Class<?>[] { UserProfile.class, UserPreferences.class, ApplicationConfig.class, DatabaseConfig.class, SessionData.class, CacheEntry.class }); // Essential framework types xstream.allowTypes(new Class<?>[] { ArrayList.class, LinkedList.class, HashMap.class, LinkedHashMap.class, HashSet.class, Date.class, BigDecimal.class, UUID.class, String.class, Integer.class, Long.class, Boolean.class }); // Safe hierarchies (use judiciously) xstream.allowTypeHierarchy(Collection.class); xstream.allowTypeHierarchy(Map.class); xstream.allowTypeHierarchy(Number.class); xstream.allowTypeHierarchy(Enum.class); } }
Different environments may need different allowlist configurations:
1 2public class ProductionXStreamConfig { public static void configureProduction(XStream xstream) { // Minimal set for production xstream.allowTypes(PRODUCTION_CORE_TYPES); xstream.allowTypes(ESSENTIAL_FRAMEWORK_TYPES); // Conservative hierarchy allowlisting xstream.allowTypeHierarchy(Collection.class); xstream.allowTypeHierarchy(Map.class); // No test or development specific types } private static final Class<?>[] PRODUCTION_CORE_TYPES = { UserProfile.class, ApplicationConfig.class, SessionData.class // Minimal set for production use }; }
1 2public class DevelopmentXStreamConfig { public static void configureDevelopment(XStream xstream) { // Include production types ProductionXStreamConfig.configureProduction(xstream); // Add development-specific types xstream.allowTypes(DEVELOPMENT_TYPES); xstream.allowTypes(TEST_FIXTURE_TYPES); // Broader hierarchies for development convenience xstream.allowTypeHierarchy(Serializable.class); } private static final Class<?>[] DEVELOPMENT_TYPES = { DebugInfo.class, TestDataBuilder.class, MockObject.class // Development and testing specific types }; }
Migrate one service/component at a time to minimize risk:
1 2@Configuration public class GradualMigrationConfig { @Value("${migration.phase3.services:}") private Set<String> migratedServices; @Bean public XStream conditionalXStream() { String serviceName = getCurrentServiceName(); if (migratedServices.contains(serviceName)) { // This service is migrated to allowlist mode return createAllowlistXStream(); } else { // Still using blocklist mode return createBlocklistXStream(); } } private XStream createAllowlistXStream() { XStream xstream = new BlocklistRestrictedXStream(); // NO AnyTypePermission.ANY - this is allowlist mode XStreamAllowlistConfig.configureAllowlist(xstream); return xstream; } private XStream createBlocklistXStream() { XStream xstream = new BlocklistRestrictedXStream(); xstream.addPermission(AnyTypePermission.ANY); // Still blocklist mode return xstream; } }
1 2@Component public class Phase3FeatureFlagService { @Value("${feature.allowlist-mode.enabled:false}") private boolean allowlistModeEnabled; @Value("${feature.allowlist-mode.percentage:0}") private int allowlistPercentage; public XStream createXStream(String context) { if (shouldUseAllowlistMode(context)) { return createAllowlistXStream(context); } else { return createBlocklistXStream(); } } private boolean shouldUseAllowlistMode(String context) { if (!allowlistModeEnabled) { return false; } // Gradual rollout based on percentage int contextHash = Math.abs(context.hashCode()); return (contextHash % 100) < allowlistPercentage; } }
CannotResolveClassException for legitimate type1 2// Problem: Type not in allowlist try { Object result = xstream.fromXML(xml); } catch (CannotResolveClassException e) { logger.warn("Type not in allowlist: {}", e.getMessage()); // Solution: Add missing type to allowlist } // Fix: Add the missing type public class AllowlistUpdater { public void addMissingType(XStream xstream, String className) { try { Class<?> clazz = Class.forName(className); // Validate it's safe to add if (isSafeToAllow(clazz)) { xstream.allowTypes(new Class<?>[] { clazz }); logger.info("Added safe type to allowlist: {}", className); } else { logger.error("Unsafe type requested for allowlist: {}", className); } } catch (ClassNotFoundException e) { logger.error("Cannot load class for allowlist: {}", className); } } private boolean isSafeToAllow(Class<?> clazz) { // Check against security policies return !clazz.getName().contains("ProcessBuilder") && !clazz.getName().contains("Runtime") && // Add more safety checks true; } }
1 2// Solution: Use hierarchy allowlisting carefully public class HierarchyAllowlistStrategy { public void allowSafeHierarchies(XStream xstream) { // Safe hierarchies that are commonly needed xstream.allowTypeHierarchy(Collection.class); xstream.allowTypeHierarchy(Map.class); xstream.allowTypeHierarchy(Number.class); xstream.allowTypeHierarchy(Enum.class); // Application-specific hierarchies xstream.allowTypeHierarchy(BaseDataObject.class); xstream.allowTypeHierarchy(ConfigurationItem.class); } }
1 2// Solution: Handle dynamic types with careful analysis public class DynamicTypeHandler { public void handleDynamicTypes(XStream xstream, Set<String> observedDynamicTypes) { for (String typeName : observedDynamicTypes) { if (isDynamicProxy(typeName)) { // Allow the interface, not the proxy String interfaceName = extractInterfaceName(typeName); allowTypeByName(xstream, interfaceName); } else if (isGenerated(typeName)) { // Handle generated classes carefully analyzeGeneratedType(xstream, typeName); } } } private boolean isDynamicProxy(String typeName) { return typeName.contains("$Proxy"); } private void analyzeGeneratedType(XStream xstream, String typeName) { // Careful analysis of generated types logger.info("Analyzing generated type: {}", typeName); // Only allow if it's from trusted generation } }
1 2@SpringBootTest @TestPropertySource(properties = { "migration.phase3.enabled=true", "xstream.mode=allowlist" }) class Phase3MigrationTest { @Autowired private XStream xstream; @Test void shouldProcessAllowlistedTypes() { // Test all types that should be allowed for (Class<?> allowedType : getAllowedTypes()) { Object instance = createTestInstance(allowedType); String xml = xstream.toXML(instance); Object restored = xstream.fromXML(xml); assertThat(restored).isInstanceOf(allowedType); } } @Test void shouldRejectNonAllowlistedTypes() { // Test that non-allowlisted types are rejected String nonAllowlistedXml = createNonAllowlistedXml(); assertThrows(CannotResolveClassException.class, () -> { xstream.fromXML(nonAllowlistedXml); }); } @Test void shouldStillBlockDangerousTypes() { // Verify dangerous types remain blocked assertThrows(IllegalArgumentException.class, () -> { xstream.allowTypes(new Class<?>[] { ProcessBuilder.class }); }); } }
1 2@Test public class Phase3LoadTest { @Test void shouldHandleLoadWithAllowlistMode() { XStream xstream = createAllowlistXStream(); ExecutorService executor = Executors.newFixedThreadPool(10); List<Future<Boolean>> futures = new ArrayList<>(); // Submit many concurrent operations for (int i = 0; i < 1000; i++) { futures.add(executor.submit(() -> { try { Object testData = createTestData(); String xml = xstream.toXML(testData); Object restored = xstream.fromXML(xml); return restored != null; } catch (Exception e) { logger.error("Load test operation failed", e); return false; } })); } // Verify all operations completed successfully long successCount = futures.stream() .mapToLong(future -> { try { return future.get() ? 1 : 0; } catch (Exception e) { return 0; } }) .sum(); assertThat(successCount).isEqualTo(1000); } }
1 2@Configuration public class BlueGreenMigrationConfig { @Value("${deployment.color:blue}") private String deploymentColor; @Bean public XStream deploymentSpecificXStream() { if ("green".equals(deploymentColor)) { // Green deployment uses allowlist mode return createAllowlistXStream(); } else { // Blue deployment still uses blocklist mode return createBlocklistXStream(); } } private XStream createAllowlistXStream() { XStream xstream = new BlocklistRestrictedXStream(); XStreamAllowlistConfig.configureAllowlist(xstream); return xstream; } }
1 2@Component public class Phase3MonitoringService { private final MeterRegistry meterRegistry; private final AlertService alertService; @EventListener public void onCannotResolveClass(CannotResolveClassException e) { // Track types that are being rejected meterRegistry.counter("xstream.allowlist.rejected", "class", e.getMessage(), "phase", "phase3").increment(); // Alert on unexpected rejections if (isUnexpectedRejection(e)) { alertService.sendAlert("Unexpected type rejection in Phase 3: " + e.getMessage()); } } @EventListener public void onSuccessfulProcessing(XStreamProcessingEvent e) { meterRegistry.counter("xstream.allowlist.success", "type", e.getResultType().getSimpleName(), "phase", "phase3").increment(); } }
1 2@Component public class EmergencyRollbackService { @Value("${emergency.rollback.enabled:false}") private boolean emergencyRollback; public XStream createXStreamWithRollback() { if (emergencyRollback) { logger.warn("Emergency rollback activated - using blocklist mode"); return createEmergencyBlocklistXStream(); } return createAllowlistXStream(); } private XStream createEmergencyBlocklistXStream() { XStream xstream = new BlocklistRestrictedXStream(); xstream.addPermission(AnyTypePermission.ANY); // Back to blocklist mode // Apply existing configuration applyLegacyConfiguration(xstream); return xstream; } }
1 2public class GradualRollbackService { public XStream createXStreamWithGradualRollback(String context) { RollbackStrategy strategy = determineRollbackStrategy(); switch (strategy) { case IMMEDIATE: return createBlocklistXStream(); case GRADUAL: return shouldRollbackContext(context) ? createBlocklistXStream() : createAllowlistXStream(); case NONE: return createAllowlistXStream(); default: return createBlocklistXStream(); // Safe default } } }
✅ Phase 3 is complete when:
BlocklistRestrictedXStream in allowlist modeAnyTypePermission.ANY in production configurations1 2@Component @ConditionalOnProperty("migration.maintenance.enabled") public class AllowlistMaintenanceService { @Scheduled(cron = "0 0 2 * * MON") // Weekly review public void performAllowlistReview() { AllowlistReviewReport report = AllowlistReviewReport.builder() .timestamp(Instant.now()) .currentAllowedTypes(getCurrentAllowedTypes()) .actuallyUsedTypes(getActuallyUsedTypes()) .unusedTypes(findUnusedTypes()) .recommendations(generateRecommendations()) .build(); // Send to development team notificationService.sendReport(report); // Store for historical analysis reportStorage.storeReport(report); } }
1 2/** * Post-Migration Documentation Template * * Migration Completed: 2024-12-19 * Mode: Allowlist (Maximum Security) * * Current Allowlist: * - Application types: 15 * - Framework types: 23 * - Hierarchy allowlists: 4 * * Security Level: MAXIMUM * Last Security Review: 2024-12-19 * Next Review Due: 2025-01-19 * * Maintenance Contact: Development Team * Security Contact: Security Team */
Congratulations! With Phase 3 complete, you now have maximum XStream security with zero-trust type processing.
Rate this page: