Skip to main content

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

RuleSeverityTitle
IND-001HighHealth Cloud PHI DML without HIPAA-aware audit logging
IND-002HighFSC relationship DML without ownership check
IND-003MediumSensitive industry field without Shield Platform Encryption
IND-004MediumRuntimeIndustriesContext accessed without permission check
IND-005High@AuraEnabled returns PHI SObject without projection
IND-006MediumFSC 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.

  1. Mark the field's *.field-meta.xml with <encrypted>true</encrypted> so Shield Platform Encryption applies at rest.
  2. Guard the write with Schema.SObjectType.<X>.fields.<field>.getDescribe().isEncrypted() and call Security.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-006 map to Object and Field Permissions (CRUD / FLS) because they describe access- control posture gaps.
  • IND-003, IND-005 map 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).