2 points par GN⁺ 2025-03-31 | 1 commentaires | Partager sur WhatsApp
  • La cause de l’affichage cassé du site de David Bushell chez certains utilisateurs pendant longtemps était du CSS injecté discrètement dans la page par l’extension de navigateur Grammarly
  • Dans Firefox, l’extension Grammarly insère une feuille de style issue de ses ressources locales d’extension, difficile à repérer via le StyleSheetList d’une page web, et contourne aussi la Content Security Policy
  • Le conflit vient du fait que Grammarly définissait globalement --rem:16 sur :root, tandis que le site utilisait aussi --rem pour calculer sa typographie fluide
  • Le --rem du site se trouvait dans une cascade layer, et à cause des règles CSS où les styles hors couche priment sur ceux dans une couche, la valeur de Grammarly pouvait écraser le calcul
  • En attendant, il a tenu avec un mutation observer et !important, mais la réponse finale a été de renommer la propriété en --🤡 ; quand une extension injecte un nom banal globalement sur :root, elle peut facilement entrer en conflit avec une page web

Le CSS de Grammarly entré dans la page

  • Pendant plusieurs mois, des signalements épars indiquaient que la mise en page du site se décalait et que les tailles étaient étranges, avec captures d’écran à l’appui
  • Des lecteurs techniquement avertis ont désigné l’extension de navigateur Grammarly comme principal suspect, et David Bushell l’a installée lui-même dans le navigateur Mullvad basé sur Firefox pour vérifier
  • Lors de l’installation, les autorisations incluent notamment
    • l’accès aux données de tous les sites web
    • l’affichage de notifications
    • l’accès aux onglets du navigateur
  • Grammarly injecte dans les pages web une feuille de style chargée depuis les ressources locales de l’extension
    • cette feuille de style ne peut pas être trouvée par la page web via StyleSheetList
    • elle contourne aussi la Content Security Policy
    • dans Firefox, elle fonctionne comme une feuille de style furtive difficile à détecter pour le site lui-même
  • L’extension ajoute aussi l’élément personnalisé <grammarly-desktop-integration> au document <html> de tous les sites web, même sans interaction de l’utilisateur

Comment un seul nom, --rem, a cassé la mise en page

  • À la fin de la feuille de style Grammarly, on trouve le CSS suivant
:host,
:root {
  --rem:16
}
  • D’autres parties de cette même feuille de style utilisent --rem pour calculer la taille de police et la hauteur de ligne
.kE2Bj {
  font-size:calc(0.86px*(var(--rem) - 2));
  line-height:calc(1.2868px*(var(--rem) - 2));
}
  • Le site utilisait lui aussi une propriété personnalisée --rem pour ses propres expérimentations de typographie fluide
@layer base {
  :root {
    --rem: 0.0625rem;
    --fluid: calc((100vi - (400 * var(--rem))) / (1920 - 400));
    --font-size-h1: clamp(
      calc(31 * var(--rem)),
      calc((31 * var(--rem)) + (80 - 31) * var(--fluid)),
      calc(80 * var(--rem))
    );
  }
}
  • Le --rem du site était défini dans une cascade layer, et les styles hors couche priment sur ceux dans une couche, indépendamment de la spécificité CSS
    • l’ordre des sources joue aussi, donc le --rem de Grammarly a pu l’emporter
    • résultat : les formules de calcul du site se sont retrouvées cassées, provoquant les problèmes de mise en page
  • Au début, une parade consistait à détecter le web component ajouté via un mutation observer puis à ajouter des styles !important
  • Une fois la cause exacte identifiée, le site a renommé sa propriété personnalisée en --🤡
    • ce nom reste un nom de propriété personnalisée valide en CSS
    • --rem devient un nom à risque de conflit puisque Grammarly l’utilise globalement
  • Grammarly génère des noms de classes aléatoires, mais a appliqué globalement sur :root un nom de propriété personnalisée aussi générique que --rem, et injecte du code dans toutes les pages web même quand l’extension n’est pas réellement utilisée
  • L’équipe de support de Grammarly a été contactée, mais il n’a pas encore été possible d’atteindre un interlocuteur technique qui comprenne réellement le problème

