From 37c30461db07452964ab55fb2154647e9bd6519a Mon Sep 17 00:00:00 2001 From: Ori Gold Date: Wed, 22 Oct 2025 15:18:55 -0700 Subject: [PATCH 01/63] chore: add .smarttomcat dir to gitignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index d6827f60..de33b11b 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,5 @@ examples/examples.iml .DS_Store .classpath .project -.factorypath \ No newline at end of file +.factorypath +.smarttomcat \ No newline at end of file From 1d6f336b1ab622b021f6805577665aad9cdb624b Mon Sep 17 00:00:00 2001 From: Ori Gold Date: Wed, 22 Oct 2025 15:19:53 -0700 Subject: [PATCH 02/63] feat: add is_sensitive_route to activities --- src/main/java/com/perimeterx/models/PXContext.java | 7 +++++++ .../models/activities/CommonActivityDetails.java | 5 ++++- .../java/com/perimeterx/models/httpmodels/Additional.java | 4 ++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/perimeterx/models/PXContext.java b/src/main/java/com/perimeterx/models/PXContext.java index 90b7c38c..ca812caf 100644 --- a/src/main/java/com/perimeterx/models/PXContext.java +++ b/src/main/java/com/perimeterx/models/PXContext.java @@ -232,6 +232,7 @@ public class PXContext { private String pxhdDomain; private String pxCtsCookie; private long enforcerStartTime; + private boolean isSensitiveRequest; /** * The cookie key used to decrypt the cookie @@ -291,6 +292,7 @@ private void postInitContext(final HttpServletRequest request, PXConfiguration p String protocolDetails[] = request.getProtocol().split("/"); this.httpVersion = protocolDetails.length > 1 ? protocolDetails[1] : StringUtils.EMPTY; + this.isSensitiveRequest = determineIsSensitiveRequest(); CustomParametersProvider customParametersProvider = pxConfiguration.getCustomParametersProvider(); Function customParametersExtraction = pxConfiguration.getCustomParametersExtraction(); @@ -310,7 +312,12 @@ private IPXLogger getLogger(){ boolean isLoggerHeaderRequest = requestLoggerAuthToken!=null && this.getPxConfiguration().getLoggerAuthToken().equals(requestLoggerAuthToken); return pxConfiguration.getLoggerFactory().getRequestContextLogger(isLoggerHeaderRequest); } + public boolean isSensitiveRequest() { + return this.isSensitiveRequest; + } + + private boolean determineIsSensitiveRequest() { return this.isContainCredentialsIntelligence() || checkSensitiveRoute(pxConfiguration.getSensitiveRoutes(), servletPath) || checkSensitiveRouteRegex(pxConfiguration.getSensitiveRoutesRegex(), servletPath) diff --git a/src/main/java/com/perimeterx/models/activities/CommonActivityDetails.java b/src/main/java/com/perimeterx/models/activities/CommonActivityDetails.java index c5baf184..e66da0d2 100644 --- a/src/main/java/com/perimeterx/models/activities/CommonActivityDetails.java +++ b/src/main/java/com/perimeterx/models/activities/CommonActivityDetails.java @@ -59,6 +59,9 @@ public class CommonActivityDetails implements ActivityDetails { @JsonProperty("cross_tab_session") public String pxCtsCookie; + @JsonProperty("is_sensitive_route") + public Boolean isSensitiveRoute; + public CommonActivityDetails(PXContext context) { final LoginData loginData = context.getLoginData(); @@ -85,6 +88,6 @@ public CommonActivityDetails(PXContext context) { this.riskStartTime = additional.riskStartTime; this.enforcerStartTime = additional.enforcerStartTime; this.pxCtsCookie = additional.pxCtsCookie; - + this.isSensitiveRoute = additional.isSensitiveRoute; } } diff --git a/src/main/java/com/perimeterx/models/httpmodels/Additional.java b/src/main/java/com/perimeterx/models/httpmodels/Additional.java index 2c4df51c..228d4a6d 100644 --- a/src/main/java/com/perimeterx/models/httpmodels/Additional.java +++ b/src/main/java/com/perimeterx/models/httpmodels/Additional.java @@ -93,6 +93,9 @@ public class Additional { @JsonProperty("cross_tab_session") public String pxCtsCookie; + @JsonProperty("is_sensitive_route") + public Boolean isSensitiveRoute; + public static Additional fromContext(PXContext ctx) { Additional additional = new Additional(); additional.pxCookie = ctx.getRiskCookie(); @@ -114,6 +117,7 @@ public static Additional fromContext(PXContext ctx) { additional.enforcerStartTime = ctx.getEnforcerStartTime(); additional.riskStartTime = new Date().getTime(); additional.pxCtsCookie = ctx.getPxCtsCookie(); + additional.isSensitiveRoute = ctx.isSensitiveRequest(); setLoginCredentials(ctx, additional); From 235a85bb36152afce7b151cbc796a1d00d8b4db0 Mon Sep 17 00:00:00 2001 From: Ori Gold Date: Wed, 22 Oct 2025 15:32:07 -0700 Subject: [PATCH 03/63] feat: added support for px_logger_severity in example --- web/src/main/java/com/web/Config.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/web/src/main/java/com/web/Config.java b/web/src/main/java/com/web/Config.java index 1d7333f6..0dc57323 100644 --- a/web/src/main/java/com/web/Config.java +++ b/web/src/main/java/com/web/Config.java @@ -6,6 +6,7 @@ import com.perimeterx.models.configuration.PXConfiguration; import com.perimeterx.models.configuration.credentialsIntelligenceconfig.CILoginMap; import com.perimeterx.models.risk.CustomParameters; +import com.perimeterx.utils.logger.LoggerSeverity; import org.json.JSONArray; import org.json.JSONObject; @@ -153,6 +154,12 @@ public PXConfiguration getPxConfiguration() { case "px_login_successful_status": builder.loginResponseValidationStatusCode(extractStatusCode(key)); break; + case "px_logger_severity": + LoggerSeverity loggerSeverity = LoggerSeverity.DEBUG.jsonName().equals(enforcerConfig.getString(key)) ? + LoggerSeverity.DEBUG : LoggerSeverity.NONE.jsonName().equals(enforcerConfig.getString(key)) ? + LoggerSeverity.NONE : LoggerSeverity.ERROR; + PXConfiguration.setPxLoggerSeverity(loggerSeverity); + break; case "px_user_agent_max_length": case "px_risk_cookie_max_length": case "px_risk_cookie_max_iterations": From c420fda315c61fc247853640af8da3e3acab0f47 Mon Sep 17 00:00:00 2001 From: Ori Gold Date: Wed, 22 Oct 2025 15:32:47 -0700 Subject: [PATCH 04/63] chore: very minor refactoring fixes --- src/main/java/com/perimeterx/models/PXContext.java | 2 +- src/main/java/com/perimeterx/utils/logger/LoggerFactory.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/perimeterx/models/PXContext.java b/src/main/java/com/perimeterx/models/PXContext.java index ca812caf..9752eac2 100644 --- a/src/main/java/com/perimeterx/models/PXContext.java +++ b/src/main/java/com/perimeterx/models/PXContext.java @@ -290,7 +290,7 @@ private void postInitContext(final HttpServletRequest request, PXConfiguration p this.enforcerErrorReasonInfo = new EnforcerErrorReasonInfo(); this.sensitiveHeaders = pxConfiguration.getSensitiveHeaders(); - String protocolDetails[] = request.getProtocol().split("/"); + String[] protocolDetails = request.getProtocol().split("/"); this.httpVersion = protocolDetails.length > 1 ? protocolDetails[1] : StringUtils.EMPTY; this.isSensitiveRequest = determineIsSensitiveRequest(); diff --git a/src/main/java/com/perimeterx/utils/logger/LoggerFactory.java b/src/main/java/com/perimeterx/utils/logger/LoggerFactory.java index 42b400c3..26ed9eb3 100644 --- a/src/main/java/com/perimeterx/utils/logger/LoggerFactory.java +++ b/src/main/java/com/perimeterx/utils/logger/LoggerFactory.java @@ -9,7 +9,7 @@ public IPXLogger getRequestContextLogger(boolean isMemoryEnabled) { if (pxLoggerSeverity == null) { return new Slf4JLogger(isMemoryEnabled); } else { - return new ConsoleLogger(pxLoggerSeverity,isMemoryEnabled); + return new ConsoleLogger(pxLoggerSeverity, isMemoryEnabled); } } public IPXLogger getRequestContextLogger() { From 516aa9ae436ee7b977ca3960735a8098ed3132a0 Mon Sep 17 00:00:00 2001 From: Ori Gold Date: Wed, 22 Oct 2025 15:48:06 -0700 Subject: [PATCH 05/63] chore: updating dependencies --- pom.xml | 20 ++++++++++---------- web/pom.xml | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pom.xml b/pom.xml index e32fe360..69125d25 100644 --- a/pom.xml +++ b/pom.xml @@ -132,19 +132,19 @@ com.fasterxml.jackson.core jackson-databind - 2.11.4 + 2.17.2 com.fasterxml.jackson.core jackson-annotations - 2.10.2 + 2.17.2 commons-io commons-io - 2.8.0 + 2.14.0 @@ -156,7 +156,7 @@ org.apache.commons commons-lang3 - 3.12.0 + 3.18.0 @@ -168,19 +168,19 @@ org.slf4j slf4j-api - 1.7.30 + 1.7.36 org.apache.httpcomponents httpasyncclient - 4.1.4 + 4.1.5 org.apache.httpcomponents httpclient - 4.5.13 + 4.5.14 @@ -207,14 +207,14 @@ org.springframework spring-test - 4.3.1.RELEASE + 4.3.30.RELEASE test org.springframework spring-web - 4.3.1.RELEASE + 4.3.30.RELEASE test @@ -228,7 +228,7 @@ com.google.code.gson gson - 2.8.6 + 2.10.1 compile diff --git a/web/pom.xml b/web/pom.xml index 404ffa14..a0585a32 100644 --- a/web/pom.xml +++ b/web/pom.xml @@ -31,7 +31,7 @@ org.slf4j slf4j-api - 1.7.25 + 1.7.36 org.slf4j From 4061c97fc77cc1464d5ecb86bf004dfcda12abab Mon Sep 17 00:00:00 2001 From: Ori Gold Date: Wed, 22 Oct 2025 16:00:13 -0700 Subject: [PATCH 06/63] chore: alphabetized px_metadata supported features --- px_metadata.json | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/px_metadata.json b/px_metadata.json index d4dacd1d..041b8c7c 100644 --- a/px_metadata.json +++ b/px_metadata.json @@ -2,41 +2,41 @@ "version": "6.15.1", "supported_features": [ "advanced_blocking_response", - "bypass_monitor_header", + "batched_activities", "block_activity", "block_page_captcha", + "block_page_hard_block", "block_page_js_challenge", "block_page_rate_limit", + "bypass_monitor_header", "client_ip_extraction", "cookie_v3", "credentials_intelligence", "css_ref", + "custom_cookie_header", "custom_logo", "custom_parameters", "custom_proxy", "custom_sensitive_request", "enforced_routes", - "logger", + "enforcer_error", "filter_by_extension", "first_party", + "header_based_logger", "js_ref", + "logger", "mobile_support", "module_enable", "module_mode", "monitored_routes", "page_requested_activity", "pxde", - "vid_extraction", + "pxhd", "risk_api", - "custom_cookie_header", + "sensitive_headers", "sensitive_routes", "telemetry_command", - "enforcer_error", - "pxhd", - "batched_activities", - "sensitive_headers", - "block_page_hard_block", - "header_based_logger" + "vid_extraction" ], "excluded_tests": [ "test_pxde_extraction_(s2s|unverified|verified)", From 38842916ffad05ca6fbae24934e73f1a87e60ba6 Mon Sep 17 00:00:00 2001 From: Ori Gold Date: Wed, 22 Oct 2025 19:03:45 -0700 Subject: [PATCH 07/63] fix: update module mode to resolve unit test failures --- .../models/configuration/ModuleMode.java | 22 +++---------------- 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/src/main/java/com/perimeterx/models/configuration/ModuleMode.java b/src/main/java/com/perimeterx/models/configuration/ModuleMode.java index ef121579..901b0761 100644 --- a/src/main/java/com/perimeterx/models/configuration/ModuleMode.java +++ b/src/main/java/com/perimeterx/models/configuration/ModuleMode.java @@ -10,13 +10,11 @@ * Created by nitzangoldfeder on 26/06/2017. */ public enum ModuleMode { - MONITOR(0), BLOCKING(1); - private int value; - - private static Map namesMap = new HashMap<>(2); + private final int value; + private static final Map namesMap = new HashMap<>(2); static { namesMap.put(0, MONITOR); namesMap.put(1, BLOCKING); @@ -27,26 +25,12 @@ public static ModuleMode forValue(Integer value) { return namesMap.get(value); } - @JsonValue - public Integer toValue() { - for (Map.Entry entry : namesMap.entrySet()) { - if (entry.getValue() == this) - return entry.getKey(); - } - return 0; - } - - ModuleMode(int value) { - this.value = value; - } - @JsonValue public int getValue() { return this.value; } - @JsonCreator - public void setValue(int value) { + ModuleMode(int value) { this.value = value; } } From 7f3d4816bb0277019bd59d120f7347a80729a226 Mon Sep 17 00:00:00 2001 From: Ori Gold Date: Thu, 23 Oct 2025 09:12:42 -0700 Subject: [PATCH 08/63] chore: updating gha checkout, setup-node actions --- .github/workflows/cd.yaml | 10 +++++----- .github/workflows/ci_e2e.yaml | 14 +++++++------- .github/workflows/ci_verify_version.yaml | 4 ++-- .github/workflows/docs_enforcement.yml | 6 +++--- .github/workflows/fuzzer.yaml | 14 +++++++------- 5 files changed, 24 insertions(+), 24 deletions(-) diff --git a/.github/workflows/cd.yaml b/.github/workflows/cd.yaml index 830f54a6..e99e0760 100644 --- a/.github/workflows/cd.yaml +++ b/.github/workflows/cd.yaml @@ -11,11 +11,11 @@ jobs: version: ${{ steps.version.outputs.value }} steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: - node-version: '18.x' + node-version: '22.x' - name: Get package version id: version run: echo "value=$(node -p -e "require('./px_metadata.json').version")" >> "$GITHUB_OUTPUT" @@ -28,7 +28,7 @@ jobs: contents: write steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v5 - run: gh release create v${{ needs.extract_version.outputs.version }} --generate-notes -t "Version ${{ needs.extract_version.outputs.version }}" env: GITHUB_TOKEN: ${{ github.TOKEN }} @@ -42,7 +42,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Set up JDK 8 uses: actions/setup-java@v3 diff --git a/.github/workflows/ci_e2e.yaml b/.github/workflows/ci_e2e.yaml index f3e6a9bf..91e222fd 100644 --- a/.github/workflows/ci_e2e.yaml +++ b/.github/workflows/ci_e2e.yaml @@ -12,11 +12,11 @@ jobs: supported-features: ${{ steps.supported-features.outputs.value }} steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: - node-version: '20.x' + node-version: '22.x' - name: extract supported features id: supported-features run: echo "value=$(node -p -e "require('./px_metadata.json').supported_features?.join(' or ') || ''")" >> "$GITHUB_OUTPUT" @@ -37,7 +37,7 @@ jobs: steps: - name: Checkout Repo - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Set up Docker uses: docker/setup-buildx-action@v3 @@ -55,7 +55,7 @@ jobs: version: '3.14.1' - name: Clone helm charts repo - mock-collector - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: repository: PerimeterX/connect-helm-charts token: ${{ secrets.CONNECT_PULL_TOKEN }} @@ -64,7 +64,7 @@ jobs: - name: Clone helm charts repo - enforcer-tests - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: repository: PerimeterX/connect-helm-charts token: ${{ secrets.CONNECT_PULL_TOKEN }} @@ -73,7 +73,7 @@ jobs: - name: Clone helm charts repo - sample-site - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: repository: PerimeterX/connect-helm-charts token: ${{ secrets.CONNECT_PULL_TOKEN }} diff --git a/.github/workflows/ci_verify_version.yaml b/.github/workflows/ci_verify_version.yaml index 9fcd468a..64a18a71 100644 --- a/.github/workflows/ci_verify_version.yaml +++ b/.github/workflows/ci_verify_version.yaml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - ${{ github.base_ref }} - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: ref: ${{ github.base_ref }} @@ -18,7 +18,7 @@ jobs: run: echo "project=$( mvn help:evaluate -Dexpression=project.version -q -DforceStdout )" >> "$GITHUB_OUTPUT" - name: Checkout code - current commit - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Get current SDK versions id: new-version diff --git a/.github/workflows/docs_enforcement.yml b/.github/workflows/docs_enforcement.yml index 213c85c7..22fa0a2c 100644 --- a/.github/workflows/docs_enforcement.yml +++ b/.github/workflows/docs_enforcement.yml @@ -16,12 +16,12 @@ jobs: version: ${{ steps.version.outputs.value }} steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: - node-version: '22' + node-version: '22.x' - name: Get package version id: version diff --git a/.github/workflows/fuzzer.yaml b/.github/workflows/fuzzer.yaml index 4c939349..59c81c80 100644 --- a/.github/workflows/fuzzer.yaml +++ b/.github/workflows/fuzzer.yaml @@ -12,11 +12,11 @@ jobs: supported-features: ${{ steps.version.outputs.value }} steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: - node-version: 'latest' + node-version: '22.x' - name: Get package version id: version run: echo "value=$(node -p -e "require('./px_metadata.json').version")" >> "$GITHUB_OUTPUT" @@ -42,7 +42,7 @@ jobs: steps: - name: Checkout Repo - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Set up Docker uses: docker/setup-buildx-action@v3 @@ -60,7 +60,7 @@ jobs: version: '3.14.2' - name: Clone helm charts repo - mock-collector - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: repository: PerimeterX/connect-helm-charts token: ${{ secrets.CONNECT_PULL_TOKEN }} @@ -68,7 +68,7 @@ jobs: path: ./deploy_charts/mock-collector - name: Clone helm charts repo - fuzzer - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: repository: PerimeterX/connect-helm-charts token: ${{ secrets.CONNECT_PULL_TOKEN }} @@ -76,7 +76,7 @@ jobs: path: ./deploy_charts/fuzzer - name: Clone helm charts repo - sample-site - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: repository: PerimeterX/connect-helm-charts token: ${{ secrets.CONNECT_PULL_TOKEN }} From 23ed5c18871bcacb98f28419e369ce40471174c5 Mon Sep 17 00:00:00 2001 From: Ori Gold Date: Thu, 23 Oct 2025 09:23:57 -0700 Subject: [PATCH 09/63] update: fuzzer, mock collector, specs test tags --- .github/workflows/ci_e2e.yaml | 4 ++-- .github/workflows/fuzzer.yaml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci_e2e.yaml b/.github/workflows/ci_e2e.yaml index 91e222fd..71388002 100644 --- a/.github/workflows/ci_e2e.yaml +++ b/.github/workflows/ci_e2e.yaml @@ -25,9 +25,9 @@ jobs: CI: name: "E2E tests" env: - MOCK_COLLECTOR_IMAGE_TAG: 1.3.5 + MOCK_COLLECTOR_IMAGE_TAG: 2.0.5 SAMPLE_SITE_IMAGE_TAG: 1.0.0 - ENFORCER_SPEC_TESTS_IMAGE_TAG: 1.8.1 + ENFORCER_SPEC_TESTS_IMAGE_TAG: 1.21.0 runs-on: ubuntu-latest timeout-minutes: 60 diff --git a/.github/workflows/fuzzer.yaml b/.github/workflows/fuzzer.yaml index 59c81c80..b5eae821 100644 --- a/.github/workflows/fuzzer.yaml +++ b/.github/workflows/fuzzer.yaml @@ -25,8 +25,8 @@ jobs: Fuzzing: name: "Fuzzing Test" env: - MOCK_COLLECTOR_IMAGE_TAG: 1.3.6 - FUZZER_TAG: 1.0.4 + MOCK_COLLECTOR_IMAGE_TAG: 2.0.5 + FUZZER_TAG: 1.1.0 SAMPLE_SITE_IMAGE_TAG: 1.0.0 ENFORCER_TAG: ${{ needs.extract_version.outputs.version }} From 434732bda13562483434da073a29acc941f8dd40 Mon Sep 17 00:00:00 2001 From: Ori Gold Date: Thu, 23 Oct 2025 10:31:38 -0700 Subject: [PATCH 10/63] update: helm chart versions --- .github/workflows/ci_e2e.yaml | 10 +++++++--- .github/workflows/fuzzer.yaml | 10 +++++++--- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci_e2e.yaml b/.github/workflows/ci_e2e.yaml index 71388002..6ffa8f0f 100644 --- a/.github/workflows/ci_e2e.yaml +++ b/.github/workflows/ci_e2e.yaml @@ -59,7 +59,7 @@ jobs: with: repository: PerimeterX/connect-helm-charts token: ${{ secrets.CONNECT_PULL_TOKEN }} - ref: mock-collector-0.1.1 + ref: mock-collector-0.1.2 path: ./deploy_charts/mock-collector @@ -68,7 +68,7 @@ jobs: with: repository: PerimeterX/connect-helm-charts token: ${{ secrets.CONNECT_PULL_TOKEN }} - ref: enforcer-spec-tests-0.7.1 + ref: enforcer-spec-tests-0.9.1 path: ./deploy_charts/enforcer-spec-tests @@ -77,7 +77,7 @@ jobs: with: repository: PerimeterX/connect-helm-charts token: ${{ secrets.CONNECT_PULL_TOKEN }} - ref: sample-site-0.5.0 + ref: sample-site-0.6.1 path: ./deploy_charts/sample-site - name: Set up Google Cloud SDK @@ -101,6 +101,7 @@ jobs: helm install mock-collector ./deploy_charts/mock-collector/charts/mock-collector \ --set image.repository=localhost:5001/mock-collector \ --set image.tag=$MOCK_COLLECTOR_IMAGE_TAG \ + --set authToken=${{ secrets.PX_AUTH_TOKEN }} \ --set imagePullPolicy=Always --wait - name: set secrets in enforcer config @@ -118,6 +119,9 @@ jobs: helm install java-enforcer ./deploy_charts/sample-site/charts/sample-site \ -f ./ci_files/enforcer-values.yaml \ --set image.name=localhost:5001/java-enforcer-sample-site \ + --set appId=${{ secrets.PX_APP_ID }} \ + --set cookieSecret=${{ secrets.TEST_COOKIE_SECRET }} \ + --set authToken=${{ secrets.PX_AUTH_TOKEN }} \ --set image.tag=$SAMPLE_SITE_IMAGE_TAG \ --set-file enforcerConfig.content=/tmp/enforcer-config.json \ --wait diff --git a/.github/workflows/fuzzer.yaml b/.github/workflows/fuzzer.yaml index b5eae821..f66b6788 100644 --- a/.github/workflows/fuzzer.yaml +++ b/.github/workflows/fuzzer.yaml @@ -64,7 +64,7 @@ jobs: with: repository: PerimeterX/connect-helm-charts token: ${{ secrets.CONNECT_PULL_TOKEN }} - ref: mock-collector-0.1.1 + ref: mock-collector-0.1.2 path: ./deploy_charts/mock-collector - name: Clone helm charts repo - fuzzer @@ -72,7 +72,7 @@ jobs: with: repository: PerimeterX/connect-helm-charts token: ${{ secrets.CONNECT_PULL_TOKEN }} - ref: fuzzer-0.2.0 + ref: fuzzer-0.3.1 path: ./deploy_charts/fuzzer - name: Clone helm charts repo - sample-site @@ -80,7 +80,7 @@ jobs: with: repository: PerimeterX/connect-helm-charts token: ${{ secrets.CONNECT_PULL_TOKEN }} - ref: sample-site-0.5.0 + ref: sample-site-0.6.1 path: ./deploy_charts/sample-site - name: Set up Google Cloud SDK @@ -104,6 +104,7 @@ jobs: helm install mock-collector ./deploy_charts/mock-collector/charts/mock-collector \ --set image.repository=localhost:5001/mock-collector \ --set image.tag=$MOCK_COLLECTOR_IMAGE_TAG \ + --set authToken=${{ secrets.PX_AUTH_TOKEN }} \ --set imagePullPolicy=Always --wait - name: set secrets in enforcer config @@ -122,6 +123,9 @@ jobs: -f ./ci_files/enforcer-values.yaml \ --set image.name=localhost:5001/java-enforcer-sample-site \ --set image.tag=$SAMPLE_SITE_IMAGE_TAG \ + --set appId=${{ secrets.PX_APP_ID }} \ + --set cookieSecret=${{ secrets.TEST_COOKIE_SECRET }} \ + --set authToken=${{ secrets.PX_AUTH_TOKEN }} \ --set-file enforcerConfig.content=/tmp/enforcer-config.json \ --wait From ce64628f331c4c05c1053d2eb1ef33808ba47a44 Mon Sep 17 00:00:00 2001 From: Ori Gold Date: Thu, 23 Oct 2025 10:40:18 -0700 Subject: [PATCH 11/63] fix: adding enforcerConfigJsonContent to e2e test run --- .github/workflows/ci_e2e.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci_e2e.yaml b/.github/workflows/ci_e2e.yaml index 6ffa8f0f..d45a3566 100644 --- a/.github/workflows/ci_e2e.yaml +++ b/.github/workflows/ci_e2e.yaml @@ -141,6 +141,7 @@ jobs: --set authToken="${{ secrets.PX_AUTH_TOKEN }}" \ --set appId=${{ secrets.PX_APP_ID }} \ --set-file enforcerMetadataContent=./px_metadata.json \ + --set-file enforcerConfigJsonContent=/tmp/enforcer-config.json \ -f ./ci_files/spec-tests-values.yaml \ --wait \ --timeout 60m0s \ From 0c6e4cfda685121cc011f0486c96460dd11208f9 Mon Sep 17 00:00:00 2001 From: Ori Gold Date: Thu, 23 Oct 2025 11:21:09 -0700 Subject: [PATCH 12/63] feat: add request ID to telemetry activity --- .../api/activities/DefaultActivityHandler.java | 2 +- .../activities/EnforcerTelemetryActivityDetails.java | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/perimeterx/api/activities/DefaultActivityHandler.java b/src/main/java/com/perimeterx/api/activities/DefaultActivityHandler.java index 045341d2..53bd4d0b 100644 --- a/src/main/java/com/perimeterx/api/activities/DefaultActivityHandler.java +++ b/src/main/java/com/perimeterx/api/activities/DefaultActivityHandler.java @@ -48,7 +48,7 @@ public void handlePageRequestedActivity(PXContext context) throws PXException { @Override public void handleEnforcerTelemetryActivity(PXConfiguration pxConfiguration, UpdateReason updateReason, PXContext context) { try { - EnforcerTelemetryActivityDetails details = new EnforcerTelemetryActivityDetails(pxConfiguration, updateReason); + EnforcerTelemetryActivityDetails details = new EnforcerTelemetryActivityDetails(pxConfiguration, context, updateReason); EnforcerTelemetry enforcerTelemetry = new EnforcerTelemetry("enforcer_telemetry", pxConfiguration.getAppId(), details); this.client.sendEnforcerTelemetry(enforcerTelemetry, context); } catch (Exception e) { diff --git a/src/main/java/com/perimeterx/models/activities/EnforcerTelemetryActivityDetails.java b/src/main/java/com/perimeterx/models/activities/EnforcerTelemetryActivityDetails.java index 3117a07e..9e5e6d6c 100644 --- a/src/main/java/com/perimeterx/models/activities/EnforcerTelemetryActivityDetails.java +++ b/src/main/java/com/perimeterx/models/activities/EnforcerTelemetryActivityDetails.java @@ -3,11 +3,13 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.google.gson.Gson; import com.google.gson.JsonIOException; +import com.perimeterx.models.PXContext; import com.perimeterx.models.configuration.PXConfiguration; import com.perimeterx.utils.Constants; import java.net.InetAddress; import java.net.UnknownHostException; +import java.util.UUID; /** * Created by nitzangoldfeder on 29/10/2017. @@ -24,11 +26,14 @@ public class EnforcerTelemetryActivityDetails implements ActivityDetails { private String nodeName; @JsonProperty("update_reason") private UpdateReason updateReason; + @JsonProperty + private UUID requestId; - public EnforcerTelemetryActivityDetails(PXConfiguration pxConfiguration, UpdateReason updateReason) { + public EnforcerTelemetryActivityDetails(PXConfiguration pxConfiguration, PXContext context, UpdateReason updateReason) { this.moduleVersion = Constants.SDK_VERSION; this.osName = System.getProperty("os.name"); this.updateReason = updateReason; + this.requestId = context.getRequestId(); try { this.nodeName = InetAddress.getLocalHost().getHostName(); } catch (UnknownHostException e) { @@ -59,4 +64,8 @@ public String getOsName() { public String getNodeName() { return nodeName; } + + public UUID getRequestId() { + return requestId; + } } From 9b3817f34c71c78212229a964426525af02c6edd Mon Sep 17 00:00:00 2001 From: Ori Gold Date: Thu, 23 Oct 2025 11:45:59 -0700 Subject: [PATCH 13/63] fix: redacting sensitive fields in telemetry, changing telemetry config structure --- .../activities/EnforcerTelemetryActivityDetails.java | 12 ++++++++++-- .../models/configuration/PXConfiguration.java | 11 ++++++++++- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/perimeterx/models/activities/EnforcerTelemetryActivityDetails.java b/src/main/java/com/perimeterx/models/activities/EnforcerTelemetryActivityDetails.java index 9e5e6d6c..ae1b54a3 100644 --- a/src/main/java/com/perimeterx/models/activities/EnforcerTelemetryActivityDetails.java +++ b/src/main/java/com/perimeterx/models/activities/EnforcerTelemetryActivityDetails.java @@ -1,6 +1,8 @@ package com.perimeterx.models.activities; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; import com.google.gson.Gson; import com.google.gson.JsonIOException; import com.perimeterx.models.PXContext; @@ -9,6 +11,7 @@ import java.net.InetAddress; import java.net.UnknownHostException; +import java.util.Properties; import java.util.UUID; /** @@ -43,8 +46,13 @@ public EnforcerTelemetryActivityDetails(PXConfiguration pxConfiguration, PXConte try { Gson gson = new Gson(); String pxConfigJson = gson.toJson(pxConfiguration.getTelemetryConfig()); - this.enforcerConfigs = pxConfigJson; - } catch (JsonIOException e) { + ObjectMapper mapper = new ObjectMapper(); + Properties p = new Properties(); + p.put("active_config", pxConfigJson); + p.put("static_config", pxConfigJson); + p.put("remote_config", "{}"); // remote config not supported + this.enforcerConfigs = mapper.writeValueAsString(p); + } catch (JsonIOException | JsonProcessingException e) { this.enforcerConfigs = "Could not retrieve pxConfiguration"; } } diff --git a/src/main/java/com/perimeterx/models/configuration/PXConfiguration.java b/src/main/java/com/perimeterx/models/configuration/PXConfiguration.java index 1f0665d1..678e1f80 100644 --- a/src/main/java/com/perimeterx/models/configuration/PXConfiguration.java +++ b/src/main/java/com/perimeterx/models/configuration/PXConfiguration.java @@ -312,7 +312,16 @@ public static void setPxLoggerSeverity(LoggerSeverity severity) { * @return Configuration Object clone without cookieKey and authToken **/ public PXConfiguration getTelemetryConfig() { - return this.toBuilder().clearCookieKeys().authToken(null).build(); + int trailingChars = 4; + String redactedPrefix = "***REDACTED***"; + String redactedAuthToken = redactedPrefix.concat(this.authToken.substring(this.authToken.length() - trailingChars)); + List redactedCookieKeys = this.cookieKeys.stream().map((key) -> "***REDACTED***".concat(key.substring(key.length() - trailingChars))).collect(Collectors.toList()); + String redactedLoggerAuthToken = this.loggerAuthToken.isEmpty() ? null : redactedPrefix.concat(this.loggerAuthToken.substring(this.loggerAuthToken.length() - trailingChars)); + return this.toBuilder() + .authToken(redactedAuthToken) + .cookieKeys(redactedCookieKeys) + .loggerAuthToken(redactedLoggerAuthToken) + .build(); } public void disableModule() { From 53e63af7633a09f73457cfefa51d7e136157afad Mon Sep 17 00:00:00 2001 From: Ori Gold Date: Thu, 23 Oct 2025 11:54:00 -0700 Subject: [PATCH 14/63] fix: compilation issues re: request id in telemetry --- .../com/perimeterx/api/activities/BufferedActivityHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/perimeterx/api/activities/BufferedActivityHandler.java b/src/main/java/com/perimeterx/api/activities/BufferedActivityHandler.java index fb6c006e..d704f37c 100644 --- a/src/main/java/com/perimeterx/api/activities/BufferedActivityHandler.java +++ b/src/main/java/com/perimeterx/api/activities/BufferedActivityHandler.java @@ -57,7 +57,7 @@ public void handlePageRequestedActivity(PXContext context) throws PXException { @Override public void handleEnforcerTelemetryActivity(PXConfiguration pxConfig, UpdateReason updateReason, PXContext context) { try { - EnforcerTelemetryActivityDetails details = new EnforcerTelemetryActivityDetails(pxConfig, updateReason); + EnforcerTelemetryActivityDetails details = new EnforcerTelemetryActivityDetails(pxConfig, context, updateReason); EnforcerTelemetry enforcerTelemetry = new EnforcerTelemetry("enforcer_telemetry", pxConfig.getAppId(), details); this.client.sendEnforcerTelemetry(enforcerTelemetry, context); } catch (IOException e) { From b3cbab4b847536a39f50d9855cff4433f4288ae2 Mon Sep 17 00:00:00 2001 From: Ori Gold Date: Fri, 24 Oct 2025 14:38:09 -0700 Subject: [PATCH 15/63] wip --- .../EnforcerTelemetryActivityDetails.java | 100 +++++++++++++++--- .../models/configuration/PXConfiguration.java | 51 +++++++-- 2 files changed, 125 insertions(+), 26 deletions(-) diff --git a/src/main/java/com/perimeterx/models/activities/EnforcerTelemetryActivityDetails.java b/src/main/java/com/perimeterx/models/activities/EnforcerTelemetryActivityDetails.java index ae1b54a3..c2f5a576 100644 --- a/src/main/java/com/perimeterx/models/activities/EnforcerTelemetryActivityDetails.java +++ b/src/main/java/com/perimeterx/models/activities/EnforcerTelemetryActivityDetails.java @@ -1,9 +1,7 @@ package com.perimeterx.models.activities; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.gson.Gson; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.google.gson.JsonIOException; import com.perimeterx.models.PXContext; import com.perimeterx.models.configuration.PXConfiguration; @@ -11,7 +9,6 @@ import java.net.InetAddress; import java.net.UnknownHostException; -import java.util.Properties; import java.util.UUID; /** @@ -22,14 +19,14 @@ public class EnforcerTelemetryActivityDetails implements ActivityDetails { @JsonProperty("module_version") private String moduleVersion; @JsonProperty("enforcer_configs") - private String enforcerConfigs; + private TelemetryConfiguration enforcerConfigs; @JsonProperty("os_name") private String osName; @JsonProperty("node_name") private String nodeName; @JsonProperty("update_reason") private UpdateReason updateReason; - @JsonProperty + @JsonProperty("request_id") private UUID requestId; public EnforcerTelemetryActivityDetails(PXConfiguration pxConfiguration, PXContext context, UpdateReason updateReason) { @@ -44,16 +41,13 @@ public EnforcerTelemetryActivityDetails(PXConfiguration pxConfiguration, PXConte } try { - Gson gson = new Gson(); - String pxConfigJson = gson.toJson(pxConfiguration.getTelemetryConfig()); - ObjectMapper mapper = new ObjectMapper(); - Properties p = new Properties(); - p.put("active_config", pxConfigJson); - p.put("static_config", pxConfigJson); - p.put("remote_config", "{}"); // remote config not supported - this.enforcerConfigs = mapper.writeValueAsString(p); - } catch (JsonIOException | JsonProcessingException e) { - this.enforcerConfigs = "Could not retrieve pxConfiguration"; + PXConfiguration config = pxConfiguration.getTelemetryConfig(); + enforcerConfigs = new TelemetryConfiguration(); + enforcerConfigs.activeConfig = config; + enforcerConfigs.staticConfig = config; + enforcerConfigs.remoteConfig = null; // remote config not supported + } catch (JsonIOException e) { + enforcerConfigs = null; } } @@ -61,7 +55,7 @@ public String getModuleVersion() { return moduleVersion; } - public String getEnforcerConfigs() { + public TelemetryConfiguration getEnforcerConfigs() { return enforcerConfigs; } @@ -77,3 +71,75 @@ public UUID getRequestId() { return requestId; } } + +class TelemetryConfiguration { + @JsonProperty("active_config") + @JsonIgnoreProperties({ + "customParametersProvider", + "blockHandler", + "customLoginResponseValidator", + "credentialsCustomExtractor", + "customIsSensitiveRequest", + "customParametersExtraction", + "filterByCustomFunction", + "loggerFactory", + "telemetryConfig", + "reverseProxyInstance", + "ipxHttpClientInstance", + "ipxhttpClientInstance", + "IPXHttpClientInstance", + "pxClientInstance", + "PXClientInstance", + "pxclientInstance", + "httpClient", + "pxClient", + "pxReverseProxy" + }) + public PXConfiguration activeConfig; + @JsonProperty("static_config") + @JsonIgnoreProperties({ + "customParametersProvider", + "blockHandler", + "customLoginResponseValidator", + "credentialsCustomExtractor", + "customIsSensitiveRequest", + "customParametersExtraction", + "filterByCustomFunction", + "loggerFactory", + "telemetryConfig", + "reverseProxyInstance", + "ipxHttpClientInstance", + "ipxhttpClientInstance", + "IPXHttpClientInstance", + "pxClientInstance", + "PXClientInstance", + "pxclientInstance", + "httpClient", + "pxClient", + "pxReverseProxy" + }) + public PXConfiguration staticConfig; + @JsonProperty("remote_config") + @JsonIgnoreProperties({ + "customParametersProvider", + "blockHandler", + "customLoginResponseValidator", + "credentialsCustomExtractor", + "customIsSensitiveRequest", + "customParametersExtraction", + "filterByCustomFunction", + "loggerFactory", + "telemetryConfig", + "reverseProxyInstance", + "ipxHttpClientInstance", + "ipxhttpClientInstance", + "IPXHttpClientInstance", + "pxClientInstance", + "PXClientInstance", + "pxclientInstance", + "httpClient", + "pxClient", + "pxReverseProxy" + }) + public PXConfiguration remoteConfig; +} diff --git a/src/main/java/com/perimeterx/models/configuration/PXConfiguration.java b/src/main/java/com/perimeterx/models/configuration/PXConfiguration.java index 678e1f80..bcb5aaf6 100644 --- a/src/main/java/com/perimeterx/models/configuration/PXConfiguration.java +++ b/src/main/java/com/perimeterx/models/configuration/PXConfiguration.java @@ -312,16 +312,49 @@ public static void setPxLoggerSeverity(LoggerSeverity severity) { * @return Configuration Object clone without cookieKey and authToken **/ public PXConfiguration getTelemetryConfig() { - int trailingChars = 4; - String redactedPrefix = "***REDACTED***"; - String redactedAuthToken = redactedPrefix.concat(this.authToken.substring(this.authToken.length() - trailingChars)); - List redactedCookieKeys = this.cookieKeys.stream().map((key) -> "***REDACTED***".concat(key.substring(key.length() - trailingChars))).collect(Collectors.toList()); - String redactedLoggerAuthToken = this.loggerAuthToken.isEmpty() ? null : redactedPrefix.concat(this.loggerAuthToken.substring(this.loggerAuthToken.length() - trailingChars)); - return this.toBuilder() - .authToken(redactedAuthToken) - .cookieKeys(redactedCookieKeys) - .loggerAuthToken(redactedLoggerAuthToken) + + PXConfiguration telemetry = this.toBuilder() + .authToken(this.redactString(this.authToken)) + .clearCookieKeys() + .cookieKeys(this.cookieKeys.stream().map(this::redactString).collect(Collectors.toList())) + .loggerAuthToken(this.redactString(this.loggerAuthToken)) + .sensitiveRoutesRegex(this.stringifyRegexSet(this.sensitiveRoutesRegex)) + // prune non-serializable/runtime members for telemetry clone + .customParametersProvider(null) + .blockHandler(null) + .customLoginResponseValidator(null) + .credentialsCustomExtractor(null) + .customIsSensitiveRequest(null) + .customParametersExtraction(null) + .filterByCustomFunction(null) + .loggerFactory(null) + .httpClient(null) + .pxClient(null) + .pxReverseProxy(null) .build(); + // ensure transient instances are not serialized + telemetry.pxClientInstance = null; + telemetry.ipxHttpClientInstance = null; + telemetry.reverseProxyInstance = null; + return telemetry; + } + + private Set stringifyRegexSet(Set regexSet) { + if (regexSet == null) { + return null; + } + return regexSet.stream() + .map(r -> r != null && r.startsWith("_REGEXP ") ? r : "_REGEXP /" + r + "/") + .collect(Collectors.toSet()); + } + + private String redactString(String str) { + int trailingChars = 5; + String redactedPrefix = "***REDACTED***"; + if (str == null || str.length() <= trailingChars) { + return redactedPrefix; + } + return redactedPrefix.concat(str.substring(str.length() - trailingChars)); } public void disableModule() { From 129252128b6a4c1b1ea250976f51fd1547f2f9e5 Mon Sep 17 00:00:00 2001 From: Ori Gold Date: Mon, 27 Oct 2025 10:36:05 -0700 Subject: [PATCH 16/63] fix: ci_files config and px_metadata excluded tests (custom function hashes in telemetry) --- ci_files/enforcer-config.json | 3 --- px_metadata.json | 3 ++- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/ci_files/enforcer-config.json b/ci_files/enforcer-config.json index 6f3ed977..e0bd186c 100644 --- a/ci_files/enforcer-config.json +++ b/ci_files/enforcer-config.json @@ -19,9 +19,6 @@ "px_custom_first_party_xhr_endpoint": "/custom_first_party_xhr_endpoint", "px_custom_first_party_captcha_endpoint": "/custom_first_party_captcha_endpoint", "px_custom_first_party_prefix": "/custom_first_party_prefix", - "px_filter_by_route": [ - "/filtered_route" - ], "px_monitored_routes": [ "/monitored_route", "/monitored_route/suffix", diff --git a/px_metadata.json b/px_metadata.json index 041b8c7c..0e52f582 100644 --- a/px_metadata.json +++ b/px_metadata.json @@ -48,6 +48,7 @@ "test_block_page_captcha_response_contains_http_reason_phrase", "test_cookie_v3_cookie_decryption_failed_iterations_above_max_allowed_value", "test_cookie_v3_cookie_validation_failed_big_cookie", - "test_client_ip_extraction_order_risk_api" + "test_client_ip_extraction_order_risk_api", + "test_telemetry_command_verify_custom_function_hash" ] } From 08c9bb9fa07e4f5bde9876ce8ca3fc2774b3a149 Mon Sep 17 00:00:00 2001 From: Ori Gold Date: Mon, 27 Oct 2025 10:36:41 -0700 Subject: [PATCH 17/63] feat: allow non-string types in Custom Parameters (backwards-compatible change) --- .../models/risk/CustomParameters.java | 42 ++++++++++--------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/src/main/java/com/perimeterx/models/risk/CustomParameters.java b/src/main/java/com/perimeterx/models/risk/CustomParameters.java index 6a51cb9c..34e4c993 100644 --- a/src/main/java/com/perimeterx/models/risk/CustomParameters.java +++ b/src/main/java/com/perimeterx/models/risk/CustomParameters.java @@ -6,30 +6,32 @@ /** * Created by nitzangoldfeder on 03/04/2018. */ + +// Note: Parameters are of type Object to allow flexibility in the type of data being sent (e.g., String, Number, Boolean, etc.) @JsonInclude(JsonInclude.Include.NON_NULL) public class CustomParameters { @JsonProperty("custom_param1") - public String customParam1; + public Object customParam1; @JsonProperty("custom_param2") - public String customParam2; + public Object customParam2; @JsonProperty("custom_param3") - public String customParam3; + public Object customParam3; @JsonProperty("custom_param4") - public String customParam4; + public Object customParam4; @JsonProperty("custom_param5") - public String customParam5; + public Object customParam5; @JsonProperty("custom_param6") - public String customParam6; + public Object customParam6; @JsonProperty("custom_param7") - public String customParam7; + public Object customParam7; @JsonProperty("custom_param8") - public String customParam8; + public Object customParam8; @JsonProperty("custom_param9") - public String customParam9; + public Object customParam9; @JsonProperty("custom_param10") - public String customParam10; + public Object customParam10; - public String getCustomParam1() { + public Object getCustomParam1() { return customParam1; } @@ -37,7 +39,7 @@ public void setCustomParam1(String customParam1) { this.customParam1 = customParam1; } - public String getCustomParam2() { + public Object getCustomParam2() { return customParam2; } @@ -45,7 +47,7 @@ public void setCustomParam2(String customParam2) { this.customParam2 = customParam2; } - public String getCustomParam3() { + public Object getCustomParam3() { return customParam3; } @@ -53,7 +55,7 @@ public void setCustomParam3(String customParam3) { this.customParam3 = customParam3; } - public String getCustomParam4() { + public Object getCustomParam4() { return customParam4; } @@ -61,7 +63,7 @@ public void setCustomParam4(String customParam4) { this.customParam4 = customParam4; } - public String getCustomParam5() { + public Object getCustomParam5() { return customParam5; } @@ -69,7 +71,7 @@ public void setCustomParam5(String customParam5) { this.customParam5 = customParam5; } - public String getCustomParam6() { + public Object getCustomParam6() { return customParam6; } @@ -77,7 +79,7 @@ public void setCustomParam6(String customParam6) { this.customParam6 = customParam6; } - public String getCustomParam7() { + public Object getCustomParam7() { return customParam7; } @@ -85,7 +87,7 @@ public void setCustomParam7(String customParam7) { this.customParam7 = customParam7; } - public String getCustomParam8() { + public Object getCustomParam8() { return customParam8; } @@ -93,7 +95,7 @@ public void setCustomParam8(String customParam8) { this.customParam8 = customParam8; } - public String getCustomParam9() { + public Object getCustomParam9() { return customParam9; } @@ -101,7 +103,7 @@ public void setCustomParam9(String customParam9) { this.customParam9 = customParam9; } - public String getCustomParam10() { + public Object getCustomParam10() { return customParam10; } From 697fcbe7c79384d3bb9b6eadd41dd0bee1a3aadf Mon Sep 17 00:00:00 2001 From: Ori Gold Date: Mon, 27 Oct 2025 10:37:15 -0700 Subject: [PATCH 18/63] update: configs in example --- web/src/main/java/com/web/Config.java | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/web/src/main/java/com/web/Config.java b/web/src/main/java/com/web/Config.java index 0dc57323..5e092baf 100644 --- a/web/src/main/java/com/web/Config.java +++ b/web/src/main/java/com/web/Config.java @@ -175,13 +175,18 @@ public PXConfiguration getPxConfiguration() { CustomParameters customParameters = new CustomParameters(); customParameters.customParam1 = "test1"; customParameters.customParam2 = "test2"; - customParameters.customParam3 = "3"; - customParameters.customParam4 = "4"; - customParameters.customParam5 = "5"; - customParameters.customParam6 = "6"; + customParameters.customParam3 = 3; + customParameters.customParam4 = 4; + customParameters.customParam5 = 5; + customParameters.customParam6 = 6; + customParameters.customParam7 = req.getRequestURI(); return customParameters; }); + builder.customIsSensitiveRequest((req -> { + return req.getRequestURI().startsWith("/sensitive") && req.getMethod().equals("POST"); + })); + return builder.build(); } From 3425343871eec3d6337035ed803a92ac73896c41 Mon Sep 17 00:00:00 2001 From: Ori Gold Date: Mon, 27 Oct 2025 12:20:47 -0700 Subject: [PATCH 19/63] feat: added px_secured_pxhd_enabled configuration --- ci_files/enforcer-config.json | 3 ++- .../api/verificationhandler/DefaultVerificationHandler.java | 6 ++++-- .../perimeterx/models/configuration/PXConfiguration.java | 4 ++++ web/src/main/java/com/web/Config.java | 3 +++ 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/ci_files/enforcer-config.json b/ci_files/enforcer-config.json index e0bd186c..b085b4f1 100644 --- a/ci_files/enforcer-config.json +++ b/ci_files/enforcer-config.json @@ -113,5 +113,6 @@ ], "px_cors_support_enabled": true, "px_cors_preflight_request_filter_enabled": true, - "px_url_decode_reserved_characters": true + "px_url_decode_reserved_characters": true, + "px_secured_pxhd_enabled": true } \ No newline at end of file diff --git a/src/main/java/com/perimeterx/api/verificationhandler/DefaultVerificationHandler.java b/src/main/java/com/perimeterx/api/verificationhandler/DefaultVerificationHandler.java index 716dcfee..cdb193f3 100644 --- a/src/main/java/com/perimeterx/api/verificationhandler/DefaultVerificationHandler.java +++ b/src/main/java/com/perimeterx/api/verificationhandler/DefaultVerificationHandler.java @@ -1,13 +1,11 @@ package com.perimeterx.api.verificationhandler; -import com.perimeterx.api.PerimeterX; import com.perimeterx.api.activities.ActivityHandler; import com.perimeterx.api.additionalContext.PXHDSource; import com.perimeterx.api.blockhandler.BlockHandler; import com.perimeterx.models.PXContext; import com.perimeterx.models.configuration.PXConfiguration; import com.perimeterx.models.exceptions.PXException; -import com.perimeterx.utils.logger.IPXLogger; import com.perimeterx.utils.logger.LogReason; import javax.servlet.http.HttpServletResponseWrapper; @@ -96,6 +94,10 @@ private String getPxhdCookie(PXContext context) throws UnsupportedEncodingExcept cookieValue += COOKIE_SEPARATOR + COOKIE_DOMAIN_KEY + context.getPxhdDomain(); } + if (pxConfiguration.isSecuredPxhdEnabled()) { + cookieValue += COOKIE_SEPARATOR + "Secure"; + } + return cookieValue; } diff --git a/src/main/java/com/perimeterx/models/configuration/PXConfiguration.java b/src/main/java/com/perimeterx/models/configuration/PXConfiguration.java index bcb5aaf6..6904f6a1 100644 --- a/src/main/java/com/perimeterx/models/configuration/PXConfiguration.java +++ b/src/main/java/com/perimeterx/models/configuration/PXConfiguration.java @@ -256,6 +256,10 @@ public static void setPxLoggerSeverity(LoggerSeverity severity) { @JsonProperty("px_login_successful_status") private int[] loginResponseValidationStatusCode = {200}; + @Builder.Default + @JsonProperty("px_secured_pxhd_enabled") + private boolean securedPxhdEnabled = false; + @Builder.Default private LoginResponseValidator customLoginResponseValidator = new DefaultCustomLoginResponseValidator(); diff --git a/web/src/main/java/com/web/Config.java b/web/src/main/java/com/web/Config.java index 5e092baf..516860bd 100644 --- a/web/src/main/java/com/web/Config.java +++ b/web/src/main/java/com/web/Config.java @@ -160,6 +160,9 @@ public PXConfiguration getPxConfiguration() { LoggerSeverity.NONE : LoggerSeverity.ERROR; PXConfiguration.setPxLoggerSeverity(loggerSeverity); break; + case "px_secured_pxhd_enabled": + builder.securedPxhdEnabled(enforcerConfig.getBoolean(key)); + break; case "px_user_agent_max_length": case "px_risk_cookie_max_length": case "px_risk_cookie_max_iterations": From 82e5dec8d12d470699df4367ac882fa2c3e19cd9 Mon Sep 17 00:00:00 2001 From: Ori Gold Date: Mon, 27 Oct 2025 13:17:27 -0700 Subject: [PATCH 20/63] update: tests v1.23.1 --- .github/workflows/ci_e2e.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci_e2e.yaml b/.github/workflows/ci_e2e.yaml index d45a3566..e3f0fb10 100644 --- a/.github/workflows/ci_e2e.yaml +++ b/.github/workflows/ci_e2e.yaml @@ -27,7 +27,7 @@ jobs: env: MOCK_COLLECTOR_IMAGE_TAG: 2.0.5 SAMPLE_SITE_IMAGE_TAG: 1.0.0 - ENFORCER_SPEC_TESTS_IMAGE_TAG: 1.21.0 + ENFORCER_SPEC_TESTS_IMAGE_TAG: 1.23.1 runs-on: ubuntu-latest timeout-minutes: 60 From c2b7f6448d6b1eab00c91830dffa53459a398caa Mon Sep 17 00:00:00 2001 From: Ori Gold Date: Mon, 27 Oct 2025 13:17:41 -0700 Subject: [PATCH 21/63] fix: exclude ad block test --- px_metadata.json | 1 + 1 file changed, 1 insertion(+) diff --git a/px_metadata.json b/px_metadata.json index 0e52f582..95597c87 100644 --- a/px_metadata.json +++ b/px_metadata.json @@ -39,6 +39,7 @@ "vid_extraction" ], "excluded_tests": [ + "test_block_activity_ad_block", "test_pxde_extraction_(s2s|unverified|verified)", "test_risk_cookie_valid_cookie_with_user_agent_of_max_length", "test_path_parsing_in_(block|page_requested|risk_api|additional_s2s)\\[with_dots", From 0e83cc39121c3c73b8845b5b98c3dbb63c6349e4 Mon Sep 17 00:00:00 2001 From: Ori Gold Date: Mon, 27 Oct 2025 13:32:46 -0700 Subject: [PATCH 22/63] update: captcha template --- .../templates/captcha_template.mustache | 127 +++++++++--------- 1 file changed, 64 insertions(+), 63 deletions(-) diff --git a/src/main/resources/com/perimeterx/api/blockhandler/templates/captcha_template.mustache b/src/main/resources/com/perimeterx/api/blockhandler/templates/captcha_template.mustache index 4a1503bf..38407441 100644 --- a/src/main/resources/com/perimeterx/api/blockhandler/templates/captcha_template.mustache +++ b/src/main/resources/com/perimeterx/api/blockhandler/templates/captcha_template.mustache @@ -6,74 +6,75 @@ Access to this page has been denied {{#cssRef}} - + {{/cssRef}} - -{{#jsRef}} - -{{/jsRef}} + function isContentLoaded() { + return !!document.querySelector('div,span'); + } + window._pxOnError = function () { + var style = document.createElement('style'); + style.innerText = '@import url(https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap);body{background-color:#fafbfc}.px-captcha-error-container{position:fixed;height:340px;background-color:#fff;font-family:Roboto,sans-serif}.px-captcha-error-header{color:#f0f1f2;font-size:29px;margin:67px 0 33px;font-weight:500;line-height:.83;text-align:center}.px-captcha-error-message{color:#f0f1f2;font-size:18px;margin:0 0 29px;line-height:1.33;text-align:center}.px-captcha-error-button{text-align:center;line-height:48px;width:253px;margin:auto;border-radius:50px;border:solid 1px #f0f1f2;font-size:20px;color:#f0f1f2}.px-captcha-error-wrapper{margin:18px 0 0}div.px-captcha-error{margin:auto;text-align:center;width:400px;height:30px;font-size:12px;background-color:#fcf0f2;color:#ce0e2d}img.px-captcha-error{margin:6px 8px -2px 0}.px-captcha-error-refid{border-top:solid 1px #f0eeee;height:27px;margin:13px 0 0;border-radius:0 0 3px 3px;background-color:#fafbfc;font-size:10px;line-height:2.5;text-align:center;color:#b1b5b8}@media (min-width:620px){.px-captcha-error-container{width:530px;top:50%;left:50%;margin-top:-170px;margin-left:-265px;border-radius:3px;box-shadow:0 2px 9px -1px rgba(0,0,0,.13)}}@media (min-width:481px) and (max-width:620px){.px-captcha-error-container{width:85%;top:50%;left:50%;margin-top:-170px;margin-left:-42.5%;border-radius:3px;box-shadow:0 2px 9px -1px rgba(0,0,0,.13)}}@media (max-width:480px){body{background-color:#fff}.px-captcha-error-header{color:#f0f1f2;font-size:29px;margin:55px 0 33px}.px-captcha-error-container{width:530px;top:50%;left:50%;margin-top:-170px;margin-left:-265px}.px-captcha-error-refid{position:fixed;width:100%;left:0;bottom:0;border-radius:0;font-size:14px;line-height:2}}@media (max-width:390px){div.px-captcha-error{font-size:10px}.px-captcha-error-refid{font-size:11px;line-height:2.5}}'; + document.head.appendChild(style); + var div = document.createElement('div'); + div.className = 'px-captcha-error-container'; + div.innerHTML = '
Before we continue...
Press & Hold to confirm you are
a human (and not a bot).
Press & Hold
Please check your internet connection' + (window._pxMobile ? '' : ' or disable your ad-blocker') + '.
Reference ID ' + window._pxUuid + '
'; + document.body.appendChild(div); + if (window._pxMobile) { + setTimeout(function() { + location.href = '/px/captcha_close?status=-1'; + }, 5000); + } + }; + + {{#jsRef}} + + {{/jsRef}} - + \ No newline at end of file From 93e363f84d15df095265da5415b04fa43ca10b62 Mon Sep 17 00:00:00 2001 From: Ori Gold Date: Mon, 27 Oct 2025 17:07:21 -0700 Subject: [PATCH 23/63] feat: added additional token info --- .../com/perimeterx/internals/cookie/AbstractPXCookie.java | 5 +++++ src/main/java/com/perimeterx/internals/cookie/PXCookie.java | 1 + src/main/java/com/perimeterx/models/PXContext.java | 2 ++ .../perimeterx/models/activities/CommonActivityDetails.java | 4 ++++ .../java/com/perimeterx/models/httpmodels/Additional.java | 6 ++++++ 5 files changed, 18 insertions(+) diff --git a/src/main/java/com/perimeterx/internals/cookie/AbstractPXCookie.java b/src/main/java/com/perimeterx/internals/cookie/AbstractPXCookie.java index d4c40bb8..0899761a 100644 --- a/src/main/java/com/perimeterx/internals/cookie/AbstractPXCookie.java +++ b/src/main/java/com/perimeterx/internals/cookie/AbstractPXCookie.java @@ -186,4 +186,9 @@ public String getUUID() { public String getVID() { return decodedCookie.get("v").asText(); } + + @Override + public String additionalTokenInfo() { + return this.decodedCookie.get("add") != null ? this.decodedCookie.get("add").asText() : null; + } } diff --git a/src/main/java/com/perimeterx/internals/cookie/PXCookie.java b/src/main/java/com/perimeterx/internals/cookie/PXCookie.java index 84d36b5f..3b977af9 100644 --- a/src/main/java/com/perimeterx/internals/cookie/PXCookie.java +++ b/src/main/java/com/perimeterx/internals/cookie/PXCookie.java @@ -23,4 +23,5 @@ public interface PXCookie { boolean isSecured() throws PXException; + String additionalTokenInfo(); } diff --git a/src/main/java/com/perimeterx/models/PXContext.java b/src/main/java/com/perimeterx/models/PXContext.java index 9752eac2..56b7ddd4 100644 --- a/src/main/java/com/perimeterx/models/PXContext.java +++ b/src/main/java/com/perimeterx/models/PXContext.java @@ -233,6 +233,7 @@ public class PXContext { private String pxCtsCookie; private long enforcerStartTime; private boolean isSensitiveRequest; + private String additionalTokenInfo; /** * The cookie key used to decrypt the cookie @@ -460,6 +461,7 @@ public void setOriginalTokenCookie(String originalTokenCookie) { public void setRiskCookie(AbstractPXCookie riskCookie) { this.riskCookie = riskCookie.getDecodedCookie().toString(); + this.additionalTokenInfo = riskCookie.additionalTokenInfo(); } public void setBlockAction(String blockAction) { diff --git a/src/main/java/com/perimeterx/models/activities/CommonActivityDetails.java b/src/main/java/com/perimeterx/models/activities/CommonActivityDetails.java index e66da0d2..f268f5de 100644 --- a/src/main/java/com/perimeterx/models/activities/CommonActivityDetails.java +++ b/src/main/java/com/perimeterx/models/activities/CommonActivityDetails.java @@ -50,6 +50,9 @@ public class CommonActivityDetails implements ActivityDetails { @JsonProperty("additional_risk_info") public String additionalRiskInfo; + @JsonProperty("additional_token_info") + public String additionalTokenInfo; + @JsonProperty("user") public String username; @@ -89,5 +92,6 @@ public CommonActivityDetails(PXContext context) { this.enforcerStartTime = additional.enforcerStartTime; this.pxCtsCookie = additional.pxCtsCookie; this.isSensitiveRoute = additional.isSensitiveRoute; + this.additionalTokenInfo = additional.additionalTokenInfo; } } diff --git a/src/main/java/com/perimeterx/models/httpmodels/Additional.java b/src/main/java/com/perimeterx/models/httpmodels/Additional.java index 228d4a6d..46235630 100644 --- a/src/main/java/com/perimeterx/models/httpmodels/Additional.java +++ b/src/main/java/com/perimeterx/models/httpmodels/Additional.java @@ -85,8 +85,10 @@ public class Additional { @JsonProperty("request_id") public UUID requestId; + @JsonProperty("enforcer_start_time") public long enforcerStartTime; + @JsonProperty("risk_start_time") public long riskStartTime; @@ -96,6 +98,9 @@ public class Additional { @JsonProperty("is_sensitive_route") public Boolean isSensitiveRoute; + @JsonProperty("additional_token_info") + public String additionalTokenInfo; + public static Additional fromContext(PXContext ctx) { Additional additional = new Additional(); additional.pxCookie = ctx.getRiskCookie(); @@ -118,6 +123,7 @@ public static Additional fromContext(PXContext ctx) { additional.riskStartTime = new Date().getTime(); additional.pxCtsCookie = ctx.getPxCtsCookie(); additional.isSensitiveRoute = ctx.isSensitiveRequest(); + additional.additionalTokenInfo = ctx.getAdditionalTokenInfo(); setLoginCredentials(ctx, additional); From dc860f5ca4197df3c1ab0672b8daeb844e63c6b4 Mon Sep 17 00:00:00 2001 From: Ori Gold Date: Mon, 27 Oct 2025 17:07:40 -0700 Subject: [PATCH 24/63] fix: align new block page (blank space) --- .../api/blockhandler/templates/captcha_template.mustache | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/resources/com/perimeterx/api/blockhandler/templates/captcha_template.mustache b/src/main/resources/com/perimeterx/api/blockhandler/templates/captcha_template.mustache index 38407441..bd4aade7 100644 --- a/src/main/resources/com/perimeterx/api/blockhandler/templates/captcha_template.mustache +++ b/src/main/resources/com/perimeterx/api/blockhandler/templates/captcha_template.mustache @@ -6,7 +6,7 @@ Access to this page has been denied {{#cssRef}} - + {{/cssRef}} @@ -77,4 +77,4 @@ {{/jsRef}} - \ No newline at end of file + From e1b9272dd2d0115234931cbc48f8f1f9fedeacee Mon Sep 17 00:00:00 2001 From: Ori Gold Date: Mon, 27 Oct 2025 17:08:17 -0700 Subject: [PATCH 25/63] fix: align first party block script with expected value in block page --- .../perimeterx/api/blockhandler/templates/TemplateFactory.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/perimeterx/api/blockhandler/templates/TemplateFactory.java b/src/main/java/com/perimeterx/api/blockhandler/templates/TemplateFactory.java index 998aa3cf..e626ee97 100644 --- a/src/main/java/com/perimeterx/api/blockhandler/templates/TemplateFactory.java +++ b/src/main/java/com/perimeterx/api/blockhandler/templates/TemplateFactory.java @@ -61,7 +61,7 @@ public static Map getProps(PXContext pxContext, PXConfiguration String hostUrl = pxContext.getCollectorURL(); if (pxConfig.isFirstPartyEnabled() && !pxContext.isMobileToken()) { String prefix = pxConfig.getAppId().substring(2); - blockScript = SLASH + prefix + Constants.FIRST_PARTY_CAPTCHA_PATH + QUESTION_MARK + captchaSrcParams; + blockScript = SLASH + prefix + Constants.FIRST_PARTY_CAPTCHA_PATH + CAPTCHA_FIRST_PARTY_FILE_PATH + QUESTION_MARK + captchaSrcParams; jsClientSrc = SLASH + prefix + Constants.FIRST_PARTY_VENDOR_PATH; hostUrl = SLASH + prefix + Constants.FIRST_PARTY_XHR_PATH; } From 5825be3074f6a3d0f810351fe8fda9c2c2fa3a8f Mon Sep 17 00:00:00 2001 From: Ori Gold Date: Mon, 27 Oct 2025 17:58:49 -0700 Subject: [PATCH 26/63] chore: changelog --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 809ba2ce..aa6eca50 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,14 @@ # Change Log +## [x.x.x] - YYYY-MM-DD +- Added `px_secured_pxhd_enabled` configuration option to enable secure flag on `pxhd` cookie +- Added `is_sensitive_route` to risk api and async activities +- Added `additional_token_info` to risk api and async activities +- Updated telemetry activity to new format (`static_config` and `active_config`; `remote_config` is not supported) +- Updated telemetry activity to include `request_id` +- Updated captcha page template to newest version +- Updated dependencies minor and patch versions (major versions unchanged) +- Changed custom parameters to be of type `Object` instead of `String` to allow more flexibility +- Changed first party block script in captcha template to end with expected `/captcha.js` ## [v6.15.1](https://github.com/PerimeterX/perimeterx-java-sdk/compare/6.15.1...HEAD) (2025-09-08) - Added additional updateReason RISK to Telemetry flow From c1c0aff776bfb4a4baa55cc582b7cdba3eb07886 Mon Sep 17 00:00:00 2001 From: Ori Gold Date: Tue, 28 Oct 2025 09:04:00 -0700 Subject: [PATCH 27/63] update: e2e tests to 1.23.2 --- .github/workflows/ci_e2e.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci_e2e.yaml b/.github/workflows/ci_e2e.yaml index e3f0fb10..7c109065 100644 --- a/.github/workflows/ci_e2e.yaml +++ b/.github/workflows/ci_e2e.yaml @@ -27,7 +27,7 @@ jobs: env: MOCK_COLLECTOR_IMAGE_TAG: 2.0.5 SAMPLE_SITE_IMAGE_TAG: 1.0.0 - ENFORCER_SPEC_TESTS_IMAGE_TAG: 1.23.1 + ENFORCER_SPEC_TESTS_IMAGE_TAG: 1.23.2 runs-on: ubuntu-latest timeout-minutes: 60 From 147ad3f37c97afe2adaeec8fa3cc7a26794c7da3 Mon Sep 17 00:00:00 2001 From: Ori Gold Date: Tue, 28 Oct 2025 09:21:14 -0700 Subject: [PATCH 28/63] fix: adding logger auth token to ci tests --- .github/workflows/ci_e2e.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci_e2e.yaml b/.github/workflows/ci_e2e.yaml index 7c109065..6d091a80 100644 --- a/.github/workflows/ci_e2e.yaml +++ b/.github/workflows/ci_e2e.yaml @@ -109,7 +109,8 @@ jobs: cat ./ci_files/enforcer-config.json |\ jq '.px_app_id="${{ secrets.PX_APP_ID }}"' |\ jq '.px_cookie_secret="${{ secrets.TEST_COOKIE_SECRET }}"' |\ - jq '.px_auth_token="${{ secrets.PX_AUTH_TOKEN }}"' > /tmp/enforcer-config.json + jq '.px_auth_token="${{ secrets.PX_AUTH_TOKEN }}"' |\ + jq '.px_logger_auth_token="${{ secrets.PX_LOGGER_AUTH_TOKEN }}"' > /tmp/enforcer-config.json - name: log enforcer config run: cat /tmp/enforcer-config.json From c9e4afd579c0c262b254a7481baa3784c97241db Mon Sep 17 00:00:00 2001 From: Ori Gold Date: Tue, 28 Oct 2025 10:17:09 -0700 Subject: [PATCH 29/63] update: adding retry to e2e tests --- ci_files/spec-tests-values.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ci_files/spec-tests-values.yaml b/ci_files/spec-tests-values.yaml index 2b025ef9..ec158e28 100644 --- a/ci_files/spec-tests-values.yaml +++ b/ci_files/spec-tests-values.yaml @@ -1,3 +1,9 @@ +additionalArgs: + - "--retries" + - "3" + - "--retry-delay" + - "10" + internalMockCollectorURL: "http://mock-collector-mock-collector:3001" siteURL: "http://java-enforcer-sample-site:3000" From 71c18e4b75d4b1f3b61a3912781d8f08b1669421 Mon Sep 17 00:00:00 2001 From: Ori Gold Date: Wed, 29 Oct 2025 08:50:11 -0700 Subject: [PATCH 30/63] update: mock collector 2.0.5 -> 2.0.6 --- .github/workflows/ci_e2e.yaml | 2 +- .github/workflows/fuzzer.yaml | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci_e2e.yaml b/.github/workflows/ci_e2e.yaml index 6d091a80..fc374407 100644 --- a/.github/workflows/ci_e2e.yaml +++ b/.github/workflows/ci_e2e.yaml @@ -25,7 +25,7 @@ jobs: CI: name: "E2E tests" env: - MOCK_COLLECTOR_IMAGE_TAG: 2.0.5 + MOCK_COLLECTOR_IMAGE_TAG: 2.0.6 SAMPLE_SITE_IMAGE_TAG: 1.0.0 ENFORCER_SPEC_TESTS_IMAGE_TAG: 1.23.2 diff --git a/.github/workflows/fuzzer.yaml b/.github/workflows/fuzzer.yaml index f66b6788..e7aac8d1 100644 --- a/.github/workflows/fuzzer.yaml +++ b/.github/workflows/fuzzer.yaml @@ -25,7 +25,7 @@ jobs: Fuzzing: name: "Fuzzing Test" env: - MOCK_COLLECTOR_IMAGE_TAG: 2.0.5 + MOCK_COLLECTOR_IMAGE_TAG: 2.0.6 FUZZER_TAG: 1.1.0 SAMPLE_SITE_IMAGE_TAG: 1.0.0 ENFORCER_TAG: ${{ needs.extract_version.outputs.version }} @@ -55,9 +55,9 @@ jobs: docker build . -t localhost:5001/java-enforcer-sample-site:$SAMPLE_SITE_IMAGE_TAG && \ docker push localhost:5001/java-enforcer-sample-site:$SAMPLE_SITE_IMAGE_TAG - - uses: azure/setup-helm@v3 + - uses: azure/setup-helm@v4 with: - version: '3.14.2' + version: '3.16.1' - name: Clone helm charts repo - mock-collector uses: actions/checkout@v5 @@ -105,7 +105,8 @@ jobs: --set image.repository=localhost:5001/mock-collector \ --set image.tag=$MOCK_COLLECTOR_IMAGE_TAG \ --set authToken=${{ secrets.PX_AUTH_TOKEN }} \ - --set imagePullPolicy=Always --wait + --set imagePullPolicy=Always \ + --wait - name: set secrets in enforcer config run: | From f081f7fb0db7859e47f5636fb65f69788316a4a0 Mon Sep 17 00:00:00 2001 From: Ori Gold Date: Wed, 29 Oct 2025 10:24:23 -0700 Subject: [PATCH 31/63] chore: upgrade setup-helm and helm version --- .github/workflows/ci_e2e.yaml | 4 ++-- .github/workflows/fuzzer.yaml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci_e2e.yaml b/.github/workflows/ci_e2e.yaml index fc374407..f91d8431 100644 --- a/.github/workflows/ci_e2e.yaml +++ b/.github/workflows/ci_e2e.yaml @@ -50,9 +50,9 @@ jobs: docker build . -t localhost:5001/java-enforcer-sample-site:$SAMPLE_SITE_IMAGE_TAG && \ docker push localhost:5001/java-enforcer-sample-site:$SAMPLE_SITE_IMAGE_TAG - - uses: azure/setup-helm@v3 + - uses: azure/setup-helm@v4 with: - version: '3.14.1' + version: '3.19.0' - name: Clone helm charts repo - mock-collector uses: actions/checkout@v5 diff --git a/.github/workflows/fuzzer.yaml b/.github/workflows/fuzzer.yaml index e7aac8d1..6027489d 100644 --- a/.github/workflows/fuzzer.yaml +++ b/.github/workflows/fuzzer.yaml @@ -57,7 +57,7 @@ jobs: - uses: azure/setup-helm@v4 with: - version: '3.16.1' + version: '3.19.0' - name: Clone helm charts repo - mock-collector uses: actions/checkout@v5 From 1105074f1e726dc007304c148fe7c3438e36df6e Mon Sep 17 00:00:00 2001 From: Ori Gold Date: Wed, 29 Oct 2025 11:53:52 -0700 Subject: [PATCH 32/63] tmp: setup helm workaround --- .github/workflows/ci_e2e.yaml | 13 ++++++++++--- .github/workflows/fuzzer.yaml | 13 ++++++++++--- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci_e2e.yaml b/.github/workflows/ci_e2e.yaml index f91d8431..223391b3 100644 --- a/.github/workflows/ci_e2e.yaml +++ b/.github/workflows/ci_e2e.yaml @@ -50,9 +50,16 @@ jobs: docker build . -t localhost:5001/java-enforcer-sample-site:$SAMPLE_SITE_IMAGE_TAG && \ docker push localhost:5001/java-enforcer-sample-site:$SAMPLE_SITE_IMAGE_TAG - - uses: azure/setup-helm@v4 - with: - version: '3.19.0' +# - uses: azure/setup-helm@v4 +# with: +# version: '3.19.0' + + - name: Setup helm (temp) + run: | + curl -fsSL https://packages.buildkite.com/helm-linux/helm-debian/gpgkey | gpg --dearmor | sudo tee /usr/share/keyrings/helm.gpg > /dev/null + echo "deb [signed-by=/usr/share/keyrings/helm.gpg] https://packages.buildkite.com/helm-linux/helm-debian/any/ any main" | sudo tee /etc/apt/sources.list.d/helm-stable-debian.list + sudo apt-get update + sudo apt-get install helm - name: Clone helm charts repo - mock-collector uses: actions/checkout@v5 diff --git a/.github/workflows/fuzzer.yaml b/.github/workflows/fuzzer.yaml index 6027489d..6b0c5206 100644 --- a/.github/workflows/fuzzer.yaml +++ b/.github/workflows/fuzzer.yaml @@ -55,9 +55,16 @@ jobs: docker build . -t localhost:5001/java-enforcer-sample-site:$SAMPLE_SITE_IMAGE_TAG && \ docker push localhost:5001/java-enforcer-sample-site:$SAMPLE_SITE_IMAGE_TAG - - uses: azure/setup-helm@v4 - with: - version: '3.19.0' +# - uses: azure/setup-helm@v4 +# with: +# version: '3.19.0' + + - name: Setup helm (temp) + run: | + curl -fsSL https://packages.buildkite.com/helm-linux/helm-debian/gpgkey | gpg --dearmor | sudo tee /usr/share/keyrings/helm.gpg > /dev/null + echo "deb [signed-by=/usr/share/keyrings/helm.gpg] https://packages.buildkite.com/helm-linux/helm-debian/any/ any main" | sudo tee /etc/apt/sources.list.d/helm-stable-debian.list + sudo apt-get update + sudo apt-get install helm - name: Clone helm charts repo - mock-collector uses: actions/checkout@v5 From e3e90293391d55f41775b8bbb729fa519e18b0bc Mon Sep 17 00:00:00 2001 From: Ori Gold Date: Thu, 30 Oct 2025 13:04:54 -0700 Subject: [PATCH 33/63] test: reverting setup-helm, filtering for tests, seeing enforcer logs --- .github/workflows/ci_e2e.yaml | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci_e2e.yaml b/.github/workflows/ci_e2e.yaml index 223391b3..656e50e5 100644 --- a/.github/workflows/ci_e2e.yaml +++ b/.github/workflows/ci_e2e.yaml @@ -50,16 +50,9 @@ jobs: docker build . -t localhost:5001/java-enforcer-sample-site:$SAMPLE_SITE_IMAGE_TAG && \ docker push localhost:5001/java-enforcer-sample-site:$SAMPLE_SITE_IMAGE_TAG -# - uses: azure/setup-helm@v4 -# with: -# version: '3.19.0' - - - name: Setup helm (temp) - run: | - curl -fsSL https://packages.buildkite.com/helm-linux/helm-debian/gpgkey | gpg --dearmor | sudo tee /usr/share/keyrings/helm.gpg > /dev/null - echo "deb [signed-by=/usr/share/keyrings/helm.gpg] https://packages.buildkite.com/helm-linux/helm-debian/any/ any main" | sudo tee /etc/apt/sources.list.d/helm-stable-debian.list - sudo apt-get update - sudo apt-get install helm + - uses: azure/setup-helm@v4 + with: + version: '3.19.0' - name: Clone helm charts repo - mock-collector uses: actions/checkout@v5 @@ -150,6 +143,7 @@ jobs: --set appId=${{ secrets.PX_APP_ID }} \ --set-file enforcerMetadataContent=./px_metadata.json \ --set-file enforcerConfigJsonContent=/tmp/enforcer-config.json \ + --set additionalArgs="-k \"(test_vid_extraction_on_first_party_xhr or test_header_based_logger_logs_on_first_party_requests)\"" \ -f ./ci_files/spec-tests-values.yaml \ --wait \ --timeout 60m0s \ @@ -158,3 +152,7 @@ jobs: - name: get tests results if: ${{ always() }} run: kubectl logs job/enforcer-spec-tests + + - name: get enforcer logs + if: ${{ always() }} + run: kubectl logs deployment/java-enforcer-sample-site From 858677afa3fd0ba8dea8166aaa4fa4adcd4b6895 Mon Sep 17 00:00:00 2001 From: Ori Gold Date: Thu, 30 Oct 2025 13:10:19 -0700 Subject: [PATCH 34/63] fix --- .github/workflows/ci_e2e.yaml | 1 - ci_files/spec-tests-values.yaml | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci_e2e.yaml b/.github/workflows/ci_e2e.yaml index 656e50e5..538cf8e3 100644 --- a/.github/workflows/ci_e2e.yaml +++ b/.github/workflows/ci_e2e.yaml @@ -143,7 +143,6 @@ jobs: --set appId=${{ secrets.PX_APP_ID }} \ --set-file enforcerMetadataContent=./px_metadata.json \ --set-file enforcerConfigJsonContent=/tmp/enforcer-config.json \ - --set additionalArgs="-k \"(test_vid_extraction_on_first_party_xhr or test_header_based_logger_logs_on_first_party_requests)\"" \ -f ./ci_files/spec-tests-values.yaml \ --wait \ --timeout 60m0s \ diff --git a/ci_files/spec-tests-values.yaml b/ci_files/spec-tests-values.yaml index ec158e28..2acd6a57 100644 --- a/ci_files/spec-tests-values.yaml +++ b/ci_files/spec-tests-values.yaml @@ -3,6 +3,8 @@ additionalArgs: - "3" - "--retry-delay" - "10" + - "-k" + - "(test_vid_extraction_on_first_party_xhr or test_header_based_logger_logs_on_first_party_requests)" internalMockCollectorURL: "http://mock-collector-mock-collector:3001" siteURL: "http://java-enforcer-sample-site:3000" From 16d5304baeb3b1b12ae7c788bd53d36ce3ec6861 Mon Sep 17 00:00:00 2001 From: Ori Gold Date: Thu, 30 Oct 2025 13:27:15 -0700 Subject: [PATCH 35/63] fix: first party log to lowercase --- src/main/java/com/perimeterx/api/proxy/DefaultReverseProxy.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/perimeterx/api/proxy/DefaultReverseProxy.java b/src/main/java/com/perimeterx/api/proxy/DefaultReverseProxy.java index 3158ca86..3360d4df 100644 --- a/src/main/java/com/perimeterx/api/proxy/DefaultReverseProxy.java +++ b/src/main/java/com/perimeterx/api/proxy/DefaultReverseProxy.java @@ -107,7 +107,7 @@ public boolean reversePxXhr(HttpServletRequest req, HttpServletResponse res, PXC final String host = pxConfiguration.getCollectorUrl().replaceFirst("https?:\\/\\/", ""); if (!isValidThirdPartyUrl(url, host, path)) { - context.logger.error("First party XHR URL is inaccurate: " + url + ", rendering default response"); + context.logger.error("first party XHR URL is inaccurate: " + url + ", rendering default response"); predefinedResponseHelper.handlePredefinedResponse(res, predefinedResponse, context); return true; } From ba6999e8e02436c82ff0575dd99fdc9e03857d7b Mon Sep 17 00:00:00 2001 From: Ori Gold Date: Thu, 30 Oct 2025 15:02:04 -0700 Subject: [PATCH 36/63] fix: validate third party url with port in host if needed --- ci_files/spec-tests-values.yaml | 2 -- .../api/proxy/DefaultReverseProxy.java | 20 +++++++++++++++---- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/ci_files/spec-tests-values.yaml b/ci_files/spec-tests-values.yaml index 2acd6a57..ec158e28 100644 --- a/ci_files/spec-tests-values.yaml +++ b/ci_files/spec-tests-values.yaml @@ -3,8 +3,6 @@ additionalArgs: - "3" - "--retry-delay" - "10" - - "-k" - - "(test_vid_extraction_on_first_party_xhr or test_header_based_logger_logs_on_first_party_requests)" internalMockCollectorURL: "http://mock-collector-mock-collector:3001" siteURL: "http://java-enforcer-sample-site:3000" diff --git a/src/main/java/com/perimeterx/api/proxy/DefaultReverseProxy.java b/src/main/java/com/perimeterx/api/proxy/DefaultReverseProxy.java index 3360d4df..4329ed70 100644 --- a/src/main/java/com/perimeterx/api/proxy/DefaultReverseProxy.java +++ b/src/main/java/com/perimeterx/api/proxy/DefaultReverseProxy.java @@ -106,7 +106,7 @@ public boolean reversePxXhr(HttpServletRequest req, HttpServletResponse res, PXC final String url = pxConfiguration.getCollectorUrl() + path; final String host = pxConfiguration.getCollectorUrl().replaceFirst("https?:\\/\\/", ""); - if (!isValidThirdPartyUrl(url, host, path)) { + if (!isValidThirdPartyUrl(url, host, path, context)) { context.logger.error("first party XHR URL is inaccurate: " + url + ", rendering default response"); predefinedResponseHelper.handlePredefinedResponse(res, predefinedResponse, context); return true; @@ -134,13 +134,25 @@ private String getPath(HttpServletRequest req) { return isBlank(req.getRequestURI()) ? "" : req.getRequestURI().substring(xhrPrefix.length()); } - private boolean isValidThirdPartyUrl(String rawThirdPartyUrl, String expectedHost, String expectedUrl) { + private boolean isValidThirdPartyUrl(String rawThirdPartyUrl, String expectedHost, String expectedUrl, PXContext context) { try { URL url = new URL(rawThirdPartyUrl); String uri = url.getPath() + (url.getQuery() != null ? url.getQuery() : ""); - return url.getHost().equalsIgnoreCase(expectedHost) && uri.startsWith(expectedUrl); + if (!uri.startsWith(expectedUrl)) { + context.logger.debug("Validating third party URL failed, expected URL does not match the request URL. expectedUrl: " + expectedUrl + ", requestUrl: " + uri); + return false; + } + String host = url.getHost(); + if (url.getPort() != url.getDefaultPort()) { + host += ":" + url.getPort(); + } + if (!host.equalsIgnoreCase(expectedHost)) { + context.logger.debug("Validating third party URL failed, expected host does not match the request host. expectedHost: " + expectedHost + ", requestHost: " + host); + return false; + } + return true; } catch (Exception e) { - PerimeterX.globalLogger.error("Failed to parse rawUrl. ", e.getMessage()); + context.logger.error("Failed to parse rawUrl. ", e.getMessage()); } return false; From 5b411eb13831b2f134400eb41fd9c3d5f7282a7f Mon Sep 17 00:00:00 2001 From: Ori Gold Date: Thu, 30 Oct 2025 15:15:48 -0700 Subject: [PATCH 37/63] fix --- .../java/com/perimeterx/api/proxy/DefaultReverseProxy.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/perimeterx/api/proxy/DefaultReverseProxy.java b/src/main/java/com/perimeterx/api/proxy/DefaultReverseProxy.java index 4329ed70..04ee6728 100644 --- a/src/main/java/com/perimeterx/api/proxy/DefaultReverseProxy.java +++ b/src/main/java/com/perimeterx/api/proxy/DefaultReverseProxy.java @@ -143,8 +143,9 @@ private boolean isValidThirdPartyUrl(String rawThirdPartyUrl, String expectedHos return false; } String host = url.getHost(); - if (url.getPort() != url.getDefaultPort()) { - host += ":" + url.getPort(); + final int port = url.getPort(); + if (port != -1 && port != url.getDefaultPort()) { + host += ":" + port; } if (!host.equalsIgnoreCase(expectedHost)) { context.logger.debug("Validating third party URL failed, expected host does not match the request host. expectedHost: " + expectedHost + ", requestHost: " + host); From d99b16a711c9977abccbe8ee876c08f8117241b5 Mon Sep 17 00:00:00 2001 From: Ori Gold Date: Thu, 30 Oct 2025 15:36:32 -0700 Subject: [PATCH 38/63] fix: skipping first party timeout test (no fp timeout config supported) --- px_metadata.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/px_metadata.json b/px_metadata.json index 95597c87..fc12507f 100644 --- a/px_metadata.json +++ b/px_metadata.json @@ -50,6 +50,7 @@ "test_cookie_v3_cookie_decryption_failed_iterations_above_max_allowed_value", "test_cookie_v3_cookie_validation_failed_big_cookie", "test_client_ip_extraction_order_risk_api", - "test_telemetry_command_verify_custom_function_hash" + "test_telemetry_command_verify_custom_function_hash", + "test_first_party_timeout" ] } From 1d5ab019625d128d85e98727d7ab7212c7502bfd Mon Sep 17 00:00:00 2001 From: Ori Gold Date: Thu, 30 Oct 2025 16:44:21 -0700 Subject: [PATCH 39/63] try timeout 90 mins --- .github/workflows/fuzzer.yaml | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/.github/workflows/fuzzer.yaml b/.github/workflows/fuzzer.yaml index 6b0c5206..526bdfbf 100644 --- a/.github/workflows/fuzzer.yaml +++ b/.github/workflows/fuzzer.yaml @@ -35,7 +35,7 @@ jobs: mode: [ "url", "first_party", "headers", "cookies", "user_agent" ] runs-on: ubuntu-latest - timeout-minutes: 60 + timeout-minutes: 90 needs: - extract_version @@ -55,16 +55,9 @@ jobs: docker build . -t localhost:5001/java-enforcer-sample-site:$SAMPLE_SITE_IMAGE_TAG && \ docker push localhost:5001/java-enforcer-sample-site:$SAMPLE_SITE_IMAGE_TAG -# - uses: azure/setup-helm@v4 -# with: -# version: '3.19.0' - - - name: Setup helm (temp) - run: | - curl -fsSL https://packages.buildkite.com/helm-linux/helm-debian/gpgkey | gpg --dearmor | sudo tee /usr/share/keyrings/helm.gpg > /dev/null - echo "deb [signed-by=/usr/share/keyrings/helm.gpg] https://packages.buildkite.com/helm-linux/helm-debian/any/ any main" | sudo tee /etc/apt/sources.list.d/helm-stable-debian.list - sudo apt-get update - sudo apt-get install helm + - uses: azure/setup-helm@v4 + with: + version: '3.19.0' - name: Clone helm charts repo - mock-collector uses: actions/checkout@v5 @@ -120,7 +113,8 @@ jobs: cat ./ci_files/enforcer-config.json |\ jq '.px_app_id="${{ secrets.PX_APP_ID }}"' |\ jq '.px_cookie_secret="${{ secrets.TEST_COOKIE_SECRET }}"' |\ - jq '.px_auth_token="${{ secrets.PX_AUTH_TOKEN }}"' > /tmp/enforcer-config.json + jq '.px_auth_token="${{ secrets.PX_AUTH_TOKEN }}"' |\ + jq '.px_logger_auth_token="${{ secrets.PX_LOGGER_AUTH_TOKEN }}"' > /tmp/enforcer-config.json - name: log enforcer config run: cat /tmp/enforcer-config.json @@ -152,7 +146,7 @@ jobs: --set mode=$FUZZ_MODE \ --set siteURL=$SITE_URL \ --wait \ - --timeout 60m0s \ + --timeout 90m0s \ --wait-for-jobs env: FUZZ_MODE: ${{ matrix.mode }} From b0583b2800ba2901d56ed1cb11a14cc3dd55dc7b Mon Sep 17 00:00:00 2001 From: Ori Gold Date: Mon, 3 Nov 2025 09:05:23 -0800 Subject: [PATCH 40/63] fix: connection leak bug in first party --- .../java/com/perimeterx/api/proxy/RemoteServer.java | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/perimeterx/api/proxy/RemoteServer.java b/src/main/java/com/perimeterx/api/proxy/RemoteServer.java index dafc32d2..e69ddb03 100644 --- a/src/main/java/com/perimeterx/api/proxy/RemoteServer.java +++ b/src/main/java/com/perimeterx/api/proxy/RemoteServer.java @@ -109,7 +109,7 @@ public IPXOutgoingRequest prepareProxyRequest() throws IOException { return requestBuilder.build(); } - public IPXIncomingResponse handleResponse(IPXOutgoingRequest proxyRequest, PXContext context) { + public void handleResponse(IPXOutgoingRequest proxyRequest, PXContext context) { IPXIncomingResponse proxyResponse = null; try { // Execute the request @@ -119,7 +119,7 @@ public IPXIncomingResponse handleResponse(IPXOutgoingRequest proxyRequest, PXCon // In failure we can check if we enable predefined request or proxy the original response if (this.isAllowedPredefinedResponse() && statusCode >= HttpStatus.SC_BAD_REQUEST) { predefinedResponseHelper.handlePredefinedResponse(res, predefinedResponse, context); - return proxyResponse; + return; } res.setStatus(statusCode); @@ -143,8 +143,15 @@ public IPXIncomingResponse handleResponse(IPXOutgoingRequest proxyRequest, PXCon if (this.isAllowedPredefinedResponse()) { predefinedResponseHelper.handlePredefinedResponse(res, predefinedResponse, context); } + } finally { + if (proxyResponse != null) { + try { + proxyResponse.close(); + } catch (IOException e) { + context.logger.debug("Failed to close proxy response", e); + } + } } - return proxyResponse; } /** From cf250400144a40b137e2df473707aeb6b528a61f Mon Sep 17 00:00:00 2001 From: Ori Gold Date: Mon, 3 Nov 2025 09:29:44 -0800 Subject: [PATCH 41/63] fix: possible telemetry connection leak --- .../com/perimeterx/http/PXHttpClient.java | 27 ++++++++++++++----- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/perimeterx/http/PXHttpClient.java b/src/main/java/com/perimeterx/http/PXHttpClient.java index 93993591..6680fc11 100644 --- a/src/main/java/com/perimeterx/http/PXHttpClient.java +++ b/src/main/java/com/perimeterx/http/PXHttpClient.java @@ -217,14 +217,27 @@ public PXDynamicConfiguration getConfigurationFromServer() { @Override public void sendEnforcerTelemetry(EnforcerTelemetry enforcerTelemetry, PXContext context) throws IOException { - String requestBody = JsonUtils.writer.writeValueAsString(enforcerTelemetry); - if (context!=null){ - context.logger.debug("Sending enforcer telemetry: {}", requestBody); - } else{ - logger.debug("Sending enforcer telemetry: {}", requestBody); + IPXIncomingResponse httpResponse = null; + try { + String requestBody = JsonUtils.writer.writeValueAsString(enforcerTelemetry); + if (context != null) { + context.logger.debug("Sending enforcer telemetry: {}", requestBody); + } else { + logger.debug("Sending enforcer telemetry: {}", requestBody); + } + IPXOutgoingRequest request = buildOutgoingRequest(this.pxConfiguration.getServerURL() + Constants.API_ENFORCER_TELEMETRY, PXHttpMethod.POST, requestBody); + httpResponse = client.send(request); + } catch (Exception e) { + if (context != null) { + context.logger.debug("Sending enforcer telemetry failed. Error: {}", e.getMessage()); + } else { + logger.debug("Sending enforcer telemetry failed. Error: {}", e.getMessage()); + } + } finally { + if (httpResponse != null) { + httpResponse.close(); + } } - IPXOutgoingRequest request = buildOutgoingRequest(this.pxConfiguration.getServerURL() + Constants.API_ENFORCER_TELEMETRY,PXHttpMethod.POST, requestBody); - client.send(request); } private IPXOutgoingRequest buildOutgoingRequest(String url , PXHttpMethod method, String requestBody, BasicHeader... headers) { From 01f3e77cbea8408a486f7b23c52a29a6738d50dd Mon Sep 17 00:00:00 2001 From: Ori Gold Date: Mon, 3 Nov 2025 10:57:02 -0800 Subject: [PATCH 42/63] fix: first party max url length enforced, returns 400 --- .../perimeterx/api/proxy/RemoteServer.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/main/java/com/perimeterx/api/proxy/RemoteServer.java b/src/main/java/com/perimeterx/api/proxy/RemoteServer.java index e69ddb03..44f810fb 100644 --- a/src/main/java/com/perimeterx/api/proxy/RemoteServer.java +++ b/src/main/java/com/perimeterx/api/proxy/RemoteServer.java @@ -112,6 +112,9 @@ public IPXOutgoingRequest prepareProxyRequest() throws IOException { public void handleResponse(IPXOutgoingRequest proxyRequest, PXContext context) { IPXIncomingResponse proxyResponse = null; try { + if (proxyRequest != null && proxyRequest.getUrl().length() > maxUrlLength) { + throw new IllegalArgumentException("URL too long: " + proxyRequest.getUrl().length()); + } // Execute the request proxyResponse = doExecute(proxyRequest); int statusCode = proxyResponse.status().getStatusCode(); @@ -139,6 +142,9 @@ public void handleResponse(IPXOutgoingRequest proxyRequest, PXContext context) { copyResponseEntity(proxyResponse); } + } catch (IllegalArgumentException e) { + context.logger.debug("Invalid request in first-party proxy: {}", e.getMessage()); + handleClientError(context); } catch (Exception e) { if (this.isAllowedPredefinedResponse()) { predefinedResponseHelper.handlePredefinedResponse(res, predefinedResponse, context); @@ -154,6 +160,19 @@ public void handleResponse(IPXOutgoingRequest proxyRequest, PXContext context) { } } + private void handleClientError(PXContext context) { + try { + res.setStatus(HttpStatus.SC_BAD_REQUEST); + res.setContentType("text/plain"); + res.setCharacterEncoding("UTF-8"); + res.getWriter().print("Bad Request"); + res.getWriter().flush(); + } catch (IOException e) { + context.logger.error("Failed to write error response: {}", e.getMessage()); + res.setStatus(HttpStatus.SC_BAD_REQUEST); + } + } + /** * Copy response body data (the entity) from the proxy to the servlet client. */ From 06b526888941ca78c42e1a860cce9b0012da9fa4 Mon Sep 17 00:00:00 2001 From: Ori Gold Date: Mon, 3 Nov 2025 12:38:58 -0800 Subject: [PATCH 43/63] refactor: set logger severity in example --- web/src/main/java/com/web/Config.java | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/web/src/main/java/com/web/Config.java b/web/src/main/java/com/web/Config.java index 516860bd..462b3a05 100644 --- a/web/src/main/java/com/web/Config.java +++ b/web/src/main/java/com/web/Config.java @@ -155,10 +155,7 @@ public PXConfiguration getPxConfiguration() { builder.loginResponseValidationStatusCode(extractStatusCode(key)); break; case "px_logger_severity": - LoggerSeverity loggerSeverity = LoggerSeverity.DEBUG.jsonName().equals(enforcerConfig.getString(key)) ? - LoggerSeverity.DEBUG : LoggerSeverity.NONE.jsonName().equals(enforcerConfig.getString(key)) ? - LoggerSeverity.NONE : LoggerSeverity.ERROR; - PXConfiguration.setPxLoggerSeverity(loggerSeverity); + this.setLoggerSeverity(enforcerConfig.getString(key)); break; case "px_secured_pxhd_enabled": builder.securedPxhdEnabled(enforcerConfig.getBoolean(key)); @@ -193,6 +190,20 @@ public PXConfiguration getPxConfiguration() { return builder.build(); } + private void setLoggerSeverity(String severity) { + switch (severity) { + case "debug": + PXConfiguration.setPxLoggerSeverity(LoggerSeverity.DEBUG); + break; + case "error": + PXConfiguration.setPxLoggerSeverity(LoggerSeverity.ERROR); + break; + case "none": + PXConfiguration.setPxLoggerSeverity(LoggerSeverity.NONE); + break; + } + } + private int[] extractStatusCode(String key) { final JSONArray jsonField = enforcerConfig.getJSONArray(key); final int[] statusCode = new int[jsonField.length()]; From 991e2ada75e3f7b5628a65f4df7ab21d3e10b3cc Mon Sep 17 00:00:00 2001 From: Ori Gold Date: Mon, 3 Nov 2025 12:40:06 -0800 Subject: [PATCH 44/63] chore: changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index aa6eca50..db763821 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ - Updated dependencies minor and patch versions (major versions unchanged) - Changed custom parameters to be of type `Object` instead of `String` to allow more flexibility - Changed first party block script in captcha template to end with expected `/captcha.js` +- Fixed possible connection leak issue due to unclosed responses in first party and telemetry requests +- Fixed first party fuzzing errors by returning 400 on first party requests with URL length > 1000 characters ## [v6.15.1](https://github.com/PerimeterX/perimeterx-java-sdk/compare/6.15.1...HEAD) (2025-09-08) - Added additional updateReason RISK to Telemetry flow From df32e9fc89c46916831851b5d5503893cbe0ab1b Mon Sep 17 00:00:00 2001 From: Ori Gold Date: Tue, 4 Nov 2025 07:24:06 -0800 Subject: [PATCH 45/63] pr fixes: test v1.23.3 --- .github/workflows/ci.yml | 2 +- .github/workflows/ci_e2e.yaml | 2 +- .github/workflows/fuzzer.yaml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b29753ae..c541b8b0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,7 +5,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v5 - name: Set up JDK 8 uses: actions/setup-java@v3 with: diff --git a/.github/workflows/ci_e2e.yaml b/.github/workflows/ci_e2e.yaml index 538cf8e3..3a8e52e6 100644 --- a/.github/workflows/ci_e2e.yaml +++ b/.github/workflows/ci_e2e.yaml @@ -27,7 +27,7 @@ jobs: env: MOCK_COLLECTOR_IMAGE_TAG: 2.0.6 SAMPLE_SITE_IMAGE_TAG: 1.0.0 - ENFORCER_SPEC_TESTS_IMAGE_TAG: 1.23.2 + ENFORCER_SPEC_TESTS_IMAGE_TAG: 1.23.3 runs-on: ubuntu-latest timeout-minutes: 60 diff --git a/.github/workflows/fuzzer.yaml b/.github/workflows/fuzzer.yaml index 526bdfbf..c703e737 100644 --- a/.github/workflows/fuzzer.yaml +++ b/.github/workflows/fuzzer.yaml @@ -35,7 +35,7 @@ jobs: mode: [ "url", "first_party", "headers", "cookies", "user_agent" ] runs-on: ubuntu-latest - timeout-minutes: 90 + timeout-minutes: 60 needs: - extract_version @@ -146,7 +146,7 @@ jobs: --set mode=$FUZZ_MODE \ --set siteURL=$SITE_URL \ --wait \ - --timeout 90m0s \ + --timeout 60m0s \ --wait-for-jobs env: FUZZ_MODE: ${{ matrix.mode }} From 52a0445d66773c988f0676971668313de513eb66 Mon Sep 17 00:00:00 2001 From: Ori Gold Date: Tue, 4 Nov 2025 13:18:05 -0800 Subject: [PATCH 46/63] pr fix: json ignore properties moved to pxconfiguration --- .../EnforcerTelemetryActivityDetails.java | 71 ++----------------- .../models/configuration/PXConfiguration.java | 22 ++++++ 2 files changed, 29 insertions(+), 64 deletions(-) diff --git a/src/main/java/com/perimeterx/models/activities/EnforcerTelemetryActivityDetails.java b/src/main/java/com/perimeterx/models/activities/EnforcerTelemetryActivityDetails.java index c2f5a576..fdda722e 100644 --- a/src/main/java/com/perimeterx/models/activities/EnforcerTelemetryActivityDetails.java +++ b/src/main/java/com/perimeterx/models/activities/EnforcerTelemetryActivityDetails.java @@ -1,7 +1,6 @@ package com.perimeterx.models.activities; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.google.gson.JsonIOException; import com.perimeterx.models.PXContext; import com.perimeterx.models.configuration.PXConfiguration; @@ -18,14 +17,19 @@ public class EnforcerTelemetryActivityDetails implements ActivityDetails { @JsonProperty("module_version") private String moduleVersion; + @JsonProperty("enforcer_configs") private TelemetryConfiguration enforcerConfigs; + @JsonProperty("os_name") private String osName; + @JsonProperty("node_name") private String nodeName; + @JsonProperty("update_reason") private UpdateReason updateReason; + @JsonProperty("request_id") private UUID requestId; @@ -74,72 +78,11 @@ public UUID getRequestId() { class TelemetryConfiguration { @JsonProperty("active_config") - @JsonIgnoreProperties({ - "customParametersProvider", - "blockHandler", - "customLoginResponseValidator", - "credentialsCustomExtractor", - "customIsSensitiveRequest", - "customParametersExtraction", - "filterByCustomFunction", - "loggerFactory", - "telemetryConfig", - "reverseProxyInstance", - "ipxHttpClientInstance", - "ipxhttpClientInstance", - "IPXHttpClientInstance", - "pxClientInstance", - "PXClientInstance", - "pxclientInstance", - "httpClient", - "pxClient", - "pxReverseProxy" - }) public PXConfiguration activeConfig; + @JsonProperty("static_config") - @JsonIgnoreProperties({ - "customParametersProvider", - "blockHandler", - "customLoginResponseValidator", - "credentialsCustomExtractor", - "customIsSensitiveRequest", - "customParametersExtraction", - "filterByCustomFunction", - "loggerFactory", - "telemetryConfig", - "reverseProxyInstance", - "ipxHttpClientInstance", - "ipxhttpClientInstance", - "IPXHttpClientInstance", - "pxClientInstance", - "PXClientInstance", - "pxclientInstance", - "httpClient", - "pxClient", - "pxReverseProxy" - }) public PXConfiguration staticConfig; + @JsonProperty("remote_config") - @JsonIgnoreProperties({ - "customParametersProvider", - "blockHandler", - "customLoginResponseValidator", - "credentialsCustomExtractor", - "customIsSensitiveRequest", - "customParametersExtraction", - "filterByCustomFunction", - "loggerFactory", - "telemetryConfig", - "reverseProxyInstance", - "ipxHttpClientInstance", - "ipxhttpClientInstance", - "IPXHttpClientInstance", - "pxClientInstance", - "PXClientInstance", - "pxclientInstance", - "httpClient", - "pxClient", - "pxReverseProxy" - }) public PXConfiguration remoteConfig; } diff --git a/src/main/java/com/perimeterx/models/configuration/PXConfiguration.java b/src/main/java/com/perimeterx/models/configuration/PXConfiguration.java index 6904f6a1..8bb1baad 100644 --- a/src/main/java/com/perimeterx/models/configuration/PXConfiguration.java +++ b/src/main/java/com/perimeterx/models/configuration/PXConfiguration.java @@ -1,6 +1,7 @@ package com.perimeterx.models.configuration; import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.perimeterx.api.PerimeterX; import com.perimeterx.api.additionalContext.credentialsIntelligence.CIProtocol; @@ -48,6 +49,27 @@ @AllArgsConstructor @NoArgsConstructor @Getter +@JsonIgnoreProperties({ + "customParametersProvider", + "blockHandler", + "customLoginResponseValidator", + "credentialsCustomExtractor", + "customIsSensitiveRequest", + "customParametersExtraction", + "filterByCustomFunction", + "loggerFactory", + "telemetryConfig", + "reverseProxyInstance", + "ipxHttpClientInstance", + "ipxhttpClientInstance", + "IPXHttpClientInstance", + "pxClientInstance", + "PXClientInstance", + "pxclientInstance", + "httpClient", + "pxClient", + "pxReverseProxy" +}) public class PXConfiguration { private static LoggerSeverity loggerSeverity = null; From 9875ed88bfec80750185404fa98b5154e95504b9 Mon Sep 17 00:00:00 2001 From: Ori Gold Date: Tue, 28 Oct 2025 12:38:59 -0700 Subject: [PATCH 47/63] feat: jwt user identifiers feature added --- .../JwtUserIdentifiersExtractor.java | 105 ++++++++++++++++++ .../java/com/perimeterx/models/PXContext.java | 11 ++ .../activities/CommonActivityDetails.java | 9 ++ .../models/configuration/PXConfiguration.java | 21 ++++ .../models/httpmodels/Additional.java | 9 ++ 5 files changed, 155 insertions(+) create mode 100644 src/main/java/com/perimeterx/internals/JwtUserIdentifiersExtractor.java diff --git a/src/main/java/com/perimeterx/internals/JwtUserIdentifiersExtractor.java b/src/main/java/com/perimeterx/internals/JwtUserIdentifiersExtractor.java new file mode 100644 index 00000000..a1f8b545 --- /dev/null +++ b/src/main/java/com/perimeterx/internals/JwtUserIdentifiersExtractor.java @@ -0,0 +1,105 @@ +package com.perimeterx.internals; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.perimeterx.models.PXContext; +import com.perimeterx.models.configuration.PXConfiguration; + +import javax.servlet.http.Cookie; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; +import java.util.*; + +public final class JwtUserIdentifiersExtractor { + private static final ObjectMapper OM = new ObjectMapper(); + + private JwtUserIdentifiersExtractor() {} + + public static void attachJwtIfConfigured(PXContext ctx, PXConfiguration cfg) { + tryExtractFromCookie(ctx, cfg); + if (ctx.getJwtAppUserId() != null || (ctx.getJwtAdditionalFields() != null && !ctx.getJwtAdditionalFields().isEmpty())) { + return; + } + tryExtractFromHeader(ctx, cfg); + } + + private static void tryExtractFromCookie(PXContext ctx, PXConfiguration cfg) { + String cookieName = nullToEmpty(cfg.getPxJwtCookieName()); + if (cookieName.isEmpty()) return; + String token = findCookieValue(ctx, cookieName); + buildAndSet(ctx, token, cfg.getPxJwtCookieUserIdFieldName(), safeList(cfg.getPxJwtCookieAdditionalFieldNames())); + } + + private static void tryExtractFromHeader(PXContext ctx, PXConfiguration cfg) { + String headerName = nullToEmpty(cfg.getPxJwtHeaderName()); + if (headerName.isEmpty()) return; + String raw = ctx.getRequest().getHeader(headerName); + if (raw == null) return; + String token = raw.startsWith("Bearer ") ? raw.substring(7).trim() : raw.trim(); + buildAndSet(ctx, token, cfg.getPxJwtHeaderUserIdFieldName(), safeList(cfg.getPxJwtHeaderAdditionalFieldNames())); + } + + private static void buildAndSet(PXContext ctx, String token, String userPath, List additionalPaths) { + if (isEmpty(token)) return; + try { + Map payload = decodePayload(token); + String appUserId = asString(getByDotPath(payload, userPath)); + Map additional = collectByDotPaths(payload, additionalPaths); + if (isEmpty(appUserId) && additional.isEmpty()) return; + ctx.setJwtAppUserId(appUserId); + ctx.setJwtAdditionalFields(additional.isEmpty() ? null : additional); + } catch (Exception e) { + ctx.logger.debug("JWT extraction skipped: invalid token", e); + } + } + + private static Map decodePayload(String jwt) throws Exception { + String[] parts = jwt.split("\\."); + if (parts.length < 2) throw new IllegalArgumentException("Invalid JWT"); + byte[] json = Base64.getUrlDecoder().decode(parts[1]); + return OM.readValue(json, Map.class); + } + + private static String findCookieValue(PXContext ctx, String name) { + Cookie[] cookies = ctx.getRequest().getCookies(); + if (cookies == null) return null; + for (Cookie c : cookies) { + if (name.equals(c.getName())) { + String v = c.getValue(); + try { + return v != null ? URLDecoder.decode(v, StandardCharsets.UTF_8.toString()) : null; + } catch (UnsupportedEncodingException e) { + return v; + } + } + } + return null; + } + + private static Object getByDotPath(Map json, String path) { + if (json == null || isEmpty(path)) return null; + Object cur = json; + for (String seg : path.split("\\.")) { + if (!(cur instanceof Map)) return null; + cur = ((Map) cur).get(seg); + if (cur == null) return null; + } + return cur; + } + + private static Map collectByDotPaths(Map json, List paths) { + Map out = new LinkedHashMap<>(); + for (String p : paths) { + Object v = getByDotPath(json, p); + if (v != null) out.put(p, v); + } + return out; + } + + private static List safeList(List l) { return l == null ? Collections.emptyList() : l; } + private static boolean isEmpty(String s) { return s == null || s.isEmpty(); } + private static String nullToEmpty(String s) { return s == null ? "" : s; } + private static String asString(Object v) { return v == null ? null : String.valueOf(v); } +} + + diff --git a/src/main/java/com/perimeterx/models/PXContext.java b/src/main/java/com/perimeterx/models/PXContext.java index 56b7ddd4..cab4b02a 100644 --- a/src/main/java/com/perimeterx/models/PXContext.java +++ b/src/main/java/com/perimeterx/models/PXContext.java @@ -13,6 +13,7 @@ import com.perimeterx.internals.cookie.cookieparsers.CookieHeaderParser; import com.perimeterx.internals.cookie.cookieparsers.HeaderParser; import com.perimeterx.internals.cookie.cookieparsers.MobileCookieHeaderParser; +import com.perimeterx.internals.JwtUserIdentifiersExtractor; import com.perimeterx.models.configuration.ModuleMode; import com.perimeterx.models.configuration.PXConfiguration; import com.perimeterx.models.enforcererror.EnforcerErrorReasonInfo; @@ -235,6 +236,10 @@ public class PXContext { private boolean isSensitiveRequest; private String additionalTokenInfo; + // JWT user identifiers + private String jwtAppUserId; + private Map jwtAdditionalFields; + /** * The cookie key used to decrypt the cookie */ @@ -306,6 +311,12 @@ private void postInitContext(final HttpServletRequest request, PXConfiguration p } catch (Exception e) { logger.debug("failed to extract custom parameters from custom function", e); } + + try { + JwtUserIdentifiersExtractor.attachJwtIfConfigured(this, pxConfiguration); + } catch (Exception e) { + logger.debug("jwt identifiers extraction failed", e); + } } private IPXLogger getLogger(){ diff --git a/src/main/java/com/perimeterx/models/activities/CommonActivityDetails.java b/src/main/java/com/perimeterx/models/activities/CommonActivityDetails.java index f268f5de..8fbde006 100644 --- a/src/main/java/com/perimeterx/models/activities/CommonActivityDetails.java +++ b/src/main/java/com/perimeterx/models/activities/CommonActivityDetails.java @@ -9,6 +9,7 @@ import com.perimeterx.models.httpmodels.Additional; import lombok.Getter; +import java.util.Map; import java.util.UUID; @Getter @@ -62,6 +63,12 @@ public class CommonActivityDetails implements ActivityDetails { @JsonProperty("cross_tab_session") public String pxCtsCookie; + @JsonProperty("app_user_id") + public String appUserId; + + @JsonProperty("jwt_additional_fields") + public Map jwtAdditionalFields; + @JsonProperty("is_sensitive_route") public Boolean isSensitiveRoute; @@ -91,6 +98,8 @@ public CommonActivityDetails(PXContext context) { this.riskStartTime = additional.riskStartTime; this.enforcerStartTime = additional.enforcerStartTime; this.pxCtsCookie = additional.pxCtsCookie; + this.appUserId = additional.appUserId; + this.jwtAdditionalFields = additional.jwtAdditionalFields; this.isSensitiveRoute = additional.isSensitiveRoute; this.additionalTokenInfo = additional.additionalTokenInfo; } diff --git a/src/main/java/com/perimeterx/models/configuration/PXConfiguration.java b/src/main/java/com/perimeterx/models/configuration/PXConfiguration.java index 8bb1baad..c1e30b89 100644 --- a/src/main/java/com/perimeterx/models/configuration/PXConfiguration.java +++ b/src/main/java/com/perimeterx/models/configuration/PXConfiguration.java @@ -334,6 +334,27 @@ public static void setPxLoggerSeverity(LoggerSeverity severity) { @Builder.Default private Predicate filterByCustomFunction = req -> false; + + // --- JWT user identifiers configuration --- + @JsonProperty("px_jwt_cookie_name") + private String pxJwtCookieName; + + @JsonProperty("px_jwt_cookie_user_id_field_name") + private String pxJwtCookieUserIdFieldName; + + @Builder.Default + @JsonProperty("px_jwt_cookie_additional_field_names") + private List pxJwtCookieAdditionalFieldNames = new ArrayList<>(); + + @JsonProperty("px_jwt_header_name") + private String pxJwtHeaderName; + + @JsonProperty("px_jwt_header_user_id_field_name") + private String pxJwtHeaderUserIdFieldName; + + @Builder.Default + @JsonProperty("px_jwt_header_additional_field_names") + private List pxJwtHeaderAdditionalFieldNames = new ArrayList<>(); /** * @return Configuration Object clone without cookieKey and authToken **/ diff --git a/src/main/java/com/perimeterx/models/httpmodels/Additional.java b/src/main/java/com/perimeterx/models/httpmodels/Additional.java index 46235630..aebce120 100644 --- a/src/main/java/com/perimeterx/models/httpmodels/Additional.java +++ b/src/main/java/com/perimeterx/models/httpmodels/Additional.java @@ -12,6 +12,7 @@ import com.perimeterx.utils.Constants; import java.util.Date; +import java.util.Map; import java.util.Objects; import java.util.UUID; @@ -95,6 +96,12 @@ public class Additional { @JsonProperty("cross_tab_session") public String pxCtsCookie; + @JsonProperty("app_user_id") + public String appUserId; + + @JsonProperty("jwt_additional_fields") + public Map jwtAdditionalFields; + @JsonProperty("is_sensitive_route") public Boolean isSensitiveRoute; @@ -122,6 +129,8 @@ public static Additional fromContext(PXContext ctx) { additional.enforcerStartTime = ctx.getEnforcerStartTime(); additional.riskStartTime = new Date().getTime(); additional.pxCtsCookie = ctx.getPxCtsCookie(); + additional.appUserId = ctx.getJwtAppUserId(); + additional.jwtAdditionalFields = ctx.getJwtAdditionalFields(); additional.isSensitiveRoute = ctx.isSensitiveRequest(); additional.additionalTokenInfo = ctx.getAdditionalTokenInfo(); From ad25a41dc8794f0220862a3f187bd6cf4bd85f1f Mon Sep 17 00:00:00 2001 From: Ori Gold Date: Tue, 28 Oct 2025 12:39:44 -0700 Subject: [PATCH 48/63] chore: add user_identifiers config values to example site --- web/src/main/java/com/web/Config.java | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/web/src/main/java/com/web/Config.java b/web/src/main/java/com/web/Config.java index 462b3a05..ce771c49 100644 --- a/web/src/main/java/com/web/Config.java +++ b/web/src/main/java/com/web/Config.java @@ -160,6 +160,24 @@ public PXConfiguration getPxConfiguration() { case "px_secured_pxhd_enabled": builder.securedPxhdEnabled(enforcerConfig.getBoolean(key)); break; + case "px_jwt_cookie_name": + builder.pxJwtCookieName(enforcerConfig.getString(key)); + break; + case "px_jwt_cookie_user_id_field_name": + builder.pxJwtCookieUserIdFieldName(enforcerConfig.getString(key)); + break; + case "px_jwt_cookie_additional_field_names": + builder.pxJwtCookieAdditionalFieldNames(extractStringList(key)); + break; + case "px_jwt_header_name": + builder.pxJwtHeaderName(enforcerConfig.getString(key)); + break; + case "px_jwt_header_user_id_field_name": + builder.pxJwtHeaderUserIdFieldName(enforcerConfig.getString(key)); + break; + case "px_jwt_header_additional_field_names": + builder.pxJwtHeaderAdditionalFieldNames(extractStringList(key)); + break; case "px_user_agent_max_length": case "px_risk_cookie_max_length": case "px_risk_cookie_max_iterations": @@ -213,5 +231,14 @@ private int[] extractStatusCode(String key) { } return statusCode; } + + private java.util.List extractStringList(String key) { + final JSONArray jsonField = enforcerConfig.getJSONArray(key); + final java.util.List out = new java.util.ArrayList<>(jsonField.length()); + for (int i = 0; i < jsonField.length(); i++) { + out.add(jsonField.getString(i)); + } + return out; + } } From 6b918bcda3a8e86479331981dea315dac6a26199 Mon Sep 17 00:00:00 2001 From: Ori Gold Date: Tue, 28 Oct 2025 12:39:59 -0700 Subject: [PATCH 49/63] chore: add user_identifiers to px_metadata.json file --- px_metadata.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/px_metadata.json b/px_metadata.json index fc12507f..56ee33fe 100644 --- a/px_metadata.json +++ b/px_metadata.json @@ -36,6 +36,7 @@ "sensitive_headers", "sensitive_routes", "telemetry_command", + "user_identifiers", "vid_extraction" ], "excluded_tests": [ @@ -51,6 +52,7 @@ "test_cookie_v3_cookie_validation_failed_big_cookie", "test_client_ip_extraction_order_risk_api", "test_telemetry_command_verify_custom_function_hash", - "test_first_party_timeout" + "test_first_party_timeout", + "test_huge_jwt_(cookie|header)_with_user_id_and_additional_fields" ] } From 87727d1161f979e5908e02cec5f4bc402d097e25 Mon Sep 17 00:00:00 2001 From: Ori Gold Date: Mon, 3 Nov 2025 14:18:09 -0800 Subject: [PATCH 50/63] feat: data enrichment header name feature added --- .../java/com/perimeterx/api/PerimeterX.java | 19 +++++++++++++++++++ .../models/configuration/PXConfiguration.java | 5 +++++ web/src/main/java/com/web/Config.java | 3 +++ 3 files changed, 27 insertions(+) diff --git a/src/main/java/com/perimeterx/api/PerimeterX.java b/src/main/java/com/perimeterx/api/PerimeterX.java index 1813b53f..5cc425da 100644 --- a/src/main/java/com/perimeterx/api/PerimeterX.java +++ b/src/main/java/com/perimeterx/api/PerimeterX.java @@ -240,6 +240,7 @@ private void addCustomHeadersToRequest(HttpServletRequest request, PXContext con setBreachedAccount(request, context); setAdditionalS2SActivityHeaders(request, context); } + setDataEnrichmentHeader(request, context); } private void setBreachedAccount(HttpServletRequest request, PXContext context) { @@ -260,6 +261,24 @@ private void setAdditionalS2SActivityHeaders(HttpServletRequest request, PXConte } } + private void setDataEnrichmentHeader(HttpServletRequest request, PXContext context) { + try { + String headerName = configuration.getPxDataEnrichmentHeaderName(); + if (headerName == null || headerName.isEmpty()) { + return; + } + + if (context.getPxde() == null || !context.isPxdeVerified()) { + return; + } + + String pxdeJson = new Gson().toJson(context.getPxde()); + ((RequestWrapper) request).addHeader(headerName, pxdeJson); + } catch (Exception e) { + context.logger.debug("Failed to add data enrichment header", e); + } + } + public void pxPostVerify(ResponseWrapper response, PXContext context) throws PXException { try { if (context != null) { diff --git a/src/main/java/com/perimeterx/models/configuration/PXConfiguration.java b/src/main/java/com/perimeterx/models/configuration/PXConfiguration.java index c1e30b89..ca3ca7f2 100644 --- a/src/main/java/com/perimeterx/models/configuration/PXConfiguration.java +++ b/src/main/java/com/perimeterx/models/configuration/PXConfiguration.java @@ -355,6 +355,11 @@ public static void setPxLoggerSeverity(LoggerSeverity severity) { @Builder.Default @JsonProperty("px_jwt_header_additional_field_names") private List pxJwtHeaderAdditionalFieldNames = new ArrayList<>(); + + @Builder.Default + @JsonProperty("px_data_enrichment_header_name") + private String pxDataEnrichmentHeaderName = ""; + /** * @return Configuration Object clone without cookieKey and authToken **/ diff --git a/web/src/main/java/com/web/Config.java b/web/src/main/java/com/web/Config.java index ce771c49..c00a994d 100644 --- a/web/src/main/java/com/web/Config.java +++ b/web/src/main/java/com/web/Config.java @@ -178,6 +178,9 @@ public PXConfiguration getPxConfiguration() { case "px_jwt_header_additional_field_names": builder.pxJwtHeaderAdditionalFieldNames(extractStringList(key)); break; + case "px_data_enrichment_header_name": + builder.pxDataEnrichmentHeaderName(enforcerConfig.getString(key)); + break; case "px_user_agent_max_length": case "px_risk_cookie_max_length": case "px_risk_cookie_max_iterations": From 719e5fa31f45dd5508b476123374161cc3fb7442 Mon Sep 17 00:00:00 2001 From: Ori Gold Date: Mon, 3 Nov 2025 14:18:41 -0800 Subject: [PATCH 51/63] chore: added feature to px_metadata --- px_metadata.json | 1 + 1 file changed, 1 insertion(+) diff --git a/px_metadata.json b/px_metadata.json index 56ee33fe..beb1e147 100644 --- a/px_metadata.json +++ b/px_metadata.json @@ -18,6 +18,7 @@ "custom_parameters", "custom_proxy", "custom_sensitive_request", + "data_enrichment_header", "enforced_routes", "enforcer_error", "filter_by_extension", From 1ad2136401488dfd73d7804695d861edcaf0b336 Mon Sep 17 00:00:00 2001 From: Ori Gold Date: Mon, 3 Nov 2025 14:20:46 -0800 Subject: [PATCH 52/63] chore: changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index db763821..f4a49f74 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Change Log ## [x.x.x] - YYYY-MM-DD +- Added support for data enrichment header feature (`px_data_enrichment_header_name` configuration) - Added `px_secured_pxhd_enabled` configuration option to enable secure flag on `pxhd` cookie - Added `is_sensitive_route` to risk api and async activities - Added `additional_token_info` to risk api and async activities From 6d88261ad27bf29ce492539c0346eb6b12e493af Mon Sep 17 00:00:00 2001 From: Ori Gold Date: Fri, 7 Nov 2025 13:34:22 -0800 Subject: [PATCH 53/63] pr fixes: adding px_data_enrichment_header_name to ci config for e2e tests --- ci_files/enforcer-config.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ci_files/enforcer-config.json b/ci_files/enforcer-config.json index b085b4f1..a8d5a097 100644 --- a/ci_files/enforcer-config.json +++ b/ci_files/enforcer-config.json @@ -114,5 +114,6 @@ "px_cors_support_enabled": true, "px_cors_preflight_request_filter_enabled": true, "px_url_decode_reserved_characters": true, - "px_secured_pxhd_enabled": true + "px_secured_pxhd_enabled": true, + "px_data_enrichment_header_name": "X-PX-Data-Enrichment" } \ No newline at end of file From 2e86058fdcfce2796ebdd17355414cc84076e630 Mon Sep 17 00:00:00 2001 From: Ori Gold Date: Fri, 7 Nov 2025 14:04:54 -0800 Subject: [PATCH 54/63] fix: java example site copying data enrichment header to http response for tests --- web/src/main/java/com/web/PXFilter.java | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/web/src/main/java/com/web/PXFilter.java b/web/src/main/java/com/web/PXFilter.java index 135140c7..dffa6013 100644 --- a/web/src/main/java/com/web/PXFilter.java +++ b/web/src/main/java/com/web/PXFilter.java @@ -1,7 +1,6 @@ package com.web; import com.perimeterx.api.PerimeterX; -import com.perimeterx.api.additionalContext.credentialsIntelligence.loginrequest.CredentialsExtractorFactory; import com.perimeterx.http.RequestWrapper; import com.perimeterx.http.ResponseWrapper; import com.perimeterx.models.PXContext; @@ -46,7 +45,7 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha response = new ResponseWrapper((HttpServletResponse) response); pxFilter.pxPostVerify((ResponseWrapper) response, context); - + copyDataEnrichmentHeaderToResponse((HttpServletRequest) request, (ResponseWrapper) response); } catch (PXException e) { filterChain.doFilter(request, response); } @@ -61,4 +60,16 @@ public void destroy() { e.printStackTrace(); } } + + private void copyDataEnrichmentHeaderToResponse(HttpServletRequest request, ResponseWrapper response) { + String dataEnrichmentHeaderName = config.getPxConfiguration().getPxDataEnrichmentHeaderName(); + if (dataEnrichmentHeaderName == null || dataEnrichmentHeaderName.isEmpty()) { + return; + } + + String dataEnrichmentHeaderValue = request.getHeader(dataEnrichmentHeaderName); + if (dataEnrichmentHeaderValue != null && !dataEnrichmentHeaderValue.isEmpty()) { + response.setHeader(dataEnrichmentHeaderName, dataEnrichmentHeaderValue); + } + } } From fee07e14eae5af0bd9d2389c290d23447f11ec07 Mon Sep 17 00:00:00 2001 From: Ori Gold Date: Fri, 7 Nov 2025 15:23:10 -0800 Subject: [PATCH 55/63] fix: feature works properly, encoded as needed --- src/main/java/com/perimeterx/api/PerimeterX.java | 9 +++++---- .../java/com/perimeterx/internals/PXS2SValidator.java | 10 ++++------ 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/perimeterx/api/PerimeterX.java b/src/main/java/com/perimeterx/api/PerimeterX.java index 5cc425da..42d7bd85 100644 --- a/src/main/java/com/perimeterx/api/PerimeterX.java +++ b/src/main/java/com/perimeterx/api/PerimeterX.java @@ -58,18 +58,17 @@ import com.perimeterx.utils.logger.IPXLogger; import com.perimeterx.utils.StringUtils; import com.perimeterx.utils.logger.LoggerFactory; -import edu.emory.mathcs.backport.java.util.Collections; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponseWrapper; import java.io.Closeable; import java.io.IOException; import java.net.URISyntaxException; +import java.nio.charset.StandardCharsets; import java.security.InvalidKeyException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Base64; -import java.util.List; import static com.perimeterx.utils.Constants.*; import static com.perimeterx.utils.PXCommonUtils.cookieKeysToCheck; @@ -272,8 +271,10 @@ private void setDataEnrichmentHeader(HttpServletRequest request, PXContext conte return; } - String pxdeJson = new Gson().toJson(context.getPxde()); - ((RequestWrapper) request).addHeader(headerName, pxdeJson); + 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); } catch (Exception e) { context.logger.debug("Failed to add data enrichment header", e); } diff --git a/src/main/java/com/perimeterx/internals/PXS2SValidator.java b/src/main/java/com/perimeterx/internals/PXS2SValidator.java index 1c6ce83e..5822f448 100644 --- a/src/main/java/com/perimeterx/internals/PXS2SValidator.java +++ b/src/main/java/com/perimeterx/internals/PXS2SValidator.java @@ -1,9 +1,7 @@ package com.perimeterx.internals; -import com.perimeterx.api.PerimeterX; import com.perimeterx.api.additionalContext.PXHDSource; import com.perimeterx.http.PXClient; -import com.perimeterx.internals.cookie.DataEnrichmentCookie; import com.perimeterx.models.PXContext; import com.perimeterx.models.configuration.PXConfiguration; import com.perimeterx.models.exceptions.PXException; @@ -14,7 +12,6 @@ import com.perimeterx.models.risk.S2SErrorReasonInfo; import com.perimeterx.utils.Constants; import com.perimeterx.utils.EnforcerErrorUtils; -import com.perimeterx.utils.logger.IPXLogger; import com.perimeterx.utils.logger.LogReason; import org.apache.http.conn.ConnectTimeoutException; @@ -99,9 +96,10 @@ private void updateContextFromResponse(PXContext pxContext, RiskResponse respons pxContext.setRiskScore(response.getScore()); pxContext.setUuid(response.getUuid()); pxContext.setBlockAction(response.getAction()); - DataEnrichmentCookie dataEnrichment = new DataEnrichmentCookie(response.getDataEnrichment(), true); - pxContext.setPxde(dataEnrichment.getJsonPayload()); - pxContext.setPxdeVerified(dataEnrichment.isValid()); + if (response.getDataEnrichment() != null) { + pxContext.setPxde(response.getDataEnrichment()); + pxContext.setPxdeVerified(true); + } if(isNoneBlank(response.getPxhd())) { pxContext.setPxhd(response.getPxhd()); From 46dfa0f94cb29f7d01e30f7455f61dfbe5d81a5c Mon Sep 17 00:00:00 2001 From: Ori Gold Date: Fri, 7 Nov 2025 15:23:37 -0800 Subject: [PATCH 56/63] fix: example adds data enrichment header to response properly --- web/src/main/java/com/web/PXFilter.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/src/main/java/com/web/PXFilter.java b/web/src/main/java/com/web/PXFilter.java index dffa6013..d9dc6508 100644 --- a/web/src/main/java/com/web/PXFilter.java +++ b/web/src/main/java/com/web/PXFilter.java @@ -38,6 +38,7 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha final PXContext context = pxFilter.pxVerify((HttpServletRequest) request, new HttpServletResponseWrapper((HttpServletResponse) response)); setDefaultPageAttributes((HttpServletRequest) request, config); + copyDataEnrichmentHeaderToResponse((HttpServletRequest) request, (HttpServletResponse) response); if (context != null && context.isRequestLowScore()) { filterChain.doFilter(request, response); @@ -45,7 +46,6 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha response = new ResponseWrapper((HttpServletResponse) response); pxFilter.pxPostVerify((ResponseWrapper) response, context); - copyDataEnrichmentHeaderToResponse((HttpServletRequest) request, (ResponseWrapper) response); } catch (PXException e) { filterChain.doFilter(request, response); } @@ -61,7 +61,7 @@ public void destroy() { } } - private void copyDataEnrichmentHeaderToResponse(HttpServletRequest request, ResponseWrapper response) { + private void copyDataEnrichmentHeaderToResponse(HttpServletRequest request, HttpServletResponse response) { String dataEnrichmentHeaderName = config.getPxConfiguration().getPxDataEnrichmentHeaderName(); if (dataEnrichmentHeaderName == null || dataEnrichmentHeaderName.isEmpty()) { return; From 4f7755eeff2ad51127d4ee1ae938d973102f9860 Mon Sep 17 00:00:00 2001 From: Ori Gold Date: Mon, 3 Nov 2025 13:55:14 -0800 Subject: [PATCH 57/63] fix: request wrapper header methods adjusted to include custom headers --- .../com/perimeterx/http/RequestWrapper.java | 53 ++++++++++- .../perimeterx/api/RequestWrapperTest.java | 93 +++++++++++++++++++ 2 files changed, 141 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/perimeterx/http/RequestWrapper.java b/src/main/java/com/perimeterx/http/RequestWrapper.java index 84e29844..d9730c27 100644 --- a/src/main/java/com/perimeterx/http/RequestWrapper.java +++ b/src/main/java/com/perimeterx/http/RequestWrapper.java @@ -7,8 +7,7 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStreamReader; -import java.util.HashMap; -import java.util.Map; +import java.util.*; /** * Reading HttpServletRequest is limited to one time only @@ -25,6 +24,12 @@ public RequestWrapper(HttpServletRequest request) { this.customHeaders = new HashMap<>(); } + // Add a custom header to the request + public void addHeader(String name, String value) { + this.customHeaders.put(name, value); + } + + // Modify body methods to read from the cached body @Override public ServletInputStream getInputStream() throws IOException { final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(getBody().getBytes()); @@ -36,6 +41,7 @@ public BufferedReader getReader() throws IOException { return new BufferedReader(new InputStreamReader(this.getInputStream())); } + // Modify header methods to include custom headers @Override public String getHeader(String name) { String headerValue = customHeaders.get(name); @@ -43,11 +49,48 @@ public String getHeader(String name) { if (headerValue != null) { return headerValue; } - return ((HttpServletRequest) getRequest()).getHeader(name); + return super.getHeader(name); } - public void addHeader(String name, String value) { - this.customHeaders.put(name, value); + @Override + public Enumeration getHeaderNames() { + Enumeration headerNames = super.getHeaderNames(); + List list = Collections.list(headerNames); + for (String customHeaderName : customHeaders.keySet()) { + if (!list.contains(customHeaderName)) { + list.add(customHeaderName); + } + } + return Collections.enumeration(list); + } + + @Override + public Enumeration getHeaders(String name) { + String headerValue = customHeaders.get(name); + if (headerValue != null) { + List list = new ArrayList<>(); + list.add(headerValue); + return Collections.enumeration(list); + } + return super.getHeaders(name); + } + + @Override + public int getIntHeader(String name) { + final String headerValue = getHeader(name); + if (headerValue != null) { + return Integer.parseInt(headerValue); + } + return -1; + } + + @Override + public long getDateHeader(String name) { + final String headerValue = getHeader(name); + if (headerValue != null) { + return Long.parseLong(headerValue); + } + return -1L; } public synchronized String getBody() throws IOException { diff --git a/src/test/java/com/perimeterx/api/RequestWrapperTest.java b/src/test/java/com/perimeterx/api/RequestWrapperTest.java index 4f2a122b..1fea8485 100644 --- a/src/test/java/com/perimeterx/api/RequestWrapperTest.java +++ b/src/test/java/com/perimeterx/api/RequestWrapperTest.java @@ -6,6 +6,8 @@ import java.io.BufferedReader; import java.io.IOException; +import java.util.Collections; +import java.util.List; import static org.testng.Assert.*; @@ -64,4 +66,95 @@ public void testSpecialCharacters() throws IOException { RequestWrapper requestWrapper = new RequestWrapper(req); assertEquals(requestWrapper.getBody(), new String(bytes)); } + + @Test + public void testGetHeader() { + MockHttpServletRequest req = new MockHttpServletRequest(); + req.addHeader("header1", "value1"); + RequestWrapper requestWrapper = new RequestWrapper(req); + requestWrapper.addHeader("header2", "value2"); + + assertEquals(requestWrapper.getHeader("header1"), "value1"); + assertEquals(requestWrapper.getHeader("header2"), "value2"); + } + + @Test + public void testGetHeaderNames() { + MockHttpServletRequest req = new MockHttpServletRequest(); + req.addHeader("header1", "value1"); + RequestWrapper requestWrapper = new RequestWrapper(req); + requestWrapper.addHeader("header2", "value2"); + + boolean foundHeader1 = false; + boolean foundHeader2 = false; + for (String headerName : Collections.list(requestWrapper.getHeaderNames())) { + if (headerName.equals("header1")) { + foundHeader1 = true; + } + if (headerName.equals("header2")) { + foundHeader2 = true; + } + } + assertTrue(foundHeader1); + assertTrue(foundHeader2); + } + + @Test + public void testGetHeaders() { + MockHttpServletRequest req = new MockHttpServletRequest(); + req.addHeader("header1", "value1"); + RequestWrapper requestWrapper = new RequestWrapper(req); + requestWrapper.addHeader("header2", "value2"); + + List header1Values = Collections.list(requestWrapper.getHeaders("header1")); + assertEquals(header1Values.size(), 1); + for (String headerValue : header1Values) { + assertEquals(headerValue, "value1"); + } + + List header2Values = Collections.list(requestWrapper.getHeaders("header2")); + assertEquals(header2Values.size(), 1); + for (String headerValue : header2Values) { + assertEquals(headerValue, "value2"); + } + } + + @Test + public void testGetIntHeader() { + MockHttpServletRequest req = new MockHttpServletRequest(); + req.addHeader("intHeader", "123"); + RequestWrapper requestWrapper = new RequestWrapper(req); + requestWrapper.addHeader("customIntHeader", "456"); + requestWrapper.addHeader("stringHeader", "stringValue"); + + assertEquals(requestWrapper.getIntHeader("intHeader"), 123); + assertEquals(requestWrapper.getIntHeader("customIntHeader"), 456); + assertEquals(requestWrapper.getIntHeader("nonExistentHeader"), -1); + try { + requestWrapper.getIntHeader("stringHeader"); + fail("Expected NumberFormatException"); + } catch (NumberFormatException e) { + // Expected exception + } + } + + @Test + public void testGetDateHeader() { + MockHttpServletRequest req = new MockHttpServletRequest(); + long now = System.currentTimeMillis(); + req.addHeader("dateHeader", Long.toString(now)); + RequestWrapper requestWrapper = new RequestWrapper(req); + requestWrapper.addHeader("customDateHeader", Long.toString(now + 1000)); + requestWrapper.addHeader("stringHeader", "stringValue"); + + assertEquals(requestWrapper.getDateHeader("dateHeader"), now); + assertEquals(requestWrapper.getDateHeader("customDateHeader"), now + 1000); + assertEquals(requestWrapper.getDateHeader("nonExistentHeader"), -1); + try { + requestWrapper.getDateHeader("stringHeader"); + fail("Expected NumberFormatException"); + } catch (NumberFormatException e) { + // Expected exception + } + } } From 5217486f8035949245eeef5d45525ec92b4b2e6f Mon Sep 17 00:00:00 2001 From: Ori Gold Date: Mon, 3 Nov 2025 14:39:48 -0800 Subject: [PATCH 58/63] test: add context unit tests to confirm request wrapper headers are included --- .../com/perimeterx/models/PXContextTest.java | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/test/java/com/perimeterx/models/PXContextTest.java b/src/test/java/com/perimeterx/models/PXContextTest.java index 36eef547..fe2705c1 100644 --- a/src/test/java/com/perimeterx/models/PXContextTest.java +++ b/src/test/java/com/perimeterx/models/PXContextTest.java @@ -5,6 +5,7 @@ import com.perimeterx.api.providers.HostnameProvider; import com.perimeterx.api.providers.IPProvider; import com.perimeterx.api.providers.RemoteAddressIPProvider; +import com.perimeterx.http.RequestWrapper; import com.perimeterx.models.configuration.PXConfiguration; import com.perimeterx.models.risk.CustomParameters; import org.mockito.Mockito; @@ -14,6 +15,7 @@ import org.testng.annotations.Test; import javax.servlet.http.HttpServletRequest; +import java.util.Collections; /** * Test {@link PXContext} @@ -53,4 +55,41 @@ public void customParamsTest() { Mockito.verify(spyTestCustomParamProvider).buildCustomParameters(pxConfig, context); } + + @Test + public void allRequestHeadersShouldBeInPXContext() { + CustomParameters customParameters = new CustomParameters(); + customParameters.setCustomParam1("number1"); + TestCustomParamProvider spyTestCustomParamProvider = Mockito.spy(new TestCustomParamProvider(customParameters)); + PXConfiguration pxConfig = PXConfiguration.builder() + .appId("APP_ID") + .authToken("AUTH_123") + .cookieKey("COOKIE_123") + .customParametersProvider(spyTestCustomParamProvider) + .build(); + ((MockHttpServletRequest) request).addHeader("TEST-BYPASS", "0"); + PXContext context = new PXContext(request, this.ipProvider, this.hostnameProvider, pxConfig); + Assert.assertEquals(context.getHeaders().size(), Collections.list(request.getHeaderNames()).size()); + } + + @Test + public void allRequestWrapperHeadersShouldBeInPXContext() { + CustomParameters customParameters = new CustomParameters(); + customParameters.setCustomParam1("number1"); + TestCustomParamProvider spyTestCustomParamProvider = Mockito.spy(new TestCustomParamProvider(customParameters)); + PXConfiguration pxConfig = PXConfiguration.builder() + .appId("APP_ID") + .authToken("AUTH_123") + .cookieKey("COOKIE_123") + .customParametersProvider(spyTestCustomParamProvider) + .build(); + ((MockHttpServletRequest) request).addHeader("TEST-BYPASS", "0"); + RequestWrapper requestWrapper = new RequestWrapper(request); + requestWrapper.addHeader("client-ip", "127.0.0.1"); + requestWrapper.addHeader("accept", "application/json"); + requestWrapper.addHeader("content-type", "application/json"); + + PXContext context = new PXContext(requestWrapper, this.ipProvider, this.hostnameProvider, pxConfig); + Assert.assertEquals(context.getHeaders().size(), Collections.list(request.getHeaderNames()).size() + 3); + } } From 409ca2119dbed308545f3bd3bba6c3c2cf830792 Mon Sep 17 00:00:00 2001 From: Ori Gold Date: Mon, 10 Nov 2025 13:54:06 -0800 Subject: [PATCH 59/63] chore: release v6.16.0 --- CHANGELOG.md | 2 +- pom.xml | 2 +- px_metadata.json | 2 +- web/pom.xml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f4a49f74..51d96d1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,5 @@ # Change Log -## [x.x.x] - YYYY-MM-DD +## [v6.16.0](https://github.com/PerimeterX/perimeterx-java-sdk/compare/6.16.0...HEAD) (2025-11-12) - Added support for data enrichment header feature (`px_data_enrichment_header_name` configuration) - Added `px_secured_pxhd_enabled` configuration option to enable secure flag on `pxhd` cookie - Added `is_sensitive_route` to risk api and async activities diff --git a/pom.xml b/pom.xml index 69125d25..cb429817 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ PerimeterX JAVA SDK com.perimeterx perimeterx-sdk - 6.15.1 + 6.16.0 jar PerimeterX Java SDK diff --git a/px_metadata.json b/px_metadata.json index beb1e147..107c7889 100644 --- a/px_metadata.json +++ b/px_metadata.json @@ -1,5 +1,5 @@ { - "version": "6.15.1", + "version": "6.16.0", "supported_features": [ "advanced_blocking_response", "batched_activities", diff --git a/web/pom.xml b/web/pom.xml index a0585a32..6134691e 100644 --- a/web/pom.xml +++ b/web/pom.xml @@ -65,7 +65,7 @@ 8 8 - 6.15.1 + 6.16.0 From c2e1529fd21d154762c7327cf5a2f03cf821cb81 Mon Sep 17 00:00:00 2001 From: Ori Gold Date: Tue, 11 Nov 2025 11:35:42 -0800 Subject: [PATCH 60/63] chore: changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 51d96d1d..7f0afa5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Change Log ## [v6.16.0](https://github.com/PerimeterX/perimeterx-java-sdk/compare/6.16.0...HEAD) (2025-11-12) - Added support for data enrichment header feature (`px_data_enrichment_header_name` configuration) +- Added support for AD user identifiers feature - Added `px_secured_pxhd_enabled` configuration option to enable secure flag on `pxhd` cookie - Added `is_sensitive_route` to risk api and async activities - Added `additional_token_info` to risk api and async activities From b24220ef36552c9a77d13bf1a15a9e8df34ee1f4 Mon Sep 17 00:00:00 2001 From: Ori Gold Date: Tue, 11 Nov 2025 11:40:20 -0800 Subject: [PATCH 61/63] chore: changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f0afa5e..a1112973 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ - Updated dependencies minor and patch versions (major versions unchanged) - Changed custom parameters to be of type `Object` instead of `String` to allow more flexibility - Changed first party block script in captcha template to end with expected `/captcha.js` +- Changed `RequestWrapper` to include custom headers in methods that retrieve request headers - Fixed possible connection leak issue due to unclosed responses in first party and telemetry requests - Fixed first party fuzzing errors by returning 400 on first party requests with URL length > 1000 characters From ad6611ebf8e1e1da4326e9770d6436f72e113993 Mon Sep 17 00:00:00 2001 From: Ori Gold Date: Tue, 11 Nov 2025 12:03:46 -0800 Subject: [PATCH 62/63] chore: documentation --- CONFIGURATIONS.md | 8 +++++ README.md | 78 +++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 83 insertions(+), 3 deletions(-) diff --git a/CONFIGURATIONS.md b/CONFIGURATIONS.md index d6ba5f63..5d64eed9 100644 --- a/CONFIGURATIONS.md +++ b/CONFIGURATIONS.md @@ -53,6 +53,14 @@ Directives |loginResponseValidationStatusCode|Array of status codes that is used to validate if the login was successful.|{200}|int[] |customLoginResponseValidator|Custom class that validates if the login was successful. LoginResponseValidator must be implemented to be able to use this class.|DefaultCustomLoginResponseValidator|LoginResponseValidator |credentialsCustomExtractor|Custom class that extracts the login credentials. CredentialsExtractor must be implemented to be able to use this class.|DefaultCredentialsCustomExtractor|CredentialsExtractor +||pxDataEnrichmentHeaderName|Header name for forwarding data enrichment payload to origin server. When set, the SDK will add the PXDE payload as a header that can be forwarded to backend services.|"" (empty string)|String|Used with data enrichment feature +||securedPxhdEnabled|Enable secure flag on pxhd cookie for enhanced security in HTTPS-only environments.|false|boolean| +||pxJwtCookieName|Name of the cookie containing JWT token for user identifier extraction.|null|String|Part of Account Defender JWT user identifiers feature +||pxJwtCookieUserIdFieldName|Field name in JWT payload to extract as user ID from cookie.|null|String|Supports dot notation for nested fields (e.g., "user.id") +||pxJwtCookieAdditionalFieldNames|List of additional field names to extract from JWT cookie payload.|Empty List|List|Supports dot notation for nested fields +||pxJwtHeaderName|Name of the header containing JWT token for user identifier extraction.|null|String|Part of Account Defender JWT user identifiers feature +||pxJwtHeaderUserIdFieldName|Field name in JWT payload to extract as user ID from header.|null|String|Supports dot notation for nested fields (e.g., "sub") +||pxJwtHeaderAdditionalFieldNames|List of additional field names to extract from JWT header payload.|Empty List|List|Supports dot notation for nested fields ## Interfaces `perimeterx-java-sdk` can be tuned and set a different type of interface in order to make the module more flexible diff --git a/README.md b/README.md index 431e3c02..134a856f 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ # [PerimeterX](http://www.perimeterx.com) Java SDK -> Latest stable version: [v6.15.0](https://search.maven.org/#artifactdetails%7Ccom.perimeterx%7Cperimeterx-sdk%7C6.15.0%7Cjar) +> Latest stable version: [v6.16.0](https://search.maven.org/#artifactdetails%7Ccom.perimeterx%7Cperimeterx-sdk%7C6.16.0%7Cjar) ## Table of Contents @@ -152,8 +152,12 @@ Please continue reading about the various configurations available on the sdk in #### Data Enrichment - pxde(PerimeterX Data Enrichment) -Users can use the additional activity handler to retrieve information for the request using the pxde object. -First, check that the data enrichment object is verified, then you can access it's properties. +Users can access data enrichment information in two ways: + +1. **Using context.getPxde()** - Access the data enrichment payload directly in your Java code +2. **Using a custom header** - Forward the data enrichment payload as a header to another server (e.g., your origin server) + +##### Accessing Data Enrichment in Java Code MyVerificationHandler.java: ```java @@ -191,6 +195,26 @@ enforcer.setVerificationHandler(new MyVerificationHandler(config)); ... ``` +##### Forwarding Data Enrichment as a Header + +To forward the data enrichment payload to your backend/origin server, configure the header name. After `pxVerify` completes, the PXDE payload will be automatically added as a header to the request, which can then be forwarded: + +```java +PXConfiguration config = new PXConfiguration.Builder() + ... + .pxDataEnrichmentHeaderName("X-PX-Data-Enrichment") + .build(); +PerimeterX enforcer = new PerimeterX(config); + +// In your filter: +PXContext ctx = enforcer.pxVerify(request, response); + +// After pxVerify, the request now contains the data enrichment header +// and can be forwarded to your backend/origin server +// The header will be available as "X-PX-Data-Enrichment" in the request +filterChain.doFilter(request, response); +``` + #### Custom Sensitive Request With the `customIsSensitive` predicate you can force the request to be sensitive. The input of the function is the same request that sent to the method `pxVerify`. @@ -220,6 +244,8 @@ The input of the function is the same request that sent to the method `pxVerify` If the function throws exception, it is equivalent to returning empty custom params. Implementing this configuration overrides the deprecated configuration `customParameterProvider`. +Custom parameters support various types including strings, numbers, and booleans, allowing flexibility in the data sent to PerimeterX. + > **Note** > The request body can only be read once by default. If your function requires reading the body > consider using RequestWrapper which caches the body. Send the wrapped request to @@ -234,12 +260,58 @@ PXConfiguration pxConfiguration = new PXConfiguration.Builder() CustomParameters customParameters = new CustomParameters(); customParameters.setCustomParam1("example-value"); customParameters.setCustomParam2(req.getHeader("example-header")); + customParameters.setCustomParam3(123); // Numbers are supported + customParameters.setCustomParam4(true); // Booleans are supported return customParameters; }) .build(); ... ``` +#### JWT User Identifiers (Account Defender) + +The SDK can extract user identifiers from JWT tokens in cookies or headers to enhance Account Defender capabilities. This allows PerimeterX to correlate user activity across sessions and improve detection accuracy. + +Configure JWT extraction from cookies: +```java +PXConfiguration pxConfiguration = new PXConfiguration.Builder() + ... + .pxJwtCookieName("authCookie") + .pxJwtCookieUserIdFieldName("userId") + .pxJwtCookieAdditionalFieldNames(Arrays.asList("email", "role")) + .build(); +``` + +Configure JWT extraction from headers: +```java +PXConfiguration pxConfiguration = new PXConfiguration.Builder() + ... + .pxJwtHeaderName("Authorization") + .pxJwtHeaderUserIdFieldName("sub") + .pxJwtHeaderAdditionalFieldNames(Arrays.asList("exp", "iss")) + .build(); +``` + +The SDK will: +1. First attempt to extract user identifiers from the configured cookie +2. If not found, attempt to extract from the configured header +3. Support dot notation for nested fields (e.g., "user.id") +4. Automatically handle Bearer token prefixes in headers + +#### Secured PXHD Cookie + +For enhanced security in HTTPS-only environments, you can enable the secure flag on the `pxhd` cookie. This ensures the cookie is only transmitted over secure connections: + +```java +PXConfiguration pxConfiguration = new PXConfiguration.Builder() + ... + .securedPxhdEnabled(true) + .build(); +``` + +> **Note** +> Only enable this in environments where all traffic is served over HTTPS, as the cookie will not be sent over HTTP connections when this flag is enabled. + #### Multiple Application Support Simply create multiple instances of the PerimeterX class: ```java From 9815ba40c6dc55f1c10ebab31b2f0453e42a0aea Mon Sep 17 00:00:00 2001 From: Ori Gold Date: Tue, 11 Nov 2025 13:13:10 -0800 Subject: [PATCH 63/63] fix: clarifying exceptions for RequestWrapper getIntHeader() and getDateHeader() --- src/main/java/com/perimeterx/http/RequestWrapper.java | 10 +++++++--- .../java/com/perimeterx/api/RequestWrapperTest.java | 4 ++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/perimeterx/http/RequestWrapper.java b/src/main/java/com/perimeterx/http/RequestWrapper.java index d9730c27..448130b2 100644 --- a/src/main/java/com/perimeterx/http/RequestWrapper.java +++ b/src/main/java/com/perimeterx/http/RequestWrapper.java @@ -76,7 +76,7 @@ public Enumeration getHeaders(String name) { } @Override - public int getIntHeader(String name) { + public int getIntHeader(String name) throws NumberFormatException { final String headerValue = getHeader(name); if (headerValue != null) { return Integer.parseInt(headerValue); @@ -85,10 +85,14 @@ public int getIntHeader(String name) { } @Override - public long getDateHeader(String name) { + public long getDateHeader(String name) throws IllegalArgumentException { final String headerValue = getHeader(name); if (headerValue != null) { - return Long.parseLong(headerValue); + try { + return Long.parseLong(headerValue); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Header " + name + " is not a valid date"); + } } return -1L; } diff --git a/src/test/java/com/perimeterx/api/RequestWrapperTest.java b/src/test/java/com/perimeterx/api/RequestWrapperTest.java index 1fea8485..ca0a5ba2 100644 --- a/src/test/java/com/perimeterx/api/RequestWrapperTest.java +++ b/src/test/java/com/perimeterx/api/RequestWrapperTest.java @@ -152,8 +152,8 @@ public void testGetDateHeader() { assertEquals(requestWrapper.getDateHeader("nonExistentHeader"), -1); try { requestWrapper.getDateHeader("stringHeader"); - fail("Expected NumberFormatException"); - } catch (NumberFormatException e) { + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException e) { // Expected exception } }