Unauthorized Record Access via Inherited Sharing Call Chains
This lab explains how mixed usage of with sharing, without sharing, and inherited sharing across service layers can create unexpected record visibility and authorization bypass conditions in managed package call chains.
Executive Summary
inherited sharing is safer than implicit defaults, but still context-dependent. When entry points and downstream services use inconsistent sharing models, execution may run with broader data access than intended.
Attackers exploit this confusion by triggering call paths from permissive contexts (for example, async jobs or helper classes) that bypass expected record-level restrictions.
Salesforce Attack Surface
Multi-layer service chains: controller to helper to repository patterns with mixed sharing declarations
Security review risk: AppExchange reviewers flag unclear sharing semantics and inconsistent enforcement
Hard-to-debug incidents: same method behaves differently depending on caller type
PoC Use Cases
public inherited sharing class AccountService {
public static List<Account> getAccounts() {
return [SELECT Id, Name, OwnerId FROM Account LIMIT 200];
}
}
public without sharing class AccountAsyncRunner implements Queueable {
public void execute(QueueableContext qc) {
List<Account> rows = AccountService.getAccounts(); // Runs in elevated context
// Unexpectedly broad record set is processed
}
}
Caller from without sharing context invokes inherited-sharing method.
Inherited-sharing service returns broader records than UI entry-point tests suggested.
Call-graph mapping: document every caller of inherited-sharing classes
Context matrix testing: execute same method from with-sharing, without-sharing, and async entry points
Data-scope validation: compare returned record sets across contexts
Privilege abuse simulation: trigger methods through low-trust invocation paths
Evidence capture: show context-dependent overreach and business impact
Secure Engineering Patterns
Explicit boundaries: define sharing model at every critical layer, not only entry points
Least-privilege service design: avoid shared methods callable from both permissive and restrictive contexts
Scoped query enforcement: apply business ownership filters independent of caller context
Async hardening: ensure background jobs enforce intended data scope explicitly
Security regression tests: include call-context permutations in automated tests
public with sharing class AccountReadService {
public static List<Account> getAccountsForUser(Id userId) {
return [
SELECT Id, Name, OwnerId
FROM Account
WHERE OwnerId = :userId
LIMIT 200
];
}
}
Verification Checklist
All critical classes explicitly declare sharing model
Inherited-sharing methods are reviewed for caller-context sensitivity
Async and system-context callers enforce explicit data scope filters
Security tests compare behavior across multiple call-chain contexts
No privilege-sensitive operation depends solely on inherited caller semantics
Lab Exercises
Exercise 1: Build a call-chain map for mixed sharing classes
Exercise 2: Reproduce record overreach via permissive async caller
Exercise 3: Compare output scope from UI vs async vs API invocation paths
Exercise 4: Refactor services with explicit sharing and business-scope controls
Exercise 5: Re-test and create AppExchange-ready remediation evidence