2 Punkte von GN⁺ 2025-03-31 | 1 Kommentare | Auf WhatsApp teilen
  • Dass die Website von David Bushell für einige Nutzer seit Längerem kaputt dargestellt wurde, lag an CSS, das die Grammarly-Browser-Erweiterung heimlich in die Seite injizierte
  • In Firefox fügt die Grammarly-Erweiterung ein Stylesheet aus lokalen Erweiterungs-Assets ein, das sich über die StyleSheetList der Webseite nur schwer finden lässt und zudem die Content Security Policy umgeht
  • Der Konflikt entstand, weil Grammarly mit --rem:16 eine globale Definition auf :root setzte und die Website für die Berechnung ihrer Fluid-Typografie ebenfalls --rem als Namen verwendete
  • Das --rem der Website befand sich innerhalb einer Cascade Layer, und wegen der CSS-Regel, dass Styles außerhalb von Layern Vorrang haben, konnte der Grammarly-Wert die Berechnung überschreiben
  • Übergangsweise halfen ein Mutation Observer und !important, die endgültige Lösung war jedoch, den Attributnamen in --🤡 zu ändern; wenn eine Erweiterung gewöhnliche Namen global in :root injiziert, kann sie leicht mit Webseiten kollidieren

Grammarly-CSS innerhalb der Seite

  • Über mehrere Monate gab es vereinzelte Meldungen, dass das Layout der Website verrutscht sei und Größen seltsam wirkten; dazu wurden auch Screenshots geschickt
  • Technisch versierte Leser nannten die Grammarly browser extension als Hauptursache, und David Bushell installierte sie selbst im Firefox-basierten Mullvad browser, um das zu überprüfen
  • Bei der Installation fordert die Erweiterung unter anderem folgende Berechtigungen an
    • Zugriff auf die Daten aller Websites
    • Anzeigen von Benachrichtigungen
    • Zugriff auf Browser-Tabs
  • Grammarly injiziert ein Stylesheet in die Webseite, das aus lokalen Erweiterungs-Assets geladen wird
    • Dieses Stylesheet kann von der Webseite nicht über die StyleSheetList gefunden werden
    • Es umgeht auch die Content Security Policy
    • In Firefox verhält es sich wie ein Stealth-Stylesheet, das von der Website selbst nur schwer erkannt werden kann
  • Die Erweiterung fügt dem <html>-Dokument jeder Website auch dann ein benutzerdefiniertes <grammarly-desktop-integration>-Element hinzu, wenn der Nutzer gar nicht mit ihr interagiert

Wie ein einzelner Name --rem das Layout zerstörte

  • Am Ende des Grammarly-Stylesheets steht folgendes CSS
:host,
:root {
  --rem:16
}
  • In anderen Teilen desselben Stylesheets wird --rem verwendet, um Schriftgröße und Zeilenhöhe zu berechnen
.kE2Bj {
  font-size:calc(0.86px*(var(--rem) - 2));
  line-height:calc(1.2868px*(var(--rem) - 2));
}
  • Auch die Website selbst verwendete für eigene Experimente mit Fluid-Typografie die Custom Property --rem
@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))
    );
  }
}
  • Das --rem der Website war innerhalb einer Cascade Layer definiert, und Styles außerhalb von Layern haben unabhängig von der CSS-Spezifität Vorrang vor Styles innerhalb von Layern
    • Auch die Reihenfolge im Quelltext spielt eine Rolle, daher könnte Grammarlys --rem gewonnen haben
    • Dadurch wurden die Berechnungsformeln der Website beschädigt und Layout-Probleme ausgelöst
  • Anfangs reagierte man, indem man mit einem Mutation Observer die hinzugefügte Web-Komponente erkannte und dann Styles mit !important ergänzte
  • Nachdem die genaue Ursache geklärt war, wurde der Name der Custom Property der Website in --🤡 geändert
    • Dieser Name ist in CSS ein gültiger Name für eine Custom Property
    • --rem ist ein riskanter Name geworden, wenn Grammarly ihn global verwendet
  • Obwohl Grammarly zufällige Klassennamen erzeugt, wendet es die generische Custom Property --rem global auf :root an und injiziert Code in jede Webseite, selbst wenn die Erweiterung dort gar nicht aktiv genutzt wird
  • Das Grammarly-Supportteam wurde kontaktiert, aber bisher hat die Anfrage noch keine technisch zuständige Person erreicht

