iframeの中のゲームは外を見られない:sandboxが止めていること
遊びを見つける執筆にClaudeを使用しています。

ブラウザゲームの投稿サイトでは、他人が書いたJavaScriptが、あなたのブラウザで動きます。そのコードが、ゲームを載せているページの中身を読み取ったり、保存されたデータを覗いたりしたら困ります。そのためゲームは、ページに埋め込まれた小さな窓であるiframeの中で、制限をかけた状態で動かすのが一般的です。この制限をかける仕組みが、iframeのsandbox属性です。 この記事で確かめるのは、sandbox="allow-scripts"だけを付けた箱で、ゲームは何ができて何ができなくなるのか、という問いです。スクリプトは動くので、描画も音もキー入力も問題ありません。一方で、親ページの読み取りや通常の保存領域は使えなくなります。この境目を知っておくと、遊ぶ人は「なぜ記録が残らないのか」が分かります。作る人は、公開前に起きる不具合を予想できます。 アソバースの案内で確認できるのは、「ゲームはsandbox付きiframeで実行され、アップロードしたコードはメインサイトと別のオリジンから配信される」という点までです。どの許可値を付けているかは、確認済み事項に含まれていません。そのため以降のallow-scriptsだけの箱は、一般的なブラウザの仕様に基づく「考え方のための箱」として扱います。実際の許可値は、後半で読者が自分で確かめる手順を示します。
この記事の目次
sandboxは「全部禁止」から始まり、許可を足していく仕組みです
sandbox属性を値なしで付けると、iframeの中身はかなり強く制限されます。一般的な仕様では、スクリプトの実行、フォーム送信、ポップアップ、親ページの遷移、モーダルダイアログなどが止まります。さらに、中身は「どこの出身でもないページ」として扱われます。
ここで出てくるオリジンは、ページの出身を表す「プロトコル+ドメイン+ポート」の組です。ブラウザは、オリジンが違うページ同士がお互いの中身を読めないようにしています。sandboxの箱は、このオリジンを一時的に空っぽ(不透明なオリジン)にします。見た目は同じ画面でも、ブラウザからは他人として扱われるわけです。
そこにallow-scriptsのようなトークンを足すと、その分だけ禁止が外れます。つまり「許可を足す方式」です。ゲームが動くには少なくともスクリプトが必要なので、allow-scriptsは最小限の許可と言えます。
allow-scriptsだけの箱:できること・できないこと
一般的な仕様と主要ブラウザの挙動に基づく整理です。細かな表示や例外の種類はブラウザごとに違うので、次の節で自分の環境で確かめられるようにします。
- JavaScriptの実行、canvas描画、Web Audioでの音、allow-scriptsだけの箱:できます、理由・補足:スクリプトが許可されているためです。
- キー入力・タッチ・マウスの受け取り、allow-scriptsだけの箱:できます、理由・補足:箱にフォーカスがあれば届きます。
- postMessageで親ページと会話、allow-scriptsだけの箱:できます、理由・補足:決まった形のメッセージ交換だけが、窓の間の正規の通路です。
- 親ページのDOMや変数を読む、allow-scriptsだけの箱:できません、理由・補足:オリジンが違うため、参照するとSecurityErrorになります。
- localStorage・sessionStorage・Cookie・IndexedDB、allow-scriptsだけの箱:通常はできません、理由・補足:不透明なオリジンには、保存領域が割り当てられません。
- フォーム送信、window.openでのポップアップ、allow-scriptsだけの箱:できません、理由・補足:それぞれ別の許可が必要です。
- 親ページ全体の移動(top.locationの書き換え)、allow-scriptsだけの箱:できません、理由・補足:別の許可が必要です。
- alertなどのモーダル、allow-scriptsだけの箱:出ません、理由・補足:allow-modalsが必要です。
- ネットワークへのリクエスト、allow-scriptsだけの箱:sandboxでは止まりません、理由・補足:通信の可否は別の仕組み(CSPやCORSなど)が決めます。
最後の行は誤解されやすい点です。sandboxは「箱の中の出身を消す」仕組みであって、通信を遮断する仕組みではありません。外部通信を止めたいなら、別の手段が必要です。
なお、フルスクリーンのような機能はsandboxではなく、iframeのallow属性(権限ポリシー)で決まります。「箱の中で動かない機能」を調べるときは、sandboxの許可と権限ポリシーの両方を見ます。
手元で確かめる:最小の実験設計
結果を読むのではなく、自分のブラウザで確かめるための手順です。ファイルは1つだけで、サーバーも外部ライブラリも要りません。
準備するもの
- テキストエディタ
- PCのブラウザ(開発者ツールを開ける環境)
- 下のコードを保存したsandbox-test.html
<!doctype html>
<meta charset="utf-8">
<pre id="out">実行中…</pre>
<iframe id="box" sandbox="allow-scripts" style="display:none"></iframe>
<script>
const inner = `
<script>
const r = {};
const t = (name, f) => {
try { r[name] = String(f()); }
catch (e) { r[name] = 'エラー: ' + e.name; }
};
t('スクリプト', () => '動いた');
t('origin', () => location.origin);
t('localStorage', () => { localStorage.setItem('a', '1'); return '書けた'; });
t('cookie', () => { document.cookie = 'a=1'; return document.cookie || '空'; });
t('親のDOM', () => parent.document.title || '読めた');
t('popup', () => window.open('about:blank') ? '開いた' : '開かない');
parent.postMessage(r, '*');
<\/script>`;
addEventListener('message', e => {
if (e.source !== box.contentWindow) return;
out.textContent = Object.entries(e.data)
.map(([k, v]) => k + ' → ' + v).join('\n');
});
box.srcdoc = inner;
</script>手順と記録の取り方
- ファイルをブラウザで開き、画面に出た6行を、そのまま書き写します。1回で十分です。
- 開発者ツールのコンソールも開いて、赤や黄色の警告文を項目ごとに控えます。ブロックされた理由が書かれていることがあります。
- sandbox="allow-scripts"をsandbox="allow-scripts allow-popups"に書き換えて再読み込みし、変わった行だけを記録します。
- もう1つ別のブラウザでも手順1を行い、行ごとに差があるかを比べます。
結果の読み方
- 「親のDOM」と「localStorage」がエラー: SecurityErrorなら、箱が出身を消している状態と読めます。
- originが文字列のnullなら、不透明なオリジンになっています。
- 手順3で「popup」だけが変わるなら、許可が項目ごとに独立していると分かります。
- ブラウザ間で行が違うなら、その差が「仕様の幅」です。公開前の確認は、複数のブラウザで行うほうが安全です。
起こりやすい誤り
- allow-same-originを安易に足さないでください。allow-scriptsと一緒に付けて、さらに親と同じオリジンで配信すると、箱の中のコードが自分でsandbox属性を外せる、という一般的な警告があります。保存領域が使えるようになる代わりに、箱の意味がなくなります。
- file://で開く場合、親ページ自体の扱いがブラウザごとに違います。コンソールの警告も合わせて読みます。
- 通信を試したい場合は、コードにfetchを足し、開発者ツールのネットワーク欄で成功・失敗を確認します。この結果は、sandboxではなく通信先の応答ヘッダーなどにも左右されます。
アソバースの確認済み事項から読み取れること
ここからは、アソバースの案内で確認できる事実と、そこから言える範囲に限った話です。
1. 箱の外側にも壁があります。 ゲームはsandbox付きiframeで動き、アップロードされたコードはメインサイトとは別のオリジンから配信されます。箱の属性に加えて、配信元そのものも分けているため、片方に頼らない二重の作りだと読み取れます。また、ゲームにはFirebaseのUID・認証トークン・メールアドレスを渡さないとされています。「見せないものは最初から渡さない」という設計です。
2. 保存は箱の外の道を通ります。 確認済みの内容では、アップロード作品内のlocalStorage/sessionStorageは「一時メモリーの互換処理」です。保存領域が通常は使えない箱でも、ゲームが止まらないように用意されたものと読めます。恒久的な保存には、自動で追加されるSDK(URL投稿の作者は自分で読み込む)が使われ、作品専用のプレイヤーIDと、作品ごとにJSONで128KiBまでのクラウドセーブが使えます。
3. 公式作品の記録が「画面を開いている間だけ」の理由。 13作品のうち保存があるのは余白の庭・WAVE / WAVE・かたちのあいだの3作品で、他の10作品の記録や録音は画面を開いている間だけです。「こだまの森」の最高記録や「小さな軌道」の最速記録がこれにあたります。ただし、各作品の保存の仕組みの細部は確認済み事項にありません。実際の動きは、作品を開いて確かめてください。
4. 範囲外の事項。 sandboxに付いている許可値の一覧、iframeのallow属性、通信を制限する設定の細部は、確認済み事項にありません。「現在の案内で確認する」のが正確です。
作る人が迷いやすい点
投稿を考える人が、箱の性質から先回りして確認しておきたい点を挙げます。
- 保存をlocalStorageだけに頼らない。 同じ端末でも、再読み込みで消えると考えて設計します。残したい進行は、SDKのクラウドセーブ(128KiBまで)を使う前提で、保存データを小さく作ります。
- 親ページに依存しない。 parentやtopへの参照は、エラーになる前提で書きます。必要な情報はpostMessageでやり取りする形が、一般的な方法です。
- alertやconfirmを使わない。 箱の中では出ないことがあるため、ゲーム内の画面で確認UIを作ります。
- 入口と素材の置き方。 アップロードは直下のindex.htmlが入口で、素材は相対パスです。サーバー用コードやサービスワーカー、SharedArrayBufferを使うマルチスレッドは対象外とされています。
保護者や学習者の視点では、「ゲームが親ページを覗けない」ことと「ゲームが外部と通信しない」ことは別の話だと覚えておくと、ニュースや規約を読むときに混乱しません。13の公式作品が外部通信なしの独立したHTMLで動くという点は、確認済みの事実です。
次の行動
上のコードを保存して開き、6行の表示と、コンソールの警告を書き留めてください。次に、許可値を1つだけ足して、どの行が変わるかを見比べます。そのうえで、アソバースの公式作品を1つ開き、記録や設定がどの時点まで残るかを、再読み込みして確かめてみてください。
参考資料
- HTML Standard(WHATWG)のiframe要素とsandbox属性の項
- MDN Web Docs「<iframe>: インラインフレーム要素」のsandbox属性の項

