Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Change Log

## [v6.17.1](https://github.com/PerimeterX/perimeterx-java-sdk/compare/v6.17.1...HEAD) (2026-06-29)
- Fix ClassCastException when adding headers to non-RequestWrapper request


## [v6.17.0](https://github.com/PerimeterX/perimeterx-java-sdk/compare/v6.17.0...HEAD) (2026-05-24)
- Added Jakarta EE support via new `perimeterx-sdk-jakarta` artifact (Spring Boot 3, Tomcat 10+)
- Fixed first party captcha reverse proxy to use `startsWith` instead of `contains` when matching the captcha prefix
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

# [PerimeterX](http://www.perimeterx.com) Java SDK

> Latest stable version: [v6.17.0](https://search.maven.org/#artifactdetails%7Ccom.perimeterx%7Cperimeterx-sdk%7C6.17.0%7Cjar)
> Latest stable version: [v6.17.1](https://search.maven.org/#artifactdetails%7Ccom.perimeterx%7Cperimeterx-sdk%7C6.17.0%7Cjar)

## Table of Contents

Expand Down
2 changes: 1 addition & 1 deletion perimeterx-sdk-jakarta/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<parent>
<groupId>com.perimeterx</groupId>
<artifactId>perimeterx-java-sdk-parent</artifactId>
<version>6.17.0</version>
<version>6.17.1</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
2 changes: 1 addition & 1 deletion perimeterx-sdk/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<parent>
<groupId>com.perimeterx</groupId>
<artifactId>perimeterx-java-sdk-parent</artifactId>
<version>6.17.0</version>
<version>6.17.1</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
16 changes: 12 additions & 4 deletions perimeterx-sdk/src/main/java/com/perimeterx/api/PerimeterX.java
Original file line number Diff line number Diff line change
Expand Up @@ -242,9 +242,17 @@ private void addCustomHeadersToRequest(HttpServletRequest request, PXContext con
setDataEnrichmentHeader(request, context);
}

private void addHeaderSafely(HttpServletRequest request, String headerName, String headerValue, PXContext context) {
if (request instanceof RequestWrapper) {
((RequestWrapper) request).addHeader(headerName, headerValue);
} else {
request.setAttribute(headerName, headerValue);
}
}

private void setBreachedAccount(HttpServletRequest request, PXContext context) {
if (configuration.isLoginCredentialsExtractionEnabled() && context.isBreachedAccount()) {
((RequestWrapper) request).addHeader(configuration.getPxCompromisedCredentialsHeader(), String.valueOf(context.getPxde().get(BREACHED_ACCOUNT_KEY_NAME)));
addHeaderSafely(request, configuration.getPxCompromisedCredentialsHeader(), String.valueOf(context.getPxde().get(BREACHED_ACCOUNT_KEY_NAME)), context);
}
}

Expand All @@ -255,8 +263,8 @@ private void setAdditionalS2SActivityHeaders(HttpServletRequest request, PXConte
final String stringifyActivity = new Gson().toJson(activity);
final String urlHeader = configuration.getServerURL() + API_ACTIVITIES;

((RequestWrapper) request).addHeader(ADDITIONAL_ACTIVITY_HEADER, stringifyActivity);
((RequestWrapper) request).addHeader(ADDITIONAL_ACTIVITY_URL_HEADER, urlHeader);
addHeaderSafely(request, ADDITIONAL_ACTIVITY_HEADER, stringifyActivity, context);
addHeaderSafely(request, ADDITIONAL_ACTIVITY_URL_HEADER, urlHeader, context);
}
}

Expand All @@ -274,7 +282,7 @@ private void setDataEnrichmentHeader(HttpServletRequest request, PXContext conte
String pxdeJson = context.getPxde().toString();
byte[] utf8Bytes = pxdeJson.getBytes(StandardCharsets.UTF_8);
String encodedPxde = new String(utf8Bytes, StandardCharsets.ISO_8859_1);
((RequestWrapper) request).addHeader(headerName, encodedPxde);
addHeaderSafely(request, headerName, encodedPxde, context);
} catch (Exception e) {
context.logger.debug("Failed to add data enrichment header", e);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package com.perimeterx.api;

import com.perimeterx.http.PXClient;
import com.perimeterx.http.RequestWrapper;
import com.perimeterx.models.PXContext;
import com.perimeterx.models.configuration.ModuleMode;
import com.perimeterx.models.configuration.PXConfiguration;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.testng.Assert;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import testutils.TestObjectUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponseWrapper;

@Test
public class DataEnrichmentHeaderTest {

private static final String DE_HEADER_NAME = "X-PX-Data-Enrichment";

private PXConfiguration configuration;

@BeforeMethod
public void setup() {
configuration = PXConfiguration.builder()
.appId("appId")
.authToken("token")
.cookieKey("cookieKey")
.moduleMode(ModuleMode.BLOCKING)
.remoteConfigurationEnabled(false)
.blockingScore(70)
.pxDataEnrichmentHeaderName(DE_HEADER_NAME)
.build();
}

/**
* Simulates the customer's scenario: a custom HttpServletRequestWrapper
* that is NOT our RequestWrapper. Before the fix, this would throw a
* ClassCastException inside setDataEnrichmentHeader.
*/
@Test
public void testNonRequestWrapper_oldBehaviorWouldThrowClassCastException() {
MockHttpServletRequest mockRequest = new MockHttpServletRequest();
HttpServletRequest customWrapper = new HttpServletRequestWrapper(mockRequest);

Assert.assertFalse(customWrapper instanceof RequestWrapper,
"Test precondition: the custom wrapper must NOT be a RequestWrapper");

boolean wouldThrow = false;
try {
((RequestWrapper) customWrapper).addHeader(DE_HEADER_NAME, "test");
} catch (ClassCastException e) {
wouldThrow = true;
}
Assert.assertTrue(wouldThrow,
"Direct cast to RequestWrapper should throw ClassCastException for non-RequestWrapper requests");
}

/**
* With the fix: passing a non-RequestWrapper request to pxVerify should
* NOT throw, and the DE data should be available via request.getAttribute().
*/
@Test
public void testNonRequestWrapper_dataEnrichmentSetAsAttribute() throws Exception {
PXClient client = TestObjectUtils.nonBlockingPXClient(configuration.getBlockingScore());
PerimeterX perimeterx = TestObjectUtils.testablePerimeterXObject(configuration, client);

MockHttpServletRequest mockRequest = new MockHttpServletRequest();
HttpServletRequest customWrapper = new HttpServletRequestWrapper(mockRequest);

Assert.assertFalse(customWrapper instanceof RequestWrapper,
"Test precondition: must not be a RequestWrapper");

MockHttpServletResponse response = new MockHttpServletResponse();
PXContext context = perimeterx.pxVerify(customWrapper, new HttpServletResponseWrapper(response));

Assert.assertNotNull(context, "pxVerify should not return null");
Assert.assertNotEquals(response.getStatus(), 403, "Request should not be blocked");

String deValue = (String) customWrapper.getAttribute(DE_HEADER_NAME);
Assert.assertNotNull(deValue,
"Data enrichment should be available as a request attribute when request is not a RequestWrapper");
Assert.assertTrue(deValue.contains("cookieMonster"),
"Data enrichment attribute should contain the expected PXDE data");
}

/**
* With RequestWrapper: existing behavior is preserved.
* The DE data should be available via request.getHeader().
*/
@Test
public void testRequestWrapper_dataEnrichmentSetAsHeader() throws Exception {
PXClient client = TestObjectUtils.nonBlockingPXClient(configuration.getBlockingScore());
PerimeterX perimeterx = TestObjectUtils.testablePerimeterXObject(configuration, client);

MockHttpServletRequest mockRequest = new MockHttpServletRequest();
RequestWrapper wrappedRequest = new RequestWrapper(mockRequest);

MockHttpServletResponse response = new MockHttpServletResponse();
PXContext context = perimeterx.pxVerify(wrappedRequest, new HttpServletResponseWrapper(response));

Assert.assertNotNull(context, "pxVerify should not return null");
Assert.assertNotEquals(response.getStatus(), 403, "Request should not be blocked");

String deHeader = wrappedRequest.getHeader(DE_HEADER_NAME);
Assert.assertNotNull(deHeader,
"Data enrichment should be available as a request header when using RequestWrapper");
Assert.assertTrue(deHeader.contains("cookieMonster"),
"Data enrichment header should contain the expected PXDE data");
}

/**
* When DE header is not configured, neither header nor attribute should be set,
* regardless of request type.
*/
@Test
public void testNoHeaderConfigured_nothingSet() throws Exception {
PXConfiguration noDeConfig = PXConfiguration.builder()
.appId("appId")
.authToken("token")
.cookieKey("cookieKey")
.moduleMode(ModuleMode.BLOCKING)
.remoteConfigurationEnabled(false)
.blockingScore(70)
.build();

PXClient client = TestObjectUtils.nonBlockingPXClient(noDeConfig.getBlockingScore());
PerimeterX perimeterx = TestObjectUtils.testablePerimeterXObject(noDeConfig, client);

MockHttpServletRequest mockRequest = new MockHttpServletRequest();
HttpServletRequest customWrapper = new HttpServletRequestWrapper(mockRequest);

MockHttpServletResponse response = new MockHttpServletResponse();
perimeterx.pxVerify(customWrapper, new HttpServletResponseWrapper(response));

Assert.assertNull(customWrapper.getAttribute(DE_HEADER_NAME),
"No attribute should be set when DE header name is not configured");
}
}
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.perimeterx</groupId>
<artifactId>perimeterx-java-sdk-parent</artifactId>
<version>6.17.0</version>
<version>6.17.1</version>
<packaging>pom</packaging>

<name>PerimeterX Java SDK Parent</name>
Expand Down
2 changes: 1 addition & 1 deletion px_metadata.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": "6.17.0",
"version": "6.17.1",
"supported_features": [
"advanced_blocking_response",
"batched_activities",
Expand Down
2 changes: 1 addition & 1 deletion web-jakarta/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

<properties>
<maven.compiler.release>17</maven.compiler.release>
<com.perimeterx.version>6.17.0</com.perimeterx.version>
<com.perimeterx.version>6.17.1</com.perimeterx.version>
</properties>

<dependencies>
Expand Down
Loading