# Salesforce Permission Model — In-Depth Research

**Date:** 2026-04-03
**Method:** 3 parallel research agents (Document Specialist + Architect + Analyst)
**Purpose:** Design reference baseline for NexusCRM permission system refactoring

---

## I. SF Permission Model Overview

### Two Orthogonal Dimensions

```
Dimension 1: What can you do (What)       Dimension 2: Which records can you see (Which Record)
┌──────────────────────┐             ┌──────────────────────────────────┐
│ Object-Level         │             │ Layer 1: OWD (strictest baseline) │
│  Profile + PermSet   │             │          ↓ can only open up       │
│  → CRUD + ViewAll    │             │ Layer 2: Role Hierarchy           │
│    + ModifyAll       │             │          ↓ can only open up       │
│                      │             │ Layer 3: Sharing Rules            │
│ Field-Level          │             │          ↓ can only open up       │
│  Profile + PermSet   │             │ Layer 4: Manual Sharing           │
│  → Read / Edit       │             │          ↓ can only open up       │
│                      │             │ Layer 5: Apex Managed Sharing     │
│ System Permissions   │             │          ↓ automatic              │
│  60+ permission      │             │ Layer 6: Implicit Sharing         │
│  switches            │             │                                   │
└──────────────────────┘             └──────────────────────────────────┘

All three layers must pass simultaneously: Object ✓ + Record ✓ + Field ✓
```

### "Who Sees What" — Complete Decision Tree

```
Can user X access field Z of record Y?

1. Is the user active? → NO → Deny
   ↓ YES
2. Object Permission (merged from Profile + PermSets)
   Does the user have Read permission on this object? → NO → Deny
   ↓ YES
3. Record-Level (stacked in order; pass if any layer passes)
   3a. Is the user the record Owner? → YES → Pass
   3b. OWD = Public Read/Write? → YES → Pass
   3c. Is the user a Role Hierarchy superior? → YES → Inherits subordinate's access
   3d. Matches a Sharing Rule? → YES → Grant per rule
   3e. Manual Share? → YES → Grant per share level
   3f. Apex Managed Share? → YES → Grant per code
   3g. Implicit Share? → YES → Read Only
   3h. All NO → Deny (record not visible)
   ↓ Pass
4. Field Permission (merged from Profile + PermSets)
   Hidden → Field not shown
   Read Only → Field visible but not editable
   Read/Write → Full access
```

---

## II. OWD (Organization-Wide Defaults)

### Design Philosophy: Strictest by Default, Open Up Layer by Layer

**Why not default to Public Read/Write and then restrict?**

- **Additive model**: Missing one grant = user cannot see what they should = functional defect, users will report it (observable)
- **Subtractive model**: Missing one restriction = user sees what they shouldn't = security hole, nobody notices (unobservable)
- Defaulting to Private converts the failure mode from "silent data leak" to "explicit error"

### OWD Options

| Option | Meaning |
|--------|---------|
| **Private** | Only the Owner and Role Hierarchy superiors can see the record |
| **Public Read Only** | Everyone can view; only Owner/superiors can edit |
| **Public Read/Write** | Everyone can view and edit |
| **Controlled by Parent** | Child record access is determined by the parent record |
| **Public Read/Write/Transfer** | Leads/Cases only; everyone can transfer ownership |

Each object can have its OWD set independently. A separate, stricter OWD can also be set for external users (Portal/Community).

---

## III. Profile → Permission Set → PSG Evolution

### Evolution Timeline

```
~2000s  Profile introduced — one Profile per user, storing all permissions + default settings
        Problem: role explosion (Sales + Project Manager + Auditor = blended Profile?)

~2010   Permission Set introduced — a user can have multiple PermSets, pure additive stacking
        Profile demoted to "minimum permission baseline"

~2020   Permission Set Group introduced — packages multiple PermSets by job function
        Accompanied by Muting PermSet (the only subtractive tool within a PSG)

2022    SF announced Profile permissions will be EOL (planned mandatory retirement in Spring '26)

2024    SF delays EOL — many customers not ready
        Current state: no forced deadline, but all new feature investment is in PermSets
```

### Positioning of the Three

