Skip to main content

Security Model

SimplePerm's security implementation, for security reviewers and developers.

Sharing Model

Every Apex class uses with sharing. Zero without sharing classes exist in the package. Record-level access always respects the running user's sharing rules, role hierarchy, and ownership-based sharing.

CRUD/FLS Enforcement

CRUD/FLS enforcement depends on the object type.

Standard Objects

All SOQL queries against standard objects (Profile, PermissionSet, ObjectPermissions, FieldPermissions, etc.) use WITH USER_MODE or AccessLevel.USER_MODE. Both CRUD and FLS are enforced at the query level -- the running user only sees objects and fields their profile or permission sets allow.

Package-Internal Custom Objects

DML on SimplePerm's own custom objects (Migration_Project__c, Migration_Log__c, SimplePermConfig__c) uses plain insert/update/delete without AccessLevel.USER_MODE. This is deliberate.

AccessLevel.USER_MODE enforces FLS based on the running user's profile. In managed packages, the subscriber org's admin may not have explicitly set FLS on namespace-prefixed fields (e.g., simpleperm__Status__c). When FLS is undefined, USER_MODE treats those fields as inaccessible and the DML fails. Known platform limitation.

Access to these custom objects is gated by the SimplePerm_Admin permission set -- the only path to SimplePerm's objects and tabs. All classes still use with sharing, so record-level access is enforced regardless.

Documented in the false-positive justifications (Category 2: ApexCRUDViolation, 23 suppressions).

Session ID Usage

Three classes call UserInfo.getSessionId():

ClassMethodPurpose
MetadataRetrieverreadProfileAppVisibilities(), readProfileMetadata()SOAP Metadata API readMetadata() SessionHeader
MetadataDeployerdeploy()SOAP Metadata API createMetadata() SessionHeader
MigrationOrchestratorreassignOverwriteUsersViaRest()REST API Bearer token for PermissionSetAssignment re-creation

Every call targets Salesforce's own APIs:

  • SOAP Metadata API at /services/Soap/m/62.0
  • REST API at /services/data/v62.0/composite/sobjects

The session ID is:

  • Never stored in any database record, custom setting, or log
  • Never logged -- error handling strips SOAP envelopes before writing to Migration_Log__c
  • Never transmitted externally -- callouts go to the customer's own Salesforce instance only
  • Never exposed to the client -- LWC components never receive or handle it

Named Credentials are not an option here. Salesforce does not support Named Credential authentication for same-org Metadata API calls in first-generation managed packages.

Dynamic SOQL

Four classes use Database.query():

ClassPurposeField SourceBind Variables
MetadataRetrieverRetrieves profile permissions dynamically based on available fieldsSchema.SObjectType.*.fields.getMap()Yes -- profile IDs
VerificationServiceVerifies deployed permission sets match expected stateSchema.describe()Yes -- permission set IDs
ProfileViewerControllerLoads profile and permission set details for the viewerSchema.describe()Yes -- profile/PS IDs
PermissionCatalogDiscovers available permission types in the orgSchema.describe()Yes -- filter values

In every case:

  • Field names come from Schema.describe() calls (compile-time metadata), never from user input
  • Filter values use bind variables (:paramName), preventing SOQL injection
  • Access level uses AccessLevel.USER_MODE where applicable

No user-supplied strings are concatenated into query text.

HTTP Callouts

All HTTP callouts target Salesforce's own SOAP Metadata API and REST API. Zero third-party endpoints.

ClassEndpointProtocol
MetadataRetriever/services/Soap/m/62.0SOAP (readMetadata)
MetadataDeployer/services/Soap/m/62.0SOAP (createMetadata)
MigrationOrchestrator/services/data/v62.0/composite/sobjectsREST (sObject operations)

Remote Site Settings for the org's instance URL are configured automatically. No external domains are contacted.

Data Privacy

SimplePerm processes Salesforce metadata only -- profile definitions and permission set definitions. It does not touch end-user personal data.

All data stays within the customer's Salesforce org:

  • Migration_Project__c -- migration run records
  • Migration_Log__c -- per-step log entries
  • SimplePermConfig__c -- package configuration
  • Migration_Event__e -- transient platform events (not persisted beyond the event bus TTL)

No external data transmission, no analytics, no telemetry, no cookies.

Code Analyzer Compliance

SimplePerm passes Salesforce Code Analyzer v5 with the AppExchange rule set at severity threshold 2. No severity-1 or severity-2 violations.

53 suppressions at severity 3, each with a reviewer-ready justification, organized into 5 categories:

  1. UserSessionId (4 suppressions) -- Same-org SOAP/REST API authentication
  2. ApexCRUDViolation (23 suppressions) -- Package-internal custom object DML
  3. ApexSuggestUsingNamedCred (2 suppressions) -- Same-org API calls where Named Credentials are not viable
  4. EmptyCatchBlock (6 suppressions) -- Intentional non-fatal exception swallowing for notifications
  5. Intentional full-table SOQL (3 suppressions) -- Bounded queries for admin UI data

The complete justification document is available at docs/security/false-positive-justifications.md.