1 Kommentare

 
GN⁺ 2025-03-31
Meinungen auf Hacker News
  • Mein Fall mit einem Extension-Problem ist etwas anders. Ich veröffentliche eine Extension, mit der man für Geolocation-Tests einfach Proxy-Server wechseln kann.
    Vor ein paar Monaten hatte ich die schlimmste Kundendemo überhaupt: Es sah einfach so aus, als würde das Produkt gar nicht funktionieren. Nach langem Debugging stellte sich heraus, dass ein aktuelles Update der 1Password-Extension unsere Extension kaputtgemacht hatte. 1Password hatte Authentifizierungs-Events abonniert, sie aber nicht zurückgegeben, sodass es zu einem Timeout kam und unser Subscriber deshalb nie aufgerufen wurde. Unsere Extension hatte den Browser angewiesen, den Proxy-Server zu ändern, und war bereit, Zugangsdaten bereitzustellen, aber die Anfrage kam nie. Der Support von 1Password war besser als Grammarlys, aber es ist schwer, über den Support irgendeinen unbekannten PM von der Priorität zu überzeugen.
    Später stellte sich heraus, dass eine bestimmte Extension, die für Websites der russischen Regierung nötig ist, dasselbe Problem hat.

    • Ähnliche Situation. 1Password macht weiterhin die Funktion kaputt, aus dem Content Script anderer Extensions heraus die Chrome-Side-Panel-UI zu öffnen. Es zerstört das Trust-Flag, das anzeigt, dass ein Event aus einer Nutzerinteraktion stammt.
      Aus der Sicht von jemandem, der seit über zehn Jahren mit Extensions zu tun hat, trägt am Ende Google einen großen Teil der Verantwortung. Abgesehen von der politischen Frage rund um Änderungen bei Adblockern ist Manifest v3 in vielerlei Hinsicht deutlich schlechter als erwartet.
      Insgesamt fühlt es sich so an, als sei die Qualität der Chromium-Codebasis im Vergleich zu früher stark gesunken.
  • Wenn man Skripte oder Styles in unbekannte Seiten injiziert, sollte man wenigstens Variablen-Namensräume trennen.

    • Das macht mich wirklich wütend: Vor etwa fünf oder sechs Monaten habe ich in einem Vorstellungsgespräch von einem Instagram-/Branding-Startup erzählt, bei dem ich 2014 CTO und Lead Developer war. Ich erklärte, dass ich damals ein Build-System gebaut hatte, das CSS-Klassen und JavaScript-Objekte sauber namespaced, Kollisionen ausschloss und verwaltete, welche Skripte je nach den Widgets auf den Third-Party-Kundenseiten genau geladen werden mussten.
      Der Interviewer tat das abfällig ab, als würden heutige Tools so etwas sowieso alles erledigen und jeder würde es machen. Ich musste ihm bis zu einem gewissen Grad zustimmen, weil ich aktuell nicht mehr in dem Bereich arbeite und es daher nicht wirklich weiß. Wie sich herausstellt, macht es aber eben doch nicht jeder.
    • Namespacing ist nicht nur für andere praktisch, sondern auch für einen selbst. In einem früheren Job habe ich Browser-Automatisierung gebaut, die für Nutzer nicht sichtbar war; es war nicht einmal eine Extension, aber Namespacing war trotzdem nützlich.
      So konnten wir klar unterscheiden, was von uns eingefügt wurde und was ursprünglich vorhanden war, und potenzielle Konflikte vermeiden.
    • Ich bin schon eine Weile aus dem Frontend-Bereich raus: Wie wird CSS-Namespacing heutzutage üblicherweise gehandhabt?
    • Noch besser: Shadow DOM verwenden.
  • Es ist beängstigend, diesen grünen Eindringling bei Screen-Sharing oder Aufzeichnungen standardmäßig auf jeder Website zu sehen. Es geht nicht nur darum, dass es optisch stört; damit kommen Datenschutzprobleme und ein offensichtlicher Angriffsvektor.
    In Chrome kann man Extensions nur bei Bedarf aktivieren – ich verstehe nicht, warum das niemand macht. Und ich frage mich auch, warum das nicht in allen Browsern die Standardeinstellung ist.

    • Ich fühle mich ziemlich glücklich, Kollegen zu haben, denen so etwas wichtig ist. Wir haben Meetings auch schon gestoppt, wenn offensichtlich war, dass einige Teilnehmer bestimmte Extensions oder verschiedene Arten von AI Assistants installiert hatten.
      Einige Kollegen fühlen sich unwohl damit, dass Informationen möglicherweise an Dritte gelangen, und unterbrechen das Meeting, bis die Extensions deaktiviert sind.
  • Ich bin Engineer an der Grammarly Extension. Zunächst einmal tut es uns wirklich leid, dass unsere Extension die User Experience auf dbushell.com beeinträchtigt und den Autor Zeit und Mühe gekostet hat, die Ursache zu finden.
    Das war nicht beabsichtigt, und wir verwenden mehrere Techniken, um solche Fälle zu verhindern. Sie waren aber nicht ausreichend, und der Beitrag zeigt klar, wo Verbesserungsbedarf besteht.
    Als schnelle Korrektur haben wir dbushell.com vorübergehend als Ausnahme hinzugefügt. Gleichzeitig arbeiten wir an Änderungen, die eine angemessene Style-Isolation sicherstellen; solche Probleme sollten niemals auftreten.

  • Ich habe ein ähnliches Problem, bei dem Google Translate meine Web-App kaputtmacht. Nutzer verwenden Google Translate und beschweren sich dann, dass meine App defekt sei, tatsächlich aber hat Google auf einer höheren Meta-Ebene den Zustand der App verändert. Das ist wirklich schlechte Praxis.
    Ich versuche gerade, Google Translate zu erkennen und eine Warnung anzuzeigen.

    • Könnte mit dem Fall von vor zwei Tagen zusammenhängen: https://www.pewresearch.org/decoded/2025/03/21/how-a-glitch-... / https://news.ycombinator.com/item?id=43441880
    • Die Eingriffe von Google Translate sind nervig, aber mit den heutigen Browser-Tools sehe ich ehrlich gesagt kaum, wie es anders funktionieren könnte.
      Manchmal muss zum Beispiel ein Satz wie „[Hier klicken], um weitere Informationen zu sehen“ übersetzt werden. In einer anderen Sprache muss der Link vielleicht ans Satzende verschoben werden, etwa zu „Um weitere Informationen zu sehen, [hier klicken]“. Dafür ist eine Neuanordnung von DOM-Elementen nötig, und das kann mit interaktiven Apps kollidieren.
      Das Google-Translate-Team kann viel tun, um Eingriffe zu reduzieren, aber ohne neue Browser-APIs dürfte es schwer sein, sie vollständig zu eliminieren.
  • Ich habe es an das Engineering-Team weitergeleitet.

    • Es ist ziemlich frustrierend, wenn solche Ein-Zeilen-Fixes lange in der Backlog-Hölle liegen bleiben. Ich will in einem Unternehmen arbeiten, in dem Entwickler sagen: „Das jetzt zu fixen geht schneller, als ein Ticket zu schreiben, also machen wir es einfach.“
      Auch dort, wo ich arbeite, macht mich wahnsinnig, dass die Leute das nicht tun. Sogar der Engineering Director fügt Dinge als eigenes Ticket hinzu, die weniger Zeit kosten würden, wenn man sie einfach erledigt. Trotzdem ist es ein gutes Zeichen, dass ich oft höre: „Ich habe kein Ticket erstellt, um eine Nachricht zu schicken, sondern deiner Vorgehensweise folgend direkt der Person geschrieben.“
  • In Unternehmen gibt es viele Sentry-Fehler, die dadurch entstehen, dass Browser-Erweiterungen seltsame Dinge tun.
    Auch Google Translate in Chrome ist berüchtigt dafür, React-basierte Websites kaputtzumachen.
    Am Ende wird daraus eine mühsame Triage-Arbeit, bei der man neue Erweiterungsprobleme einzeln ignoriert. Um das Erfassungsvolumen zu reduzieren, nutzen wir clientseitiges Filtering. Insgesamt gibt es im Frontend viel mehr Rauschen als im Backend, daher müssen die Schwellenwerte deutlich höher liegen.

    • Es ist nicht nur simples Rauschen. Nutzer erleben dadurch tatsächlich Abstürze oder andere Probleme. Es gibt einen ausführlichen Artikel über die Interferenzen der Google-Translate-Erweiterung mit React und anderen Web-Apps: https://martijnhols.nl/blog/everything-about-google-translat...
      Dass es im Frontend viel mehr Fehler gibt, ist nicht überraschend. Man muss dort deutlich mehr Client-Varianten unterstützen als bei einem typischen Backend. Eine große Web-App zu bauen, die für alle gut funktioniert, kann sehr schwierig sein.
    • Meinst du den Fehler „Object captured as exception“? Falls es dieser Fehler ist, zu dem Sentry keinerlei Anleitung gibt: Wir filtern ihn einfach clientseitig heraus.
  • Ich frage mich, welche einzelne Variable man einschleusen müsste, um das Web maximal kaputtzumachen. Mir fällt so etwas ein:
    --primary-color: transparent

    • --serif: "Comic Sans MS"
  • Wie sollte man mit feindseligen Browser-Erweiterungen umgehen?

    • Meine Lieblingsbeschwerde auf der Community-Website, die ich betreibe: „Auf der Anzeigenseite werden keine Fotos angezeigt.“ Nutzt du einen Adblocker? „Ja.“ Was glaubst du, was ein Adblocker macht ...
    • Man könnte den gültigen Zustand des Seiten-DOM definieren und ein paar Sekunden nach dem Laden der Seite nach „feindseligen“ Elementen und CSS-Styles scannen und sie löschen.
      Während ich darüber nachdachte, öffnete ich irgendeine Seite von The Guardian in den DevTools, und jemand hatte dort ein Script und ein iframe eingefügt, die auf twitter.com verweisen.
    • In diesem Fall finde ich „feindselig“ etwas übertrieben. „Mangelnde Kompetenz“ reicht aus. Hat allerdings mehr Silben.
      Ich mag Grammarly oder dessen technisches Modell nicht, aber es ist nicht fair, Böswilligkeit anzunehmen, wenn Dummheit als Erklärung ausreicht.
      Es ist schon eine Weile her, dass ich Frontend-Arbeit gemacht habe, aber sollten nicht sowohl die Grammarly-Erweiterung als auch der eigene Code namensraumgetrennte Attributnamen verwenden?
    • An diesem Punkt installiere ich grundsätzlich keine Browser-Erweiterungen mehr.
    • Könnte man sie nicht einfach entfernen?
  • Ich frage mich, ob man das nutzen könnte, um dieses Plugin zu hijacken. Zumindest dürfte man Text injizieren können, und vermutlich könnte man auch ein hübsches Login-Formular rendern, das das Vertrauen der Nutzer in die Erweiterung ausnutzt.
    Ist es wirklich sicher, Elemente in ein Dokument einzufügen, das von jemand anderem kontrolliert wird?

    • Ich verstehe nicht, wie das funktionieren soll. Sie injizieren CSS in deine Seite, aber die Website kann nichts in die UI der Erweiterung injizieren.
      Was man tun kann, ist, die UI der Erweiterung innerhalb der Website nachzuahmen; dafür braucht man aber keine Injektion. Man kopiert einfach das Design.