$ cat post.metadata

Sentryで検出された「Can't find variable: CONFIG」エラーの調査 ― サードパーティスクリプト起因の見極め方

FrontendMonitoring

Sentryで報告されたReferenceError「Can't find variable: CONFIG」の原因調査。自プロダクトのバグか、サードパーティスクリプト起因かを切り分ける方法を解説する。

$ cat post.content | render --format=markdown

発端:Sentryに見慣れないエラーが飛んできた

本番環境のSentryに以下のエラーが報告された。

ReferenceError: Can't find variable: CONFIG

スタックトレースは以下の通り。

updateGapFiller at line 311:46
updateFooterPositions at line 449:18

ページのURLは公開用のランディングページ。ユーザーがTwitter経由でアクセスした際に発生している。

一見すると自分たちのコードにバグがあるように見える。CONFIGという変数を定義し忘れたのでは? という疑いが最初に浮かぶ。

調査ステップ1:自プロダクトのコードを検索する

まず、コードベース全体でCONFIGupdateGapFillerupdateFooterPositionsをgrepした。

結果:該当なし。

プロジェクト内のどこにもこれらの識別子は存在しなかった。ランディングページのレンダリングに関わるコンポーネントも全て確認したが、これらの関数やグローバル変数を定義・参照している箇所はゼロ。

この時点で「自プロダクトのバグではない可能性が高い」と判断できる。

調査ステップ2:Sentryのメタデータを読む

エラーの詳細情報から、以下の手がかりが得られた。

項目
browserTwitter 11.71
OSiOS 16.3.1
deviceiPhone
mechanismauto.browser.global_handlers.onerror
handledfalse
Refererhttps://t.co/

ここで重要なのは3つ。

1. ブラウザが「Twitter 11.71」

SafariでもChromeでもなく、Twitterアプリ内ブラウザ(In-App Browser) で発生している。iOSのIn-App BrowserはWKWebViewベースだが、アプリ側が独自のJavaScriptを注入することがある。

2. キャプチャメカニズムが global_handlers.onerror

Sentryが window.onerror 経由でキャッチしたエラー。つまり、自分たちのtry-catchで捕捉したエラーではなく、グローバルスコープで発生した未捕捉エラー。サードパーティスクリプトのエラーもここに流れてくる。

3. Refererが t.co

TwitterのURL短縮サービス経由のアクセス。ユーザーがTwitterのタイムライン上でリンクをタップし、Twitterアプリ内ブラウザで開いたことがわかる。

調査ステップ3:Breadcrumbsを確認する

Sentryのブレッドクラムを確認すると、エラー発生直前に以下のネットワークリクエストが記録されていた。

XHR  GET  score.im-apps.net/v1/fraud       [200]
Fetch POST ad.doubleclick.net/activity       [0]
Fetch POST www.google.com/ccm/collect        [0]

これらはすべて広告トラッキング・不正検知系のサードパーティスクリプトによるリクエスト。自プロダクトのAPIコールではない。

結論:Twitterアプリ内ブラウザが注入したスクリプトのエラー

調査結果をまとめると、原因は以下の通り。

TwitterのiOSアプリ内ブラウザは、ページの表示を調整するために独自のJavaScriptを注入する。その中に updateGapFiller(ギャップフィラーの更新)や updateFooterPositions(フッター位置の更新)といったレイアウト調整用の関数が含まれている。

これらの関数が CONFIG というグローバル変数を参照するが、特定のバージョン(Twitter 11.71 / iOS 16.3.1)でこの変数の初期化が完了する前に関数が実行されてしまい、ReferenceError が発生する。

サードパーティスクリプト起因かを見極めるチェックリスト

今回の調査で得られた、エラーが自プロダクト由来かサードパーティ由来かを判断するためのチェックリストをまとめる。

チェック項目自プロダクト由来サードパーティ由来
関数名・変数名がコードベースに存在するか存在する存在しない
Sentryのmechanismは何かgeneric / instrumentglobal_handlers.onerror
特定のブラウザ・環境に限定されるか複数環境で再現特定環境のみ
Breadcrumbsに自プロダクトのAPI呼び出しがあるか直前にあるサードパーティのリクエストのみ
エラーの行番号がソースマップで解決できるか解決できる解決できない

対処法

このようなサードパーティスクリプト起因のエラーに対しては、以下の対処が有効。

Sentryの ignoreErrors でフィルタリング

typescript
Sentry.init({ dsn: "...", ignoreErrors: [ // Twitter In-App Browserの注入スクリプト "Can't find variable: CONFIG", // その他よくあるサードパーティエラー "ResizeObserver loop limit exceeded", "ResizeObserver loop completed with undelivered notifications", ], });

denyUrls で特定ドメインのスクリプトを除外

typescript
Sentry.init({ dsn: "...", denyUrls: [ /extensions\//i, /^chrome:\/\//i, /^moz-extension:\/\//i, ], });

beforeSend でより細かくフィルタリング

typescript
Sentry.init({ dsn: "...", beforeSend(event) { const frames = event.exception?.values?.[0]?.stacktrace?.frames; if (frames?.some(frame => frame.function === "updateGapFiller" || frame.function === "updateFooterPositions" )) { return null; // このイベントを送信しない } return event; }, });

まとめ

Sentryに見慣れないエラーが飛んできたとき、まず疑うべきは「本当に自分たちのコードのバグか?」ということ。特に以下のケースではサードパーティスクリプト起因を強く疑う。

  • コードベースにエラーメッセージの関数名・変数名が存在しない
  • 特定のブラウザ(特にIn-App Browser)でのみ発生する
  • window.onerror 経由でキャッチされている
  • Breadcrumbsが広告・トラッキング系のリクエストで埋まっている

ノイズを適切にフィルタリングすることで、本当に対処すべきエラーに集中できるようになる。

$ echo $TAGS
#Sentry#JavaScript#Debugging#Twitter#In-App Browser