概要
ブラウザのプライベートモード(またはシークレットモード、InPrivateブラウジングなど)の識別は、JavaScriptでは困難である。
結論として、執筆時点ではJavaScriptを利用して全ブラウザ・バージョンにわたりプライベートモードを100%判定する方法は存在しない。ある程度は判定可能だが、それも限定条件下でしか作用しない。
ブラウザのプライベートモード(またはシークレットモード、InPrivateブラウジングなど)の識別は、JavaScriptでは困難である。
結論として、執筆時点ではJavaScriptを利用して全ブラウザ・バージョンにわたりプライベートモードを100%判定する方法は存在しない。ある程度は判定可能だが、それも限定条件下でしか作用しない。
判定が不可能な主な要因は、ブラウザベンダーがプライベートモードを示す標準APIを提供していない点にある1。
プライベートモードはプライバシー保護が目的であり、容易な検知はプライバシー保護の目的を損ない、フィンガープリンティングに悪用されるリスクもあるため、意図的に制限されている。
標準APIがないため、開発者はハックを利用した手法(非公式な回避策や挙動の利用)で検出を試みている。
プライベートモードで制限されるAPIの挙動差異を利用して検出を試みる。
QuotaExceededError
発生(iOS 10以前)indexedDB.open()
失敗(旧バージョン)ただし、これらは副作用を利用した判定であり、ディスク逼迫で同様のエラーが発生する場合もある。そのため、複数APIの挙動を組み合わせて「プライベートモードである蓋然性が高い」と推測するのが基本的なアプローチとなる。
以下はサンプルコードであり、ブラウザ間の差異やエラー処理などは未考慮のため、実運用には適さない。
/**
* プライベートモード検出を試みる
* 複数のAPIチェックを組み合わせて蓋然性を判定
*/
async function detectPrivateMode() {
let score = 0;
const maxChecks = 2; // 現在のチェック数
// 1. localStorage チェック
// プライベートモードではQuotaExceededErrorが発生する可能性
try {
const testKey = '__private_mode_test__';
localStorage.setItem(testKey, '1');
localStorage.removeItem(testKey);
} catch (e) {
// iOS 10以前のSafariプライベートモードではここで失敗
score++;
}
// 2. IndexedDB チェック
// プライベートモードでは接続自体が失敗する場合がある
const dbResult = await checkIndexedDB();
if (!dbResult) score++;
// 3. ディスククォータチェックを追加可能
// navigator.storage.estimate()で容量を確認
return {
isPrivate: score > 0,
confidence: score / maxChecks, // 0〜1の範囲で信頼度を表現
detectedChecks: score, // 検出されたチェック数
};
}
/**
* IndexedDBの利用可否を確認
* Firefox旧バージョンではプライベートモードで失敗
*/
async function checkIndexedDB() {
return new Promise((resolve) => {
try {
const dbName = '__private_mode_test__';
const request = indexedDB.open(dbName);
// エラーが発生 = プライベートモードの可能性
request.onerror = () => resolve(false);
// 成功 = 通常モードの可能性が高い
request.onsuccess = (e) => {
const db = e.target.result;
db.close();
// テストDBを削除してクリーンアップ
indexedDB.deleteDatabase(dbName);
resolve(true);
};
// 1秒でタイムアウト(無限待機防止)
setTimeout(() => resolve(true), 1000);
} catch {
// 例外発生 = プライベートモードの可能性
resolve(false);
}
});
}
// 使用例
detectPrivateMode().then((result) => {
if (result.isPrivate) {
// プライベートモードの可能性が高い
console.log(`検出結果: プライベートモードの可能性`);
console.log(`信頼度: ${result.confidence * 100}%`);
console.log(`検出チェック数: ${result.detectedChecks}`);
// アプリケーションの動作を調整
// 例:代替ストレージを使用、機能制限など
}
});
プライベートモード検出が必要とされる場面は存在するが、完全な検出が困難であることを理解した上での利用が重要である。
ストレージ依存のアプリケーション
分析・トラッキング
課金システム
検出結果に大きく依存するのではなく、段階的な機能制限(グレースフル・フォールバック)を実施する。
プライベートモードの実装はブラウザごとに大きく異なる。
ブラウザ | localStorage | IndexedDB | ディスククォータ | 特記事項 |
---|---|---|---|---|
Chrome/Edge | 一時保存、セッション終了時削除 | 利用可能 | 120MB程度 | 検出が比較的困難 |
Firefox | 利用可能 | 旧バージョンでブロック | 極小値 | バージョンで挙動変化 |
Safari(iOS) | QuotaExceededError (iOS 10以前) | 利用可能 | 0バイト | iOS 10以前は検出容易 |
Safari(macOS) | 利用可能 | 利用可能 | 制限あり | iOSと挙動が異なる |
Opera | Chromeと同様 | Chromeと同様 | Chromeと同様 | Chromiumベース |
ハック的手法が依存するAPIの挙動(副作用)は、Web標準として保証されたものではなく、特定バージョンのブラウザにおける実装の詳細に過ぎない。
ブラウザがセキュリティパッチ、新機能追加、仕様変更などでアップデートされるたびに、これらの副作用は変更される可能性がある。例えば、以前はエラーを返していたストレージ操作がエラーを返さなくなったり、逆の変更が生じたりする可能性がある。
さらに、ブラウザベンダーがプライバシー保護強化の一環として、検出手法を意図的に無効化する(プライベートモードでも通常モードと同じ挙動に見せる)ケースもある。
プライベートモードの実装方法は、ブラウザ(Chrome、Firefox、Safari、Edge等)や動作するOS(Windows、macOS、Android、iOS等)によって差異が存在する。
例えば、iOS上のSafariにおけるプライベートモードのストレージ制限は、デスクトップ版Chromeのシークレットモードとは異なる挙動を示す。このような環境による仕様差があるため、一貫して動作する検出ロジックの作成は困難である。
偽陽性(False Positive): 実際は通常モードだが、プライベートモードと誤判定。例:ディスク容量不足でストレージAPIがエラーを返す、ブラウザ拡張機能がAPIに干渉。
偽陰性(False Negative): 実際はプライベートモードだが、検出に失敗。例:ブラウザ実装の変更により、プライベートモード固有の挙動が隠蔽される。
JavaScriptを用いたフロントエンドでのプライベートモード判定は、ハック的手法の技術的限界により、確実な実現は困難であり、実運用上のリスクが高い。
W3CやWHATWGのHTML仕様にはプライベートモード検出用のAPIは定義されていない。MDN Web Docsでも公式な検出方法は文書化されていない。 ↩