Developer
News and Updates
Get Support
Sign in
Get Support
Sign in
DOCUMENTATION
Cloud
Data Center
Resources
Sign in
Sign in
DOCUMENTATION
Cloud
Data Center
Resources
Sign in
Last updated Sep 25, 2025

Migration guide

This guide provides a structured approach to migrate from unsafe XStream usage to the secure Blocklist XStream adapter, minimizing disruption while maximizing security benefits.

Migration strategy overview

The recommended migration follows a three-phase approach:

  1. Phase 1: Immediate security (Blocklist mode) - Quick protection against known threats
  2. Phase 2: Analysis and preparation - Identify actual type usage patterns
  3. Phase 3: Maximum security (Allowlist mode) - Zero-trust configuration

Pre-migration assessment

Inventory current XStream usage

First, identify all XStream usage in your codebase:

1
2
# Find direct XStream instantiation
grep -r "new XStream" src/
grep -r "XStream.*=" src/

# Find XStream imports
grep -r "import.*XStream" src/

# Find XML processing patterns
grep -r "fromXML\|toXML" src/

Categorize usage patterns

Document each XStream usage by:

  • Purpose: Configuration loading, API serialization, caching, etc.
  • Data source: Trusted internal, external API, user input, etc.
  • Types processed: Known classes, dynamic types, complex hierarchies
  • Security sensitivity: High (user input), Medium (internal), Low (static config)

Risk assessment

Prioritize migration based on risk:

  • Critical: User input processing, external API data
  • High: Internal service communication, cached data
  • Medium: Configuration loading, development tools
  • Low: Test fixtures, build-time processing

Phase 1: Immediate security (Blocklist mode)

Goal: Rapidly protect against known threats with minimal code changes

Step 1.1: Add dependency

Replace existing XStream dependency:

1
2
<!-- Remove or comment out existing XStream dependency -->
<!--
<dependency>
    <groupId>com.thoughtworks</groupId>
    <artifactId>xstream</artifactId>
    <version>1.4.x</version>
</dependency>
-->

<!-- Add blocklist adapter -->
<dependency>
    <groupId>com.atlassian.security.serialblocklist</groupId>
    <artifactId>blocklist-xstream-adapter</artifactId>
    <version>${serialblocklist.version}</version>
    <scope>provided</scope>
</dependency>

Step 1.2: Replace XStream instantiation

Before (unsafe):

1
2
XStream xstream = new XStream();
xstream.addPermission(AnyTypePermission.ANY);

After (blocklist mode):

1
2
import com.atlassian.security.serialblocklist.xstream.BlocklistRestrictedXStream;

XStream xstream = new BlocklistRestrictedXStream();
xstream.addPermission(AnyTypePermission.ANY);  // Blocklist mode

Step 1.3: Update factory methods

If you use factory methods or dependency injection:

Before:

1
2
@Bean
public XStream xstream() {
    XStream xstream = new XStream();
    xstream.addPermission(AnyTypePermission.ANY);
    return xstream;
}

After:

1
2
@Bean
public XStream xstream() {
    XStream xstream = new BlocklistRestrictedXStream();
    xstream.addPermission(AnyTypePermission.ANY);
    return xstream;
}

Step 1.4: Test and deploy

  1. Unit tests: Verify existing functionality works
  2. Integration tests: Check end-to-end workflows
  3. Security tests: Confirm dangerous classes are blocked
  4. Deploy gradually: Canary, staging, then production

Security test example:

1
2
@Test
public void shouldBlockDangerousClasses() {
    XStream xstream = new BlocklistRestrictedXStream();
    xstream.addPermission(AnyTypePermission.ANY);
    
    // This should throw ForbiddenClassException
    assertThrows(ForbiddenClassException.class, () -> {
        String maliciousXml = "<java.lang.ProcessBuilder>" +
            "<command><string>calc.exe</string></command>" +
            "</java.lang.ProcessBuilder>";
        xstream.fromXML(maliciousXml);
    });
}

Phase 2: Analysis and preparation

Goal: Understand actual type usage to prepare for allowlist mode

Step 2.1: Add usage monitoring

Create a wrapper to log type usage:

1
2
public class MonitoringXStreamWrapper {
    private static final Logger logger = LoggerFactory.getLogger(MonitoringXStreamWrapper.class);
    private final XStream delegate;
    private final Set<String> observedTypes = ConcurrentHashMap.newKeySet();
    
    public MonitoringXStreamWrapper(XStream delegate) {
        this.delegate = delegate;
    }
    
    public Object fromXML(String xml) {
        try {
            Object result = delegate.fromXML(xml);
            logTypeUsage(result);
            return result;
        } catch (Exception e) {
            logger.warn("XStream processing failed", e);
            throw e;
        }
    }
    
    private void logTypeUsage(Object obj) {
        if (obj != null) {
            String typeName = obj.getClass().getName();
            if (observedTypes.add(typeName)) {
                logger.info("XStream type observed: {}", typeName);
            }
        }
    }
    
    public Set<String> getObservedTypes() {
        return new HashSet<>(observedTypes);
    }
}

Step 2.2: Run analysis in production

Deploy monitoring wrapper and collect data:

1
2
// Wrap existing XStream instances
XStream xstream = new BlocklistRestrictedXStream();
xstream.addPermission(AnyTypePermission.ANY);
MonitoringXStreamWrapper wrapper = new MonitoringXStreamWrapper(xstream);

// Use wrapper instead of direct XStream
Object result = wrapper.fromXML(xmlData);