| | Profile | Permission Set | Permission Set Group |
|---|---|---|---|
| Count per user | **1** | Multiple | Multiple |
| Contains | Permissions + layouts + login settings + defaults | Permissions only | A collection of PermSets |
| Designed for | User type | Atomic task | Job function (Persona) |
| SF current positioning | **Minimum permission safety net** + non-permission settings | **Primary permission carrier** | **Preferred role modeling** |
| Can subtract? | Yes (as baseline) | No | Yes, via Muting PS |

### Muting Permission Set

Solves the problem of "over-granting a permission" within a PSG:

```
Service Rep PSG
├── Case Management PS (includes Delete)
├── Lead Management PS
└── [Muting PS] → mutes Case Delete

Result: Service Rep has Case CRUD (no Delete); others who were directly assigned Case Management PS are unaffected
```

This is the **only subtractive tool** in the SF permission model, and its scope is limited to within a PSG.

---

## IV. Sharing Rules

### Two Types

| Type | Trigger | Typical Scenario |
|------|---------|-----------------|
| **Owner-Based** | Record Owner belongs to a certain group (Role/Group) | Sales manager's Opps shared with executives |
| **Criteria-Based** | Record field values meet a condition (independent of Owner) | Accounts with Region='APAC' shared with the APAC team |

Owner-Based is stable and predictable; Criteria-Based is dynamic (automatically adjusts when record content changes).

### Implicit Sharing (non-configurable)

- **Child → Parent**: Having access to a Contact/Opportunity/Case → automatically gains Read Only on its parent Account
- **Parent → Child**: Account Owner automatically gains access to all child records
- Built into the platform; cannot be disabled

---

## V. Design Philosophy

### 1. The Nature of the Additive Model

```
Effective permissions = Profile baseline ∪ PermSet_1 ∪ PermSet_2 ∪ ... ∪ PermSet_N
```

Each layer can only expand permissions, never shrink them.

| Aspect | Additive Model (SF) | Subtractive Model (Linux) |
|--------|--------------------|-----------------------------|
| Predictability | High — permissions only increase | Low — deny may override allow |
| Debugging difficulty | Low — check layer by layer "which layer didn't grant" | High — find which deny overrides which allow |
| Flexibility | Limited — cannot express "has everything except X" | High — can precisely express any combination |
| Audit-friendliness | Very high — every permission can be traced to its source | Medium — must evaluate allow/deny interactions |

**Why not use subtraction?** SF permission administrators are business admins, not programmers. The core advantage of the additive model is **cognitive simplicity**.

### 2. Three-Dimensional Orthogonal Design

The Object / Field / Record dimensions vary independently without affecting each other:

- Granting a person access to a field ≠ affecting which records they can see
- Expanding record visibility ≠ automatically granting access to new fields
- Orthogonal design reduces the combinatorial explosion (100 objects × 50 fields × N records) to addition

### 3. Declarative vs. Imperative

Permissions are metadata (data), not code (if/else):

- Changes require no deployment — administrators modify at runtime with no code changes
- Permissions are not scattered throughout the codebase — definitions are centralized in metadata, enforcement is centralized at the enforcement point
- Audit-friendly — can directly SQL query "who has Delete permission on Account"

### 4. Composition Over Inheritance

Permission Set stacking design vs. traditional RBAC role inheritance:

- Traditional RBAC: Admin → Manager → User (inheritance chain), N permission sets = 2^N possible roles
- SF: Profile (baseline) + PermSet_A + PermSet_B (free composition), essentially has-a rather than is-a
- Consistent with the "composition over inheritance" principle in OOP

### 5. Why Role Hierarchy Is a Tree, Not a Graph

- **Determinism**: In a tree, "who can see my records" is deterministic (ancestor chain). In a graph, permissions propagate along multiple paths and results can be counterintuitive
- **Pre-computation**: In a tree, the number of ancestors per node = depth (< 10). In a graph, upstream nodes can be any subset
- **Comprehensibility**: Tree structure is intuitively understandable to administrators. Graph relationships require graph algorithms
- **Organizational assumption**: Most enterprise reporting relationships are trees (single superior)
- **Supplement**: Territory Management adds a "business dimension" (segmentation of customers), rather than turning the role dimension into a graph

### 6. Relationship to NIST RBAC

SF fully covers RBAC₀–₃, but **goes beyond pure RBAC**:

- Criteria-Based Sharing Rules = **ABAC** (based on record attributes, not user roles)
- Owner-Based Sharing = **ReBAC** (based on the relationship between user and record)
- Territory Management = spatial/geographic attribute dimension

