Data security is the most important and most difficult aspect when it comes to Healthcare organizations, especially when ensuring the systems remain HIPAA complaint and maintain PHI/PII information safe. This effort is no less easy when you throw SaaS application in to mix. We will try here to address some common pitfalls and easy solutions to ensure that the clients do not falter on these compliance matters when it comes to Salesforce.

Let’s get some terms cleared out before we proceed with our approach:

Salesforce’s default HIPAA compliance features

  • Salesforce is a Business Associate under HIPAA, which requires them to apply safeguards and requirements that are outlined by HIPAA security Rule. This means that the underlying mechanism applied by Salesforce in their offering is HIPAA compliant.
  • Salesforce has defined TLS 1.1 as the minimum standard security protocol and by default requires HTTPS to access standard orgs.

Salesforce’s Customizable Features

Apart from these default features, Salesforce also provides various customizable features to allow customers to apply tighter security controls:

  • Customize user session security: Logout idle users
  • Disable caching usernames: Enforce users to enter username every time they login
  • Prevent PHI/PII information in sandboxes through refresh and cloning: We will touch this topic in detail below.

Salesforce Shield Platform Encryption

Salesforce provides add-on security features like Shield Platform Encryption:

CloudFountain’s custom approach to security. When we work with Healthcare clients, apart from applying the above practices and features, we apply additional solutions to ensure that there is no leakage when it comes to full sandbox refresh or cloning sandboxes.

Introducing SandboxPostCopy Interface

With Spring ’16 release, Salesforce introduced SandboxPostCopy interface. We have tried to leverage this interface to provide additional security when it comes to sandbox refresh and cloning.

In the below snippet, we have defined class CreateDummyEmails which implements the SandboxPostCopy interface:

global class CreateDummyEmails implements SandboxPostCopy {
init();
}

view rawCreateDummyEmails.cls hosted with ❤ by GitHub


The init function calls two functions:

  • findEmailFields: Finds all the email fields in all the SObjects
  • cleanEmailFields: replaces the emails fields with PHI compliant email addresses
global static void init() {
// Map of sObject and list of email fields
Map < String, List < String >> soEmailFieldMap = findAllEmailFields();
// create dummy emails on Email fields found in the sObjects
cleanEmailFields(soEmailFieldMap);
}

view rawinit.CreateDummyEmails.cls hosted with ❤ by GitHub

 

public static Map < String, List < String >> findAllEmaiLFields() {
Map < String, List < String >> mSObjEmailFields = new Map < String, List < String >> ();
// Iterate through all SObjects
for (SObjectType sObjType: Schema.getGlobalDescribe().values()) {
DescribeSObjectResult sObjDescribe = sObjType.getDescribe();
// Avoid objects that cannot be queried or updated.
if (!sObjDescribe.isQueryable() || !sObjDescribe.isUpdateable()) continue;
String sObjName = sObjDescribe.getName();
// Iterate through all fields
for (SObjectField sObjField: sObjDescribe.fields.getMap().values()) {
DescribeFieldResult sObjFld = sObjField.getDescribe();
// Consider only Email fields
if (sObjFld.getType() != Schema.DisplayType.EMAIL) continue;
if (!sObjFld.isFilterable()) continue;
// Add all Email fields in the map.
if (mSObjEmailFields.containsKey(sObjName)) {
mSObjEmailFields.get(sObjName).add(sObjFld.getName());
} else {
mSObjEmailFields.put(sObjName, new List < String > {
sObjFld.getName()
});
}
}
}
return mSObjEmailFields;
}

view rawfindAllEmaiLFields.CreateDummyEmails.cls hosted with ❤ by GitHub

 

public static void cleanEmailFields(Map < String, List < String >> mSobjField) {
// Iterate through SObject->Email Fields
for (String sObjName: mSobjField.keySet()) {
// Build a list of all fields that need to be queried
List < String > lstEmailFields = mSobjField.get(sObjName);
// Generate a SOQL query to get records with non null emails
String soql = createSOQL(sObjName, lstEmailFields);
List < SObject > sObjRecords = new List < SObject > ();
// Iterate through SObject records
for (SObject sObjRecord: Database.query(soql)) {
// Iterate through email fields found on SObject and replace with dummy values
for (String strField: lstEmailFields) {
String strEmail = (String) sObjRecord.get(strField);
if (String.isEmpty(strEmail)) continue;
sObjRecord.put(strField, strEmail.replaceAll(‘\W’, ‘_’) + ‘@invalid.invalid.co’);
}
sObjRecords.add(sObjRecord);
}
update sObjRecords;
}
}

view rawcleanEmailFields.CreateDummyEmails.cls hosted with ❤ by GitHub

This is just a glimpse into a more sophisticated solution. Using custom metadata, you can use the same function to create dummy values on other fields e.g. patient name, address, phone numbers etc.

We would love to hear your feedback.

Author: CloudFountain

CloudFountain, founded in 2012, helps organizations accelerate growth through Salesforce, AI, and custom application development. We partner with businesses to modernize operations, unlock data-driven insights, and build scalable digital solutions that drive measurable impact. From strategy to execution, we focus on delivering high-quality, tailored solutions that improve efficiency, enhance customer experience, and enable smarter decision-making. Our approach is hands-on, outcome-driven, and aligned with executive priorities—helping leaders turn technology into a competitive advantage.