- David Bushellのサイトが一部ユーザーには長い間壊れて見えていた原因は、Grammarlyブラウザー拡張機能がページにこっそり注入していたCSSだった
- Firefoxでは、Grammarly拡張機能はローカルの拡張機能アセットのスタイルシートを挿入し、Webページの
StyleSheetListでは見つけにくく、Content Security Policyも回避する
- 衝突は、Grammarlyが
:rootにグローバル定義した--rem:16と、サイトの流動タイポグラフィ計算用の--remが同じ名前を使っていたことから発生した
- サイト側の
--remはcascade layer内にあり、レイヤー外のスタイルが優先されるCSSルールのため、Grammarlyの値が計算を上書きできてしまった
- 一時的にはmutation observerと
!importantでしのいだが、最終的な対応はプロパティ名を--🤡に変えることだった。拡張機能がグローバルな:rootにありふれた名前を注入すると、Webページと簡単に衝突し得る
ページ内に入り込んだGrammarly CSS
- 数か月にわたり、サイトのレイアウトがずれ、サイズがおかしいという散発的な報告があり、スクリーンショットも送られてきていた
- 技術に詳しい読者たちはGrammarly browser extensionを主な原因として指摘し、David BushellはFirefoxベースのMullvad browserに直接インストールして確認した
- 拡張機能のインストール時の権限には以下が含まれる
- すべてのWebサイトデータへのアクセス
- 通知の表示
- ブラウザータブへのアクセス
- GrammarlyはWebページに、ローカルの拡張機能アセットから読み込まれるスタイルシートを注入する
- 拡張機能は、ユーザーが操作しなくても、すべてのWebサイトの
<html>文書に<grammarly-desktop-integration>カスタム要素を追加する
--remという名前ひとつがレイアウトを壊した過程
- Grammarlyのスタイルシートの末尾には、次のCSSが含まれている
:host,
:root {
--rem:16
}
- 同じスタイルシートの別の箇所では、
--remを使ってフォントサイズと行の高さを計算している
.kE2Bj {
font-size:calc(0.86px*(var(--rem) - 2));
line-height:calc(1.2868px*(var(--rem) - 2));
}
- サイトもまた、独自の流動タイポグラフィ実験のために
--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))
);
}
}
- サイトの
--remはcascade layer内に定義されており、レイヤー外のスタイルはCSS specificityに関係なく、レイヤー内のスタイルより優先される
- ソース順も影響するため、Grammarlyの
--remが勝った可能性がある
- その結果、サイトの計算式が壊れ、レイアウト問題が発生した
- 当初はmutation observerで追加されたWebコンポーネントを検知した後、
!importantスタイルを加えて対処していた
- 正確な原因を把握した後、サイトのカスタムプロパティ名を
--🤡に変更した
- この名前はCSSで有効なカスタムプロパティ名である
--remはGrammarlyがグローバルに使うため、衝突リスクのある名前になった
- Grammarlyはランダムなクラス名を作っている一方で、
--remという一般的なカスタムプロパティ名を:rootにグローバル適用しており、拡張機能を実際に使わなくても、すべてのWebページにコードを注入する
- Grammarlyサポートチームには連絡したが、まだ問題を理解する技術担当者にはたどり着けていない
1件のコメント
Hacker News の意見
拡張機能の問題で経験した事例は少し違います。地理位置情報テスト用にプロキシサーバーの切り替えを簡単にする拡張機能を配布しています。
数か月前に最悪の顧客デモをしたのですが、製品がまったく動いていないように見えました。しばらくデバッグした末、最近の 1Password 拡張機能のアップデートが私たちの拡張機能を壊していたことを発見しました。1Password が認証イベントを購読したものの返さなかったためタイムアウトし、その結果、私たちの購読者が呼び出されませんでした。私たちの拡張機能はブラウザにプロキシサーバーの変更を指示したあと、認証情報を提供する準備をしていましたが、リクエストが来なかったのです。1Password のサポートチームは Grammarly よりは良かったものの、サポート経由で正体の分からない PM に優先順位を説得するのは難しいです。
その後、ロシア政府のウェブサイトに必要なある拡張機能にも同じ問題があることが分かりました。
10年以上拡張機能の分野に関わってきた立場からすると、結局 Google の責任が大きいです。広告ブロッカー変更という政治的問題とは別に、Manifest v3 は多くの面で期待を大きく下回っています。
全体として、Chromium のコードベースの品質は以前よりかなり低下したように感じます。
未知のページにスクリプトやスタイルを注入するなら、少なくとも変数の名前空間は分離すべきです。
ところが面接官は、そういうことは今どきのツールが全部やってくれるし、誰でもやっている、という感じで軽くあしらいました。その言葉にはある程度同意せざるを得ませんでした。今はその仕事をしていないので、実際のところは分からないからです。ところが、ふたを開けてみると、みんながそうしているわけでもありませんでした。
自分たちが挿入したものと元からあったものを明確に区別でき、潜在的な衝突も避けられました。
画面共有や録画で、あの緑色の侵入者がすべてのウェブサイトにデフォルトで入り込んでいるのを見ると怖くなります。単に見た目が邪魔というだけでなく、プライバシー問題と明らかな攻撃ベクトルがついて回ります。
Chrome では必要なときだけ拡張機能を有効にできるのに、なぜ誰もそうしないのか分かりません。なぜすべてのブラウザのデフォルトがそうなっていないのかも疑問です。
一部の同僚は情報が第三者に渡る可能性を不快に思っているため、拡張機能をオフにするまで会議を中断します。
Grammarly Extension のエンジニアです。まず、私たちの拡張機能が dbushell.com のユーザー体験を壊し、作者に原因究明のための時間と労力を使わせてしまったことを本当に申し訳なく思っています。
意図したことではなく、このようなことが起きないよう複数の手法を使っています。しかし十分ではなく、記事から改善の余地が明確に示されています。
迅速な修正として dbushell.com に一時的な例外を追加しました。同時に、適切なスタイル分離を保証する変更に取り組んでおり、このような問題は絶対に起きるべきではありません。
Google Translate が私の Web アプリを壊すという似た問題があります。ユーザーは Google Translate を使いながら私のアプリが壊れたと文句を言いますが、実際には Google がより高いメタレイヤーでアプリの状態を変えているのです。本当に悪い慣行です。
Google Translate を検出して警告を表示しようとしているところです。
たとえば「[ここをクリック]すると、さらに詳しい情報を見られます」のような文を翻訳しなければならないことがあります。別の言語にすると、リンクを文末に移して「さらに詳しい情報を見るには[ここをクリック]」のようにする必要があるかもしれません。これを行うには DOM 要素の再配置が必要で、それがインタラクティブなアプリと衝突する可能性があります。
Google Translate チームが干渉を減らすためにできることは多いですが、新しいブラウザ API なしに完全に取り除くのは難しいと思います。
エンジニアリングチームに伝えました。
私の職場でも人々がそうしないので、気が狂いそうになります。エンジニアリングディレクターでさえ、すぐ処理するより時間がかからないようなことを自分のチケットとして追加します。それでも「メッセージを送るためのチケットを作らず、あなたのやり方どおりその人に直接メッセージしました」とよく言われるのは良い兆候です。
会社では、ブラウザ拡張機能が妙なことをして発生する Sentry エラーが多い。
Chrome の Google Translate も、React ベースのサイトを壊すことで悪名高い。
結局、新しい拡張機能の問題を一つずつ無視扱いにする、退屈な分類作業になる。収集量を減らすためにクライアント側フィルタリングを使っている。全体的にバックエンドよりノイズが多いので、はるかに高いしきい値を設ける必要がある。
フロントエンドにエラーがはるかに多いのは驚くことではない。一般的なバックエンドよりも、はるかに多くの クライアント側の差異をサポートしなければならないからだ。誰にでもうまく動く大規模な Web アプリを作るのは、非常に難しいことがある。
Web を最も大きく壊せる変数を一つ注入するとしたら何だろう、と思う。こういうのが浮かぶ:
--primary-color: transparent--serif: "Comic Sans MS"敵対的なブラウザ拡張機能にはどう対応すべきか?
そう考えながら The Guardian の適当なページを DevTools で開いてみたら、誰かが twitter.com を指すスクリプトと iframe を挿入していた。
Grammarly やその技術モデルが好きなわけではないが、愚かさで十分説明できることに悪意を見いだすのは公平ではない。
フロントエンド作業をしてから長いが、Grammarly 拡張機能と自分のコードの双方が 名前空間で分離された属性名を使うべきではないのだろうか?
これを利用してあのプラグインをハイジャックできるのではないかと思う。少なくともテキストを注入することはできそうだし、おそらくユーザーが拡張機能に寄せる信頼を悪用して、見栄えのいいログインフォームもレンダリングできるだろう。
他人が制御するドキュメントに要素を注入するのは本当に安全なのか?
できるのは Web サイト内で拡張機能の UI をまねる程度だが、それには注入など必要ない。単にデザインをコピーすればいい。