Step 2.3: Analyze collected data

After some time of monitoring:

1
2
// Export observed types
Set<String> types = wrapper.getObservedTypes();
types.forEach(type -> System.out.println(type));

// Categorize types
Set<String> applicationTypes = types.stream()
    .filter(type -> type.startsWith("com.yourcompany"))
    .collect(Collectors.toSet());
    
Set<String> frameworkTypes = types.stream()
    .filter(type -> type.startsWith("java.") || type.startsWith("org."))
    .collect(Collectors.toSet());

Step 2.4: Create allowlist plan

Document the migration plan:

1
2
// Essential application types
Class<?>[] coreTypes = {
    UserProfile.class,
    ApplicationConfig.class,
    CacheEntry.class
};

// Framework types needed
Class<?>[] frameworkTypes = {
    ArrayList.class,
    HashMap.class,
    Date.class
};

// Type hierarchies to allow
Class<?>[] hierarchies = {
    Collection.class,  // For lists, sets, etc.
    Map.class,         // For various map implementations
    Number.class       // For numeric types
};

Phase 3: Maximum security (Allowlist mode)

Goal: Implement zero-trust security with explicit type allowlisting

Step 3.1: Prepare allowlist configuration

Based on Phase 2 analysis, create allowlist configurations:

1
2
public class XStreamSecurityConfig {
    
    public static void configureAllowlist(XStream xstream) {
        // Core application types
        xstream.allowTypes(new Class<?>[] {
            UserProfile.class,
            UserPreferences.class,
            ApplicationConfig.class,
            DatabaseConfig.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
        });
        
        // Safe hierarchies
        xstream.allowTypeHierarchy(Collection.class);
        xstream.allowTypeHierarchy(Map.class);
        xstream.allowTypeHierarchy(Number.class);
        xstream.allowTypeHierarchy(Enum.class);
    }
}

Step 3.2: Gradual allowlist migration

Migrate one service/component at a time:

Before (blocklist mode):

1
2
XStream xstream = new BlocklistRestrictedXStream();
xstream.addPermission(AnyTypePermission.ANY);  // Permissive mode

After (allowlist mode):

1
2
XStream xstream = new BlocklistRestrictedXStream();
// Remove AnyTypePermission.ANY - now in allowlist mode
XStreamSecurityConfig.configureAllowlist(xstream);

Step 3.3: Handle migration issues

Common issues and solutions:

Issue: ForbiddenClassException for legitimate type

1
2
// Solution: Add missing type to allowlist
xstream.allowTypes(new Class<?>[] { MissingType.class });

Issue: Too many types to allowlist individually

1
2
// Solution: Use hierarchy allowlisting carefully
xstream.allowTypeHierarchy(CommonBaseClass.class);

Issue: Dynamic or unknown types

1
2
// Solution: Consider if these are really needed
// If yes, use controlled hierarchy allowlisting
// If no, refactor to use known types

Testing migration

Functional testing

1
2
@Test
public void shouldSerializeKnownTypes() {
    XStream xstream = new BlocklistRestrictedXStream();
    XStreamSecurityConfig.configureAllowlist(xstream);
    
    UserProfile user = new UserProfile("john", "john@example.com");
    String xml = xstream.toXML(user);
    UserProfile restored = (UserProfile) xstream.fromXML(xml);
    
    assertEquals(user.getUsername(), restored.getUsername());
}

Security testing

1
2
@Test
public void shouldBlockUnknownTypes() {
    XStream xstream = new BlocklistRestrictedXStream();
    XStreamSecurityConfig.configureAllowlist(xstream);
    
    // Should fail for types not in allowlist
    assertThrows(ForbiddenClassException.class, () -> {
        String xml = "<some.unknown.Class><field>value</field></some.unknown.Class>";
        xstream.fromXML(xml);
    });
}

@Test
public void shouldBlockDangerousTypes() {
    XStream xstream = new BlocklistRestrictedXStream();
    XStreamSecurityConfig.configureAllowlist(xstream);
    
    // Should fail for blocklisted types even if someone tries to allow them
    assertThrows(IllegalArgumentException.class, () -> {
        xstream.allowTypes(new Class<?>[] { ProcessBuilder.class });
    });
}

Rollback procedures

Emergency rollback to blocklist mode

If allowlist mode causes production issues:

1
2
// Quick rollback - re-enable permissive mode
XStream xstream = new BlocklistRestrictedXStream();
xstream.addPermission(AnyTypePermission.ANY);  // Back to blocklist mode
// Remove allowlist configuration temporarily

Post-migration maintenance

Regular allowlist reviews

1
2
// Periodic audit of allowed types
public void auditAllowedTypes() {
    // Log current allowlist
    logger.info("Current allowed types: {}", getAllowedTypes());
    
    // Check for unused types (requires monitoring)
    Set<String> unused = findUnusedAllowedTypes();
    if (!unused.isEmpty()) {
        logger.warn("Potentially unused allowed types: {}", unused);
    }
}

Documentation maintenance

  • Keep allowlist documentation updated
  • Document business justification for each allowed type
  • Regular security reviews of allowed types
  • Update migration procedures based on lessons learned

Success criteria

Migration is complete when:

  • ✅ All XStream instances use BlocklistRestrictedXStream
  • ✅ No AnyTypePermission.ANY in production (allowlist mode active)
  • ✅ All legitimate types are explicitly allowed
  • ✅ Security tests pass
  • ✅ Functional tests pass
  • ✅ Production monitoring shows no unexpected failures

Rate this page: