From 6ad84403c81648bcb36e7ec0f6188bff91862c78 Mon Sep 17 00:00:00 2001
From: Mark Coburn <mark.coburn@contractors.mx.com>
Date: Mon, 27 Jan 2025 08:13:36 -0800
Subject: [PATCH] fix: initial log masking support

---
 .../mx/path/model/mdx/model/MdxLogMasker.java | 427 +++++++++
 .../model/mdx/model/MdxLogMaskerTest.groovy   | 878 ++++++++++++++++++
 .../web/filter/PathRequestLoggingFilter.java  |  15 +-
 .../PathRequestLoggingFilterTest.groovy       |   4 +-
 4 files changed, 1314 insertions(+), 10 deletions(-)
 create mode 100644 mdx-models/src/main/java/com/mx/path/model/mdx/model/MdxLogMasker.java
 create mode 100644 mdx-models/src/test/groovy/com/mx/path/model/mdx/model/MdxLogMaskerTest.groovy

diff --git a/mdx-models/src/main/java/com/mx/path/model/mdx/model/MdxLogMasker.java b/mdx-models/src/main/java/com/mx/path/model/mdx/model/MdxLogMasker.java
new file mode 100644
index 00000000..e4ebbda7
--- /dev/null
+++ b/mdx-models/src/main/java/com/mx/path/model/mdx/model/MdxLogMasker.java
@@ -0,0 +1,427 @@
+package com.mx.path.model.mdx.model;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import com.mx.path.core.common.lang.Strings;
+import com.mx.path.core.common.security.LogValueRegex;
+
+public class MdxLogMasker {
+
+  private static final HashSet<String> MDX_PAYLOAD_REGEX = new HashSet<>();
+  private static final HashSet<String> HEADER_KEY_SET = new HashSet<>();
+  private static final List<Pattern> PAYLOAD_PATTERN_SET = new ArrayList<>();
+  private static final String MASK = "**MASKED**";
+
+  public static String maskHeaderValue(String header, String value) {
+    if (HEADER_KEY_SET.contains(header.toLowerCase(Locale.ENGLISH))) {
+      return MASK;
+    }
+
+    return value;
+  }
+
+  public static String maskPayload(String payload) {
+    return applyPatternsToPayload(payload);
+  }
+
+  private static void registerHeaderKeys() {
+    HEADER_KEY_SET.add("mdx-session-key");
+    HEADER_KEY_SET.add("mx-auth-token");
+    HEADER_KEY_SET.add("mx-refresh-token");
+    HEADER_KEY_SET.add("mx-session-key");
+    HEADER_KEY_SET.add("x-csrf-token");
+    HEADER_KEY_SET.add("x-request-token");
+  }
+
+  private static void registerPayloadPatterns() {
+    buildAccountPayloadPatterns();
+    buildAchTransferPayloadPatterns();
+    buildAuthorizationPayloadPatterns();
+    buildChallengesPayloadPatterns();
+    buildCheckPayloadPatterns();
+    buildCreditReportPayloadPatterns();
+    buildCrossAccountTransferPayloadPatterns();
+    buildDevicePayloadPatterns();
+    buildDisputePayloadPatterns();
+    buildDocumentPayloadPatterns();
+    buildIdentificationPayloadPatterns();
+    buildManagedCardsPayloadPatterns();
+    buildOriginationPayloadPatterns();
+    buildPaymentPayloadPatterns();
+    buildPayoutPayloadPatterns();
+    buildProfilePayloadPatterns();
+    buildRemoteDepositPayloadPatterns();
+    buildTransferPayloadPatterns();
+
+    for (String regex : MDX_PAYLOAD_REGEX) {
+      Pattern mask = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
+      PAYLOAD_PATTERN_SET.add(mask);
+    }
+  }
+
+  private static void buildAccountPayloadPatterns() {
+    // Account - https://developer.mx.com/mdx/v5/#mdx-data-models-account-fields
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("account_number"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("id"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("name"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("nickname"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("routing_number"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("routing_transit_number"));
+
+    // Account - MDX v5
+    MDX_PAYLOAD_REGEX.add(xmlElementRegex("account_number"));
+    MDX_PAYLOAD_REGEX.add(xmlElementRegex("id"));
+    MDX_PAYLOAD_REGEX.add(xmlElementRegex("name"));
+    MDX_PAYLOAD_REGEX.add(xmlElementRegex("nickname"));
+    MDX_PAYLOAD_REGEX.add(xmlElementRegex("routing_number"));
+    MDX_PAYLOAD_REGEX.add(xmlElementRegex("routing_transit_number"));
+
+    // AccountDetails - https://developer.mx.com/drafts/mdx/accounts/#accounts-account-details-additional
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonArray("extended_fields"));
+
+    // AccountNumbers - https://developer.mx.com/drafts/mdx/accounts/#accounts-retrieve-full-account-and-routing-numbers
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("account_number"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("routing_number"));
+
+    // AccountOwnerDetails - https://developer.mx.com/mdx/v5/index.html#mdx-data-models-account-owners
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("address"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("city"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("email"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("owner_name"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("phone"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("state"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("zip_code"));
+
+    // DeliveryMethod (Alert) - https://developer.mx.com/drafts/mdx/accounts/#accounts-alerts-delivery-method
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("description"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("target"));
+
+    // Transaction - https://developer.mx.com/mdx/v5/#mdx-data-models-transaction-fields
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("account_id"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("check_number"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("description"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("id"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("memo"));
+
+    // Transaction - MDX v5
+    MDX_PAYLOAD_REGEX.add(xmlElementRegex("account_id"));
+    MDX_PAYLOAD_REGEX.add(xmlElementRegex("check_number"));
+    MDX_PAYLOAD_REGEX.add(xmlElementRegex("description"));
+    MDX_PAYLOAD_REGEX.add(xmlElementRegex("id"));
+    MDX_PAYLOAD_REGEX.add(xmlElementRegex("memo"));
+  }
+
+  private static void buildAchTransferPayloadPatterns() {
+    // AccountListOptions|AchAccountListOptions - https://developer.mx.com/drafts/mdx/ach_transfer/#accounts-ach-accounts-list-ach-accounts
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("account_id"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("ach_account_id"));
+
+    // AchAccount - https://developer.mx.com/drafts/mdx/ach_transfer/#accounts-ach-accounts-vs-held-accounts
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("account_id"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("account_number"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("bank_name"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("id"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("name"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("nickname"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("routing_number"));
+
+    // AchScheduledTransfer - https://developer.mx.com/drafts/mdx/ach_transfer/#ach-scheduled-transfers
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("from_account_id"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("from_ach_account_id"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("id"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("memo"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("to_account_id"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("to_ach_account_id"));
+
+    // AchTransfer - https://developer.mx.com/drafts/mdx/ach_transfer/#ach-transfers
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("from_account_id"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("from_ach_account_id"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("id"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("memo"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("to_account_id"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("to_ach_account_id"));
+  }
+
+  private static void buildAuthorizationPayloadPatterns() {
+    // Authorization - https://developer.mx.com/drafts/mdx/authorization/#authorizations
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("account_id"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonArray("cookies"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("device_id"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonArray("headers"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("token"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonArray("tokens"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("url"));
+  }
+
+  private static void buildChallengesPayloadPatterns() {
+    // Question - https://developer.mx.com/drafts/mdx/challenge/#draft-documentation-question-fields
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("answer"));
+  }
+
+  private static void buildCheckPayloadPatterns() {
+    // CheckImage - https://developer.mx.com/drafts/mdx/accounts/index.html#check-images-check-image-fields
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("account_id"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("back_image"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("check_number"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("front_image"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("transaction_id"));
+  }
+
+  private static void buildCreditReportPayloadPatterns() {
+    // CreditReportSettings - https://developer.mx.com/drafts/mdx/credit_report/index.html#credit-report-settings-credit-report-fields
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("first_name"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("last_name"));
+  }
+
+  private static void buildCrossAccountTransferPayloadPatterns() {
+    // CrossAccountRecurringTransfer - https://developer.mx.com/drafts/mdx/cross_account_transfer/index.html#recurring-cross-account-transfers-delete-a-destination-data-flow
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("destination_id"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("from_account_id"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("memo"));
+
+    // CrossAccountTransfer - https://developer.mx.com/drafts/mdx/cross_account_transfer/index.html#cross-account-transfers-cross-account-transfer-fields
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("destination_id"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("from_account_id"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("memo"));
+
+    // DestinationAccount - https://developer.mx.com/drafts/mdx/cross_account_transfer/index.html#destinations
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("account_holder"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("account_number"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("id"));
+
+    // FeeListOptions - https://developer.mx.com/drafts/mdx/cross_account_transfer/index.html#fees-list-cross-account-transfer-fees
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("destination_id"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("from_account_id"));
+  }
+
+  private static void buildDevicePayloadPatterns() {
+    // VerificationMethod - https://developer.mx.com/drafts/mdx/device/index.html#verification-methods
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("email_address"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("phone_number"));
+  }
+
+  private static void buildDisputePayloadPatterns() {
+    // Dispute - https://developer.mx.com/drafts/mdx/accounts/index.html#disputes-dispute
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("account_id"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("card_id"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("case_number"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("contact_phone"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("member_name"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("source_image"));
+  }
+
+  private static void buildDocumentPayloadPatterns() {
+    // DeliveryPreferences - https://developer.mx.com/drafts/mdx/documents/index.html#documents-update-delivery-preferences
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("account_id"));
+
+    // Document - https://developer.mx.com/drafts/mdx/documents/index.html#documents-document-fields
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("account_id"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("file_data"));
+
+    // DocumentSearch - https://developer.mx.com/drafts/mdx/documents/index.html#documents-list-documents
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("account_id"));
+  }
+
+  private static void buildIdentificationPayloadPatterns() {
+    // Authentication - https://developer.mx.com/drafts/mdx/id/#authentications-authenticate
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("access_token"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("client_device_token"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("device_make"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("device_model"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("device_operating_system"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("device_operating_system_version"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonNumber("device_latitude"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonNumber("device_longitude"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("device_iovation_token"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("login"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("password"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("refresh_token"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("token"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("userkey"));
+
+    // MfaChallenge - https://developer.mx.com/drafts/mdx/id/#authentications-multi-factor-authentication-version-20240213-mfa-challenge-fields
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("answer"));
+
+    // MfaChallengeQuestion - https://developer.mx.com/drafts/mdx/id/#authentications-multi-factor-authentication-version-20240213-question-fields
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("answer"));
+  }
+
+  private static void buildManagedCardsPayloadPatterns() {
+    // ManagedCard - https://developer.mx.com/drafts/mdx/managed_cards/index.html#managed-cards
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("account_id"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("expiration_on_card"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("image_url"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("name_on_card"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("new_pin"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("pin"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("unmasked_cvv"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("unmasked_number_on_card"));
+
+    // TravelSchedule - https://developer.mx.com/drafts/mdx/managed_cards/index.html#destinations-list-global-destinations
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonArray("card_ids"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("email_address"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("primary_phone_number"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("secondary_phone_number"));
+  }
+
+  private static void buildOriginationPayloadPatterns() {
+    // Origination - https://developer.mx.com/drafts/mdx/origination/index.html#mdx-origination-origination-fields
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("login_token"));
+  }
+
+  private static void buildPaymentPayloadPatterns() {
+    // Payee - https://developer.mx.com/drafts/mdx/payment/#payees-payee-fields
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("account_number"));
+
+    // Payment - https://developer.mx.com/drafts/mdx/payment/#payments
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("account_id"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("memo"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("routing_transit_number"));
+
+    // RecurringPayment - https://developer.mx.com/drafts/mdx/payment/#recurring-payments-recurring-payment-fields
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("account_id"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("memo"));
+  }
+
+  private static void buildPayoutPayloadPatterns() {
+    // ChallengeAnswer - https://developer.mx.com/drafts/mdx/payout/#dealing-with-challenges-answer-a-challenge
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("answer"));
+
+    // Payout - https://developer.mx.com/drafts/mdx/payout/#payouts-payout-fields
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("account_id"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("challenge_answer"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("memo"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("sender_name"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("token"));
+
+    // PayoutContactMethod - https://developer.mx.com/drafts/mdx/payout/#payout-contact-methods-payout-contact-method-fields
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("email_address"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("phone_number"));
+
+    // PayoutMethod - https://developer.mx.com/drafts/mdx/payout/#payout-methods-payout-method-fields
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("account_number"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("routing_number"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("send_to"));
+
+    // PayoutRequest - https://developer.mx.com/drafts/mdx/payout/#payout-requests-payout-request-fields
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("account_id"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("memo"));
+
+    // PayoutSettings
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("email_address"));
+
+    // Question
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("answer"));
+
+    // Recipient - https://developer.mx.com/drafts/mdx/payout/#recipients-recipient-fields
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("first_name"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("last_name"));
+
+    // RecurringPayout - https://developer.mx.com/drafts/mdx/payout/#recurring-payouts-recurring-payout-fields
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("account_id"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("challenge_answer"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("memo"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("token"));
+  }
+
+  private static void buildProfilePayloadPatterns() {
+    // Address - https://developer.mx.com/drafts/mdx/profile/#addresses-address-fields
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("account_id"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("address_line_one"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("address_line_two"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("city"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("country"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("postal_code"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("state"));
+
+    // Email - https://developer.mx.com/drafts/mdx/profile/#email-addresses-email-address-fields
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("email_address"));
+
+    // NewPassword - https://developer.mx.com/drafts/mdx/profile/#update-password-new-update-password-fields
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("current_password"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("new_password"));
+
+    // NewUserName - https://developer.mx.com/drafts/mdx/profile/#update-username-update-username-fields
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("new_username"));
+
+    // Phone - https://developer.mx.com/drafts/mdx/profile/#phone-numbers-phone-number-fields
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("phone_number"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("work_extension"));
+
+    // Profile - https://developer.mx.com/drafts/mdx/profile/#profile-profile-fields
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("birth_date_on"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("first_name"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("gender"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("last_name"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("middle_name"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("ssn"));
+  }
+
+  private static void buildRemoteDepositPayloadPatterns() {
+    // RemoteDeposit - https://developer.mx.com/drafts/mdx/remote_deposit/#remote-deposits-remote-deposit-fields
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("account_id"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("back_of_check_image"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("front_of_check_image"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("memo"));
+  }
+
+  private static void buildTransferPayloadPatterns() {
+    // AccountListOptions - https://developer.mx.com/drafts/mdx/transfer/#transfer-accounts-list-accounts
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("account_id"));
+
+    // FeeListOptions - https://developer.mx.com/drafts/mdx/transfer/#fees-list-transfer-fees
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("from_account_id"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("to_account_id"));
+
+    // RecurringTransfer - https://developer.mx.com/drafts/mdx/transfer/#recurring-transfers-data-flows
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("from_account_id"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("memo"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("to_account_id"));
+
+    // Transfer - https://developer.mx.com/drafts/mdx/transfer/#transfers-data-flows
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("from_account_id"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("memo"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("repayment_account_id"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("to_account_id"));
+
+    // TransferAmountOptionListOptions - https://developer.mx.com/drafts/mdx/transfer/#transfer-amount-options-list-all-transfer-amount-options
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("destination_account_id"));
+    MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("source_account_id"));
+  }
+
+  private static String xmlElementRegex(String fieldName) {
+    return String.format("\\<[\\w:]*%1$s\\>([\\s\\S]+?)\\<\\/[\\w:]*%1$s\\>", fieldName);
+  }
+
+  private static String applyPatternsToPayload(String payload) {
+    for (Pattern p : PAYLOAD_PATTERN_SET) {
+      Matcher m = p.matcher(payload);
+      int start = 0;
+
+      while (m.find(start)) {
+        String patternMatch = m.group();
+
+        // Apply masking to all matching groups
+        for (int i = 1; i <= m.groupCount(); i++) {
+          if (!Strings.isBlank(m.group(i))) {
+            patternMatch = patternMatch.replace(m.group(i), MASK);
+          }
+        }
+        payload = payload.replace(m.group(), patternMatch);
+        start = m.start() + 1;
+      }
+    }
+
+    return payload;
+  }
+
+  static {
+    registerHeaderKeys();
+    registerPayloadPatterns();
+  }
+}
diff --git a/mdx-models/src/test/groovy/com/mx/path/model/mdx/model/MdxLogMaskerTest.groovy b/mdx-models/src/test/groovy/com/mx/path/model/mdx/model/MdxLogMaskerTest.groovy
new file mode 100644
index 00000000..c38788e0
--- /dev/null
+++ b/mdx-models/src/test/groovy/com/mx/path/model/mdx/model/MdxLogMaskerTest.groovy
@@ -0,0 +1,878 @@
+package com.mx.path.model.mdx.model
+
+import spock.lang.Specification
+import spock.lang.Unroll
+
+class MdxLogMaskerTest extends Specification {
+  @Unroll
+  def "maskHeaderValue() masks #header to #expectedResult"() {
+    when:
+    String result = MdxLogMasker.maskHeaderValue(header, "something_sensitive")
+
+    then:
+    result == expectedResult
+
+    where:
+    header             || expectedResult
+    "mx-refresh-token" || "**MASKED**"
+  }
+
+  @Unroll
+  def "maskPayload() masks Account JSON fields"() {
+    when:
+    String result = MdxLogMasker.maskPayload(payload)
+
+    then:
+    result == expectedResult
+
+    where:
+    payload                                      || expectedResult
+    "\"account_number\":\"1234567890\""          || "\"account_number\":\"**MASKED**\""
+    "\"id\":\"ACCOUNT-123\""                     || "\"id\":\"**MASKED**\""
+    "\"name\":\"Checking Account\""              || "\"name\":\"**MASKED**\""
+    "\"nickname\":\"My Checking\""               || "\"nickname\":\"**MASKED**\""
+    "\"routing_number\":\"121122676\""           || "\"routing_number\":\"**MASKED**\""
+    "\"routing_transit_number\":\"0260-0959-3\"" || "\"routing_transit_number\":\"**MASKED**\""
+  }
+
+  @Unroll
+  def "maskPayload() masks Account XML fields"() {
+    when:
+    String result = MdxLogMasker.maskPayload(payload)
+
+    then:
+    result == expectedResult
+
+    where:
+    payload                                                        || expectedResult
+    "<account_number>1234567890</account_number>"                  || "<account_number>**MASKED**</account_number>"
+    "<id>ACCOUNT-123</id>"                                         || "<id>**MASKED**</id>"
+    "<name>Checking Account</name>"                                || "<name>**MASKED**</name>"
+    "<nickname>My Checking</nickname>"                             || "<nickname>**MASKED**</nickname>"
+    "<routing_number>121122676</routing_number>"                   || "<routing_number>**MASKED**</routing_number>"
+    "<routing_transit_number>0260-0959-3</routing_transit_number>" || "<routing_transit_number>**MASKED**</routing_transit_number>"
+  }
+
+  @Unroll
+  def "maskPayload() masks AccountDetails JSON fields"() {
+    when:
+    String result = MdxLogMasker.maskPayload(payload)
+
+    then:
+    result == expectedResult
+
+    where:
+    payload                                                                                                                          || expectedResult
+    "\"extended_fields\":[{\n    \"type\": \"STRING\",\n    \"name\": \"secret field\",\n    \"string_value\": \"secret value\"\n}]" || "\"extended_fields\":[**MASKED**]"
+  }
+
+  @Unroll
+  def "maskPayload() masks AccountNumbers JSON fields"() {
+    when:
+    String result = MdxLogMasker.maskPayload(payload)
+
+    then:
+    result == expectedResult
+
+    where:
+    payload                             || expectedResult
+    "\"account_number\":\"1234567890\"" || "\"account_number\":\"**MASKED**\""
+    "\"routing_number\":\"121122676\""  || "\"routing_number\":\"**MASKED**\""
+  }
+
+  @Unroll
+  def "maskPayload() masks AccountOwnerDetails JSON fields"() {
+    when:
+    String result = MdxLogMasker.maskPayload(payload)
+
+    then:
+    result == expectedResult
+
+    where:
+    payload                                        || expectedResult
+    "\"address\":\"3401 N Thanksgiving Way #500\"" || "\"address\":\"**MASKED**\""
+    "\"city\":\"Lehi\""                            || "\"city\":\"**MASKED**\""
+    "\"email\":\"john.doe@example.com\""           || "\"email\":\"**MASKED**\""
+    "\"owner_name\":\"John Doe\""                  || "\"owner_name\":\"**MASKED**\""
+    "\"phone\":\"1-222-333-4444\""                 || "\"phone\":\"**MASKED**\""
+    "\"zip_code\":\"84043\""                       || "\"zip_code\":\"**MASKED**\""
+    "\"state\":\"UT\""                             || "\"state\":\"**MASKED**\""
+  }
+
+  @Unroll
+  def "maskPayload() masks DeliveryMethod JSON fields"() {
+    when:
+    String result = MdxLogMasker.maskPayload(payload)
+
+    then:
+    result == expectedResult
+
+    where:
+    payload                                            || expectedResult
+    "\"description\":\"3401 N Thanksgiving Way #500\"" || "\"description\":\"**MASKED**\""
+    "\"target\":\"Email - *1@example.com\""            || "\"target\":\"**MASKED**\""
+  }
+
+  @Unroll
+  def "maskPayload() masks Transaction JSON fields"() {
+    when:
+    String result = MdxLogMasker.maskPayload(payload)
+
+    then:
+    result == expectedResult
+
+    where:
+    payload                                || expectedResult
+    "\"account_id\":\"ACCOUNT-123\""       || "\"account_id\":\"**MASKED**\""
+    "\"check_number\":\"1234\""            || "\"check_number\":\"**MASKED**\""
+    "\"description\":\"Test transaction\"" || "\"description\":\"**MASKED**\""
+    "\"id\":\"TRANSACTION-123\""           || "\"id\":\"**MASKED**\""
+    "\"memo\":\"Test memo\""               || "\"memo\":\"**MASKED**\""
+  }
+
+  @Unroll
+  def "maskPayload() masks Transaction XML fields"() {
+    when:
+    String result = MdxLogMasker.maskPayload(payload)
+
+    then:
+    result == expectedResult
+
+    where:
+    payload                                       || expectedResult
+    "<account_id>ACCOUNT-123</account_id>"        || "<account_id>**MASKED**</account_id>"
+    "<check_number>1234</check_number>"           || "<check_number>**MASKED**</check_number>"
+    "<description>Test transaction</description>" || "<description>**MASKED**</description>"
+    "<id>TRANSACTION-123</id>"                    || "<id>**MASKED**</id>"
+    "<memo>Test memo</memo>"                      || "<memo>**MASKED**</memo>"
+  }
+
+  @Unroll
+  def "maskPayload() masks AccountListOptions JSON fields"() {
+    when:
+    String result = MdxLogMasker.maskPayload(payload)
+
+    then:
+    result == expectedResult
+
+    where:
+    payload                              || expectedResult
+    "\"account_id\":\"ACCOUNT-123\""     || "\"account_id\":\"**MASKED**\""
+    "\"ach_account_id\":\"ACCOUNT-456\"" || "\"ach_account_id\":\"**MASKED**\""
+  }
+
+  @Unroll
+  def "maskPayload() masks AchAccount JSON fields"() {
+    when:
+    String result = MdxLogMasker.maskPayload(payload)
+
+    then:
+    result == expectedResult
+
+    where:
+    payload                               || expectedResult
+    "\"account_id\":\"ACCOUNT-123\""      || "\"account_id\":\"**MASKED**\""
+    "\"account_number\":\"1234567890\""   || "\"account_number\":\"**MASKED**\""
+    "\"bank_name\":\"Test Credit Union\"" || "\"bank_name\":\"**MASKED**\""
+    "\"id\":\"ACH-ACCOUNT-123\""          || "\"id\":\"**MASKED**\""
+    "\"name\":\"Test Name\""              || "\"name\":\"**MASKED**\""
+    "\"nickname\":\"Test Nickname\""      || "\"nickname\":\"**MASKED**\""
+    "\"routing_number\":\"121122676\""    || "\"routing_number\":\"**MASKED**\""
+  }
+
+  @Unroll
+  def "maskPayload() masks AchScheduledTransfer JSON fields"() {
+    when:
+    String result = MdxLogMasker.maskPayload(payload)
+
+    then:
+    result == expectedResult
+
+    where:
+    payload                                     || expectedResult
+    "\"from_account_id\":\"ACCOUNT-1\""         || "\"from_account_id\":\"**MASKED**\""
+    "\"from_ach_account_id\":\"ACH-ACCOUNT-1\"" || "\"from_ach_account_id\":\"**MASKED**\""
+    "\"id\":\"ACH-TRANSFER-1\""                 || "\"id\":\"**MASKED**\""
+    "\"memo\":\"Test ACH Transfer\""            || "\"memo\":\"**MASKED**\""
+    "\"to_account_id\":\"ACCOUNT-2\""           || "\"to_account_id\":\"**MASKED**\""
+    "\"to_ach_account_id\":\"ACH-ACCOUNT-2\""   || "\"to_ach_account_id\":\"**MASKED**\""
+  }
+
+  @Unroll
+  def "maskPayload() masks AchTransfer JSON fields"() {
+    when:
+    String result = MdxLogMasker.maskPayload(payload)
+
+    then:
+    result == expectedResult
+
+    where:
+    payload                                     || expectedResult
+    "\"from_account_id\":\"ACCOUNT-1\""         || "\"from_account_id\":\"**MASKED**\""
+    "\"from_ach_account_id\":\"ACH-ACCOUNT-1\"" || "\"from_ach_account_id\":\"**MASKED**\""
+    "\"id\":\"ACH-TRANSFER-1\""                 || "\"id\":\"**MASKED**\""
+    "\"memo\":\"Test ACH Transfer\""            || "\"memo\":\"**MASKED**\""
+    "\"to_account_id\":\"ACCOUNT-2\""           || "\"to_account_id\":\"**MASKED**\""
+    "\"to_ach_account_id\":\"ACH-ACCOUNT-2\""   || "\"to_ach_account_id\":\"**MASKED**\""
+  }
+
+  @Unroll
+  def "maskPayload() masks Authorization JSON fields"() {
+    when:
+    String result = MdxLogMasker.maskPayload(payload)
+
+    then:
+    result == expectedResult
+
+    where:
+    payload                                                                         || expectedResult
+    "\"account_id\":\"ACCOUNT-1\""                                                  || "\"account_id\":\"**MASKED**\""
+    "\"cookies\":[{\n    \"cookie1\": \"value1\",\n    \"cookie2\": \"value2\"\n}]" || "\"cookies\":[**MASKED**]"
+    "\"device_id\":\"ACH-TRANSFER-1\""                                              || "\"device_id\":\"**MASKED**\""
+    "\"headers\":[{\n    \"header1\": \"value1\",\n    \"header2\": \"value2\"\n}]" || "\"headers\":[**MASKED**]"
+    "\"token\":\"j8tGkb0STI827r0aMBOvJN9tBU08nFsc8gSivZLIfBw=\""                    || "\"token\":\"**MASKED**\""
+    "\"tokens\":[{\n    \"token1\": \"value1\",\n    \"token2\": \"value2\"\n}]"    || "\"tokens\":[**MASKED**]"
+    "\"url\":\"http:/test.example.com/sso?token=b1d36291310644fe921b8bdff8d08d61\"" || "\"url\":\"**MASKED**\""
+  }
+
+  @Unroll
+  def "maskPayload() masks Question JSON fields"() {
+    when:
+    String result = MdxLogMasker.maskPayload(payload)
+
+    then:
+    result == expectedResult
+
+    where:
+    payload                      || expectedResult
+    "\"answer\":\"Test answer\"" || "\"answer\":\"**MASKED**\""
+  }
+
+  @Unroll
+  def "maskPayload() masks CheckImage JSON fields"() {
+    when:
+    String result = MdxLogMasker.maskPayload(payload)
+
+    then:
+    result == expectedResult
+
+    where:
+    payload                                                               || expectedResult
+    "\"account_id\":\"ACCOUNT-1\""                                        || "\"account_id\":\"**MASKED**\""
+    "\"back_image\":\"data:image/png;base64,iVBORw0KGAAAUAAAAFCJggg==\""  || "\"back_image\":\"**MASKED**\""
+    "\"check_number\":\"1234\""                                           || "\"check_number\":\"**MASKED**\""
+    "\"front_image\":\"data:image/png;base64,iVBORw0KGAAAUAAAAFCJggg==\"" || "\"front_image\":\"**MASKED**\""
+    "\"transaction_id\":\"TRANSACTION-1\""                                || "\"transaction_id\":\"**MASKED**\""
+  }
+
+  @Unroll
+  def "maskPayload() masks CreditReportSettings JSON fields"() {
+    when:
+    String result = MdxLogMasker.maskPayload(payload)
+
+    then:
+    result == expectedResult
+
+    where:
+    payload                   || expectedResult
+    "\"first_name\":\"John\"" || "\"first_name\":\"**MASKED**\""
+    "\"last_name\":\"Doe\""   || "\"last_name\":\"**MASKED**\""
+  }
+
+  @Unroll
+  def "maskPayload() masks CrossAccountRecurringTransfer JSON fields"() {
+    when:
+    String result = MdxLogMasker.maskPayload(payload)
+
+    then:
+    result == expectedResult
+
+    where:
+    payload                                  || expectedResult
+    "\"destination_id\":\"DESTINATION-123\"" || "\"destination_id\":\"**MASKED**\""
+    "\"from_account_id\":\"ACCOUNT-123\""    || "\"from_account_id\":\"**MASKED**\""
+    "\"memo\":\"Test memo\""                 || "\"memo\":\"**MASKED**\""
+  }
+
+  @Unroll
+  def "maskPayload() masks CrossAccountRecurringTransfer JSON fields"() {
+    when:
+    String result = MdxLogMasker.maskPayload(payload)
+
+    then:
+    result == expectedResult
+
+    where:
+    payload                                  || expectedResult
+    "\"destination_id\":\"DESTINATION-123\"" || "\"destination_id\":\"**MASKED**\""
+    "\"from_account_id\":\"ACCOUNT-123\""    || "\"from_account_id\":\"**MASKED**\""
+    "\"memo\":\"Test memo\""                 || "\"memo\":\"**MASKED**\""
+  }
+
+  @Unroll
+  def "maskPayload() masks CrossAccountTransfer JSON fields"() {
+    when:
+    String result = MdxLogMasker.maskPayload(payload)
+
+    then:
+    result == expectedResult
+
+    where:
+    payload                                  || expectedResult
+    "\"destination_id\":\"DESTINATION-123\"" || "\"destination_id\":\"**MASKED**\""
+    "\"from_account_id\":\"ACCOUNT-123\""    || "\"from_account_id\":\"**MASKED**\""
+    "\"memo\":\"Test memo\""                 || "\"memo\":\"**MASKED**\""
+  }
+
+  @Unroll
+  def "maskPayload() masks DestinationAccount JSON fields"() {
+    when:
+    String result = MdxLogMasker.maskPayload(payload)
+
+    then:
+    result == expectedResult
+
+    where:
+    payload                                      || expectedResult
+    "\"account_holder\":\"Test account holder\"" || "\"account_holder\":\"**MASKED**\""
+    "\"account_number\":\"1234567890\""          || "\"account_number\":\"**MASKED**\""
+    "\"id\":\"ACCOUNT-123\""                     || "\"id\":\"**MASKED**\""
+  }
+
+  @Unroll
+  def "maskPayload() masks FeeListOptions JSON fields"() {
+    when:
+    String result = MdxLogMasker.maskPayload(payload)
+
+    then:
+    result == expectedResult
+
+    where:
+    payload                                  || expectedResult
+    "\"destination_id\":\"DESTINATION-123\"" || "\"destination_id\":\"**MASKED**\""
+    "\"from_account_id\":\"ACCOUNT-123\""    || "\"from_account_id\":\"**MASKED**\""
+  }
+
+  @Unroll
+  def "maskPayload() masks VerificationMethod JSON fields"() {
+    when:
+    String result = MdxLogMasker.maskPayload(payload)
+
+    then:
+    result == expectedResult
+
+    where:
+    payload                                      || expectedResult
+    "\"email_address\":\"john.doe@example.com\"" || "\"email_address\":\"**MASKED**\""
+    "\"phone_number\":\"1-222-333-4444\""        || "\"phone_number\":\"**MASKED**\""
+  }
+
+  @Unroll
+  def "maskPayload() masks Dispute JSON fields"() {
+    when:
+    String result = MdxLogMasker.maskPayload(payload)
+
+    then:
+    result == expectedResult
+
+    where:
+    payload                                                                || expectedResult
+    "\"account_id\":\"ACCOUNT-123\""                                       || "\"account_id\":\"**MASKED**\""
+    "\"card_id\":\"CARD-123\""                                             || "\"card_id\":\"**MASKED**\""
+    "\"case_number\":\"182744AAQ093\""                                     || "\"case_number\":\"**MASKED**\""
+    "\"contact_phone\":\"1-222-333-4444\""                                 || "\"contact_phone\":\"**MASKED**\""
+    "\"member_name\":\"John Doe\""                                         || "\"member_name\":\"**MASKED**\""
+    "\"source_image\":\"data:image/png;base64,iVBORw0KGAAAUAAAAFCJggg==\"" || "\"source_image\":\"**MASKED**\""
+  }
+
+  @Unroll
+  def "maskPayload() masks DeliveryPreferences JSON fields"() {
+    when:
+    String result = MdxLogMasker.maskPayload(payload)
+
+    then:
+    result == expectedResult
+
+    where:
+    payload                          || expectedResult
+    "\"account_id\":\"ACCOUNT-123\"" || "\"account_id\":\"**MASKED**\""
+  }
+
+  @Unroll
+  def "maskPayload() masks Document JSON fields"() {
+    when:
+    String result = MdxLogMasker.maskPayload(payload)
+
+    then:
+    result == expectedResult
+
+    where:
+    payload                                                                   || expectedResult
+    "\"account_id\":\"ACCOUNT-123\""                                          || "\"account_id\":\"**MASKED**\""
+    "\"file_data\":\"data:application/pdf;base64,iVBORw0KGAAAUAAAAFCJggg==\"" || "\"file_data\":\"**MASKED**\""
+  }
+
+  @Unroll
+  def "maskPayload() masks DocumentSearch JSON fields"() {
+    when:
+    String result = MdxLogMasker.maskPayload(payload)
+
+    then:
+    result == expectedResult
+
+    where:
+    payload                          || expectedResult
+    "\"account_id\":\"ACCOUNT-123\"" || "\"account_id\":\"**MASKED**\""
+  }
+
+  @Unroll
+  def "maskPayload() masks Authentication JSON fields"() {
+    when:
+    String result = MdxLogMasker.maskPayload(payload)
+
+    then:
+    result == expectedResult
+
+    where:
+    payload                                                          || expectedResult
+    "\"access_token\":\"167f0a358b054953ae7c9527e2d273a9\""          || "\"access_token\":\"**MASKED**\""
+    "\"client_device_token\":\"UNIQUE_TOKEN_FOR_THIS_DEVICE\""       || "\"client_device_token\":\"**MASKED**\""
+    "\"device_make\":\"Apple\""                                      || "\"device_make\":\"**MASKED**\""
+    "\"device_model\":\"iPhone X\""                                  || "\"device_model\":\"**MASKED**\""
+    "\"device_operating_system\":\"iOS\""                            || "\"device_operating_system\":\"**MASKED**\""
+    "\"device_operating_system_version\":\"11.4.1\""                 || "\"device_operating_system_version\":\"**MASKED**\""
+    "\"device_latitude\":40.4296944"                                 || "\"device_latitude\":**MASKED**"
+    "\"device_longitude\":-111.8931454"                              || "\"device_longitude\":**MASKED**"
+    "\"device_iovation_token\":\"ac1d3a2455a9444ebc942c7842660ed2\"" || "\"device_iovation_token\":\"**MASKED**\""
+    "\"login\":\"johndoe\""                                          || "\"login\":\"**MASKED**\""
+    "\"password\":\"topsecret\""                                     || "\"password\":\"**MASKED**\""
+    "\"refresh_token\":\"2ed7c86985d0404a8728fe4c621711b5\""         || "\"refresh_token\":\"**MASKED**\""
+    "\"token\":\"b43cac3cfbcc45d4abecef87064b63ce\""                 || "\"token\":\"**MASKED**\""
+    "\"userkey\":\"25ff030acfba4cdf91618dc23b4795e4\""               || "\"userkey\":\"**MASKED**\""
+  }
+
+  @Unroll
+  def "maskPayload() masks MfaChallenge JSON fields"() {
+    when:
+    String result = MdxLogMasker.maskPayload(payload)
+
+    then:
+    result == expectedResult
+
+    where:
+    payload                || expectedResult
+    "\"answer\":\"12345\"" || "\"answer\":\"**MASKED**\""
+  }
+
+  @Unroll
+  def "maskPayload() masks MfaChallengeQuestion JSON fields"() {
+    when:
+    String result = MdxLogMasker.maskPayload(payload)
+
+    then:
+    result == expectedResult
+
+    where:
+    payload                || expectedResult
+    "\"answer\":\"12345\"" || "\"answer\":\"**MASKED**\""
+  }
+
+  @Unroll
+  def "maskPayload() masks ManagedCard JSON fields"() {
+    when:
+    String result = MdxLogMasker.maskPayload(payload)
+
+    then:
+    result == expectedResult
+
+    where:
+    payload                                                           || expectedResult
+    "\"account_id\":\"ACCOUNT-123\""                                  || "\"account_id\":\"**MASKED**\""
+    "\"expiration_on_card\":\"06/97\""                                || "\"expiration_on_card\":\"**MASKED**\""
+    "\"image_url\":\"https://logos.co/customers/123/card_image.jpg\"" || "\"image_url\":\"**MASKED**\""
+    "\"name_on_card\":\"John Doe\""                                   || "\"name_on_card\":\"**MASKED**\""
+    "\"new_pin\":\"12345\""                                           || "\"new_pin\":\"**MASKED**\""
+    "\"pin\":\"12345\""                                               || "\"pin\":\"**MASKED**\""
+    "\"unmasked_cvv\":\"123\""                                        || "\"unmasked_cvv\":\"**MASKED**\""
+    "\"unmasked_number_on_card\":\"1111222233334444\""                || "\"unmasked_number_on_card\":\"**MASKED**\""
+  }
+
+  @Unroll
+  def "maskPayload() masks TravelSchedule JSON fields"() {
+    when:
+    String result = MdxLogMasker.maskPayload(payload)
+
+    then:
+    result == expectedResult
+
+    where:
+    payload                                         || expectedResult
+    "\"card_ids\":[\"CARD_ID_1\", \"CARD_ID_2\"]"   || "\"card_ids\":[**MASKED**]"
+    "\"email_address\":\"john.doe@example.com\""    || "\"email_address\":\"**MASKED**\""
+    "\"primary_phone_number\":\"1-222-333-4444\""   || "\"primary_phone_number\":\"**MASKED**\""
+    "\"secondary_phone_number\":\"1-333-444-5555\"" || "\"secondary_phone_number\":\"**MASKED**\""
+  }
+
+  @Unroll
+  def "maskPayload() masks Origination JSON fields"() {
+    when:
+    String result = MdxLogMasker.maskPayload(payload)
+
+    then:
+    result == expectedResult
+
+    where:
+    payload                         || expectedResult
+    "\"login_token\":\"123456789\"" || "\"login_token\":\"**MASKED**\""
+  }
+
+  @Unroll
+  def "maskPayload() masks Payee JSON fields"() {
+    when:
+    String result = MdxLogMasker.maskPayload(payload)
+
+    then:
+    result == expectedResult
+
+    where:
+    payload                            || expectedResult
+    "\"account_number\":\"123456789\"" || "\"account_number\":\"**MASKED**\""
+  }
+
+  @Unroll
+  def "maskPayload() masks Payment JSON fields"() {
+    when:
+    String result = MdxLogMasker.maskPayload(payload)
+
+    then:
+    result == expectedResult
+
+    where:
+    payload                                      || expectedResult
+    "\"account_id\":\"ACCOUNT-123\""             || "\"account_id\":\"**MASKED**\""
+    "\"memo\":\"Test memo\""                     || "\"memo\":\"**MASKED**\""
+    "\"routing_transit_number\":\"0260-0959-3\"" || "\"routing_transit_number\":\"**MASKED**\""
+  }
+
+  @Unroll
+  def "maskPayload() masks RecurringPayment JSON fields"() {
+    when:
+    String result = MdxLogMasker.maskPayload(payload)
+
+    then:
+    result == expectedResult
+
+    where:
+    payload                          || expectedResult
+    "\"account_id\":\"ACCOUNT-123\"" || "\"account_id\":\"**MASKED**\""
+    "\"memo\":\"Test memo\""         || "\"memo\":\"**MASKED**\""
+  }
+
+  @Unroll
+  def "maskPayload() masks ChallengeAnswer (Payout) JSON fields"() {
+    when:
+    String result = MdxLogMasker.maskPayload(payload)
+
+    then:
+    result == expectedResult
+
+    where:
+    payload                      || expectedResult
+    "\"answer\":\"Test answer\"" || "\"answer\":\"**MASKED**\""
+  }
+
+  @Unroll
+  def "maskPayload() masks Payout JSON fields"() {
+    when:
+    String result = MdxLogMasker.maskPayload(payload)
+
+    then:
+    result == expectedResult
+
+    where:
+    payload                                          || expectedResult
+    "\"account_id\":\"ACCOUNT-123\""                 || "\"account_id\":\"**MASKED**\""
+    "\"challenge_answer\":\"Test answer\""           || "\"challenge_answer\":\"**MASKED**\""
+    "\"memo\":\"Test memo\""                         || "\"memo\":\"**MASKED**\""
+    "\"sender_name\":\"John Doe\""                   || "\"sender_name\":\"**MASKED**\""
+    "\"token\":\"f48c8aa0e66d4d53979b36c4c46c8abc\"" || "\"token\":\"**MASKED**\""
+  }
+
+  @Unroll
+  def "maskPayload() masks PayoutContactMethod JSON fields"() {
+    when:
+    String result = MdxLogMasker.maskPayload(payload)
+
+    then:
+    result == expectedResult
+
+    where:
+    payload                                      || expectedResult
+    "\"email_address\":\"john.doe@example.com\"" || "\"email_address\":\"**MASKED**\""
+    "\"phone_number\":\"1-222-333-4444\""        || "\"phone_number\":\"**MASKED**\""
+  }
+
+  @Unroll
+  def "maskPayload() masks PayoutMethod JSON fields"() {
+    when:
+    String result = MdxLogMasker.maskPayload(payload)
+
+    then:
+    result == expectedResult
+
+    where:
+    payload                                || expectedResult
+    "\"account_number\":\"1234567890\""    || "\"account_number\":\"**MASKED**\""
+    "\"routing_number\":\"121122676\""     || "\"routing_number\":\"**MASKED**\""
+    "\"send_to\":\"john.doe@example.com\"" || "\"send_to\":\"**MASKED**\""
+  }
+
+  @Unroll
+  def "maskPayload() masks PayoutRequest JSON fields"() {
+    when:
+    String result = MdxLogMasker.maskPayload(payload)
+
+    then:
+    result == expectedResult
+
+    where:
+    payload                          || expectedResult
+    "\"account_id\":\"ACCOUNT-123\"" || "\"account_id\":\"**MASKED**\""
+    "\"memo\":\"Test memo\""         || "\"memo\":\"**MASKED**\""
+  }
+
+  @Unroll
+  def "maskPayload() masks PayoutSettings JSON fields"() {
+    when:
+    String result = MdxLogMasker.maskPayload(payload)
+
+    then:
+    result == expectedResult
+
+    where:
+    payload                                      || expectedResult
+    "\"email_address\":\"john.doe@example.com\"" || "\"email_address\":\"**MASKED**\""
+  }
+
+  @Unroll
+  def "maskPayload() masks Question (Payout) JSON fields"() {
+    when:
+    String result = MdxLogMasker.maskPayload(payload)
+
+    then:
+    result == expectedResult
+
+    where:
+    payload                      || expectedResult
+    "\"answer\":\"Test answer\"" || "\"answer\":\"**MASKED**\""
+  }
+
+  @Unroll
+  def "maskPayload() masks Recipient (Payout) JSON fields"() {
+    when:
+    String result = MdxLogMasker.maskPayload(payload)
+
+    then:
+    result == expectedResult
+
+    where:
+    payload                   || expectedResult
+    "\"first_name\":\"John\"" || "\"first_name\":\"**MASKED**\""
+    "\"last_name\":\"Doe\""   || "\"last_name\":\"**MASKED**\""
+  }
+
+  @Unroll
+  def "maskPayload() masks RecurringPayout JSON fields"() {
+    when:
+    String result = MdxLogMasker.maskPayload(payload)
+
+    then:
+    result == expectedResult
+
+    where:
+    payload                                          || expectedResult
+    "\"account_id\":\"ACCOUNT-123\""                 || "\"account_id\":\"**MASKED**\""
+    "\"challenge_answer\":\"Test answer\""           || "\"challenge_answer\":\"**MASKED**\""
+    "\"memo\":\"Test memo\""                         || "\"memo\":\"**MASKED**\""
+    "\"token\":\"41bb602a0ab44896ad28d7de270e24ce\"" || "\"token\":\"**MASKED**\""
+  }
+
+  @Unroll
+  def "maskPayload() masks Address (Profile) JSON fields"() {
+    when:
+    String result = MdxLogMasker.maskPayload(payload)
+
+    then:
+    result == expectedResult
+
+    where:
+    payload                                                 || expectedResult
+    "\"account_id\":\"ACCOUNT-123\""                        || "\"account_id\":\"**MASKED**\""
+    "\"address_line_one\":\"3401 N Thanksgiving Way #500\"" || "\"address_line_one\":\"**MASKED**\""
+    "\"address_line_two\":\"Test address line 2\""          || "\"address_line_two\":\"**MASKED**\""
+    "\"city\":\"Lehi\""                                     || "\"city\":\"**MASKED**\""
+    "\"country\":\"US\""                                    || "\"country\":\"**MASKED**\""
+    "\"postal_code\":\"84043\""                             || "\"postal_code\":\"**MASKED**\""
+    "\"state\":\"UT\""                                      || "\"state\":\"**MASKED**\""
+  }
+
+  @Unroll
+  def "maskPayload() masks Email (Profile) JSON fields"() {
+    when:
+    String result = MdxLogMasker.maskPayload(payload)
+
+    then:
+    result == expectedResult
+
+    where:
+    payload                                      || expectedResult
+    "\"email_address\":\"john.doe@example.com\"" || "\"email_address\":\"**MASKED**\""
+  }
+
+  @Unroll
+  def "maskPayload() masks NewPassword (Profile) JSON fields"() {
+    when:
+    String result = MdxLogMasker.maskPayload(payload)
+
+    then:
+    result == expectedResult
+
+    where:
+    payload                               || expectedResult
+    "\"current_password\":\"topsecret1\"" || "\"current_password\":\"**MASKED**\""
+    "\"new_password\":\"topsecret2\""     || "\"new_password\":\"**MASKED**\""
+  }
+
+  @Unroll
+  def "maskPayload() masks NewUserName (Profile) JSON fields"() {
+    when:
+    String result = MdxLogMasker.maskPayload(payload)
+
+    then:
+    result == expectedResult
+
+    where:
+    payload                        || expectedResult
+    "\"new_username\":\"johndoe\"" || "\"new_username\":\"**MASKED**\""
+  }
+
+  @Unroll
+  def "maskPayload() masks Phone (Profile) JSON fields"() {
+    when:
+    String result = MdxLogMasker.maskPayload(payload)
+
+    then:
+    result == expectedResult
+
+    where:
+    payload                               || expectedResult
+    "\"phone_number\":\"1-222-333-4444\"" || "\"phone_number\":\"**MASKED**\""
+    "\"work_extension\":\"1234\""         || "\"work_extension\":\"**MASKED**\""
+  }
+
+  @Unroll
+  def "maskPayload() masks Profile JSON fields"() {
+    when:
+    String result = MdxLogMasker.maskPayload(payload)
+
+    then:
+    result == expectedResult
+
+    where:
+    payload                            || expectedResult
+    "\"birth_date_on\":\"2015-01-01\"" || "\"birth_date_on\":\"**MASKED**\""
+    "\"first_name\":\"John\""          || "\"first_name\":\"**MASKED**\""
+    "\"gender\":\"MALE\""              || "\"gender\":\"**MASKED**\""
+    "\"last_name\":\"Doe\""            || "\"last_name\":\"**MASKED**\""
+    "\"middle_name\":\"William\""      || "\"middle_name\":\"**MASKED**\""
+    "\"ssn\":\"123456789\""            || "\"ssn\":\"**MASKED**\""
+  }
+
+  @Unroll
+  def "maskPayload() masks RemoteDeposit JSON fields"() {
+    when:
+    String result = MdxLogMasker.maskPayload(payload)
+
+    then:
+    result == expectedResult
+
+    where:
+    payload                                                                        || expectedResult
+    "\"account_id\":\"Account-123\""                                               || "\"account_id\":\"**MASKED**\""
+    "\"back_of_check_image\":\"data:image/png;base64,iVBORw0KGAAAUAAAAFCJggg==\""  || "\"back_of_check_image\":\"**MASKED**\""
+    "\"front_of_check_image\":\"data:image/png;base64,iVBORw0KGAAAUAAAAFCJggg==\"" || "\"front_of_check_image\":\"**MASKED**\""
+    "\"memo\":\"Test memo\""                                                       || "\"memo\":\"**MASKED**\""
+  }
+
+  @Unroll
+  def "maskPayload() masks AccountListOptions (Transfer) JSON fields"() {
+    when:
+    String result = MdxLogMasker.maskPayload(payload)
+
+    then:
+    result == expectedResult
+
+    where:
+    payload                          || expectedResult
+    "\"account_id\":\"Account-123\"" || "\"account_id\":\"**MASKED**\""
+  }
+
+  @Unroll
+  def "maskPayload() masks FeeListOptions (Transfer) JSON fields"() {
+    when:
+    String result = MdxLogMasker.maskPayload(payload)
+
+    then:
+    result == expectedResult
+
+    where:
+    payload                               || expectedResult
+    "\"from_account_id\":\"Account-123\"" || "\"from_account_id\":\"**MASKED**\""
+    "\"to_account_id\":\"Account-456\""   || "\"to_account_id\":\"**MASKED**\""
+  }
+
+  @Unroll
+  def "maskPayload() masks RecurringTransfer JSON fields"() {
+    when:
+    String result = MdxLogMasker.maskPayload(payload)
+
+    then:
+    result == expectedResult
+
+    where:
+    payload                               || expectedResult
+    "\"from_account_id\":\"Account-123\"" || "\"from_account_id\":\"**MASKED**\""
+    "\"memo\":\"Test memo\""              || "\"memo\":\"**MASKED**\""
+    "\"to_account_id\":\"Account-456\""   || "\"to_account_id\":\"**MASKED**\""
+  }
+
+  @Unroll
+  def "maskPayload() masks Transfer JSON fields"() {
+    when:
+    String result = MdxLogMasker.maskPayload(payload)
+
+    then:
+    result == expectedResult
+
+    where:
+    payload                                    || expectedResult
+    "\"from_account_id\":\"Account-123\""      || "\"from_account_id\":\"**MASKED**\""
+    "\"memo\":\"Test memo\""                   || "\"memo\":\"**MASKED**\""
+    "\"repayment_account_id\":\"Account-456\"" || "\"repayment_account_id\":\"**MASKED**\""
+    "\"to_account_id\":\"Account-789\""        || "\"to_account_id\":\"**MASKED**\""
+  }
+
+  @Unroll
+  def "maskPayload() masks TransferAmountOptionListOptions (Transfer) JSON fields"() {
+    when:
+    String result = MdxLogMasker.maskPayload(payload)
+
+    then:
+    result == expectedResult
+
+    where:
+    payload                                      || expectedResult
+    "\"destination_account_id\":\"Account-123\"" || "\"destination_account_id\":\"**MASKED**\""
+    "\"source_account_id\":\"Account-456\""      || "\"source_account_id\":\"**MASKED**\""
+  }
+}
diff --git a/mdx-web/src/main/java/com/mx/path/model/mdx/web/filter/PathRequestLoggingFilter.java b/mdx-web/src/main/java/com/mx/path/model/mdx/web/filter/PathRequestLoggingFilter.java
index 30e542bf..36ff3017 100644
--- a/mdx-web/src/main/java/com/mx/path/model/mdx/web/filter/PathRequestLoggingFilter.java
+++ b/mdx-web/src/main/java/com/mx/path/model/mdx/web/filter/PathRequestLoggingFilter.java
@@ -18,9 +18,9 @@
 import com.google.gson.FieldNamingPolicy;
 import com.google.gson.Gson;
 import com.google.gson.GsonBuilder;
-import com.mx.path.core.common.security.LogValueMasker;
 import com.mx.path.core.context.RequestContext;
 import com.mx.path.gateway.util.LoggingExceptionFormatter;
+import com.mx.path.model.mdx.model.MdxLogMasker;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -43,7 +43,6 @@ public class PathRequestLoggingFilter extends OncePerRequestFilter {
 
   // Statics
   private static final Gson GSON;
-  private static final LogValueMasker LOG_MASKER;
   private static Logger logger;
 
   public static void setLogger(Logger logger) {
@@ -85,6 +84,8 @@ protected final void doFilterInternal(HttpServletRequest request, HttpServletRes
       } finally {
         resetMDC();
       }
+
+      responseWrapper.copyBodyToResponse();
     }
   }
 
@@ -130,7 +131,7 @@ private void logRequest(ContentCachingRequestWrapper request, ContentCachingResp
     }
 
     MDC.put("request_method", request.getMethod());
-    MDC.put("request_uri", request.getRequestURI());
+    MDC.put("request_uri", String.valueOf(request.getRequestURL()));
 
     if (request.getQueryString() != null) {
       final Map<String, String> queryParams = this.buildQueryStringMap(request.getQueryString());
@@ -151,7 +152,7 @@ private void logRequest(ContentCachingRequestWrapper request, ContentCachingResp
 
     final String requestBody = new String(request.getContentAsByteArray(), StandardCharsets.UTF_8);
     if (!requestBody.isEmpty()) {
-      MDC.put("request_body", LOG_MASKER.maskPayload(requestBody));
+      MDC.put("request_body", MdxLogMasker.maskPayload(requestBody));
     } else {
       MDC.remove("request_body");
     }
@@ -178,11 +179,10 @@ private void logRequest(ContentCachingRequestWrapper request, ContentCachingResp
 
     final String responseBody = new String(response.getContentAsByteArray(), StandardCharsets.UTF_8);
     if (!responseBody.isEmpty()) {
-      MDC.put("response_body", LOG_MASKER.maskPayload(responseBody));
+      MDC.put("response_body", MdxLogMasker.maskPayload(responseBody));
     } else {
       MDC.remove("response_body");
     }
-    response.copyBodyToResponse();
 
     logger.info("Incoming request");
   }
@@ -285,14 +285,13 @@ private Map<String, String> maskHeaders(Map<String, String> headers) {
 
     final Map<String, String> maskedHeaders = new HashMap<>();
     headers.forEach((name, value) -> {
-      maskedHeaders.put(name, LOG_MASKER.maskHeaderValue(name, value));
+      maskedHeaders.put(name, MdxLogMasker.maskHeaderValue(name, value));
     });
     return maskedHeaders;
   }
 
   static {
     GSON = new GsonBuilder().disableHtmlEscaping().setFieldNamingPolicy(FieldNamingPolicy.IDENTITY).create();
-    LOG_MASKER = new LogValueMasker();
     logger = LoggerFactory.getLogger(PathRequestLoggingFilter.class);
   }
 }
diff --git a/mdx-web/src/test/groovy/com/mx/path/model/mdx/web/filter/PathRequestLoggingFilterTest.groovy b/mdx-web/src/test/groovy/com/mx/path/model/mdx/web/filter/PathRequestLoggingFilterTest.groovy
index 0a197372..4721e722 100644
--- a/mdx-web/src/test/groovy/com/mx/path/model/mdx/web/filter/PathRequestLoggingFilterTest.groovy
+++ b/mdx-web/src/test/groovy/com/mx/path/model/mdx/web/filter/PathRequestLoggingFilterTest.groovy
@@ -147,7 +147,7 @@ class PathRequestLoggingFilterTest extends Specification {
   def "captures expected data in MDC"() {
     given:
     def testNoMDCClearingSubject = new PathRequestLoggingFilterWithNoMDCClearing()
-    when(request.getRequestURI()).thenReturn("/testing")
+    when(request.getRequestURL()).thenReturn(new StringBuffer("https://localhost:13024/testing"))
     when(request.getQueryString()).thenReturn("param1=value1&param2=value2")
     when(request.getMethod()).thenReturn("GET")
 
@@ -189,7 +189,7 @@ class PathRequestLoggingFilterTest extends Specification {
     MDC.get("session_trace_id") == "ebebebe"
     MDC.get("device_trace_id") == "device123"
     MDC.get("request_method") == "GET"
-    MDC.get("request_uri") == "/testing"
+    MDC.get("request_uri") == "https://localhost:13024/testing"
     MDC.get("query_params") == "param1: value1\nparam2: value2\n"
     MDC.get("request_headers_json") == "{\"Accept\":\"application/vnd.mx.mdx.v6+json\",\"x-request-token\":\"**MASKED**\",\"mx-device-ip-address\":\"10.10.10.1\"}"
     MDC.get("request_headers") == "Accept: application/vnd.mx.mdx.v6+json\nx-request-token: **MASKED**\nmx-device-ip-address: 10.10.10.1\n"