Salesforce Industries Clouds detector pack
PRO-T-B-3: Health Cloud and Financial Services Cloud (FSC) detector pack
for vulkro scan. Six stable rule IDs (IND-001 through IND-006)
that target the regulated-industry Salesforce verticals where the
AppExchange Security Review team applies HIPAA, GLBA, and state-
government audit rules on top of the standard CRUD / FLS / sharing
checks.
What it does
When vulkro scan runs against a project root, the Industries Clouds
detector first checks whether the project includes any custom-object
directory named for the Health Cloud or FSC schema (Patient__c,
Care_Plan__c, MedicalCondition__c, Practitioner__c,
HealthCloudGA__*, FinServ__*, vlocity_ins__*,
vlocity_cmt__*, vlocity_ps__*) under
force-app/main/default/objects/. If none of these are present, the
detector returns empty in microseconds without reading a single Apex
file. If at least one is present, the pack runs across every Apex
.cls and emits findings with the stable IND-NNN: rule-ID prefix.
Rules
| Rule | Severity | Title |
|---|---|---|
| IND-001 | High | Health Cloud PHI DML without HIPAA-aware audit logging |
| IND-002 | High | FSC relationship DML without ownership check |
| IND-003 | Medium | Sensitive industry field without Shield Platform Encryption |
| IND-004 | Medium | RuntimeIndustriesContext accessed without permission check |
| IND-005 | High | @AuraEnabled returns PHI SObject without projection |
| IND-006 | Medium | FSC programmatic AccountShare with Edit / All access |
IND-001: Health Cloud PHI DML without audit logging
Fires when a method performs DML (insert / update / delete) on
a Health Cloud PHI SObject (Patient__c, Care_Plan__c,
MedicalCondition__c, Practitioner__c, or any HealthCloudGA__*
managed-package SObject) and the same method body never writes an
EventLog__c or AuditLog__c record. HIPAA 45 CFR 164.312(b)
requires PHI access and modification to be recorded in an auditable
log; this rule surfaces the gap before AppExchange Security Review.
Remediation: write a matching EventLog__c / AuditLog__c record
in the same method (or in a helper called on every path). Capture
at minimum: acting user id, timestamp, target record id, operation
kind.
IND-002: FSC relationship DML without ownership check
Fires when a request-reachable method (@AuraEnabled,
@RestResource, Visualforce) performs DML on an FSC relationship
SObject (AccountAccountRelation, ContactContactRelation,
FinServ__BusinessAccount__c, FinServ__FinancialAccount__c,
FinServ__FinancialAccountRole__c, FinServ__FinancialHolding__c)
without comparing an OwnerId against UserInfo.getUserId(). An
attacker who can reach this method can rewrite the relationship
graph between accounts they do not own.
Remediation: before the DML, assert the caller owns one side of the
relationship via record.OwnerId == UserInfo.getUserId() (or the
FSC-specific predicate on FinServ__PrimaryOwner__c). For an
AccountAccountRelation insert, confirm the calling user owns at
least one of the two AccountId / RelatedAccountId values.
IND-003: Sensitive industry field without encryption
Fires when a method writes to a regulated-data field (DateOfBirth,
SSN__c, MedicalConditionCode, Patient_MRN__c,
AccountNumberMasked__c, NationalProviderId__c, TaxIDNumber,
and similar shapes from sf_pii_map::industry_fields()) and the
method body shows no Security.stripInaccessible strip, no
Schema.SObjectField.isEncrypted() describe check, and no
WITH SECURITY_ENFORCED clause. If the underlying field is not
annotated <encrypted>true</encrypted> in its field metadata XML,
the value is stored in cleartext.
Remediation: two options.
- Mark the field's
*.field-meta.xmlwith<encrypted>true</encrypted>so Shield Platform Encryption applies at rest. - Guard the write with
Schema.SObjectType.<X>.fields.<field>.getDescribe().isEncrypted()and callSecurity.stripInaccessible(AccessType.CREATABLE, records)before the DML.
AppExchange Security Review treats unencrypted PHI / GLBA fields as a hard blocker for regulated-industry packages.
IND-004: RuntimeIndustriesContext accessed without permission check
Fires when a method references RuntimeIndustriesContext (the
Industries Clouds runtime context object) without first calling
IndustriesAccessControl.check(...) or one of the equivalent
permission verifications (IndustriesAccessControl.checkPermission(...),
FeatureManagement.checkPermission(...),
PermissionsCheck.check(...)). The runtime context exposes
Industries-specific business rules and policy values; a caller who
reaches the method bypasses Industries Clouds permission evaluation.
Remediation: call the permission check before touching the runtime context. Wrap the check in a guard that throws when the permission is denied so a downstream path cannot rely on a half-initialised context.
IND-005: @AuraEnabled returns PHI without projection
Fires when an @AuraEnabled method's return type contains a Health
Cloud PHI SObject (Patient__c, Care_Plan__c,
MedicalCondition__c, Practitioner__c, HealthCloudGA__*) and the
method body has no Security.stripInaccessible, no explicit field-
by-field SELECT projection (a SELECT clause with at least one comma
between SELECT and FROM), and no WITH SECURITY_ENFORCED clause. The
full record (and every field on it) is serialised back to the
LWC / Aura client, so any caller who can invoke the method reads
every PHI field even when the running user has no FLS to those
fields.
Remediation: project explicitly in the SOQL
(SELECT Id, Name, Status FROM Patient__c WHERE ...) so only the
fields the component needs are returned, or strip inaccessible
fields before returning. A wrapper DTO with only the safe fields is
the auditor-friendly option.
IND-006: FSC programmatic AccountShare with Edit / All
Fires when a request-reachable method programmatically inserts an
AccountShare (or related *Share SObject) with
AccountAccessLevel / OpportunityAccessLevel / CaseAccessLevel /
ContactAccessLevel set to the literal 'Edit' or 'All'. These
are the most permissive grants and bypass the FSC sharing-reason
model when issued at runtime.
Remediation: lower the access level to 'Read' unless the business
flow has signed off on full edit access. Where edit access really is
required, use a declarative FSC sharing rule keyed on a
FinServ__SharingReason__c so the grant is auditable in metadata
instead of issued from runtime Apex.
Project detection
The pack is project-gated. is_industries_clouds_project(root) walks
force-app/main/default/objects/ (and the legacy MDAPI objects/
layout). The project is treated as Industries Clouds when any of:
- A custom-object directory or file is named for one of the
sf_pii_map::industry_sobjects()entries (Patient__c,Care_Plan__c,MedicalCondition__c,Practitioner__c,AccountAccountRelation,ContactContactRelation,FinServ__BusinessAccount__c,FinServ__FinancialAccount__c,FinServ__FinancialAccountRole__c,FinServ__FinancialHolding__c,HealthCloudGA__EhrCondition__c,HealthCloudGA__EhrEncounter__c,HealthCloudGA__EhrObservation__c). - A custom-object name begins with one of the Industries Clouds
managed-package namespaces:
HealthCloudGA__,FinServ__,vlocity_ins__,vlocity_cmt__,vlocity_ps__.
When neither is present the detector returns an empty result before reading any Apex source.
AppExchange Security Review mapping
Each rule contributes to the AppExchange Security Review readiness
report (vulkro sf-appexchange-report):
IND-001,IND-002,IND-004,IND-006map to Object and Field Permissions (CRUD / FLS) because they describe access- control posture gaps.IND-003,IND-005map to Sensitive Data Storage and Logging because they describe regulated-data fields written cleartext or serialised PHI fields reaching a client.
Exit codes
Standard vulkro scan contract:
0- scan succeeded with zero findings.1- scan succeeded, at least one finding was reported.2- scan errored (bad args, IO failure, internal crash).