Enable PII protection in the Tealeaf Web SDK

Before releasing your app with the Tealeaf SDK to production, you must take measures to secure your end users' personally identifiable information (PII).

Acoustic Tealeaf supports two levels of privacy protection:

  1. Client-side level. By configuring privacy settings in the Tealeaf SDK, you can ensure that PII is never captured.
  2. Server-side level. This is an additional protection layer that you can enable for the PII that has bypassed the client-side rules. For instructions, see Mask sensitive data and protect user data.

The current instruction deals with privacy protection at the client-side level.

For practical purposes, we distinguish between three categories of data.

  • 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, calendars, radio buttons, checkboxes and other.
  • 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 on the page, rather than in an input element. To prevent Tealeaf from capturing this data, configure the SDK to remove it from the DOM snapshots. Conditional logic is supported.
  • User interactions with specific content in your web application. This type of protection is needed in extremely rare circumstances where a click or a tap itself may give away sensitive information. A typical example is a virtual keyboard for password entry.

Protecting user input data

Configuration options

To enable privacy protection for input fields, open tealeaf.js and find the services.message.privacy object. You can identify UI elements using CSS selectors based on name, ID or class.

ParameterValuesDescription
excludeBoolean. Default value - false. Determines how the rules work.

- If set to false (the default), the list of targets is a blacklist. The SDK captures user input from all UI elements except those specified in the list.
- If set to true, the list of targets is a whitelist. The SDK captures user input only from UI elements in the list. Interactions with other elements are not captured.
targetsArray of selectors that match elements. Either standard CSS selectors, or element selector objects can be used. They can be mixed within one array.CSS selectors are entered as strings. For example, to match all password input fields, use"input[type=password]", and to match all elements with the class "tlPrivate" use ".tlPrivate".

Selector objects also identify elements on the page, but use a different syntax. See the "Objects nested into the targets parameter" section below for details.
maskAttributes- Boolean - use the true value to mask attributes completely masking.
- Function - lets you selectively apply privacy masking on specific attributes.
You may need this parameter if you use the logging of custom attributes and there is a requirement to mask sensitive information in the attributes or inner text of certain target elements.

If you use a function, it must accept element IDs and attribute list.
maskType1 - the field appears blank
2 - the replacement is a fixed string ("XXXXX")
3 - each character is replaced by its character class ("XxxxxXxxxx999").
4 - custom (your own masking function)
Defines how protected data will be displayed in session replays.
maskFunctionFunctionA custom masking function. You must use this parameter when settingmaskType to 4.

Objects nested into the targets parameter

ObjectValuesDescription
idString or regex matching element ID(s)The identifier of a UI element. Note this may not be the HTML ID of the element, depending on the idType.
idTypeInteger. Supported values:

-1 - HTML ID
-2 - XPath
-3 - HTML name or other element attribute identifier
Indicates what kind of identifier is being used.
Note: for idType -1 and -2 it is usually preferable to use a CSS selector (e.g. #my-id) instead. However, if a regex can be used to match several similar elements, this notation is a useful alternative to CSS selectors. It also allows elements to be selected by XPath.

📘

Note

When using id and idType to identify UI elements, make sure they match the values reported in session replay on the Raw Data tab.

Objects nested into the id parameter

ObjectValuesDescription
regexString, for example "^creditCard.\*"A regular expression that matches several input elements.
flagsStringFlags modifying the behaviour of the regular expression (e.g. case sensitive vs. case insensitive matching)

Examples

To enable different mask types, you must specify targets for each type.

privacy: [{
        targets: [
            "input [type=password]",
            ".pii"
        ],
        maskType: 3
    },
    {
        targets: [{
            id: "ssn"
            idType: -1
        }],
        maskType: 1
    }
]

You can combine different identification methods within the targets array.

targets: [{
        id: "ssn",
        idType: -1
    },
    {
        id: {
            regex: "^creditCard.*",
            flags: "g"
        },
        idType: -1
    },
    "input[type=password]",
    ".pii"
]

Here is a sample masking function. It instructs to expose only the last 4 digits of an account number.

privacy: [{
    targets: [{
        id: "accountNumber",
        idType: -1
    }],
    maskType: 4,
    maskFunction: function(val) {
        var maskedVal = "XXXXXX";
        if (val.length === 10) {
            maskedVal += val.substr(-4);
        }
        return maskedVal;
    }
}]

Privacy masking of custom attributes and inner text logged with type 4 and type 9 messages

message: {
    privacy: [{
        targets: [
            "input [type=password]"
        ],
        maskAttributes: true,
        maskType: 3
    }, {
        targets: [{
            id: "ssn"
            idType: -1
        }],
        maskType: 1,
        maskAttributes: function(id, attributes) {
            if (attributes.secret) {
                // apply selective privacy to the attribute containing sensitive information.
                attributes.secret = "BLOCKED";
            }
            // Return the object which will be logged by the library.
            return attributes;
        }
    }]
}

If you use a function to log custom attributes, it must contain the following parameters:

  • id - the ID of a UI element. String
  • attributes - an object containing name-value pairs
  • returns - an object containing the masked values which will be logged by the library

Best practices

  • Always validate the syntax in the SDK JavaScript file after making changes to it.
  • Always use CSS selectors to identify input elements within an iframe.
  • When you use regex rules or CSS selectors, be mindful of the performance impact. Always validate the rule is working as intended.
  • 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.
  • Targets in a list are processed in the order they are specified in the library.
  • If you have multiple targets objects, each with their own maskType, they are processed in the order specified in the library.

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 if possible. 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.
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;
    }
}],
  • Using DOM privacy patterns. These are regular expressions that match against the HTML content in the DOM snapshots taken by the SDK 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.

📘

Note

To mask data that users enter into fields, or select in menus and other input elements, see the Protecting user input data section above.

Configuration options for privacy patterns

To enable a privacy pattern for page content data, open tealeaf.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

Examples

Example with string replacements

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

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

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;
        }
    }
]

Best practices

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

Protecting user interactions

This type of PII protection does not block the content itself from being captured as part of a DOM snapshot. It only blocks the recording of user interaction events with the content.

Configuration instructions

  1. Open tealeaf.js.
  2. In the configuration section, find the config.core object.
  3. Update the blockedElements parameter.
ParameterValueDescription
blockedElementsString or array of string The CSS selector of a UI element. For this element and its descendants, no user interaction will be recorded.

Since this list is evaluated for each event, specifying inefficient selectors can cause performance issues.

Example 1

Let's say an application displays a virtual keyboard for password entry. If the virtual keyboard is contained within a container div element, do the following:

  1. Specify a CSS selector for the div element.
<div id="virtualKeyboard" class="keyboard">
    ...
</div>
  1. In the tealeaf.js file, update the blockedElements parameter.
config = {
        core: {
            buildNote: "Light 2024-02-16",
            blockedElements: ["#virtualKeyboard"],
            ieExcludedLinks: ["a[href*=\"javascript:void\"]", "input[onclick*='javascript:']"],
            blockedUserAgents: [{
                regex: "(Google|Bing|Face|DuckDuck|Exa)bot|spider|archiver",
                flags: "i"
            }, "PhantomJS"],
        }
  1. Navigate to the services.message.privacyPatterns object.

Example 2

Consider a panel in which sensitive information spans across multiple lines, for example when a user must act on a description of a health issue.

<div id="health_description">
    ...
</div>
<input type="button" id="health_description_confirmation" value="Confirm" />

To configure your privacy pattern to work across multiple lines, you can use a character class (\\s) and its negation (\S) together as follows: [\s\S].

Since the "." doesn't match the newline character in JS RegEx, use the alternative [\s\S] until such time as all browsers support the dotAll flag:

message: {
    privacyPatterns: [{
        pattern: {
            regex: "<div[^>]*?id=\"health_description\"[\\s\\S]*?<\\/div>",
            flags: "g"
        },
        replacement: ""
    }]
}

Best practices

Perform adequate testing to check if the blocking is working as intended and not causing any performance issues.