1 commentaires

 
GN⁺ 2025-03-31
Avis sur Hacker News
  • Mon expérience avec les problèmes d’extensions est un peu différente. Nous distribuons une extension qui facilite le changement de serveur proxy pour les tests de géolocalisation.
    Il y a quelques mois, nous avons fait la pire démo client qui soit : le produit semblait tout simplement ne pas fonctionner. Après un long débogage, nous avons découvert qu’une mise à jour récente de l’extension 1Password avait cassé notre extension. 1Password s’abonnait aux événements d’authentification mais ne renvoyait rien, ce qui provoquait un timeout ; notre propre abonné n’était donc jamais appelé. Notre extension avait demandé au navigateur de changer de serveur proxy et se tenait prête à fournir les identifiants, mais la requête n’arrivait jamais. Le support de 1Password a été meilleur que celui de Grammarly, mais il est difficile de convaincre un PM inconnu, via le support, d’en faire une priorité.
    Nous avons ensuite appris qu’une certaine extension nécessaire pour des sites web du gouvernement russe avait le même problème.

    • Situation similaire. 1Password casse toujours la possibilité d’ouvrir l’interface du panneau latéral de Chrome depuis les scripts de contenu d’autres extensions. Il casse le flag de confiance indiquant que l’événement provenait d’une interaction utilisateur.
      Ayant travaillé plus de dix ans sur les extensions, je pense qu’au final Google porte une grande part de responsabilité. Indépendamment de la question politique des changements touchant les bloqueurs de pubs, Manifest v3 est, à bien des égards, beaucoup moins bon que prévu.
      Globalement, j’ai l’impression que la qualité de la base de code de Chromium a beaucoup baissé par rapport à avant.
  • Si vous injectez des scripts ou des styles dans des pages inconnues, vous devriez au minimum isoler les espaces de noms des variables.

    • Ce qui m’énerve vraiment, c’est qu’il y a environ 5 ou 6 mois, lors d’un entretien, j’ai parlé de la startup Instagram/branding où j’avais été CTO et développeur principal en 2014. J’ai expliqué qu’à l’époque nous avions mis en place un système de build pour que les classes CSS et les objets JavaScript aient des espaces de noms correctement isolés, afin d’éviter toute possibilité de collision, et que nous gérions précisément quels scripts charger selon les widgets présents sur les sites clients tiers.
      Mais l’intervieweur a balayé ça d’un revers de main, comme si les outils modernes faisaient tout ça automatiquement et que tout le monde le faisait. J’étais obligé d’être plus ou moins d’accord, parce que je ne travaille plus là-dessus aujourd’hui et que je ne sais donc pas vraiment. Mais il s’avère qu’en fait, tout le monde ne le fait pas.
    • L’isolation des espaces de noms est pratique non seulement pour les autres, mais aussi pour soi-même. Dans un ancien poste, nous avions développé de l’automatisation navigateur non visible par l’utilisateur ; ce n’était même pas une extension, mais l’isolation des espaces de noms restait utile.
      Nous pouvions distinguer clairement ce que nous avions injecté de ce qui existait déjà, et éviter aussi les collisions potentielles.
    • Ça fait un moment que j’ai quitté le frontend ; aujourd’hui, comment gère-t-on généralement l’isolation des espaces de noms CSS ?
    • Mieux encore : utiliser le Shadow DOM.
  • Voir cet intrus vert installé par défaut sur tous les sites web lors de partages d’écran ou d’enregistrements, ça fait peur. Ce n’est pas seulement une gêne visuelle : cela entraîne aussi des problèmes de confidentialité et des vecteurs d’attaque évidents.
    Chrome permet d’activer les extensions uniquement quand on en a besoin ; je ne comprends pas pourquoi personne ne le fait. Je me demande aussi pourquoi ce n’est pas le comportement par défaut de tous les navigateurs.

    • Je me sens assez chanceux d’avoir des collègues qui se soucient de ce genre de choses. Il m’est arrivé d’interrompre des réunions lorsqu’il était évident que certains participants avaient installé une extension particulière ou divers types d’assistants IA.
      Certains collègues sont mal à l’aise à l’idée que des informations puissent être transmises à des tiers, donc nous suspendons la réunion jusqu’à ce que les extensions soient désactivées.
  • Je suis ingénieur sur l’extension Grammarly. Tout d’abord, je suis vraiment désolé que notre extension ait dégradé l’expérience utilisateur de dbushell.com et ait fait perdre du temps et des efforts à l’auteur pour en trouver la cause.
    Ce n’était pas intentionnel, et nous utilisons plusieurs techniques pour éviter ce genre de situation. Mais cela n’a pas suffi, et l’article montre clairement qu’il y a matière à amélioration.
    Comme correctif rapide, nous avons ajouté une exception temporaire pour dbushell.com. En parallèle, nous travaillons sur un changement garantissant une isolation correcte des styles ; ce type de problème ne devrait jamais se produire.

  • J’ai un problème similaire avec Google Translate, qui casse mon application web. Les utilisateurs utilisent Google Translate puis se plaignent que mon application est cassée, alors qu’en réalité c’est Google qui a modifié l’état de l’application à une couche méta supérieure. C’est vraiment une mauvaise pratique.
    Je travaille à détecter Google Translate pour afficher un avertissement.

    • Cela pourrait être lié à un cas d’il y a deux jours : https://www.pewresearch.org/decoded/2025/03/21/how-a-glitch-... / https://news.ycombinator.com/item?id=43441880
    • L’interférence de Google Translate est agaçante, mais avec les outils actuels des navigateurs, je pense qu’il est difficile de faire autrement.
      Par exemple, il faut parfois traduire une phrase comme « [cliquez ici] pour voir plus d’informations ». Dans une autre langue, il peut être nécessaire de déplacer le lien en fin de phrase, par exemple « pour voir plus d’informations, [cliquez ici] ». Pour faire cela, il faut réorganiser les éléments du DOM, ce qui peut entrer en conflit avec les applications interactives.
      L’équipe Google Translate peut faire beaucoup de choses pour réduire les interférences, mais sans nouvelle API navigateur, je pense qu’il est difficile de les éliminer complètement.
  • Je l’ai transmis à l’équipe d’ingénierie.

    • C’est assez agaçant de voir ce genre de correction d’une ligne rester longtemps dans l’enfer du backlog. J’ai envie de travailler dans une entreprise où un développeur dit : « le corriger maintenant sera plus rapide que d’écrire un ticket, donc faisons-le simplement ».
      Là où je travaille, ça me rend fou que les gens ne fassent pas ça. Même le directeur de l’ingénierie ajoute à ses tickets des choses qui prendraient moins de temps à traiter directement. Cela dit, entendre souvent « je n’ai pas créé de ticket pour envoyer un message, j’ai simplement envoyé un message directement à cette personne comme tu le préconises » est quand même bon signe.
  • Dans l’entreprise, on voit beaucoup d’erreurs Sentry dues à des extensions de navigateur qui font des choses bizarres
    Google Translate dans Chrome est aussi tristement célèbre pour casser les sites basés sur React
    Au final, cela devient un travail de tri fastidieux où l’on ignore une à une les erreurs causées par de nouvelles extensions. Pour réduire le volume collecté, on utilise du filtrage côté client. Globalement, il y a beaucoup plus de bruit que côté backend, donc il faut fixer des seuils bien plus élevés

    • Ce n’est pas qu’un simple bruit. Les utilisateurs subissent réellement des plantages ou d’autres problèmes à cause de cela. Il existe un article détaillé sur les interférences de l’extension Google Translate avec React et d’autres applications web : https://martijnhols.nl/blog/everything-about-google-translat...
      Il n’est pas surprenant qu’il y ait beaucoup plus d’erreurs côté frontend. C’est parce qu’il faut prendre en charge bien plus de variantes client que dans un backend classique. Construire une grosse application web qui fonctionne bien pour tout le monde peut être très difficile
    • Tu parles de l’erreur « Object captured as exception » ? Si c’est celle pour laquelle Sentry ne donne aucune indication, nous, on la filtre simplement côté client
  • Je me demande quelle variable unique, si on l’injectait, pourrait casser le web le plus largement. Il me vient ceci :
    --primary-color: transparent

    • --serif: "Comic Sans MS"
  • Comment faut-il gérer les extensions de navigateur hostiles ?

    • C’est ma plainte préférée sur le site communautaire que je gère. « Les photos ne s’affichent pas sur la page des annonces. » Vous utilisez un bloqueur de pubs ? « Oui. » Et selon vous, que fait un bloqueur de pubs...
    • On pourrait peut-être définir un état valide du DOM de la page, puis, quelques secondes après la fin du chargement, scanner et supprimer les éléments et styles CSS « hostiles »
      En réfléchissant à ça, j’ai ouvert une page quelconque de The Guardian dans DevTools, et quelqu’un y avait injecté un script et une iframe pointant vers twitter.com
    • Dans ce cas, je trouve que le terme « hostile » est un peu excessif. « Incompétent » suffit. Bon, c’est plus long à prononcer
      Je n’aime pas Grammarly ni son modèle technologique, mais il n’est pas juste d’attribuer à la malveillance ce qui s’explique suffisamment par la bêtise
      Ça fait longtemps que je n’ai pas fait de frontend, mais l’extension Grammarly comme votre propre code ne devraient-ils pas utiliser des noms d’attributs namespacés ?
    • À ce stade, je n’installe tout simplement plus d’extensions de navigateur
    • Il suffit de la désinstaller, non ?
  • Je me demande si on ne pourrait pas s’en servir pour détourner ce plugin. À tout le moins, on devrait pouvoir injecter du texte, et probablement aussi afficher un joli formulaire de connexion en exploitant la confiance que l’utilisateur accorde à l’extension
    Est-il vraiment sûr d’injecter des éléments dans un document contrôlé par quelqu’un d’autre ?

    • Je ne vois pas comment ça fonctionnerait. Ils injectent du CSS dans ta page, mais le site web ne peut rien injecter dans l’interface de l’extension
      Tout ce qu’il peut faire, c’est imiter l’interface de l’extension à l’intérieur du site web, mais cela ne nécessite pas d’injection. Il suffit de copier le design