CRUD et FLS en Apex : ne jamais négliger la sécurité du code

Par défaut, une classe Apex s'exécute en contexte système : elle ignore les droits CRUD (Create/Read/Update/Delete au niveau objet) et la Field-Level Security de l'utilisateur qui déclenche son exécution. C'est un choix de conception délibéré de Salesforce pour permettre aux automatisations (triggers, batchs) de fonctionner indépendamment des permissions de l'utilisateur courant — mais c'est aussi la source numéro un des failles d'élévation de privilège dans le code custom Salesforce, en particulier exposé via des composants Lightning ou des endpoints Apex REST accessibles à des profils peu privilégiés.

1. Le problème concret

Imaginez une classe Apex exposée en @AuraEnabled qui met à jour un champ "Salaire" sur l'objet Contact, appelée depuis un composant Lightning accessible à tous les commerciaux. Même si le profil Commercial n'a pas accès en écriture au champ Salaire via son profil (FLS correctement configurée), le code Apex en contexte système effectuera la mise à jour sans erreur — la FLS n'a jamais été vérifiée. L'utilisateur vient de contourner la sécurité posée à la couche déclarative, simplement parce que le développeur n'a pas ajouté de vérification explicite.

2. Les mécanismes de protection disponibles

a. WITH USER_MODE (recommandé, API 56+)

La méthode moderne et la plus simple : ajouter WITH USER_MODE à une requête SOQL ou une opération DML applique automatiquement les vérifications CRUD, FLS et le partage (sharing) selon l'utilisateur courant.

List<Contact> contacts = [SELECT Id, Name, Salaire__c FROM Contact WITH USER_MODE];
insert as user newContact; // DML en mode utilisateur

b. Security.stripInaccessible()

Pour nettoyer une liste de SObjects déjà récupérée (par exemple avant de la renvoyer à un composant Lightning), en retirant silencieusement les champs non accessibles à l'utilisateur plutôt que de lever une exception :

SObjectAccessDecision decision = Security.stripInaccessible(
    AccessType.READABLE, contactList);
List<Contact> safeContacts = decision.getRecords();

c. Vérifications manuelles historiques

Sur du code legacy (avant API 56), on trouve encore Schema.sObjectType.Contact.fields.Salaire__c.isAccessible(), isCreateable(), isUpdateable() vérifiés explicitement avant chaque opération. Plus verbeux mais toujours valide.

3. with sharing vs without sharing vs inherited sharing

Ce mot-clé de classe contrôle le partage d'enregistrements (pas la FLS ni le CRUD, qui sont gérés séparément) :

Une classe sans mot-clé de partage explicite est without sharing par défaut. C'est un piège fréquent en revue de code : l'absence de mention n'est pas neutre, elle ouvre l'accès complet.

4. Où ces vérifications sont critiques en priorité

  1. Méthodes @AuraEnabled et @RemoteAction exposées à des composants Lightning consommés par des profils variés.
  2. Endpoints Apex REST (@RestResource) exposés à des intégrations externes ou des utilisateurs de portail.
  3. Contrôleurs Visualforce sans with sharing explicite, accessibles publiquement ou à des profils externes.
  4. Batch et Queueable Apex déclenchés indirectement par une action utilisateur (moins prioritaire si purement planifiés côté système, mais à vérifier si le déclenchement dépend d'une saisie utilisateur).

5. Intégrer la vérification dans le processus de développement

Le Security Review obligatoire pour toute app publiée sur AppExchange impose ces vérifications. Même sans publication AppExchange, intégrez une revue systématique : PMD avec la règle ApexCRUDViolation, ou l'outil Salesforce Code Analyzer (CLI) en pipeline CI/CD, permettent de détecter automatiquement les requêtes SOQL/DML sans vérification CRUD/FLS avant merge, plutôt que de compter uniquement sur la relecture humaine.

Besoin d'aide sur ce sujet ?

Notre équipe DevToSpace accompagne vos projets Salesforce, de la sécurité à l'intégration Data Cloud / Marketing Cloud Next.

Parler à un expert