フィンガープリントフラグ
アイデンティティは Chromium のコマンドラインスイッチで制御します。SDK は名前付きオプションからこれらのスイッチを設定します。オープンビルドでは、自前のランチャーに args として渡すこともできます。
ほとんどのスイッチはどちらのビルドにもあります。一部はライセンスビルド(「GitHub で無料」または Pro)専用で、下のスイッチ一覧では、そうしたスイッチに、追加されたエンジンリビジョンを併記しています。ライセンスキーがなければ SDK はオープンビルドで動作し、そこではこれらのオプションは効果を持ちません。
シードモデル
すべての起点は --fingerprint=<seed> です。シード(整数または任意の文字列)からペルソナが決定論的に導出され、さらに後述のファーブリングの対象に挙げた 2 つのレンダリングサーフェスにノイズが加わります。
- 同じシード ⇒ 同じアイデンティティ。起動をまたいでも変わらないので、再訪問者として見えます。
- 新しいシード ⇒ 新しい、もっともらしいアイデンティティ。
- canvas と WebGL のノイズはサイトごとに導出されます。Brave の farbling(ファーブリング)に着想を得た方式で、これらのハッシュはドメインごとに異なります。一方、ペルソナ自体(GPU、画面、ハードウェア、フォント)は、実在する 1 台のマシンと同じく、どのサイトでも同じです。
シードから合成したアイデンティティより、実在するマシンのアイデンティティを使いたい場合は、コレクターでドナーとなる Chrome の値を取得し、--fingerprint-profile=<gzip+base64 JSON> でインポートします(SDK の fingerprint_profile / fingerprintProfile オプションを使えば、gzip+base64 への変換は SDK が行います)。プロファイルに含まれるフィールドはペルソナより優先され、含まれないフィールドは --fingerprint シードのペルソナに、シードがなければブラウザのデフォルト値にフォールバックします。インポートしたプロファイルはどちらのビルドでも読み込めます。ライセンスビルドでは 151 r19 以降、全面的に適用されます。取得したプロファイルを SDK からインポートする方法は Playwright ガイドを参照してください。
ファーブリングする対象と、あえてしない対象
サーフェスにノイズを加えると、紐付けされにくくなる代わりに整合性が損なわれます。このトレードオフは、どこでも割に合うわけではありません。Clearcote がファーブリングするのは、ノイズが通常のハードウェア由来のばらつきに最も近くなる 2 つのサーフェスだけで、それ以外にはノイズを加えません。それらのサーフェスでは、ノイズを加えた値のほうが、プロファイル間で共有される値よりも目立つシグナルになるからです。ただし、ノイズを加えないことは、何も手を加えないことと同じではありません。いくつかの値は、ペルソナの値に丸ごと置き換えられます。詳しくは下の表とその下の注記で説明します。チェッカーの中には、ノイズそのものを検知できるものもあります。それが問題になる場合は、fingerprintNoise: false でノイズをオフにし、アイデンティティはそのまま維持できます(検知の仕組みを参照)。
| サーフェス | シードごとのノイズ | 理由 |
|---|---|---|
Canvas 2D:toDataURL、getImageData など、ページが canvas を書き出すあらゆる手段(ライセンスビルドでは 152 r21 以降、すべての書き出し経路の結果が一致します) | あり | 読み出し結果は、実機同士でも GPU、ドライバ、ラスタライズ経路によってもともとばらつきます。そのため、小さなノイズなら自然なばらつきの範囲に収まります。 |
WebGL:readPixels、toDataURL | あり | 理由は同じです。disableGpuFingerprint を指定すると readPixels は本来のピクセルを返しますが、WebGL canvas の書き出し(toDataURL() など)には引き続きノイズが入ります。canvas をどの方法で読み出しても結果が一致するよう、fingerprintNoise: false と組み合わせてください。 |
オーディオ:AudioContext のサンプル値 | なし(意図的) | Web Audio の出力は、同じパイプラインなら決定論的です。ここにノイズを加えるには、仕様外のサンプルレート(44100 ではなく 44099.99)にするか、標準のレンダラーでは生じないサンプルを出すしかありません。どちらも実際のブラウザでは出力され得ない値で、2 つのプロファイルがオーディオハッシュを共有していることよりも強い手がかりになります。報告されるスカラー値は別の話で、こちらはペルソナに従います(後述)。 |
| クライアント矩形とテキストメトリクス | なし(意図的) | Chrome はレイアウトをネイティブの 1/512 px グリッドに量子化するため、本来の矩形はすべて 0.001953125 のちょうど整数倍になります。後からスケール係数を掛けると、すべての矩形がこのグリッドから外れます。グリッドから外れたジオメトリは、係数がどれほど小さくても 1 回の計測で割り出せます。 |
| WebGPU:アダプタと制限値 | なし | シードではなくペルソナの GPU に従うため、WebGL が報告する内容と一致し続けます。同じページ上の 2 つの GPU API が別々のベンダーを名乗っていれば、1 回の呼び出しで読み取れてしまいます。 |
マルチアカウント運用で実際に意味するのは次の点です。canvas と WebGL はアイデンティティを分離しますが、レンダリングされたオーディオとクライアント矩形は分離しません。これらは土台にあるマシンの性質です。同じホストは、どのプロファイルを読み込んでも同じオーディオパイプラインと同じレイアウトグリッドで描画します。同一のハードウェアを使う別々の 2 人の場合とまったく同じです。対象サイトがまさにこれらの値で紐付けを行うなら、解決策は別のスイッチではなく、マシンを分けることです。
知っておきたい例外が 1 つあります。AudioContext の 3 つのスカラー値はペルソナに従います。sampleRate、baseLatency、outputLatency は、ペルソナが有効なとき(--fingerprint シードまたはインポートしたプロファイルがあるとき)は常にペルソナから返されます。そのため、ホストのオーディオバックエンドをさらすことなく、アイデンティティごとに異なる値になります。どちらも設定していなければ、3 つとも実機の値がそのまま返ります。ページが明示的に要求したサンプルレートは引き続き尊重され、outputLatency も従来どおり Chrome 自身の権限依存の量子化を経由します。153 r26 以降は、baseLatency がページの要求するレンダーサイズにも追従し、まだレンダリングを始めていないコンテキストは通常の Chrome と同じレイテンシを報告します。
整合性のある二次サーフェス
ノイズを加えるシグナルとは別に、エンジンはペルソナの二次的なサーフェスを、そのペルソナのプラットフォームで動く実際の Chrome と一致させます。SDK はプラットフォームのデフォルトをホストの OS にします。以下の Windows 固有の値は Windows ペルソナに適用されます。
- WebGL の制限値(WebGL1 + WebGL2)は、このマシンで裏付けられる範囲でペルソナの GPU に従います。実際のドライバが課す上限より高い値を報告することはないため、ソフトウェアレンダラーでは一部の値が、名乗っている GPU の値より低くなります。
- UNMASKED_RENDERER / UNMASKED_VENDOR はセッション中一定です。オリジンごとに変わって手がかりになるのではなく、ペルソナに従った 1 つの GPU をすべてのサイトで報告します。
- navigator.getBattery() は AC 電源につながったデスクトップ(充電中、level 1.0、放電なし)を、navigator.connection は家庭用回線のプロファイル(effectiveType 4g、丸めた rtt/downlink、saveData オフ)を報告します。
- navigator.keyboard.getLayoutMap() は US-QWERTY のマップを返し、Windows ペルソナでは AudioContext が Windows のオーディオスタックのサンプルレートとレイテンシを報告します。
- window.getScreenDetails() は整合性のある単一のモニターを報告し、
@media (pointer: fine)/(hover: hover)はマウスを使うデスクトップと一致します。 - URL:Windows ペルソナでは
new URL("C:/").protocolがfile:を返します。153 r26 以降は、ページが URL を組み立てるどの方法でも同じ結果になります。navigator.share / canShare は Windows の UA に合わせて公開されます。 - WebGPU:
navigator.gpuのアダプタ情報と limits/features は、WebGL と同じ GPU に従います(ライセンスビルドでは、ホストが実際に提供できる範囲に絞り込まれます)。 - ロケール:
Accept-Language、navigator.language、navigator.languagesのリスト全体、Intl(メインスレッドとワーカー)は、すべて 1 つのリスト(acceptLanguageまたはgeoip)から決まります。SDK は--accept-langと--langを自動で設定します。スイッチを直接指定する場合は両方を渡してください。 - 音声合成:
speechSynthesisはペルソナの音声セットを返します。これには、そのバージョンの Chrome が列挙する Google のネットワーク音声も含まれます(153 r27)。fingerprintVoices: falseを指定すると、代わりにマシン自身の音声を使います(152 r22)。 - ハードウェア:プロセッサ速度の値(
navigator.cpuPerformance)は実際のプロセッサではなくペルソナに従い(152 r20)、スクリプトのメモリ上限(performance.memory.jsHeapSizeLimit)はペルソナが名乗るメモリ量に従います(153 r26)。 - コーデックとデバイス:
MediaCapabilities.decodingInfo()はペルソナのコーデック対応表を、enumerateDevices()はペルソナのメディアデバイス一式(シードごとに固定の ID、権限付与前は空のラベル)を報告します。 - UA-CH の高エントロピー値:
bitness=64 /wow64=false /model。Linux ペルソナでは、本物の Chrome が Linux で返すのと同じくplatformVersionは空です(153 r26)。storageQuotaを設定すると、navigator.storage.estimate()は現実的なディスク容量のクォータを報告します。
実験的機能:実 GPU による Canvas ブリッジ
オプションで、canvas / WebGL の操作をリモートの実 GPU ホストに転送できます。これにより、読み出し結果(getImageData / toDataURL / readPixels / measureText)が、提示している GPU と整合します。ローカルではその GPU の描画を再現できないハードウェアでも同じです。転送するのは操作そのもの(事前に記録した画像ではありません)なので、既知のプローブに限らず、ほとんどの canvas に対応できます。有効にするには --canvas-bridge-url=ws://host:port(と --no-sandbox)を指定します。未設定なら、これまでどおり完全にローカルで動作します。レンダーサーバーは実 GPU ホスト上のブラウザ(CDP 経由の通常の Chrome、またはヘッドレスの Clearcote)を操作します。セットアップの全手順は Canvas ブリッジのガイドを参照してください。
ネイティブのメタデータ上書きと light_stealth
シードから導出するペルソナとは別に、ネイティブの個別値上書きを使うと、navigator/screen の個々の値を直接偽装できます。対象は hardwareConcurrency、deviceMemory、colorDepth、devicePixelRatio、maxTouchPoints、そして(オプトインで)screen/avail* の各寸法です。どの値も各 getter が直接読み取り、優先順位はフラグ > --fingerprint ペルソナ > 実ホストです。そのため、ハードコードした値はどのシードよりも優先され、上書きは --fingerprint シードの有無にかかわらず機能します。ペルソナの仕組みは使いません。
SDK は、その軽量なサブセットを 1 つのフラグ light_stealth にまとめています。これは、整合性のある少数のメタデータバンドル(hardwareConcurrency、deviceMemory、colorDepth、devicePixelRatio、maxTouchPoints)から 1 つを、これらのネイティブスイッチだけで適用するものです。シードはどのバンドルを使うかを選ぶだけで、エンジンには渡されません。そのため canvas や WebGL のノイズはなく、同じマシン上の light_stealth セッションはすべてホストの canvas ハッシュを共有します。これらを区別する必要がある場合は、lightStealth を使わずに、アカウントごとの fingerprint シードを使ってください。TLS ClientHello と実際のブラウザバージョンは変わりません。screen の寸法は、デフォルトではあえて偽装しません(screenWidth などでオプトイン)。実際の描画サーフェスと辻褄の合わない偽の画面は見破られやすいためです。スケーリングなしのディスプレイでは、選ばれた devicePixelRatio がそのディスプレイに合っているか確認するか、自分で設定してください。明示的に指定したオプションは常にプリセットより優先されます。
import { launch } from "clearcote";
// one coherent metadata bundle; "my-seed" only picks which one — no seed reaches the engine, so no canvas noise:
const browser = await launch({ lightStealth: true, fingerprint: "my-seed" });
// or set individual values by hand (no --fingerprint needed):
const b2 = await launch({
hardwareConcurrency: 8,
deviceMemory: 8,
devicePixelRatio: 1.25,
maxTouchPoints: 0,
});全スイッチ一覧
名前や効果で絞り込んだり、1 つのサブシステムに限定したりできます。スイッチをクリックすると、その働きと、有効な最短の書き方を確認できます。
マスターシード(整数または文字列)。ペルソナ全体(GPU、画面、ハードウェア、フォント)を導出し、サイトごとの canvas と WebGL のノイズを決めます。言語とタイムゾーンはシードから導出されず、--accept-lang / --timezone(SDK:acceptLanguage、timezone または geoip)で決まります。描画されたオーディオと client rects にはノイズを加えません。ペルソナが有効な場合、AudioContext の sampleRate/baseLatency/outputLatency のスカラー値はペルソナから返されます(フィンガープリントのドキュメントを参照)。同じシード ⇒ 同じアイデンティティ。
使用例
以下はコマンドラインフラグをそのまま並べたものです。Python と Node の完全なワークフローは使用例を参照してください。
整合性のとれた、完全な Windows アイデンティティ:
--fingerprint=acme-tenant-7 \
--fingerprint-platform=windows \
--fingerprint-brand=Chrome \
--accept-lang=en-US,en \
--lang=en-US \
--timezone=America/New_York \
--fingerprint-hardware-concurrency=8GPU 文字列をカスタマイズした Windows アイデンティティ(Windows の ANGLE が報告する形式):
--fingerprint=42 \
--fingerprint-platform=windows \
--fingerprint-gpu-vendor="Google Inc. (NVIDIA)" \
--fingerprint-gpu-renderer="ANGLE (NVIDIA, NVIDIA GeForce RTX 3060 (0x00002504) Direct3D11 vs_5_0 ps_5_0, D3D11)"これらの文字列が変えるのは報告される内容であって、描画そのものではありません。ピクセルは引き続きこのマシンの GPU から得られます。実際に描画に使う GPU を指定するか、Canvas ブリッジを使ってください。macos プラットフォームはアイデンティティ文字列(UA、Client Hints、navigator.platform)を変更しますが、その裏付けとなる macOS のマシンモデルはありません。また、android はベストエフォートのモバイルペルソナです。
位置情報と時計が一致するよう、位置とタイムゾーンをセットで固定します:
--fingerprint=nyc-1 \
--timezone=America/New_York \
--fingerprint-location=40.7128,-74.0060実機の値をインポートする
合成したシードの代わりに、実際の Chrome が報告した値をそのまま報告させることもできます。対象は GPU 文字列と getParameter のテーブル、画面、フォント、音声、オーディオです。置き換わるのは値だけで、描画は引き続き自分のマシンが行います。そのため、プロファイルで 2 つのアカウントの canvas を別々にすることはできません。それができるのはアカウントごとの --fingerprint シードです。厳選された clearcote-profiles ライブラリ(GPU ベンダーのタグ付きで、実機のプロファイルが数千件)から入手するか、コレクターで自分で取得し、読み込んだうえで読み込まれたことを確認します:
import { launch } from "clearcote";
// path to a captured .json profile, a profile object, or a JSON string
const browser = await launch({ fingerprintProfile: "./real-machine.json" });
// fields present in the profile override the persona;
// absent fields fall back to the --fingerprint seed's persona, or the browser's defaults with no seed.取得の流れ:複製したい実際の Chrome でコレクターのページを開き、JSON を書き出して、fingerprintProfile(SDK)/ --fingerprint-profile(エンジン)で渡します。検証には tools/fingerprint-collect/verify_profile.py を使います。
各パッチの役割
オープンビルドの各スイッチは、それぞれ中身を読めるエンジンパッチに対応しています。差分は patches/ にあり、各パッチの 1 行要約は patches/README.md にあります。「ライセンスビルド」と記載されたスイッチは、ライセンスビルド独自のパッチセットによるもので、こちらは公開されていません。
個々の値よりも整合性のほうが重要です。プラットフォーム、タイムゾーン、ロケール、GPU が組み合わせとしてもっともらしくなるようにしてください。エンジンがそれらをどう一貫させているかはアーキテクチャを、なぜこれが JavaScript による偽装より優れているかは検知の仕組みを参照してください。
関連記事