Guide de sécurité pour développeurs

Comment corriger le broken access control

Refusez par défaut, exprimez côté serveur la politique acteur-action-ressource, scopez chaque objet au propriétaire ou tenant courant et prouvez le contrôle avec des tests négatifs qui changent identités, rôles et identifiants.

Réponse courte : Refusez par défaut, exprimez côté serveur la politique acteur-action-ressource, scopez chaque objet au propriétaire ou tenant courant et prouvez le contrôle avec des tests négatifs qui changent identités, rôles et identifiants.

1. Modéliser la décision, pas l'écran

Écrivez la politique avec trois entrées : qui agit, quelle action est tentée et quelle ressource est ciblée. Ajoutez tenant, état et relation. Authentifié suffit rarement : un membre peut lire un projet sans gérer la facturation ; un admin du workspace A n'a aucun droit dans B.

Inventoriez tous les points d'entrée : REST ou GraphQL, Server Actions, exports, URL de stockage, workers et outils d'administration.

2. Appliquer près des données

Des helpers de politique centralisent les règles, mais la requête doit inclure la frontière. Charger un document par ID global puis contrôler après ouvre une faille dès qu'un appelant oublie la seconde étape.

Retournez une réponse not-found ou forbidden cohérente avec le contrat sans révéler l'existence d'un objet privé.

return db.invoice.findFirst({
  where: {
    id: invoiceId,
    workspaceId: actor.workspaceId,
    workspace: { members: { some: { userId: actor.userId } } }
  },
  select: { id: true, status: true, total: true }
});

3. Couvrir actions et transitions d'état

Posséder l'objet n'autorise pas toutes les actions. Vérifiez des permissions distinctes pour invitation, export, impersonation, remboursement, suppression, changement de rôle et transfert. Validez l'état courant dans la même transaction que l'écriture.

Les URL signées exigent expiration courte, opération prévue et bon objet. Un identifiant imprévisible réduit le guessing mais ne remplace pas l'autorisation.

4. Construire une matrice de tests négatifs

Pour chaque action sensible, testez anonyme, session expirée, membre normal, membre privilégié, propriétaire, même rôle dans un autre tenant et membre supprimé. Remplacez les identifiants de route et ceux imbriqués dans le corps. Testez aussi endpoints bulk et références indirectes.

for (const actor of [anonymous, memberA, adminB, removedMember]) {
  const res = await updateProject({ actor, projectId: projectA.id });
  expect(res.status).toBe(actor === adminA ? 200 : 404);
}

Erreurs fréquentes

  • Se reposer sur l'UI ou le middleware de route comme seul contrôle.
  • Vérifier un rôle global sans la relation tenant.
  • Protéger les lectures mais oublier exports, pièces jointes ou mutations.
  • Utiliser un UUID comme preuve de permission.
  • Autoriser avant l'écriture sans contraindre la transaction.
  • Tester uniquement les cas autorisés.

Checklist

  • Acteur, action, ressource et contexte sont explicites.
  • Le résultat par défaut est le refus.
  • Chaque requête inclut propriétaire ou tenant.
  • Les actions sensibles ont des permissions séparées.
  • Identifiants imbriqués et bulk reçoivent les mêmes contrôles.
  • Les liens signés expirent et ciblent une opération.
  • Les tests négatifs croisent rôles, tenants et états.
  • Les refus sont journalisés sans détail privé dans la réponse.

Limites

Une matrice de rôles peut manquer les règles basées sur relations et attributs. Les systèmes distribués doivent propager l'identité et appliquer les règles dans chaque service. Revoyez stockage, files, caches et exports analytiques qui contournent parfois l'API principale.

Sources primaires

Faire relire les frontières tenant et rôles

Appliquer ces contrôles au code et aux frontières produit réelles.

Discuter d'une revue de code