Enable privacy protection in the Connect Web SDK

Before releasing your web app with the Connect library to production, you must configure the library to restrict capturing your end users' personally identifiable information (PII).

For practical purposes, we distinguish between two categories of data to protect.

  • User input data. It originates from interaction with user interface elements in your web application. Typically, these are text input fields, but these can also be drop-down lists, date/time selectors, radio buttons and checkboxes.
  • Page content displayed by your web application. For example, a user can visit the Your account section and view their home address, phone number and email address. To prevent the library from capturing this data, configure the SDK to remove it from the DOM snapshots. Conditional logic is supported.

Protecting user input data

This section explains how to enable privacy protection for interactive elements. The workflow varies slightly depending on where the library is hosted: in the Acoustic CDN or on your own server.

If your integration snippet is linking to our CDN, you will need to add your privacy settings to it. See examples below.

If you are hosting the library on your own server, here are steps to follow.

  1. Open acoconnect.js.
  2. Find the services.message.privacy object.
  3. Configure your privacy rules (see examples below).
  4. (recommended) Validate the syntax in the file.
  5. Push the changes to the server.

White-list or black-list approach

  • If your website has many interactive elements collecting visitors' personal data, consider the white-list approach. If you set the exclude parameter to true, the library will exclude all user input from capturing except for the HTML elements you specify as targets.
<script src="https://cdn.goacoustic.com/connect/latest/acoconnect.js"></script>
<script>
  let config = TLT.getDefaultConfig();
  // This will append additional privacy rules.
  config.services.message.privacy.push({
    exclude: true,
    targets: ["input[type=search]", {
      id: "country",
      idType: -1
    }],
    maskType: 1
  })
  window.TLT && window.TLT.initLibAdv("YOUR APP KEY", "YOUR ENDPOINT URL", config, true, true, true, true, true);
</script>
message: {
  privacy: [{
    exclude: true,
    targets: [
      "input[type=search]",
      {
        id: "country",
        idType: -1
      }
    ],
    maskType: 1
  }]
}
  • If you'd rather specify which interactive elements to exclude from capturing, go with the black-list approach. To enable it, set exclude to false.
<script src="https://cdn.goacoustic.com/connect/latest/acoconnect.js"></script>
<script>
    let config = TLT.getDefaultConfig();
    // This will append additional privacy rules.
    config.services.message.privacy.push({
        exclude: false,
        targets: [
            "input[type=email]",
            "input[type=tel]",
            "select[form=application]",
            "textarea[form=application]"
        ],
        maskType: 1
    })
    window.TLT && window.TLT.initLibAdv("YOUR APP KEY", "YOUR ENDPOINT URL", config, true, true, true, true, true);
</script>
message: {
  privacy: [{
    exclude: false,
    targets: [
      "input[type=email]",
      "input[type=tel]",
      "select[form=application]",
      "textarea[form=application]"
    ],
    maskType: 1
  }]
}

Creating a rule for a group of HTML elements

You can create a rule for a group of interactive elements. To identify elements within the group, use CSS attribute selectors and class selectors.

👍

Important

Use only the attributes of the input element, not of its parent elements. For example, the class of the <div> element isn't a valid identifier for a privacy rule.

This is how you can allow the library to capture input fields that do not contain any PII:

<script src="https://cdn.goacoustic.com/connect/latest/acoconnect.js"></script>
<script>
    let config = TLT.getDefaultConfig();
    // This will append additional privacy rules.
    config.services.message.privacy.push({
        exclude: true,
        targets: [
            "input[name*=model]",
            "select[id=group_1]",
            ".district"
        ],
        maskType: 3
    })
    window.TLT && window.TLT.initLibAdv("YOUR APP KEY", "YOUR ENDPOINT URL", config, true, true, true, true, true);
</script>
message: {
  privacy: [{
    exclude: true,
    targets: [
      "input[name*=model]",
      "select[id=group_1]",
      ".district"
    ],
    maskType: 3
  }]
}

Creating a rule for a particular HTML element

You can create a rule based on the IDs of interactive elements. The ID of each element must be unique within a page.

🚧

Warning

This approach doesn't work for input elements within a frame/iframe.

