Aller au contenu

Tous les billets

La correction qui a empiré les choses

J’ai corrigé un défaut mineur du presse-papier. Ma correction a transformé une exposition de vingt secondes en exposition illimitée.

sécuritérevuepresse-papier

Un gestionnaire de secrets copie une valeur dans le presse-papier, puis l’efface au bout de vingt secondes. Classique, et suffisant.

Mon implémentation avait un défaut visible : elle effaçait le presse-papier même si l’utilisateur avait copié autre chose entre-temps. Désagréable. Je l’ai corrigé de deux façons à la fois.

  1. Vérifier avant d’effacer que le presse-papier contient toujours notre valeur.
  2. Annuler la minuterie quand le composant disparaît de l’écran, pour ne pas laisser courir un traitement orphelin.

Les deux précautions sont raisonnables. Prises ensemble, elles ont produit une régression bien pire que le défaut d’origine.

Ce qui se passait

La lecture du presse-papier demande une permission que le navigateur peut refuser. Mon code traitait ce refus comme « ce n’est pas notre valeur » et n’effaçait donc rien.

Et le composant disparaît de l’écran exactement au moment où l’on verrouille le coffre — c’est-à-dire au moment où l’effacement compte le plus. La minuterie était annulée juste avant de faire son seul travail utile.

Résultat : après un verrouillage, le mot de passe restait dans le presse-papier. Indéfiniment.

Une exposition bornée à vingt secondes était devenue une exposition sans fin, au nom de la propreté.

L’erreur de modèle

Je raisonnais en cycle de vie de composant : « quand la ligne disparaît de l’écran, ce qu’elle détenait disparaît avec elle ». C’est vrai de l’affichage. Ce n’est pas vrai du presse-papier, qui appartient au système d’exploitation et ne connaît rien à React.

Le démontage d’un composant n’annule pas un effet de bord qui a quitté la page.

La forme corrigée

L’effacement vit désormais au niveau du module, pas du composant. Il survit au démontage. Et le verrouillage le déclenche explicitement plutôt que de l’annuler :

export function purgerPressePapier(): void {
  if (enAttente === null) return;
  const { valeur, minuteur } = enAttente;
  window.clearTimeout(minuteur);
  enAttente = null;
  void effacer(valeur);
}

Le refus de lecture, lui, ne vaut plus dispense : en cas de doute, on efface.

Le test qui manquait

Rien ne prouvait que le verrouillage appelait cette purge. Un relecteur a simplement commenté l’appel : aucun test n’a échoué. Une garantie que personne ne vérifie n’est pas une garantie, c’est une intention.

Le test existe maintenant, et je l’ai validé en retirant l’appel pour le voir tomber.


Ce que je retiens tient en une phrase : une correction de sécurité qui n’a pas été mise à l’épreuve dans le scénario qu’elle est censée couvrir peut déplacer le problème au lieu de le résoudre — et le déplacer vers le pire.