**Most accurate positioning: a hybrid model with RBAC as the skeleton, incorporating ABAC and ReBAC elements.**

### 7. Performance Strategy (Multi-Tenant)

SF uses a three-layer strategy to address the performance problem of "checking permissions on every data access":

| Strategy | Implementation |
|----------|---------------|
| Session-level pre-computation | Pre-compute effective permission matrix at login → cached as bitmap |
| Sharing group materialization | Group Membership materialized table, asynchronously recalculated ("Sharing Recalculation") |
| Share row pre-storage | Each Private object maintains a `<Object>Share` table; JOIN at query time |

Classic space-for-time trade-off; the cost is latency from Sharing Recalculation.

---

## VI. NexusCRM Gap Analysis

### Already Implemented (~70-75%)

| SF Capability | NexusCRM Implementation | Completion |
|--------------|------------------------|------------|
| System Permissions | Profile → PermSet → PSG → Muting 4-layer resolution | 95% |
| Object Permissions | CheckObjectPermissionWithUser (merges Profile + PermSet) | 85% |
| Field-Level Security | CheckFieldVisibilityWithUser / CheckFieldEditabilityWithUser | 90% |
| Record-Level Access | 5-layer check (Owner, Group, Role Hierarchy, Sharing Rules, Manual Share, Team) | 80% |
| Role Hierarchy | In-memory cache + ancestor traversal | 90% |
| Sharing Rules | Owner-based + Criteria-based (with formula engine) | 80% |
| Permission Cache | TTL 5min + singleflight + bulk warm | 90% |

### Critical Gaps (~25-30%)

| Gap | Severity |
|-----|---------|
| **OWD configuration mechanism** — no per-object default access level | Critical |
| **SQL RLS JOIN bypass** — applyRLS returns nil directly for JOINs | Critical |
| **applyRLS does not check ViewAllRecords** — only checks IsSuperUser | Critical |
| **applyRLS does not include Role Hierarchy subordinates** — only owner_id = current user | High |
| **Analytics routes have no RunReports guard** | High |
| **PermApiEnabled not checked at the routing layer** | Medium |
| **Role Hierarchy only propagates Read** — Edit/Delete not inherited | Medium |

### Single-Record vs. Bulk Query Consistency Problem

`CheckRecordAccess` (single-record CRUD) has a complete 5-layer check, but `applyRLS` (SQL bulk query) only has an `owner_id = ?` filter. **The security level between the two is severely inconsistent.**

---

## VII. NexusCRM Target Permission Architecture

### Design Principles

1. **Not an SF clone** — tailored for a 127-user scale
2. **PermSet-first** — skip the Profile-centric phase and go directly to the target architecture
3. **Not needed**: Territory Management, External OWD, Restriction Rules, Apex Sharing
4. **No Share table pre-computation needed** — 127 users can be calculated in real time

### Architecture Diagram

```
┌─────────────────────────────────────────────────────────────┐
│                  Authentication Layer                       │
│  JWT + RequireAuth + Session Validation                     │
│  + PermApiEnabled Gate (new)                                │
└─────────────────────────────────────────────────────────────┘
                              │
┌─────────────────────────────────────────────────────────────┐
│            Permission Resolution (already implemented)      │
│  Minimum Access Profile (5-8)                               │
│    → Permission Sets (15) → PSG + Muting (per Persona)      │
│  Cache: PermissionCache (TTL 5min, singleflight)            │
└─────────────────────────────────────────────────────────────┘
                              │
        ┌─────────────────────┼─────────────────────┐
   Object Perms          Field Perms          System Perms
   R/C/E/D/VAR/MAR      R/E per field        60+ permission switches
        │                     │                     │
┌─────────────────────────────────────────────────────────────┐
│              Record-Level Security Layer                    │
│                                                             │
│  OWD Configuration (new)                                    │
│  Per-object: Private / PublicReadOnly / PublicReadWrite     │
│  Storage: _System_ObjectMetadata.DefaultAccess              │
│                                                             │
│  OWD=PublicRW → skip record-level check                     │
│  OWD=PublicRO → check only for Edit/Delete                  │
│  OWD=Private  → full check:                                 │
│    1. ViewAllRecords/ModifyAllRecords (new)                  │
│    2. Record Ownership                                       │
│    3. Role Hierarchy (fix: support Edit/Delete propagation)  │
│    4. Sharing Rules (Owner-based + Criteria-based)           │
│    5. Manual Share + Team Member + Group                     │
└─────────────────────────────────────────────────────────────┘
                              │
        ┌─────────────────────┴─────────────────────┐
   Single Record                              Bulk Query
   (CRUD, already implemented)               (SQL RLS, needs fix)
                                              Improvements:
                                              1. OWD=PublicRW → do not inject
                                              2. ViewAllRecords → do not inject
                                              3. Private → visible-record subquery
                                                 (owner + hierarchy + sharing)
                                              4. RLS support for JOINs
```

