Skip to content

Commit a99f380

Browse files
authored
Bugfix: overzealous data masking rule for US social security numbers (#751)
* Updated pipeline script scripts/build/validate-custom-metadata-records.apex to validate that the regex values in LogEntryDataMaskRule__mdt work as expected * Corrected the regular expressions used in data mask rule 'SocialSecurityNumber' to be stricter to avoid incorrectly masking credit card numbers as social security numbers * Scope creep: made a small optimization in ComponentLogger to cache the field maps for LogEntryEvent__e * Previously, it would re-call the describe method for every component log entry that was setting 1 or more custom fields
1 parent 2894401 commit a99f380

8 files changed

Lines changed: 128 additions & 32 deletions

File tree

README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,15 @@
55

66
The most robust observability solution for Salesforce experts. Built 100% natively on the platform, and designed to work seamlessly with Apex, Lightning Components, Flow, Process Builder & integrations.
77

8-
## Unlocked Package - v4.14.6
8+
## Unlocked Package - v4.14.7
99

10-
[![Install Unlocked Package in a Sandbox](./images/btn-install-unlocked-package-sandbox.png)](https://test.salesforce.com/packaging/installPackage.apexp?p0=04t5Y0000015oRhQAI)
11-
[![Install Unlocked Package in Production](./images/btn-install-unlocked-package-production.png)](https://login.salesforce.com/packaging/installPackage.apexp?p0=04t5Y0000015oRhQAI)
10+
[![Install Unlocked Package in a Sandbox](./images/btn-install-unlocked-package-sandbox.png)](https://test.salesforce.com/packaging/installPackage.apexp?p0=04t5Y0000015oRrQAI)
11+
[![Install Unlocked Package in Production](./images/btn-install-unlocked-package-production.png)](https://login.salesforce.com/packaging/installPackage.apexp?p0=04t5Y0000015oRrQAI)
1212
[![View Documentation](./images/btn-view-documentation.png)](https://jongpie.github.io/NebulaLogger/)
1313

14-
`sf package install --wait 20 --security-type AdminsOnly --package 04t5Y0000015oRhQAI`
14+
`sf package install --wait 20 --security-type AdminsOnly --package 04t5Y0000015oRrQAI`
1515

16-
`sfdx force:package:install --wait 20 --securitytype AdminsOnly --package 04t5Y0000015oRhQAI`
16+
`sfdx force:package:install --wait 20 --securitytype AdminsOnly --package 04t5Y0000015oRrQAI`
1717

1818
---
1919

nebula-logger/core/main/configuration/customMetadata/LogEntryDataMaskRule.SocialSecurityNumber.md-meta.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@
1616
</values>
1717
<values>
1818
<field>ReplacementRegEx__c</field>
19-
<value xsi:type="xsd:string">$1XXX-XX-$4</value>
19+
<value xsi:type="xsd:string">$1XXX-XX-$4$5</value>
2020
</values>
2121
<values>
2222
<field>SensitiveDataRegEx__c</field>
23-
<value xsi:type="xsd:string">(^|[ ])(\d{3})[- ]*(\d{2})[- ]*(\d{4})</value>
23+
<value xsi:type="xsd:string">(^|[ ])(\d{3})[- ]*(\d{2})[- ]*(\d{4})([ ]|$)</value>
2424
</values>
2525
</CustomMetadata>

nebula-logger/core/main/logger-engine/classes/ComponentLogger.cls

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,16 @@ public inherited sharing class ComponentLogger {
2323
set;
2424
}
2525

26+
private static final Map<String, Schema.SObjectField> LOG_ENTRY_EVENT_FIELD_NAME_TO_FIELD {
27+
get {
28+
if (LOG_ENTRY_EVENT_FIELD_NAME_TO_FIELD == null) {
29+
LOG_ENTRY_EVENT_FIELD_NAME_TO_FIELD = Schema.LogEntryEvent__e.SObjectType.getDescribe().fields.getMap();
30+
}
31+
return LOG_ENTRY_EVENT_FIELD_NAME_TO_FIELD;
32+
}
33+
set;
34+
}
35+
2636
static {
2737
LoggerStackTrace.ignoreOrigin(ComponentLogger.class);
2838
LoggerStackTrace.ignoreOrigin(LoggerStackTrace.SourceLanguage.JavaScript, LOGGER_COMPONENT_NAME);
@@ -94,9 +104,8 @@ public inherited sharing class ComponentLogger {
94104
return resolvedFieldToFieldValue;
95105
}
96106

97-
Map<String, Schema.SObjectField> fieldNameToField = Schema.LogEntryEvent__e.SObjectType.getDescribe().fields.getMap();
98107
for (String fieldName : fieldNameToValue.keySet()) {
99-
Schema.SObjectField field = fieldNameToField.get(fieldName);
108+
Schema.SObjectField field = LOG_ENTRY_EVENT_FIELD_NAME_TO_FIELD.get(fieldName);
100109
if (field != null) {
101110
resolvedFieldToFieldValue.put(field, fieldNameToValue.get(fieldName));
102111
}

nebula-logger/core/main/logger-engine/classes/Logger.cls

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
global with sharing class Logger {
1616
// There's no reliable way to get the version number dynamically in Apex
1717
@TestVisible
18-
private static final String CURRENT_VERSION_NUMBER = 'v4.14.6';
18+
private static final String CURRENT_VERSION_NUMBER = 'v4.14.7';
1919
private static final System.LoggingLevel FALLBACK_LOGGING_LEVEL = System.LoggingLevel.DEBUG;
2020
private static final List<LogEntryEventBuilder> LOG_ENTRIES_BUFFER = new List<LogEntryEventBuilder>();
2121
private static final String MISSING_SCENARIO_ERROR_MESSAGE = 'No logger scenario specified. A scenario is required for logging in this org.';

nebula-logger/core/main/logger-engine/lwc/logger/logEntryBuilder.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import FORM_FACTOR from '@salesforce/client/formFactor';
66
import { log as lightningLog } from 'lightning/logger';
77
import { LoggerStackTrace } from './loggerStackTrace';
88

9-
const CURRENT_VERSION_NUMBER = 'v4.14.6';
9+
const CURRENT_VERSION_NUMBER = 'v4.14.7';
1010

1111
const LOGGING_LEVEL_EMOJIS = {
1212
ERROR: '⛔',

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "nebula-logger",
3-
"version": "4.14.6",
3+
"version": "4.14.7",
44
"description": "The most robust logger for Salesforce. Works with Apex, Lightning Components, Flow, Process Builder & Integrations. Designed for Salesforce admins, developers & architects.",
55
"author": "Jonathan Gillespie",
66
"license": "MIT",

scripts/build/validate-custom-metadata-records.apex

Lines changed: 103 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,37 +4,123 @@
44
// LogEntryDataMaskRule__mdt checks
55
// The field LogEntryDataMaskRule__mdt.IsEnabled__c should be set to true for any records included in the packages.
66
for (LogEntryDataMaskRule__mdt record : [SELECT DeveloperName, IsEnabled__c FROM LogEntryDataMaskRule__mdt ORDER BY DeveloperName]) {
7-
if (record.IsEnabled__c == false) {
8-
throw new System.IllegalArgumentException('😡 IsEnabled__c field should be set to true for LogEntryDataMaskRule.' + record.DeveloperName);
9-
}
7+
if (record.IsEnabled__c == false) {
8+
throw new System.IllegalArgumentException('😡 IsEnabled__c field should be set to true for LogEntryDataMaskRule.' + record.DeveloperName);
9+
}
1010
}
11-
System.debug('🥳 LogEntryDataMaskRule__mdt records have been correctly configured!');
11+
System.debug('🥳 LogEntryDataMaskRule__mdt records have been correctly enabled!');
12+
13+
// LogEntryDataMaskRule__mdt checks
14+
// The fields SensitiveDataRegEx__c and ReplacementRegEx__c should contain valid regex patterns that mask the appropriate data.
15+
// US social security number rule
16+
LogEntryDataMaskRule__mdt socialSecurityNumberRule = LogEntryDataMaskRule__mdt.getInstance('SocialSecurityNumber');
17+
System.Assert.areEqual(
18+
'>>> Here is a number XXX-XX-6789 which is the correct length for US SSN',
19+
'>>> Here is a number 123-45-6789 which is the correct length for US SSN'
20+
.replaceAll(socialSecurityNumberRule.SensitiveDataRegEx__c, socialSecurityNumberRule.ReplacementRegEx__c)
21+
);
22+
System.Assert.areEqual(
23+
'>>> Here is a number XXX-XX-6789 which is the correct length for US SSN',
24+
'>>> Here is a number 123 45 6789 which is the correct length for US SSN'
25+
.replaceAll(socialSecurityNumberRule.SensitiveDataRegEx__c, socialSecurityNumberRule.ReplacementRegEx__c)
26+
);
27+
List<String> invalidSSNLookalikes = new List<String>{
28+
'0123-45-6789',
29+
'0123-45-67890',
30+
'23-45-678',
31+
'23-45-6789',
32+
'123-45-678',
33+
// US phone number formats
34+
'111-555-1234',
35+
'111 555 1234',
36+
'(111) 555-1234'
37+
};
38+
for (String lookalike : invalidSSNLookalikes) {
39+
String originalInput = '>>> Here is a value ' + lookalike + ' and it is not a valid form for US SSN, so masking should not occur';
40+
System.Assert.areEqual(
41+
originalInput,
42+
originalInput.replaceAll(socialSecurityNumberRule.SensitiveDataRegEx__c, socialSecurityNumberRule.ReplacementRegEx__c)
43+
);
44+
}
45+
System.debug(
46+
'🥳 LogEntryDataMaskRule__mdt record \'SocialSecurityNumber\' has been correctly configured with functioning regexes for sensitive data & replacement!'
47+
);
48+
49+
// Mastercard credit card rule
50+
LogEntryDataMaskRule__mdt mastercardCreditCardNumberRule = LogEntryDataMaskRule__mdt.getInstance('MastercardCreditCardNumber');
51+
System.Assert.areEqual(
52+
'>>> Here is a credit card number ****-****-****-0005 which is the correct length for Mastercard',
53+
'>>> Here is a credit card number 5000-1111-2222-0005 which is the correct length for Mastercard'
54+
.replaceAll(mastercardCreditCardNumberRule.SensitiveDataRegEx__c, mastercardCreditCardNumberRule.ReplacementRegEx__c)
55+
);
56+
List<String> invalidMastercardLookalikes = new List<String>{
57+
'05000-1111-2222-0005',
58+
'05000-1111-2222-00050',
59+
'000-1111-2222-000',
60+
'000-1111-2222-0005',
61+
'5000-1111-2222-000'
62+
};
63+
for (String lookalike : invalidMastercardLookalikes) {
64+
String originalInput = '>>> Here is a value ' + lookalike + ' and it is not a valid form for Mastercard, so masking should not occur';
65+
System.Assert.areEqual(
66+
originalInput,
67+
originalInput.replaceAll(socialSecurityNumberRule.SensitiveDataRegEx__c, socialSecurityNumberRule.ReplacementRegEx__c)
68+
);
69+
}
70+
System.debug(
71+
'🥳 LogEntryDataMaskRule__mdt record \'MastercardCreditCardNumber\' has been correctly configured with functioning regexes for sensitive data & replacement!'
72+
);
73+
74+
// Visa credit card rule
75+
LogEntryDataMaskRule__mdt visaCreditCardNumberRule = LogEntryDataMaskRule__mdt.getInstance('VisaCreditCardNumber');
76+
System.Assert.areEqual(
77+
'>>> Here is a credit card number ****-****-****-0004 which is the correct length for Visa',
78+
'>>> Here is a credit card number 4000-1111-2222-0004 which is the correct length for Visa'
79+
.replaceAll(visaCreditCardNumberRule.SensitiveDataRegEx__c, visaCreditCardNumberRule.ReplacementRegEx__c)
80+
);
81+
List<String> invalidVisaLookalikes = new List<String>{
82+
'04000-1111-2222-0004',
83+
'04000-1111-2222-00040',
84+
'000-1111-2222-000',
85+
'000-1111-2222-0004',
86+
'4000-1111-2222-000'
87+
};
88+
for (String lookalike : invalidVisaLookalikes) {
89+
String originalInput = '>>> Here is a value ' + lookalike + ' and it is not a valid form for Visa, so masking should not occur';
90+
System.Assert.areEqual(
91+
originalInput,
92+
originalInput.replaceAll(socialSecurityNumberRule.SensitiveDataRegEx__c, socialSecurityNumberRule.ReplacementRegEx__c)
93+
);
94+
}
95+
System.debug(
96+
'🥳 LogEntryDataMaskRule__mdt record \'VisaCreditCardNumber\' has been correctly configured with functioning regexes for sensitive data & replacement!'
97+
);
1298

1399
// LoggerParameter__mdt checks
14100
// The field LoggerParameter__mdt.Description__c is a long textarea field, so it can't be marked as required - but every record
15101
// should have the field populated before being added to the unlocked & managed packages.
16102
for (LoggerParameter__mdt record : [SELECT DeveloperName, Description__c FROM LoggerParameter__mdt ORDER BY DeveloperName]) {
17-
if (record.Description__c == null) {
18-
throw new System.IllegalArgumentException('😡 Description__c field should be populated for LoggerParameter.' + record.DeveloperName);
19-
}
103+
if (record.Description__c == null) {
104+
throw new System.IllegalArgumentException('😡 Description__c field should be populated for LoggerParameter.' + record.DeveloperName);
105+
}
20106
}
21-
System.debug('🥳 LoggerParameter__mdt records have been correctly configured!');
107+
System.debug('🥳 LoggerParameter__mdt records have been correctly populated with a description!');
22108

23109
// LoggerSObjectHandler__mdt checks
24110
// The field LoggerSObjectHandler__mdt.IsEnabled__c should be set to true for any records included in the packages.
25111
for (LoggerSObjectHandler__mdt record : [SELECT DeveloperName, IsEnabled__c FROM LoggerSObjectHandler__mdt ORDER BY DeveloperName]) {
26-
if (record.IsEnabled__c == false) {
27-
throw new System.IllegalArgumentException('😡 IsEnabled__c field should be set to true for LoggerSObjectHandler.' + record.DeveloperName);
28-
}
112+
if (record.IsEnabled__c == false) {
113+
throw new System.IllegalArgumentException('😡 IsEnabled__c field should be set to true for LoggerSObjectHandler.' + record.DeveloperName);
114+
}
29115
}
30-
System.debug('🥳 LoggerSObjectHandler__mdt records have been correctly configured!');
116+
System.debug('🥳 LoggerSObjectHandler__mdt records have been correctly enabled!');
31117

32118
// LogStatus__mdt checks
33119
// The field LogStatus__mdt.IsActive__c should be set to true for any records included in the packages.
34120
for (LogStatus__mdt record : [SELECT DeveloperName, IsActive__c FROM LogStatus__mdt ORDER BY DeveloperName]) {
35-
// TODO rename LogStatus__mdt.IsActive__c to IsEnabled__c for consistency with other objects
36-
if (record.IsActive__c == false) {
37-
throw new System.IllegalArgumentException('😡 IsActive__c field should be set to true for LogStatus.' + record.DeveloperName);
38-
}
121+
// TODO rename LogStatus__mdt.IsActive__c to IsEnabled__c for consistency with other objects
122+
if (record.IsActive__c == false) {
123+
throw new System.IllegalArgumentException('😡 IsActive__c field should be set to true for LogStatus.' + record.DeveloperName);
124+
}
39125
}
40-
System.debug('🥳 LogStatus__mdt records have been correctly configured!');
126+
System.debug('🥳 LogStatus__mdt records records have been correctly enabled!');

sfdx-project.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@
99
"path": "./nebula-logger/core",
1010
"definitionFile": "./config/scratch-orgs/build-base-scratch-def.json",
1111
"scopeProfiles": true,
12-
"versionNumber": "4.14.6.NEXT",
13-
"versionName": "Custom Field Mappings Support for Lightning Components",
14-
"versionDescription": "Added the ability to set custom fields in JavaScript via a new function setField() in logEntryBuilder.js. This is equivalent to the Apex method overloads setField() in LogEntryEventBuilder.cls that were introduced in v4.13.14.",
12+
"versionNumber": "4.14.7.NEXT",
13+
"versionName": "Bugfix: US Social Security Number Data Mask Rule",
14+
"versionDescription": "Corrected the regular expressions used in data mask rule 'SocialSecurityNumber' to be stricter to avoid incorrectly masking credit card numbers as social security numbers",
1515
"releaseNotesUrl": "https://github.com/jongpie/NebulaLogger/releases",
1616
"unpackagedMetadata": {
1717
"path": "./nebula-logger/extra-tests"
@@ -191,6 +191,7 @@
191191
"Nebula Logger - Core@4.14.4-optionally-auto-call-lightning-logger-lwc": "04t5Y0000015oRNQAY",
192192
"Nebula Logger - Core@4.14.5-added-logger-settings-to-utility-bar": "04t5Y0000015oRXQAY",
193193
"Nebula Logger - Core@4.14.6-custom-field-mappings-support-for-lightning-components": "04t5Y0000015oRhQAI",
194+
"Nebula Logger - Core@4.14.7-bugfix:-us-social-security-number-data-mask-rule": "04t5Y0000015oRrQAI",
194195
"Nebula Logger - Core Plugin - Async Failure Additions": "0Ho5Y000000blO4SAI",
195196
"Nebula Logger - Core Plugin - Async Failure Additions@1.0.0": "04t5Y0000015lhiQAA",
196197
"Nebula Logger - Core Plugin - Async Failure Additions@1.0.1": "04t5Y0000015lhsQAA",

0 commit comments

Comments
 (0)