You can specify the ID either as a CSS selector (#myid) or as an identifier of type -1. Both are valid. Here is an example.

<script src="https://cdn.goacoustic.com/connect/latest/acoconnect.js"></script>
<script>
    let config = TLT.getDefaultConfig();
    // This will append additional privacy rules.
    config.services.message.privacy.push({
        exclude: false,
        targets: [
            "#ssn", // ID specified as CSS selector
            {
                id: "symptoms",  // ID specified by name and idType
                idType: -1
            }
        ],
        maskType: 2
    })
    window.TLT && window.TLT.initLibAdv("YOUR APP KEY", "YOUR ENDPOINT URL", config, true, true, true, true, true);
</script>
message: {
    privacy: [{
            targets: "#ssn",
            {
                id: "symptoms",
                idType: -1
            }],
        maskType: 2
    }]
}

👍

Advice

You can combine different types of targets within a privacy rule as long as they require the same mask type.

Summary of required configuration parameters

ParameterValuesDescription
excludeBoolean. Default value - false. Determines how a rule works.

- If set to false, the targets are treated as a blacklist. The library captures user input from all UI elements except for those specified in the target.
- If set to true, the targets are treated as a whitelist. The library captures user input only from UI elements in the target. Interactions with other elements are not captured.
targetsArray Identifies the interactive elements that the rule applies to. Use CSS selectors, nested objects or a mixture of both.
maskType1 - the field appears blank
2 - the replacement is a fixed string ("XXXXX")
3 - each character is replaced by its character class (x, X, 9 or @).
4 - custom (your own masking function)
Defines how protected data is displayed in session replays.

The targets parameter can contain objects. Each object requires two parameters: id and idType.

ParameterValuesDescription
idString The identifier of an HTML element. It must be unique.
idTypeInteger. Supported values:

-1 - stands for the HTML id attribute
-2 - stands for an XPath
Indicates what kind of identifier is being used.

Best practices

  • Do not rely on XPath IDs for privacy. If they are unavoidable, ensure adequate testing is performed so that content changes do not invalidate your privacy rules. Try to minimize the XPath length by adding unique static HTML ID to the nearest container element.
  • If you run into an issue while using multiple privacy rules, try selectively removing individual rules to isolate the issue.

Masking page content

There are two approaches to masking page content displayed by your web application or contained within the attributes of a UI element:

  • Using a custom masking function (recommended)
  • Using DOM privacy patterns

Option A: Adding a custom masking function for page content

When a DOM snapshot is taken, any element that matches the rule will be passed to the custom masking function, which can manipulate the DOM element to mask content before the snapshot is sent to the Acoustic Cloud servers.

<script src="https://cdn.goacoustic.com/connect/latest/acoconnect.js"></script>
<script>
    let config = TLT.getDefaultConfig();
    // This will append additional privacy rules.
    config.services.message.privacy.push({
        targets: [{
                id: "accNum",
                idType: -1
            },
            ".tlPrivate"
        ],
        maskType: 4,
        maskFunction: function(val, element) {
            if (element && element.innerText) {
                // Directly modify the DOM element
                element.innerText = "BLOCKED BY TEALEAF SDK";
            }
            // Returns unmasked value. May choose to apply masking to the value as appropriate.
            return val;
        }
    })
    window.TLT && window.TLT.initLibAdv("YOUR APP KEY", "YOUR ENDPOINT URL", config, true, true, true, true, true);
</script>
privacy: [{
    targets: [{
            id: "accNum",
            idType: -1
        },
        ".tlPrivate"
    ],
    maskType: 4,
    maskFunction: function(val, element) {
        if (element && element.innerText) {
            // Directly modify the DOM element
            element.innerText = "BLOCKED BY TEALEAF SDK";
        }
        // Returns unmasked value. May choose to apply masking to the value as appropriate.
        return val;
    }
}],

For the list of supported parameters, see Enable privacy protection in the Connect Web SDK.

Option B: Adding DOM privacy patterns

DOM privacy patterns are regular expressions that match against the HTML content in the DOM snapshots taken by the Connect library so specific content can be masked before the snapshot is sent to the Acoustic Cloud servers. They are a useful alternative to custom masking functions in some circumstances, but are less efficient.

To enable a privacy pattern for page content data, open acoconnect.js and find the services.message.privacyPatterns object. The DOM snapshots containing the page HTML are treated as blocks of text against which the regular expressions are applied, with the aim of replacing PII with a mask such as "XXXXX".

ParameterNested objectsValuesDescription
patternregexA regular expression matching HTML content to be masked
patternflagsStringOption flag specifying regular expression behaviour (e.g. case sensitive or not, match all occurrences or not, etc.)
replacementN/AString or functionText to display in place of the restricted values

📘

Advice

Make sure the replacement patterns work as intended and don't cause any performance issues.

