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
  • Async execution boundaries: queueable, batch, and schedulable classes invoking inherited-sharing services
  • Utility hubs: common methods callable from both trusted and untrusted call contexts
  • Dynamic query methods: inherited-sharing services executing broad SOQL without explicit scope enforcement
  • Package extension points: invocable/global methods invoked by external flows or integrations

Business Impact

  • Record overreach: users gain visibility into records outside intended sharing scope
  • Unauthorized updates: write operations occur through elevated call contexts
  • Policy drift: developers assume entry-point controls apply everywhere, but call-chain context differs
  • 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.
  • Downstream logic performs unauthorized read/write operations.

Testing Methodology

  • 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