Skip to content

Commit

Permalink
Bugfix: overzealous data masking rule for US social security numbers (#…
Browse files Browse the repository at this point in the history
…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
  • Loading branch information
jongpie authored Aug 28, 2024
1 parent 2894401 commit a99f380
Show file tree
Hide file tree
Showing 8 changed files with 128 additions and 32 deletions.
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@

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.

## Unlocked Package - v4.14.6
## Unlocked Package - v4.14.7

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

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

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

---

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@
</values>
<values>
<field>ReplacementRegEx__c</field>
<value xsi:type="xsd:string">$1XXX-XX-$4</value>
<value xsi:type="xsd:string">$1XXX-XX-$4$5</value>
</values>
<values>
<field>SensitiveDataRegEx__c</field>
<value xsi:type="xsd:string">(^|[ ])(\d{3})[- ]*(\d{2})[- ]*(\d{4})</value>
<value xsi:type="xsd:string">(^|[ ])(\d{3})[- ]*(\d{2})[- ]*(\d{4})([ ]|$)</value>
</values>
</CustomMetadata>
13 changes: 11 additions & 2 deletions nebula-logger/core/main/logger-engine/classes/ComponentLogger.cls
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,16 @@ public inherited sharing class ComponentLogger {
set;
}

private static final Map<String, Schema.SObjectField> LOG_ENTRY_EVENT_FIELD_NAME_TO_FIELD {
get {
if (LOG_ENTRY_EVENT_FIELD_NAME_TO_FIELD == null) {
LOG_ENTRY_EVENT_FIELD_NAME_TO_FIELD = Schema.LogEntryEvent__e.SObjectType.getDescribe().fields.getMap();
}
return LOG_ENTRY_EVENT_FIELD_NAME_TO_FIELD;
}
set;
}

static {
LoggerStackTrace.ignoreOrigin(ComponentLogger.class);
LoggerStackTrace.ignoreOrigin(LoggerStackTrace.SourceLanguage.JavaScript, LOGGER_COMPONENT_NAME);
Expand Down Expand Up @@ -94,9 +104,8 @@ public inherited sharing class ComponentLogger {
return resolvedFieldToFieldValue;
}

Map<String, Schema.SObjectField> fieldNameToField = Schema.LogEntryEvent__e.SObjectType.getDescribe().fields.getMap();
for (String fieldName : fieldNameToValue.keySet()) {
Schema.SObjectField field = fieldNameToField.get(fieldName);
Schema.SObjectField field = LOG_ENTRY_EVENT_FIELD_NAME_TO_FIELD.get(fieldName);
if (field != null) {
resolvedFieldToFieldValue.put(field, fieldNameToValue.get(fieldName));
}
Expand Down
2 changes: 1 addition & 1 deletion nebula-logger/core/main/logger-engine/classes/Logger.cls
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
global with sharing class Logger {
// There's no reliable way to get the version number dynamically in Apex
@TestVisible
private static final String CURRENT_VERSION_NUMBER = 'v4.14.6';
private static final String CURRENT_VERSION_NUMBER = 'v4.14.7';
private static final System.LoggingLevel FALLBACK_LOGGING_LEVEL = System.LoggingLevel.DEBUG;
private static final List<LogEntryEventBuilder> LOG_ENTRIES_BUFFER = new List<LogEntryEventBuilder>();
private static final String MISSING_SCENARIO_ERROR_MESSAGE = 'No logger scenario specified. A scenario is required for logging in this org.';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import FORM_FACTOR from '@salesforce/client/formFactor';
import { log as lightningLog } from 'lightning/logger';
import { LoggerStackTrace } from './loggerStackTrace';

const CURRENT_VERSION_NUMBER = 'v4.14.6';
const CURRENT_VERSION_NUMBER = 'v4.14.7';

const LOGGING_LEVEL_EMOJIS = {
ERROR: '⛔',
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "nebula-logger",
"version": "4.14.6",
"version": "4.14.7",
"description": "The most robust logger for Salesforce. Works with Apex, Lightning Components, Flow, Process Builder & Integrations. Designed for Salesforce admins, developers & architects.",
"author": "Jonathan Gillespie",
"license": "MIT",
Expand Down
120 changes: 103 additions & 17 deletions scripts/build/validate-custom-metadata-records.apex
Original file line number Diff line number Diff line change
Expand Up @@ -4,37 +4,123 @@
// LogEntryDataMaskRule__mdt checks
// The field LogEntryDataMaskRule__mdt.IsEnabled__c should be set to true for any records included in the packages.
for (LogEntryDataMaskRule__mdt record : [SELECT DeveloperName, IsEnabled__c FROM LogEntryDataMaskRule__mdt ORDER BY DeveloperName]) {
if (record.IsEnabled__c == false) {
throw new System.IllegalArgumentException('😡 IsEnabled__c field should be set to true for LogEntryDataMaskRule.' + record.DeveloperName);
}
if (record.IsEnabled__c == false) {
throw new System.IllegalArgumentException('😡 IsEnabled__c field should be set to true for LogEntryDataMaskRule.' + record.DeveloperName);
}
}
System.debug('🥳 LogEntryDataMaskRule__mdt records have been correctly configured!');
System.debug('🥳 LogEntryDataMaskRule__mdt records have been correctly enabled!');

// LogEntryDataMaskRule__mdt checks
// The fields SensitiveDataRegEx__c and ReplacementRegEx__c should contain valid regex patterns that mask the appropriate data.
// US social security number rule
LogEntryDataMaskRule__mdt socialSecurityNumberRule = LogEntryDataMaskRule__mdt.getInstance('SocialSecurityNumber');
System.Assert.areEqual(
'>>> Here is a number XXX-XX-6789 which is the correct length for US SSN',
'>>> Here is a number 123-45-6789 which is the correct length for US SSN'
.replaceAll(socialSecurityNumberRule.SensitiveDataRegEx__c, socialSecurityNumberRule.ReplacementRegEx__c)
);
System.Assert.areEqual(
'>>> Here is a number XXX-XX-6789 which is the correct length for US SSN',
'>>> Here is a number 123 45 6789 which is the correct length for US SSN'
.replaceAll(socialSecurityNumberRule.SensitiveDataRegEx__c, socialSecurityNumberRule.ReplacementRegEx__c)
);
List<String> invalidSSNLookalikes = new List<String>{
'0123-45-6789',
'0123-45-67890',
'23-45-678',
'23-45-6789',
'123-45-678',
// US phone number formats
'111-555-1234',
'111 555 1234',
'(111) 555-1234'
};
for (String lookalike : invalidSSNLookalikes) {
String originalInput = '>>> Here is a value ' + lookalike + ' and it is not a valid form for US SSN, so masking should not occur';
System.Assert.areEqual(
originalInput,
originalInput.replaceAll(socialSecurityNumberRule.SensitiveDataRegEx__c, socialSecurityNumberRule.ReplacementRegEx__c)
);
}
System.debug(
'🥳 LogEntryDataMaskRule__mdt record \'SocialSecurityNumber\' has been correctly configured with functioning regexes for sensitive data & replacement!'
);

// Mastercard credit card rule
LogEntryDataMaskRule__mdt mastercardCreditCardNumberRule = LogEntryDataMaskRule__mdt.getInstance('MastercardCreditCardNumber');
System.Assert.areEqual(
'>>> Here is a credit card number ****-****-****-0005 which is the correct length for Mastercard',
'>>> Here is a credit card number 5000-1111-2222-0005 which is the correct length for Mastercard'
.replaceAll(mastercardCreditCardNumberRule.SensitiveDataRegEx__c, mastercardCreditCardNumberRule.ReplacementRegEx__c)
);
List<String> invalidMastercardLookalikes = new List<String>{
'05000-1111-2222-0005',
'05000-1111-2222-00050',
'000-1111-2222-000',
'000-1111-2222-0005',
'5000-1111-2222-000'
};
for (String lookalike : invalidMastercardLookalikes) {
String originalInput = '>>> Here is a value ' + lookalike + ' and it is not a valid form for Mastercard, so masking should not occur';
System.Assert.areEqual(
originalInput,
originalInput.replaceAll(socialSecurityNumberRule.SensitiveDataRegEx__c, socialSecurityNumberRule.ReplacementRegEx__c)
);
}
System.debug(
'🥳 LogEntryDataMaskRule__mdt record \'MastercardCreditCardNumber\' has been correctly configured with functioning regexes for sensitive data & replacement!'
);

// Visa credit card rule
LogEntryDataMaskRule__mdt visaCreditCardNumberRule = LogEntryDataMaskRule__mdt.getInstance('VisaCreditCardNumber');
System.Assert.areEqual(
'>>> Here is a credit card number ****-****-****-0004 which is the correct length for Visa',
'>>> Here is a credit card number 4000-1111-2222-0004 which is the correct length for Visa'
.replaceAll(visaCreditCardNumberRule.SensitiveDataRegEx__c, visaCreditCardNumberRule.ReplacementRegEx__c)
);
List<String> invalidVisaLookalikes = new List<String>{
'04000-1111-2222-0004',
'04000-1111-2222-00040',
'000-1111-2222-000',
'000-1111-2222-0004',
'4000-1111-2222-000'
};
for (String lookalike : invalidVisaLookalikes) {
String originalInput = '>>> Here is a value ' + lookalike + ' and it is not a valid form for Visa, so masking should not occur';
System.Assert.areEqual(
originalInput,
originalInput.replaceAll(socialSecurityNumberRule.SensitiveDataRegEx__c, socialSecurityNumberRule.ReplacementRegEx__c)
);
}
System.debug(
'🥳 LogEntryDataMaskRule__mdt record \'VisaCreditCardNumber\' has been correctly configured with functioning regexes for sensitive data & replacement!'
);

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

// LoggerSObjectHandler__mdt checks
// The field LoggerSObjectHandler__mdt.IsEnabled__c should be set to true for any records included in the packages.
for (LoggerSObjectHandler__mdt record : [SELECT DeveloperName, IsEnabled__c FROM LoggerSObjectHandler__mdt ORDER BY DeveloperName]) {
if (record.IsEnabled__c == false) {
throw new System.IllegalArgumentException('😡 IsEnabled__c field should be set to true for LoggerSObjectHandler.' + record.DeveloperName);
}
if (record.IsEnabled__c == false) {
throw new System.IllegalArgumentException('😡 IsEnabled__c field should be set to true for LoggerSObjectHandler.' + record.DeveloperName);
}
}
System.debug('🥳 LoggerSObjectHandler__mdt records have been correctly configured!');
System.debug('🥳 LoggerSObjectHandler__mdt records have been correctly enabled!');

// LogStatus__mdt checks
// The field LogStatus__mdt.IsActive__c should be set to true for any records included in the packages.
for (LogStatus__mdt record : [SELECT DeveloperName, IsActive__c FROM LogStatus__mdt ORDER BY DeveloperName]) {
// TODO rename LogStatus__mdt.IsActive__c to IsEnabled__c for consistency with other objects
if (record.IsActive__c == false) {
throw new System.IllegalArgumentException('😡 IsActive__c field should be set to true for LogStatus.' + record.DeveloperName);
}
// TODO rename LogStatus__mdt.IsActive__c to IsEnabled__c for consistency with other objects
if (record.IsActive__c == false) {
throw new System.IllegalArgumentException('😡 IsActive__c field should be set to true for LogStatus.' + record.DeveloperName);
}
}
System.debug('🥳 LogStatus__mdt records have been correctly configured!');
System.debug('🥳 LogStatus__mdt records records have been correctly enabled!');
7 changes: 4 additions & 3 deletions sfdx-project.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
"path": "./nebula-logger/core",
"definitionFile": "./config/scratch-orgs/build-base-scratch-def.json",
"scopeProfiles": true,
"versionNumber": "4.14.6.NEXT",
"versionName": "Custom Field Mappings Support for Lightning Components",
"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.",
"versionNumber": "4.14.7.NEXT",
"versionName": "Bugfix: US Social Security Number Data Mask Rule",
"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",
"releaseNotesUrl": "https://github.com/jongpie/NebulaLogger/releases",
"unpackagedMetadata": {
"path": "./nebula-logger/extra-tests"
Expand Down Expand Up @@ -191,6 +191,7 @@
"Nebula Logger - Core@4.14.4-optionally-auto-call-lightning-logger-lwc": "04t5Y0000015oRNQAY",
"Nebula Logger - Core@4.14.5-added-logger-settings-to-utility-bar": "04t5Y0000015oRXQAY",
"Nebula Logger - Core@4.14.6-custom-field-mappings-support-for-lightning-components": "04t5Y0000015oRhQAI",
"Nebula Logger - Core@4.14.7-bugfix:-us-social-security-number-data-mask-rule": "04t5Y0000015oRrQAI",
"Nebula Logger - Core Plugin - Async Failure Additions": "0Ho5Y000000blO4SAI",
"Nebula Logger - Core Plugin - Async Failure Additions@1.0.0": "04t5Y0000015lhiQAA",
"Nebula Logger - Core Plugin - Async Failure Additions@1.0.1": "04t5Y0000015lhsQAA",
Expand Down

0 comments on commit a99f380

Please sign in to comment.