Example with string replacements

<script src="https://cdn.goacoustic.com/connect/latest/acoconnect.js"></script>
<script>
    let config = TLT.getDefaultConfig();
    // This will append additional privacy rules.
    config.services.message.privacyPatterns.push({
        // Restrict Social Security numbers
        pattern: {
            regex: "\\d{3}-\\d{2}-\\d{4}",
            flags: "g"
        },
        replacement: "XXX-XX-XXXX"
    }, {
        // Restrict phone numbers
        pattern: {
            regex: "\\d{3}-\\d{3}-\\d{4}",
            flags: "g"
        },
        replacement: "XXX-XXX-XXXX"
    }, {
        // Restrict US zipcode
        pattern: {
            regex: "\\d{5}-\\d{4}",
            flags: "g"
        },
        replacement: "XXXXX-XXXX"
    }
    })
    window.TLT && window.TLT.initLibAdv("YOUR APP KEY", "YOUR ENDPOINT URL", config, true, true, true, true, true);
</script>
message: {
    privacyPatterns: [{
            // Restrict Social Security numbers
            pattern: {
                regex: "\\d{3}-\\d{2}-\\d{4}",
                flags: "g"
            },
            replacement: "XXX-XX-XXXX"
        },
        {
            // Restrict phone numbers
            pattern: {
                regex: "\\d{3}-\\d{3}-\\d{4}",
                flags: "g"
            },
            replacement: "XXX-XXX-XXXX"
        },
        {
            // Restrict US zipcode
            pattern: {
                regex: "\\d{5}-\\d{4}",
                flags: "g"
            },
            replacement: "XXXXX-XXXX"
        }
    ]
}

Example of a full function replacement

<script src="https://cdn.goacoustic.com/connect/latest/acoconnect.js"></script>
<script>
    let config = TLT.getDefaultConfig();
    // This will append additional privacy rules.
    config.services.message.privacyPatterns.push({
        pattern: {
            regex: "\\d{3}-\\d{2}-\\d{4}",
            flags: "g"
        },
        replacement: function(ssn) {
            var retVal = "XXX-XX-";
            retVal += ssn.substr(-4);
            return retVal;
        }
    })
    window.TLT && window.TLT.initLibAdv("YOUR APP KEY", "YOUR ENDPOINT URL", config, true, true, true, true, true);
</script>
privacyPatterns: [
    {
        // Match Social Security numbers and mask the first 5 digits.
        pattern: {
            regex: "\\d{3}-\\d{2}-\\d{4}",
            flags: "g"
        },
        replacement: function (ssn) {
            var retVal = "XXX-XX-";
            retVal += ssn.substr(-4);
            return retVal;
        }
    }
]

Example of a partial function replacement

<script src="https://cdn.goacoustic.com/connect/latest/acoconnect.js"></script>
<script>
    let config = TLT.getDefaultConfig();
    // This will append additional privacy rules.
    config.services.message.privacyPatterns.push({
        pattern: {
            regex: "<div>(.*)\\sAccount Number:\\s*(\\d+)<\\/div>",
            flags: "g"
        },
        replacement: function(fullMatch, group1, group2) {
            var replacementNumber = "XXXXX" + (group2.length >= 8 ? group2.substr(-3) : ""),
                nameSplit = group1.split(" "),
                replacementName = nameSplit.shift() + " XXXXX",
                retVal;

            retVal = fullMatch.replace(group1, replacementName).replace(group2, replacementNumber);

            return retVal;
        }
    })
    window.TLT && window.TLT.initLibAdv("YOUR APP KEY", "YOUR ENDPOINT URL", config, true, true, true, true, true);
</script>
privacyPatterns: [
    /**
     * HTML to be masked: <div>Jane Smith's Account Number: 98776543</div>
     * Masked result: <div>Jane XXXXX Account Number: XXXXX543</div>
     * Note the double \\ escaping in the regex string to account for JSON string parsing.
     */
    {
        pattern: {
            regex: "<div>(.*)\\sAccount Number:\\s*(\\d+)<\\/div>",
            flags: "g"
        },
        replacement: function(fullMatch, group1, group2) {
            var replacementNumber = "XXXXX" + (group2.length >= 8 ? group2.substr(-3) : ""),
                nameSplit = group1.split(" "),
                replacementName = nameSplit.shift() + " XXXXX",
                retVal;

            retVal = fullMatch.replace(group1, replacementName).replace(group2, replacementNumber);

            return retVal;
        }
    }
]