### RLS Bulk Query Improvement Plan (suitable for 127-user scale)

Real-time computation; no materialized Share table needed:

```sql
SELECT ... FROM target_table t
WHERE (
    t.owner_id = @user_id                          -- Owner
    OR t.owner_id IN (
        SELECT user_id FROM _System_User
        WHERE role_id IN (@subordinate_roles)       -- Role Hierarchy
    )
    OR EXISTS (
        SELECT 1 FROM _System_RecordShare rs
        WHERE rs.record_id = t.id
        AND rs.user_or_group_id IN (@user_groups)   -- Manual Share
    )
    OR @has_view_all_records = 1                    -- ViewAllRecords
)
```

### Implementation Priority

| Priority | Work Item | Timeline |
|----------|-----------|----------|
| **P0** | Analytics routes RequirePermission(PermRunReports) | This week |
| **P0** | applyRLS: add ViewAllRecords check | This week |
| **P0** | PermApiEnabled route guard | This week |
| **P1** | OWD configuration mechanism (_System_ObjectMetadata.DefaultAccess) | 2-4 weeks |
| **P1** | applyRLS: support JOINs + Role Hierarchy subordinates | 2-4 weeks |
| **P1** | SOQL query handler: add record-level filtering | 2-4 weeks |
| **P2** | Role Hierarchy Edit/Delete propagation configurable | 4-8 weeks |
| **P2** | Sharing Rules caching | 4-8 weeks |
| **P2** | Cache invalidation events on permission changes | 4-8 weeks |

### PermSet-First Strategy

NexusCRM should adopt PermSet-first from now on:

```
Persona "Sales Rep APAC" = PSG containing:
  [Base CRM Access, APAC Region Data, Report Viewer]

Persona "Sales Manager NA" = PSG containing:
  [Base CRM Access, NA Region Data, Report Builder, Team Management]
```

46 Profiles simplified to 5-8 Minimum Access Profiles (divided by user type), with specific permissions expressed through 15 PermSets + PSGs.

### Layout and Permission Intersection

NexusCRM has already surpassed SF's layout model:

```
SF:       Profile × RecordType           = 2D
NexusCRM: Role × Profile × RecordType   = 3D (already implemented)
```

"Personalized views per user" = Role encodes Region/Team + Profile encodes job function. Layout assignment and permission assignment are orthogonal.

---

## VIII. Open Questions

1. Should the default OWD for newly created objects be Private or Public Read/Write?
2. Should Role Hierarchy propagation of Edit/Delete be enabled by default (configurable per-object)?
3. For SOQL query RLS — reuse the SQL AST approach or filter query results after retrieval?
4. Real-time requirements for permission changes — is TTL 5min acceptable?
5. Scope of applyRLS JOIN support — arbitrary depth or only two-table JOINs?

---

## IX. Summary: Five Core Design Principles of the SF Permission Model

| Principle | Manifestation | Anti-Pattern |
|-----------|--------------|--------------|
| **Default Deny** | OWD Private, additive model | Default Allow + subtraction |
| **Orthogonal Separation** | Object / Field / Record as three independent dimensions | Single permission matrix |
| **Composition Over Inheritance** | PermSet stacking | Role inheritance tree |
| **Declarative > Imperative** | Metadata defines permissions | if/else in code |
| **Pre-compute for Latency** | Share table materialization + async recalculation | Full real-time computation at runtime |

These five principles are the distillation of 25 years of experience serving 150,000+ enterprise customers. Each principle sacrifices a certain degree of flexibility in exchange for properties that matter more in enterprise scenarios: security predictability, administrative comprehensibility, and at-scale performance.
