
foxcage — Updated!
rootless Podman コンテナ内で Firefox を実行し、capabilities を削除してネットワークを分離、一時ストレージを使用することで、サンドボックスエスケープを封じ込め、ホストの侵害を防ぎます。
foxcage
セキュリティ分離のため、rootless Podman コンテナで Firefox を実行します。ブラウザはほとんど Linux ケーパビリティを持たず、独自のユーザーおよびネットワーク名前空間で動作し、ホストから分離されます — その一方で、GPU アクセラレーション、オーディオ、DRM サポートはすべて維持されます。
なぜ foxcage なのか?
Firefox にはすでに、Linux 名前空間と seccomp-bpf を使用して Web コンテンツのレンダラーを分離するマルチプロセスサンドボックスがあります。ほとんどの脅威に対しては効果的です。foxcage は第二の壁を追加します。攻撃者が Firefox のサンドボックスを脱出する脆弱性を悪用した場合(実際に発生しており、CVE もあります)、彼らは完全なユーザーセッションではなく、堅牢化されたコンテナ内に到達します。
foxcage が保護するもの
- エクスプロイト後のファイルアクセス。 素の Firefox でのサンドボックス脱出は、ユーザーが読み取れるすべてのもの(
~/.ssh、~/.gnupg、他のブラウザのプロファイル、パスワードマネージャーのデータベース、ドキュメント、ソースコード)へのアクセスを提供します。foxcage では、攻撃者は明示的にマウントしたものだけを見ることができます。 - ディスク上の追跡残骸。
@tmp一時ケージは、ウィンドウを閉じた後、ディスク上に痕跡を一切残しません。Firefox のプライベートブラウジングが依然として保持する拡張機能、HSTS 状態、TLS セッションキャッシュ、DNS キャッシュも含めて残りません。複数の@tmpケージは互いに干渉せずに並行して実行できます。 - 永続化。 素の Firefox では、マルウェアは
~/.config/autostart、~/.bashrc、cron、その他どこにでも書き込んで再起動後も生き残ることができます。foxcage の一時コンテナ (--rm) は、バインドマウントしない限り何も永続化しないことを意味します。 - ネットワーク上の横移動。 デフォルトでは、コンテナは
localhost上のサービスを調査できません。素の Firefox では、サンドボックス脱出により完全なネットワークアクセスが得られます。(ケージが localhost アクセスを必要とする場合、例えばローカル開発などでは[network] mode = "host"を使用してください。ただし、「ネットワーク」の項の注意事項を参照してください。host モードはホストの抽象 Unix ソケットも公開します。) - 権限昇格。 コンテナは
CAP_SYS_CHROOTを除くすべての Linux ケーパビリティをドロップし、新しい特権の取得をブロックします。Setuid バイナリ、特殊な syscall を介したカーネルエクスプロイト、および同様の昇格経路は遮断されます。
foxcage が保護しないもの
- ブラウザレベルの攻撃。 フィッシング、悪意のある拡張機能、および Firefox の通常機能の範囲内で動作するものは影響を受けません。foxcage はコンテナをホストから分離するものであり、ユーザーをブラウザから分離するものではありません。
- バインドマウントされたディレクトリ。 マウントしたもの(
profile、downloads_dir、追加のバインドマウント)は、侵害されたブラウザから完全にアクセス可能です。ホストのプロファイルディレクトリをマウントすると、攻撃者は素の Firefox と同様にそれを改ざんできます。 - PulseAudio を介した音声キャプチャ。 PulseAudio ソケットはコンテナにバインドマウントされます。ファイルシステムレベルでは読み取り専用でマウントされますが、Unix ドメインソケットは双方向であるため、侵害されたプロセスはソケットを介して録音要求を送信できます。ブラウザのサンドボックス脱出により、ホストのマイクから音声を録音される可能性があります。
- Wayland コンポジターのエクスプロイト。 Wayland ソケットはパススルーされます。Wayland コンポジターは設計上クライアントを相互に分離しますが、コンポジター自体の脆弱性には到達可能です。
セキュリティ設定
コンテナは次の設定で実行されます:
- すべての Linux ケーパビリティをドロップ(Firefox のコンテンツサンドボックス用に
CAP_SYS_CHROOTのみ再追加、init.rootが設定されている場合はCAP_SETUID/CAP_SETGIDを一時的に追加) - 権限昇格を防ぐための
no-new-privileges - Rootless ユーザー名前空間 (
--userns keep-id) - プライベートな
/dev/shm(ホストと共有しない)—shm_sizeでサイズを設定可能 - pasta による分離ネットワーク。デフォルトでホストのループバックをブロック
- DNS はデフォルトでホスト DNS を使用(
network.dnsで設定可能) XDG_RUNTIME_DIRの特定のソケットのみバインドマウント(Wayland、PulseAudio、PipeWire、フィルタリングされた D-Bus プロキシ)— ホストのランタイムディレクトリ全体は公開されない- ホストの D-Bus セッションバスへのアクセスは常に、ホスト上で実行されるフィルタリングされた
xdg-dbus-proxyによって仲介されます。到達可能なのはorg.freedesktop.Notifications、org.freedesktop.portal.Desktop、org.mozilla.*、および(フォークの場合)フォーク自身の名前空間(例:org.librewolf.*)のみです。キーリングや SSH/GPG エージェントなどのセッションサービスはブロックされます - ポータルアクセスは広範です。
org.freedesktop.portal.Desktopは全体として許可されます。なぜなら、ファイルピッカーや「別のアプリでリンクを開く」、画面共有がそのように機能するからです。また、RemoteDesktop(セッション全体に対する合成キーボード/マウス)、Camera、Locationも公開されます。これらは foxcage ではなく、デスクトップ自身の承認ダイアログによって制御されます。そしてRemoteDesktopプロンプトは画面共有プロンプトに似ているため、承認する前にダイアログをよく読んでください。xdg-dbus-proxyには「1 つのインターフェースを拒否する」ルールがないため、これを絞り込むには Firefox が必要とするすべてのインターフェースを列挙する必要があります。それがデフォルトで行われない理由についてはdocs/DESIGN.mdを参照してください - すべてのバインドマウント(
profile、downloads_dir、追加の[mounts] bind)はnosuid,noexecを使用 - ブラウザのダウンロードは GPG 署名に対して検証:Firefox は Mozilla の署名付き SHA-512 チェックサムに対して、LibreWolf は LibreWolf Maintainers の分離署名とそれに対応する SHA-256 に対して検証
- 一時コンテナ (
--rm) — ファイルシステムへの書き込みは終了時に失われます - 明示的に有効にしない限り、ホストデバイス(ウェブカメラ、セキュリティキー、プリンター)はパススルーされません
有効にする [network] および [mounts] オプションはそれぞれ、利便性と引き換えに分離性を一部犠牲にします。デフォルトは、それでも使いやすいブラウザを提供する最も制限の強い構成です。
要件
- Python 3.11+
- Podman(rootless)
- Wayland コンポジター(X11 はサポートされていません)
- pasta(
sudo apt install passt)—network.mode = "host"でない場合 - xdg-dbus-proxy(
sudo apt install xdg-dbus-proxy) - PulseAudio または PulseAudio 互換の PipeWire(オーディオ用)
- DRI サポートのある GPU — 任意。
/dev/driがない場合、foxcage は警告し、Firefox はソフトウェアレンダリングになります
foxcage は通常のデスクトップユーザーとして実行してください。root や sudo 経由では実行しないでください。サンドボックスはユーザーをコンテナにマップするため、root として実行すると foxcage が提供する分離が失われます。root での起動は拒否されます。
テスト済み環境: GNOME 3 を搭載した Debian 13 (Trixie)。他の Linux ディストリビューションや Wayland コンポジターでも動作する可能性がありますが、テストされていません。
インストール
foxcage は Python 標準ライブラリ以外に依存関係のない単一の Python スクリプトです。PATH 内のディレクトリにコピーしてください。```sh
sudo cp foxcage /usr/local/bin/foxcage
または、ユーザーローカルインストールの場合:```sh
cp foxcage ~/.local/bin/foxcage
スクリプトが実行可能であることを確認してください(chmod +x foxcage)。
foxcage --version でお使いのリビジョンを確認できます。これは問題を報告する際に役立ちます。foxcage は単一のファイルをコピーしてインストールされるためです。
使い方```sh
./foxcage
初回実行時、スクリプトはコンテナイメージを構築し(Mozilla から Firefox をダウンロードし、最小限の Debian 依存関係をインストールします)、その後 Firefox を起動します。以降の実行では、foxcage は Firefox の更新を確認し、新しいバージョンが利用可能になったら自動的にイメージを再構築します。イメージはまた、システムパッケージの更新を取り込むために定期的に(デフォルトでは 7 日ごとに)再構築されます。更新チェックが失敗した場合(ネットワークエラー、タイムアウト)、警告がログに記録され、既存のイメージが使用されます — 起動がブロックされることはありません。
Firefox に引数をそのまま渡す:```sh
./foxcage https://example.com
名前付きcageとFirefoxフラグを組み合わせる:```sh ./foxcage @work --kiosk https://example.com
ケージがすでに実行中の場合、2つ目のコンテナを起動する代わりに、URL は既存のブラウザの新しいタブで開きます。実行中のケージに対して URL を指定せずに `foxcage`(または `foxcage @cage`)を実行すると、"cage is already running" というメッセージを表示してクリーンに終了します — foxcage はコンテナの外部から既存の Wayland ウィンドウを前面に出すことができないため、そのような試みは行いません。
起動ごとのフラグは、ケージがすでに実行中の場合は**適用されません**。`--dns`、`--ipv4-only`、`--lifetime`、`--color`、`--fork` はコンテナの起動時に消費され、実行中のコンテナの設定は外部から変更できないため、警告付きで無視されます。これらを適用するには、ケージを閉じて再実行してください。
> `private_browsing` 設定キーをプライベートモードのセッションに使用してください — Firefox の生の `--private-window` CLI フラグでは*ありません*。この設定キーはセッション全体のプライベートモード(`browser.privatebrowsing.autostart`)を設定するため、後続の `foxcage @cage URL` の呼び出しでタブとして再度開けます。Firefox のパススルーとして `--private-window` を使用すると、最初のウィンドウだけがプライベートになり、上記の「タブで再度開く」動作が壊れます。
>
> **注意:** `private_browsing = true` で有効にしたセッションでは、Firefox の通常のプライベートウィンドウ UI の目印(紫色のアクセントバー、マスクアイコン、タイトルの "(Private Browsing)")が表示されません。これは、セッション内のすべてのウィンドウがプライベートであるため、Firefox が視覚的に対比する非プライベートウィンドウを持たず、インジケーターを抑制するからです。セッションは*確かに*プライベートです。確認したい場合は、ケージ内で `about:privatebrowsing` にアクセスする(標準のプライベートブラウジング情報ページが表示されます)か、`about:config` で `browser.privatebrowsing.autostart = true` を確認してください。
### `@tmp` を使った一時ブラウジング
痕跡を残さない一度きりのリンクには、予約済みの `tmp` ケージを使用してください:```sh
./foxcage @tmp https://somewhere-suspicious.example
すべての @tmp 起動は、永続プロファイルを持たない、新しく使い捨ての Firefox です。ウィンドウを閉じると、すべてが消えます — Cookie、キャッシュ、履歴、拡張機能、HSTS 状態、TLS セッションキャッシュ、DNS キャッシュ、保存されたタブの状態。これは、拡張機能やかなりの量のディスク上の状態を保持し続ける Firefox のプライベートブラウジングよりもさらに徹底しています。
複数の @tmp ケージは同時に実行でき、それぞれが互いに分離されています。メニューバーには FoxCage - tmp (<short id>) と表示されるため、同時に実行されている一時ウィンドウを区別できます。
一時ケージは起動時に空白ページと空白の新しいタブを開きます — デフォルトの Firefox ホームページと新規タブのコンテンツ(トップサイト、Pocket のおすすめ、アクティビティストリーム)は、まもなく破棄される新しいプロファイルでは単なるノイズなので、抑制されています。永続ケージは Firefox のデフォルトを維持します。
名前付き一時ケージ
使い捨てセッションに意味のある名前を付けたい場合(たとえば、後で新しいタブで開き直したい調査の深掘りなど)、@tmp-<name> を使用します:```sh
./foxcage @tmp-research https://example.com # first call → new window
./foxcage @tmp-research https://another.example # second call → new tab in the existing window
`@tmp-<name>` は依然として一時的です — ウィンドウを閉じるとすべてが消えます。素の `@tmp` との違いは、同じ名前での2回目以降の起動では**既存のウィンドウを再利用する**(永続ケージと同じ)ことで、並列コピーを開始することなく、後でタブを追加できます。素の `@tmp` は「起動のたびに新しい使い捨て」という動作を維持します。
メニューバーのラベルには、選択した名前(`FoxCage - tmp-research`)が表示されるため、ウィンドウに意味のあるラベルが付きます。
#### 一時的なデフォルトのカスタマイズ
`~/.config/foxcage/tmp.toml` を作成して、すべての一時ケージ(素の `@tmp` とすべての `@tmp-<name>` の両方)のデフォルトを設定します。例:```toml
private_browsing = true
lifetime = "30m"
[network]
dns = "cloudflare"
一時的な起動では毎回、プライベートウィンドウ、Cloudflare DoH が設定され、30分後に自動終了します。完全な一時性は維持されます。名前付き一時環境はデフォルトで tmp.toml を継承します。名前ごとに上書きしたい場合は、~/.config/foxcage/tmp-<name>.toml を作成してください。そのファイルは tmp.toml の代わりに適用されます。マージは行われず、より具体的なファイルが完全に優先されます。共有デフォルトを使いたい場合は、それをコピーしてください。
通常のケージの設定で設定できるものはすべてここでも機能します。ただし、一時性自体を無効化してしまう次のキーだけは例外です:
profile— ハードエラー。
これはホスト上の永続プロファイルディレクトリを指すもので、@tmp の目的と直接矛盾します。永続プロファイルを持つサンドボックス化されたケージが必要な場合は、tmp- で始まらない通常の名前付きケージ(@work、@research など)を使用してください。
起動ごとの DNS 上書き
--dns フラグ(および同等の network.dns 設定キー)は次の3つの形式を受け付けます:```sh
./foxcage @tmp --dns 1.1.1.1 https://example.com # IP
./foxcage @tmp --dns cloudflare https://example.com # alias
./foxcage @tmp --dns https://dns.nextdns.io/ # custom DoH URI
**値が既知のプロバイダー(エイリアスまたは IP)に一致すると、foxcage はそのプロバイダーへの強制 DNS over HTTPS を自動的に有効にします。** Firefox の TRR はモード 3(厳格、平文フォールバックなし)に設定され、ブートストラップアドレスが入力されるため、起動時に暗号化されていない解決の漏えいが発生しません。stderr に `Enabling DNS over HTTPS via Cloudflare` のような 1 行の通知が表示されます。
組み込みエイリアス:
| Alias | IP | フィルタリング |
|-------|------|-----------|
| `cloudflare` | 1.1.1.1 | なし |
| `cloudflare-security` | 1.1.1.2 | マルウェアをブロック |
| `cloudflare-family` | 1.1.1.3 | マルウェア+アダルトをブロック |
| `google` | 8.8.8.8 | なし |
| `quad9` | 9.9.9.9 | マルウェアをブロック(Quad9 デフォルト) |
| `quad9-unfiltered` | 9.9.9.10 | なし |
| `adguard` | 94.140.14.14 | 広告+トラッカーをブロック |
| `adguard-family` | 94.140.14.15 | 広告+トラッカー+アダルト |
| `opendns` | 208.67.222.222 | 一部 |
テーブルにない IP(例: LAN 上の Pi-hole)は平文のみのままです。foxcage は対応する DoH エンドポイントを知らないため、DoH は有効になりません。その場合は URI 形式を使用してください: `--dns https://pi.hole/dns-query`(有効な証明書が必要)で DoH が有効になり、コンテナの DNS はそのままになります。
URI 形式はコンテナの平文 DNS の設定をスキップするため、コンテナ内で Firefox 以外のものは引き続きホストの DNS を使用します。これは意図的です。`--dns URI` は「Firefox にこの DoH リゾルバーを使用させる」という意味であり、それ以上でも以下でもありません。
`--dns` は `network.mode = "host"` とは互換性がありません。host モードはすでに完全なホストネットワークアクセスを持っているためです。
### 視覚的なケージ識別
名前付きケージにはそれぞれメニューバーにアクセントカラーが付き、ウィンドウをひと目で見分けられます。**設定は一切不要です** — 色はケージ名から決定的に導出されます(SHA256 でハッシュ化して色相に変換し、彩度と明度は固定)。`@banking`、`@work`、`@personal`、`@tmp-research` はすべて、何もしなくても個別の安定した色になります。
デフォルト(匿名)ケージは組み込みのオレンジのままです。
自動導出された色を上書きしたい場合は、明示的に設定してください:```toml
# ~/.config/foxcage/banking.toml
color = "#dc2626" # red — overrides the auto-derived colour
Please provide the Markdown content to translate.```sh ./foxcage @experiment --color "#10b981" https://example.com # teal, one-off
標準のCSS hex形式に対応:`#rgb`、`#rrggbb`、または`#rrggbbaa`(アルファ付き)。自動導出される色は、ライト/ダーク両方のメニューバーで視認できるように調整されています(明度は55%固定、彩度は75%)。そのため、テーマの都合で上書きする必要は通常ありません。
### 時間制限付きケージ
`--lifetime`フラグ(および同等の`lifetime`設定キー)は、設定した時間が経過するとケージを自動的に閉じます。形式は`<数値><単位>`で、単位は`s`、`m`、`h`のいずれかです:```sh
./foxcage @tmp --lifetime 10m https://example.com
./foxcage @work --lifetime 2h
カウントダウンは、Firefox が実際に cage 内で起動したときに始まります。コンテナの起動やイメージのビルドに要する時間は、この制限時間に含まれません。cage のメニューバーのラベルには、カウントダウンが cage の識別情報と共に表示されます。例: FoxCage - tmp (a3f2b1) | 9m。残り時間が1分以上の間は1分ごとに、最後の1分間は1秒ごとに更新されます。カウントダウンがゼロになると、Firefox は自動的に閉じ、コンテナは終了します。制限時間が切れる前に自分で Firefox を閉じた場合も、特別なことは起こりません。
各 cage のデフォルトの寿命は、その設定で指定できます:```toml
~/.config/foxcage/tmp.toml — every @tmp launch auto-closes after 15 minutes
lifetime = "15m" private_browsing = true
`--lifetime` はコマンドライン上で、どの設定値よりも優先されます。
イメージの完全な再ビルドを強制します (Firefox とすべてのシステムパッケージを再ダウンロードします):```sh
./foxcage --rebuild
A running container keeps whichever image it was started from, even after foxcage rebuilds the image tag. If you try to open a tab in a cage whose image has since been updated (by --rebuild, a Firefox update, or the scheduled rebuild), foxcage refuses with an error (also surfaced as a desktop notification) and asks you to quit Firefox and re-launch — which starts a fresh container on the current image. Under --rebuild with a live cage, foxcage warns up front, does the build, then applies the same check.
更新
foxcage は起動のたびに新しいブラウザリリースを確認します — Firefox には Mozilla の release API、LibreWolf には GitLab の releases エンドポイントを使用します。更新が利用可能な場合、コンテナイメージは自動的に再ビルドされます。また、Debian のセキュリティアップデートを取り込むために、イメージは定期的に(デフォルトでは7日ごとに)再ビルドされます。ブラウザの内蔵自動更新は無効化されています。更新はイメージレベルで処理されるためです。
更新チェックが失敗した場合(ネットワークなし、API タイムアウト)は、警告が表示され、既存のイメージが使用されます — ブラウジングは常に可能です。
更新頻度は設定のトップレベルにあり、バージョンとチャネルの固定はフォークごとのセクションにあります:```toml rebuild_days = 14 # rebuild for base-image updates every 14 days (0 to disable)
[firefox] channel = "beta" # track the beta channel instead of stable (firefox only) version = "149" # pin to Firefox 149.x (latest patch release)
**Pinning an ESR version needs the channel too.** Mozillaのバージョンインデックスでは、ESRリリースがダウンロードに付く `esr` サフィックスなしでリストされます。そのため、デフォルトチャネルで単に `version = "140"` と指定すると、存在しないリリースに解決されます。両方を設定してください:```toml
[firefox]
channel = "esr"
version = "140" # → 140.13.0esr
どのリリースにも一致しないピンは、黙って最新リリースにフォールバックするのではなく、そのピンを指名するエラーになるようになりました。Mozilla の API に一時的に到達できない場合は、引き続き警告を出し、既存のイメージで続行するため、不安定なネットワークでも起動がブロックされることはありません。
- サフィックス付きピンは完全修飾である必要があります —
"140.13.0esr"と"150.0b9"は有効ですが、"140esr"と"150b9"は、一致するリリースが存在しないため、設定読み込み時に拒否されます。LibreWolf のリビジョンも同様で、"146.0.1-1"は有効ですが、"146-1"は無効です。
即時の完全リビルドを強制するには: ./foxcage --rebuild
Firefox フォーク (LibreWolf)
foxcage は、アップストリームの Firefox の代わりに、プライバシー重視の Firefox フォークを実行できます。```toml fork = "librewolf" # default is "firefox"
[librewolf] version = "146.0.1-1" # optional pin; partial pins ("146", "146.0.1") also work
または、CLI経由で起動ごとに:```sh
foxcage @tmp --fork librewolf https://example.com
LibreWolf: プライバシー強化型 Firefox フォーク — 厳格なトラッキング防止、DoH、RFP、テレメトリーはデフォルトで無効化されています。GitLab (librewolf-community/browser/bsys6) から署名付き Linux tarball を取得し、LibreWolf Maintainers キー 662E 3CDD 6FE3 2900 2D0C A5BB 4033 9DD8 2B12 EF16 に対して GPG 検証し、付属の .sha256sum でも相互チェックします。LibreWolf 同梱の librewolf.cfg は保持され、foxcage は上書きせず独自の prefs を上に追加します。
Channel は Firefox 限定: fork が "firefox" 以外の場合は firefox.channel = "beta" | "esr" が拒否されます。LibreWolf には単一のリリーストラックしかありません。
fork(config または --fork 経由)を切り替えると Containerfile ハッシュが変わり、次回起動時にリビルドがトリガーされます — 手動の --rebuild は不要です。
プロファイル互換性
フォークごとに専用プロファイルを使用してください。 最も安全なデフォルトは、foxcage に独自プロファイルをプロビジョニングさせること(config から
profileを省略)、またはホストからも開かないディレクトリにprofileを指定することです。
- LibreWolf: 通常はホストの Firefox プロファイルと共有しても問題ありません — LibreWolf は Firefox のバージョンを数日以内に追従するため、
compatibility.iniスキーマの競合は稀です。リスク: (1) 順次使用のみが安全(Firefox のロックファイルが同時起動を防ぎます); (2) Firefox 安定版リリース直後の短期間に、Firefox を先に起動してから LibreWolf を起動すると、"used by a newer version" の移行ダイアログが表示される可能性があります; (3) LibreWolf が取り除く機能(Sync、Pocket、Mozilla アカウント)は、警告なしに動作しないだけで、データを破損することはありません。
名前付きケージ
それぞれ独自の config と Firefox プロファイルを持つ、個別のサンドボックス化されたインスタンスを実行します:```sh ./foxcage @work
これは `~/.config/foxcage/work.toml` を読み込み、別のイメージ (`foxcage-work`)、コンテナ (`foxcage-work`)、ボリューム (`foxcage-work-profile`) を使用します。設定ファイルは名前付きケージに対して存在している必要があります。ケージ名に使用できるのは、文字、数字、ハイフン、アンダースコアのみです。
## 設定
設定ファイルは `$XDG_CONFIG_HOME/foxcage/` に置かれます (デフォルトは `~/.config/foxcage/`)。
- `config.toml` — デフォルトケージ (省略可能。これがなくても適切なデフォルト値が使用されます)
- `<name>.toml` — `@<name>` で読み込まれる名前付きケージ (必須)
未知の設定キーはエラーとして拒否されます。利用可能なすべてのオプションとデフォルト値については `config.toml.example` を参照してください。
### config.toml の例```toml
# Bind-mount a host Firefox profile directory into the cage
profile = "~/.mozilla/firefox/xxxxxxxx.default-release"
# Allow downloading files to ~/Downloads
downloads_dir = "~/Downloads"
# Shared memory size for Firefox IPC (default: 256m)
# shm_size = "256m"
# Pass through webcam devices (/dev/video*)
# webcam = true
# Pass through host CUPS socket for locally-connected printers (e.g. USB)
# local_printers = true
# Pass through FIDO2/U2F security key devices (/dev/hidraw*)
# security_keys = true
# Always open Firefox in private browsing mode
# private_browsing = true
# Auto-close the cage after a duration (<int> with unit s, m, or h)
# lifetime = "30m"
# Accent colour for the menu-bar label. Named cages get a colour derived
# from the name automatically; set this to override it.
# color = "#4a90e2"
# Browser fork: "firefox" (default) or "librewolf"
# fork = "librewolf"
# Full image rebuild interval in days for base-image updates (default: 7, 0 to disable)
# rebuild_days = 7
[firefox]
# Firefox release channel: "release" (default), "beta", "esr".
# Only valid when fork = "firefox".
# channel = "release"
# Pin to a specific Firefox version (overrides channel).
# Partial versions like "149" or "149.0" resolve to the latest patch release.
# Suffixed versions must be fully qualified ("140.13.0esr", "150.0b9"); to
# follow the ESR line by major version, pair a numeric pin with
# channel = "esr" above.
# version = "149.0.2"
[librewolf]
# Pin to a specific LibreWolf version. Tags are "<firefox-version>-<rev>",
# e.g. "146.0.1-1". Partial pins like "146" or "146.0.1" also work.
# version = "146.0.1-1"
[network]
# "host" for full host networking (needed if the cage has to reach services
# on the host's localhost), or omit for isolated pasta (default)
# mode = "host"
# DNS server (isolated mode only, default: host DNS)
# dns = "1.1.1.1"
# Disable IPv6 in the cage (isolated mode only)
# ipv4_only = true
[mounts]
# Additional bind mounts into the container. Supported forms:
# "~/Documents" — same path in container
# "~/Documents:~/Documents" — ~ expanded on both sides
# "~/Documents:/home/user/Documents" — explicit container path
# Append :ro for read-only, e.g. "~/Documents:ro"
# nosuid,noexec are always enforced on bind mounts; an explicit "exec" or
# "suid" is rejected rather than silently dropped.
# Host paths must be absolute or start with "~/".
bind = [
"~/Documents:ro",
]
[init]
# Commands to run at image build time (as root). Changes trigger a rebuild.
# build = ["apt-get update && apt-get install -y --no-install-recommends vim"]
# Commands to run at container startup as root, before Firefox.
# root = ["chown user:user /some/path"]
# Commands to run at container startup as your user, before Firefox.
# user = ["mkdir -p ~/custom-dir"]
ホストのFirefoxプロファイル
ケージとホストのFirefoxプロファイルを共有するには、profile にプロファイルディレクトリを設定します。ホスト上のFirefoxで about:profiles にアクセスするとプロファイルのパスを確認できます。あるいは、ホスト側に保持されるクリーンなプロファイルでケージを起動したい場合は、新しい空のディレクトリを指定しても構いません。```toml
profile = "~/.mozilla/firefox/xxxxxxxx.default-release"
この1つのディレクトリだけがケージにバインドマウントされます。`~/.mozilla/firefox/` 配下の兄弟プロファイルと `profiles.ini` レジストリは公開されません — 侵害されたケージはそれらを改ざんできません。
`profile` が未設定の場合、代わりに名前付き Podman ボリュームが Firefox プロファイルを保存します(下の「永続化されるもの」を参照)。同じプロファイルがホスト上のFirefoxで既に開かれている場合、Firefoxのプロファイルごとのロックファイルが競合を引き起こします — ケージごとに専用プロファイルを使用してください。
### ネットワーク
デフォルトでは、コンテナはホストのループバックをブロックしホストDNSを使用するpastaを使用します。pastaはpodman 4.4以降が必要です(podman 5.0以降、rootlessのデフォルトとなっています)。
**ホストネットワーキング**はネットワーク分離を完全に取り除きます。ケージがホストの `localhost` 上のサービス(例: ローカル開発サーバー、`127.0.0.1` 上のデータベース)に到達する必要がある場合にこれを使用します:```toml
[network]
mode = "host"
dns は mode = "host" と組み合わせることはできません — ホストネットワークはすでにホストのリゾルバを使用しています。
ホストモードは localhost 以上のものを犠牲にします。 ケージはホストのネットワーク名前空間に置かれ、抽象 Unix ソケットはファイルシステムではなくその名前空間にスコープされます。したがって、ホストモードのケージはホスト上の抽象アドレスソケットに直接到達できます — X11 または Xwayland を実行している場合は Xwayland の
@/tmp/.X11-unix/X0(foxcage が Wayland 専用であるにもかかわらず入力ログ)、およびunix:abstract=…で構成されたセッションバス(フィルタリングされた D-Bus プロキシをバイパスします)。これはネットワークスタックの共有に内在するものであり、foxcage がフィルタリングできるものではありません。必要なときにホストモードを使用し、その目的のためにのみ起動する名前付きケージを優先してください。
IPv4 専用ケージ は IPv6 を完全に無効にします:```toml [network] ipv4_only = true
または、起動ごとに `--ipv4-only` フラグ(短縮形 `-4`、`ssh`/`curl`/`pasta` と同様)を使用します:```sh
./foxcage @tmp -4 https://example.com
これは pasta を IPv4 のみのモード(-4)で実行するため、コンテナには IPv6 スタックがまったくなくなり、さらに Firefox の network.dns.disableIPv6 を設定して AAAA レコードを解決しないようにします。これは DoH が有効な場合に重要です。DoH の応答はコンテナのリゾルバを迂回するためです。ipv4_only は mode = "host" と組み合わせることはできません。ホストネットワークはホストのネットワークスタックを直接使用するため、代わりにホスト側で IPv6 を無効にしてください。
初期化コマンド
[init] を使用して、ビルド時またはコンテナ起動時にカスタムコマンドを実行します:
build— イメージのビルド時に root として実行されます。パッケージのインストールやその他の時間のかかるセットアップに使用します。ビルドコマンドへの変更は自動的にイメージの再ビルドをトリガーします。root— コンテナ起動時に Firefox の前に root として実行されます。迅速なランタイム root タスク(権限の調整、設定ファイルの書き込み)に使用します。user— コンテナ起動時にあなたのユーザーとして Firefox の前に実行されます。ディレクトリの作成やユーザーレベルの状態のセットアップに使用します。```toml [init] build = [ "apt-get update && apt-get install -y --no-install-recommends fonts-noto-cjk", "rm -rf /var/lib/apt/lists/*", ] root = ["chmod 777 /tmp/shared"] user = ["mkdir -p ~/workspace"]
All three keys are lists of shell command strings. If any command fails, the container exits without starting Firefox.
**Security note:** When `init.root` is set, the container starts as root with `CAP_SETUID` and `CAP_SETGID` added (on top of the default `CAP_SYS_CHROOT`) so it can drop back to the regular user. These capabilities are only held during the root init phase — after the privilege drop, the regular-user process has no extra capabilities. Without `init.root`, the container runs with the default minimal capability set.
## 永続化されるもの
設定がない場合、名前付き Podman ボリュームが Firefox プロファイル(ブックマーク、設定、拡張機能、Widevine DRM プラグイン)を保存します。その他はすべて一時的です。
- デフォルトのcage: `foxcage-profile`
- 名前付きcage: `foxcage-<name>-profile`
まっさらな状態から始めるには、ボリュームを削除します:```sh
podman volume rm foxcage-profile
profile が設定されている場合、ホストディレクトリは直接バインドマウントされ、ボリュームは作成されません。
ディスク使用量
各ケージイメージは約 1 GB です。リビルドはイメージを再タグ付けし、以前のイメージをタグなしの <none> エントリとして残すため、foxcage は成功するビルドのたびに、置き換えたイメージを削除します。削除するのはその特定のイメージのみで、実行中のケージがまだ使用しているイメージを削除することはありません。
この動作が存在する以前に孤立したイメージは、遡ってクリーンアップされません。それらを回収するには:```sh podman images --filter dangling=true # review first podman image prune # then remove
Firefoxのアップデートは起動のたびに自動的に検出されます。完全な再ビルドを強制するには(例:システムのセキュリティアップデートをすぐに反映させるため):```sh
./foxcage --rebuild
テーマ
foxcage は、ホストから以下のものを自動的に透過させるため、コンテナ内の Firefox はネイティブアプリケーションのように見え、操作感も同じになります。
- フォント。 システムフォント(
/usr/share/fonts)とユーザーフォント(~/.local/share/fonts)は、読み取り専用でバインドマウントされます。~/.config/fontconfigのフォント設定も透過されます。 - GTK テーマとダークモード。
GTK_THEMEまたはgsettingsで検出され、コンテナに渡されます。~/.config/gtk-3.0と~/.config/gtk-4.0の GTK 設定は読み取り専用でバインドマウントされます。 - タイムゾーン。 ホストのタイムゾーン名(
TZ、/etc/localtimeシンボリックリンク、または/etc/timezoneから検出)はTZとしてコンテナに渡され、/etc/localtimeは読み取り専用でバインドマウントされます。両方が必要です: Firefox は JavaScript のタイムゾーンをゾーン名から導出するのであって、ファイルの内容からではありません。TZがないと、Web サイトは時刻を UTC で表示します。 - ロケール。
LANGが透過されます。ホストのロケールはビルド時にコンテナイメージ内で生成されます。
Cage ラベル。 Firefox のメニューバーには "FoxCage" (名前付きケージの場合は "FoxCage - name") が表示されるため、コンテナ化されたセッションであることが一目でわかります。メニューバーはエンタープライズポリシーにより常に表示されます。
コンテナには Adwaita GTK テーマのみが含まれています。GNOME デスクトップではそのまま動作します。KDE やその他のデスクトップでは、お使いの GTK テーマ(例: Breeze)がコンテナにインストールされていない場合、Firefox は Adwaita にフォールバックします。ダークモード検出は、gsettings または GTK_THEME で設定されていれば引き続き機能します。
DRM (Netflix、Disney+ など)
Widevine DRM はそのまま動作します。DRM で保護されたサイトに初めてアクセスすると、Firefox は Widevine CDM を自動的にダウンロードします。これには少し時間がかかる場合があります。
ホスト統合 (常時有効)
foxcage はフィルタリングされた D-Bus プロキシを使用して、Firefox にホストの XDG Desktop Portal と通知デーモンへのアクセスを提供します。これらの機能は、すべてのアクセスがユーザーを介して行われるため安全です。ホストは、あなたが操作しなければならないネイティブダイアログを表示します。侵害されたブラウザがホストのリソースに静かにアクセスすることはできません。
- ファイルアップロード — ホストのネイティブファイルピッカー(共有するファイルを選択)
- 外部リンク —
mailto:、マグネットリンクなどはホストのアプリ選択ダイアログで開く - デスクトップ通知 — ホストの通知デーモンに転送
- 画面共有 — ポータルの画面ピッカー + PipeWire ビデオストリーム(ホストに PipeWire が必要)
デバイスパススルー (オプトイン)
これらはホストデバイスをコンテナに直接渡すもので、デフォルトではオフです。上記のポータル機能とは異なり、ホスト側の確認はありません。侵害されたブラウザは、そのハードウェアを静かに使用できる可能性があります。```toml webcam = true # /dev/video* — webcam for video calls local_printers = true # CUPS socket — USB printers (network printers work by default) security_keys = true # /dev/hidraw* — FIDO2/U2F hardware keys
## 未対応機能
一部のウェブプラットフォーム機能は、ホスト統合が不足しているためコンテナ内で動作しません。ここでは透明性のため、それらを列挙します。
**Bluetooth、USB、シリアル、NFC。** Web Bluetooth、WebUSB、Web Serial、WebNFC API は、コンテナ内では利用できないデバイスアクセスとシステムサービス(BlueZ、udev)を必要とします。
**ゲームパッドと MIDI。** Gamepad API は `/dev/input/` へのアクセスを必要とします。Web MIDI は ALSA シーケンサーへのアクセスを必要とします。どちらも透過されません。
**PWA のインストール。** プログレッシブウェブアプリは、コンテナ内からホストのデスクトップにインストールできません。
**アクセシビリティ。** AT-SPI によるスクリーンリーダーサポートは無効です(`NO_AT_BRIDGE=1`)。コンテナはホストのアクセシビリティバスに接続されていません。Web Speech API の合成は機能します。`espeak-ng` エンジンを備えた `speech-dispatcher` がケージにインストールされ、初回使用時に自動起動され、共有 PulseAudio ソケット経由でオーディオがルーティングされます。
## ホスト設定
### 推奨: fuse-overlayfs を使用したオーバーレイストレージ
Rootless Podman はデフォルトで `vfs` ストレージドライバーを使用する場合があります。これはオーバーレイマウントを使用せずにイメージレイヤー全体をコピーします。そのため、ビルド後のコンテナ起動がはるかに遅くなります。これを修正するには、`fuse-overlayfs` をインストールし、以下を `~/.config/containers/storage.conf` に追加します。```toml
[storage]
driver = "overlay"
[storage.options.overlay]
mount_program = "/usr/bin/fuse-overlayfs"
foxcage をデフォルトブラウザとして設定する
まず、foxcage スクリプトが恒久的な場所(例: ~/bin/foxcage または /usr/local/bin/foxcage)にあることを確認してください。インストールコマンドはスクリプトの現在のパスを .desktop ファイルに記録するため、後で移動するとランチャーが壊れます。
次に、実行します:```sh foxcage --install
これは、スクリプトの現在の場所を指す `.desktop` ファイルを作成し、foxcage アイコンをインストールして、デスクトップとアイコンのデータベースを更新します。FoxCage はアプリケーションメニューに表示されるはずです。
foxcage をデフォルトのウェブブラウザに設定して、他のアプリケーションでクリックされたリンクが foxcage で開かれるようにするには:```sh
xdg-settings set default-web-browser foxcage.desktop
cageがすでに実行されている場合、URLは既存のブラウザの新しいタブで開きます。
元に戻すには:```sh foxcage --uninstall
`StartupNotify=true` が `.desktop` ファイルに設定されており、foxcage の起動中にコンポジタがスピナーカーソルを表示するようになっています。イメージのビルドが必要な場合(数分かかることがあります)、foxcage はデスクトップ通知を送信して、Firefox が起動間近であることを知らせます。早期終了エラー(設定のタイポ、依存関係の欠落、不正な cage 名など)もデスクトップ通知として表示されるため、デスクトップから起動したユーザーは、ターミナルが接続されていない状態で foxcage が失敗しても、何も表示されないまま放置されることはありません。どちらも `notify-send`(Debian/Ubuntu では `libnotify-bin` に含まれる)が必要です。インストールされていない場合、通知は静かにスキップされ、エラーは引き続き stderr に出力されます。
<details>
<summary>手動セットアップ</summary>
手動で `.desktop` ファイルを作成したい場合は、`~/.local/share/applications/foxcage.desktop` を作成してください:```ini
[Desktop Entry]
Type=Application
Name=FoxCage
Comment=Firefox in a rootless Podman container
Exec=/path/to/foxcage %u
Icon=foxcage
MimeType=text/html;x-scheme-handler/http;x-scheme-handler/https;
Terminal=false
Categories=Network;WebBrowser;
StartupNotify=true
StartupWMClass=foxcage
/path/to/foxcage をスクリプトの実際のパスに置き換えてください。それを登録します:```sh
update-desktop-database ~/.local/share/applications
</details>
## テストの実行
テストスイートは、`requirements-dev.txt` に開発専用の依存関係として宣言されている pytest + pytest-cov を使用します。```
pip install -r requirements-dev.txt
pytest
テストは完全に隔離されています — podman もネットワークも、pytest の tmp_path 以外の実際のファイルシステムも使用しません。このスイートは 100% の行およびブランチカバレッジ をゲートとしており(pytest.ini と .coveragerc で設定)、カバーされていない行や、条件分岐の未実行側があると、実行が失敗します。CI は .gitlab-ci.yml を介して、すべてのプッシュでこのスイートを実行します。
謝辞
このプロジェクトは、Anthropic の AI コーディングツールである Claude Code の支援を受けて、Mike Cardwell によって開発されました。