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:
- HIPAA: Health Insurance Portability and Accountability Act
- PHI: Protected Health Information
- PII: Personally Identifying Information a.k.a. Sensitive Personal Information (SPI)
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:
- Encrypts standard and custom fields
- Encrypts Attachments, documents, Einstein data sets, Chatter, search indexes
- Customized Encryption policy: Allow different encryptions on field by field basis
- Filter encrypted data with deterministic encryption
- Does a 256-bit advanced encryption
- Features Event monitoring to keep a track of user actions
- Provides Field Audit Trail which lets you know the state and value of your data for any date, at any time. Shield’s field audit trail increases the capacity of field audit by three times (60 fields vs. 20 fields) and archives up to 10 years of history.
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(); | |
} |
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); | |
} |
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; | |
} |
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; | |
} | |
} |
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.