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():
| Class | Method | Purpose |
|---|---|---|
| MetadataRetriever | readProfileAppVisibilities(), readProfileMetadata() | SOAP Metadata API readMetadata() SessionHeader |
| MetadataDeployer | deploy() | SOAP Metadata API createMetadata() SessionHeader |
| MigrationOrchestrator | reassignOverwriteUsersViaRest() | 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():
| Class | Purpose | Field Source | Bind Variables |
|---|---|---|---|
| MetadataRetriever | Retrieves profile permissions dynamically based on available fields | Schema.SObjectType.*.fields.getMap() | Yes -- profile IDs |
| VerificationService | Verifies deployed permission sets match expected state | Schema.describe() | Yes -- permission set IDs |
| ProfileViewerController | Loads profile and permission set details for the viewer | Schema.describe() | Yes -- profile/PS IDs |
| PermissionCatalog | Discovers available permission types in the org | Schema.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_MODEwhere 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.
| Class | Endpoint | Protocol |
|---|---|---|
| MetadataRetriever | /services/Soap/m/62.0 | SOAP (readMetadata) |
| MetadataDeployer | /services/Soap/m/62.0 | SOAP (createMetadata) |
| MigrationOrchestrator | /services/data/v62.0/composite/sobjects | REST (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 recordsMigration_Log__c-- per-step log entriesSimplePermConfig__c-- package configurationMigration_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:
- UserSessionId (4 suppressions) -- Same-org SOAP/REST API authentication
- ApexCRUDViolation (23 suppressions) -- Package-internal custom object DML
- ApexSuggestUsingNamedCred (2 suppressions) -- Same-org API calls where Named Credentials are not viable
- EmptyCatchBlock (6 suppressions) -- Intentional non-fatal exception swallowing for notifications
- 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.